From cfa5789dfda508f5c299de2c95b002da04cc2851 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 11 May 2026 09:44:52 -0500 Subject: [PATCH 01/64] fix: Improved OnValueChanged door example (#3959) * update Updating the door example to provide a more recommended way of using OnValueChanged. * style removing trailing spaces. * Update networkvariable.md Fixes to the example script * Update networkvariable.md Did another pass over this script with some improvements. * style removing trailing whitespace * Apply suggestions from code review Co-authored-by: Amy Reeve --------- Co-authored-by: Amy Reeve --- .../Documentation~/basics/networkvariable.md | 272 ++++++++++++++++-- 1 file changed, 253 insertions(+), 19 deletions(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md index 0ea6c445e8..df9b3bb506 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md @@ -148,43 +148,277 @@ The [synchronization and notification example](#synchronization-and-notification The `OnValueChanged` example shows a simple server-authoritative `NetworkVariable` being used to track the state of a door (open or closed) using an RPC that's sent to the server. Each time the door is used by a client, the `Door.ToggleStateRpc` is invoked and the server-side toggles the state of the door. When the `Door.State.Value` changes, all connected clients are synchronized to the (new) current `Value` and the `OnStateChanged` method is invoked locally on each client. ```csharp -public class Door : NetworkBehaviour +using System.Runtime.CompilerServices; +using Unity.Netcode; +using UnityEngine; + +/// +/// Example of using a to drive changes +/// in state. +/// +/// +/// This is a simple state driven door example. +/// This script was written with recommended usages patterns in mind. +/// +public class Door : NetworkBehaviour, INetworkUpdateSystem { - public NetworkVariable State = new NetworkVariable(); + /// + /// The two door states. + /// + public enum DoorStates + { + Closed, + Open + } + + /// + /// Initializes the door to a specific state (server side) when first spawned. + /// + [Tooltip("Configures the door's initial state when 1st spawned.")] + public DoorStates InitialState = DoorStates.Closed; + + /// + /// Used for example purposes. + /// When true, only the server can open and close the door. + /// Clients will receive a console log saying they could not open the door. + /// + public bool IsLocked; + + /// + /// A simple door state where the server has write permissions and everyone has read permissions. + /// + private NetworkVariable m_State = new NetworkVariable(default, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Server); + + /// + /// The current state of the door. + /// + public DoorStates CurrentState => m_State.Value; + /// + /// Invoked while the is in the process of + /// being spawned. + /// public override void OnNetworkSpawn() { - State.OnValueChanged += OnStateChanged; + // The write authority (server) doesn't need to know about its + // own changes (for this example) since it's the "single point + // of truth" for the door instance. + if (IsServer) + { + // Host/Server: + // Applies the configurable state upon spawning. + m_State.Value = InitialState; + } + else + { + // Clients: + // Subscribe to changes in the door's state. + m_State.OnValueChanged += OnStateChanged; + } } - public override void OnNetworkDespawn() + /// + /// Invoked once the door and all associated components + /// have finished the spawn process. + /// + protected override void OnNetworkPostSpawn() + { + // Everyone updates their door state when finished spawning the door + // to ensure the door reflects (visually) its current state. + UpdateFromState(); + + // Begin updating this NetworkBehaviour instance once all + // netcode related components have finished the spawn process. + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update); + base.OnNetworkPostSpawn(); + } + + /// + /// Example of using the usage pattern + /// where it only updates while spawned. + /// + /// The current update stage being invoked. + public void NetworkUpdate(NetworkUpdateStage updateStage) + { + switch (updateStage) + { + case NetworkUpdateStage.Update: + { + if (Input.GetKeyDown(KeyCode.Space)) + { + Interact(); + } + break; + } + } + } + + /// + /// Invoked just before this instance runs through its despawn + /// sequence. A good time to unsubscribe from things. + /// + public override void OnNetworkPreDespawn() + { + if (!IsServer) + { + m_State.OnValueChanged -= OnStateChanged; + } + + // Stop updating this NetworkBehaviour instance prior to running + // through the despawn process. + NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.Update); + base.OnNetworkPreDespawn(); + } + + /// + /// Server makes changes to the state. + /// Clients receive the changes in state. + /// + /// + /// When the previous state equals the current state, we are a client + /// that is doing its first synchronization of this door instance. + /// + /// The previous state. + /// The current state. + public void OnStateChanged(DoorStates previous, DoorStates current) + { + UpdateFromState(); + } + + /// + /// Invoke when the state is updated to apply the change + /// in door state to the door asset itself. + /// + private void UpdateFromState() + { + switch(m_State.Value) + { + case DoorStates.Closed: + { + // door is open: + // - rotate door transform + // - play animations, sound etc. + /// + /// Override to apply specific checks (like a player having the right + /// key to open the door) or make it a non-virtual class and add logic + /// directly to this method. + /// + /// The player attempting to open the door. + /// + protected virtual bool CanPlayerToggleState(NetworkObject player) { - State.OnValueChanged -= OnStateChanged; + // For this example, if the door "is locked" then clients will + // not be able to open the door but the host-client's player can. + return !IsLocked || player.IsOwnedByServer; } - public void OnStateChanged(bool previous, bool current) + /// + /// Invoked by either a host or clients to interact with the door. + /// + public void Interact() { - // note: `State.Value` will be equal to `current` here - if (State.Value) + // Optional: + // This is only if you want clients to be able to + // interact with doors. A dedicated server would not + // be able to do this since it does not have a player. + if (IsServer && !IsHost) { - // door is open: - // - rotate door transform - // - play animations, sound etc. + // Optional to log a warning about this. + return; + } + + if (IsHost) + { + ToggleState(NetworkManager.LocalClientId); } else { - // door is closed: - // - rotate door transform - // - play animations, sound etc. + // Clients send an RPC to server (write authority) who applies the + // change in state that will be synchronized with all client observers. + ToggleStateRpc(); } } - [Rpc(SendTo.Server)] - public void ToggleStateRpc() + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private DoorStates NextToggleState() + { + return m_State.Value == DoorStates.Open ? DoorStates.Closed : DoorStates.Open; + } + + /// + /// Invoked only server-side + /// Primary method to handle toggling the door state. + /// + /// The client toggling the door state. + private void ToggleState(ulong clientId) + { + // Get the server-side client player instance + var playerObject = NetworkManager.SpawnManager.GetPlayerNetworkObject(clientId); + if (playerObject != null) + { + var nextToggleState = NextToggleState(); + if (CanPlayerToggleState(playerObject)) + { + // Host toggles the state + m_State.Value = nextToggleState; + UpdateFromState(); + } + else + { + ToggleStateFailRpc(nextToggleState, RpcTarget.Single(clientId, RpcTargetUse.Temp)); + } + } + else + { + // Optional as to how you handle this. Since ToggleState is only invoked by + // sever-side only script, this could mean many things depending upon whether + // or not a client could interact with something and not have a player object. + // If that is the case, then don't even bother checking for a player object. + // If that is not the case, then there could be a timing issue between when + // something can be "interacted with" and when a player is about to be de-spawned. + // For this example, we just log a warning as this example was built with + // the requirement that a client has a spawned player object that is used for + // reference to determine if the client's player can toggle the state of the + // door or not. + NetworkLog.LogWarningServer($"Client-{clientId} has no spawned player object!"); + } + } + + /// + /// Invoked by clients. + /// Re-directs to the common method. + /// + /// includes that is automatically populated for you. + [Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)] + private void ToggleStateRpc(RpcParams rpcParams = default) + { + ToggleState(rpcParams.Receive.SenderClientId); + } + + /// + /// Optional: + /// Handling when a player cannot open a door. + /// + /// includes that is automatically populated for you. + [Rpc(SendTo.SpecifiedInParams, InvokePermission = RpcInvokePermission.Server)] + private void ToggleStateFailRpc(DoorStates doorState, RpcParams rpcParams = default) { - // this will cause a replication over the network - // and ultimately invoke `OnValueChanged` on receivers - State.Value = !State.Value; + // Provide player feedback that toggling failed. + Debug.Log($"Failed to {doorState} the door!"); } } ``` From 885103818e85688a3477061f805fee0f686ca586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 12 May 2026 12:22:12 +0200 Subject: [PATCH 02/64] ci: Update of CI dependencies and test fixes (#3970) * corrected Android issue * Updated dependencies * Updated XRAY_PROFILE param to account for latest checks --------- Co-authored-by: Noel Stephens Co-authored-by: Emma --- .yamato/package-pack.yml | 2 +- .yamato/package-tests.yml | 2 +- .yamato/project.metafile | 6 +- .yamato/wrench/api-validation-jobs.yml | 4 +- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 62 +++++++++---------- .yamato/wrench/promotion-jobs.yml | 9 +-- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 60 +++++++++--------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 2 +- .../Runtime/Core/NetworkManager.cs | 2 +- 12 files changed, 78 insertions(+), 77 deletions(-) diff --git a/.yamato/package-pack.yml b/.yamato/package-pack.yml index 94c940b74b..7554274fe8 100644 --- a/.yamato/package-pack.yml +++ b/.yamato/package-pack.yml @@ -40,7 +40,7 @@ package_pack_-_ngo_{{ platform.name }}: {% endif %} timeout: 0.25 variables: - XRAY_PROFILE: "gold ./pvpExceptions.json" + XRAY_PROFILE: "gold ./pvpExceptions.json rme" commands: - upm-pvp pack "com.unity.netcode.gameobjects" --output upm-ci~/packages - upm-pvp xray --packages "upm-ci~/packages/com.unity.netcode.gameobjects*.tgz" --results pvp-results diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index e9df795f10..50799533dd 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -36,7 +36,7 @@ package_test_-_ngo_{{ editor }}_{{ platform.name }}: model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} variables: - XRAY_PROFILE: "gold ./pvpExceptions.json" + XRAY_PROFILE: "gold ./pvpExceptions.json rme" UNITY_EXT_LOGGING: 1 commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 871ff18710..acc0c1d405 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -24,7 +24,7 @@ small_agent_platform: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.83.0 + image: package-ci/ubuntu-22.04:v4.85.0 flavor: b1.small @@ -39,13 +39,13 @@ test_platforms: default: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.83.0 + image: package-ci/ubuntu-22.04:v4.85.0 flavor: b1.large standalone: StandaloneLinux64 desktop: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.83.0 + image: package-ci/ubuntu-22.04:v4.85.0 flavor: b1.large smaller_flavor: b1.medium larger_flavor: b1.xlarge diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index d99abd06f0..6976c10973 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -60,8 +60,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 3994a564c2..06c750a127 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -29,5 +29,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 55736dc6b8..445f507a73 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -27,7 +27,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -82,10 +82,10 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -140,10 +140,10 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -199,10 +199,10 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -257,10 +257,10 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -315,10 +315,10 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -374,10 +374,10 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (MacOS). preview_apv_-_6000_4_-_macos13: @@ -432,10 +432,10 @@ preview_apv_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (Ubuntu). preview_apv_-_6000_4_-_ubuntu2204: @@ -490,10 +490,10 @@ preview_apv_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (Windows). preview_apv_-_6000_4_-_win10: @@ -549,10 +549,10 @@ preview_apv_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (MacOS). preview_apv_-_6000_5_-_macos13: @@ -607,10 +607,10 @@ preview_apv_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). preview_apv_-_6000_5_-_ubuntu2204: @@ -665,10 +665,10 @@ preview_apv_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (Windows). preview_apv_-_6000_5_-_win10: @@ -724,10 +724,10 @@ preview_apv_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (MacOS). preview_apv_-_6000_6_-_macos13: @@ -782,10 +782,10 @@ preview_apv_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). preview_apv_-_6000_6_-_ubuntu2204: @@ -840,10 +840,10 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (Windows). preview_apv_-_6000_6_-_win10: @@ -899,8 +899,8 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index e4beeec200..7fd22a018d 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -183,10 +183,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -365,8 +365,9 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 1550408dd2..b9eba06e54 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index f247f7d6a4..84a92fcd75 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -69,10 +69,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -139,10 +139,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -209,10 +209,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -279,10 +279,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -349,10 +349,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -419,10 +419,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -489,10 +489,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -559,10 +559,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -629,10 +629,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -699,10 +699,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -769,10 +769,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -839,10 +839,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -909,10 +909,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -979,10 +979,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects @@ -1049,10 +1049,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.7.0.0 + UPMPVP_CONTEXT_WRENCH: 2.9.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.7.0.0 + Wrench: 2.9.0.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 2d7eb092b2..9aa8ecfde1 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "2.7.0.0", + "wrench_version": "2.9.0.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index aa420f1382..452a22131a 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -12,7 +12,7 @@ - + diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index b9a2c15789..8fc97dd84e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1013,7 +1013,7 @@ internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = fals var isParented = transform.root != transform; if (isParented) { - throw new Exception(GenerateNestedNetworkManagerMessage(transform)); + Log.Error(new Context(LogLevel.Error, GenerateNestedNetworkManagerMessage(transform))); } #endif return isParented; From d6e96d6f6bb36a8ff695722ed87addf9528ebb85 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 13 May 2026 14:58:41 -0500 Subject: [PATCH 03/64] fix: UniversalRpcTests failing when using IL2CPP backend (#3973) fix Fixing the issue with `UniversalRpcTests` failing on IL2CPP release builds using the "Method Name" stack trace information setting. --- .../Tests/Runtime/Rpc/UniversalRpcTests.cs | 67 ++++++++++++++----- 1 file changed, 52 insertions(+), 15 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs index f943422b39..fb90660844 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/UniversalRpcTests.cs @@ -2,9 +2,9 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; using NUnit.Framework; using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; @@ -29,22 +29,14 @@ internal class UniversalRpcNetworkBehaviour : NetworkBehaviour public ulong ReceivedFrom = ulong.MaxValue; public int ReceivedCount; - public void OnRpcReceived() + public void OnRpcReceived([CallerMemberName] string methodName = "") { - var st = new StackTrace(); - var sf = st.GetFrame(1); - - var currentMethod = sf.GetMethod(); - Received = currentMethod.Name; + Received = methodName; ReceivedCount++; } - public void OnRpcReceivedWithParams(int a, bool b, float f, string s) + public void OnRpcReceivedWithParams(int a, bool b, float f, string s, [CallerMemberName] string methodName = "") { - var st = new StackTrace(); - var sf = st.GetFrame(1); - - var currentMethod = sf.GetMethod(); - Received = currentMethod.Name; + Received = methodName; ReceivedCount++; ReceivedParams = new Tuple(a, b, f, s); } @@ -1031,7 +1023,8 @@ public void VerifySentToNotServerWithParams(ulong objectOwner, ulong sender, str public void VerifySentToClientsAndHostWithParams(ulong objectOwner, ulong sender, string methodName, int i, bool b, float f, string s) { - if (m_ServerNetworkManager.IsHost) + var authority = GetAuthorityNetworkManager(); + if (authority.IsHost) { VerifySentToEveryoneWithParams(objectOwner, sender, methodName, i, b, f, s); } @@ -1319,11 +1312,33 @@ private void SendingNoOverrideWithParams(SendTo sendTo, ulong objectOwner, ulong var sendMethodName = $"DefaultTo{sendTo}WithParamsRpc"; var verifyMethodName = $"VerifySentTo{sendTo}WithParams"; + VerboseDebug("Get player object..."); var senderObject = GetPlayerObject(objectOwner, sender); + if (senderObject == null) + { + Debug.LogError($"Sender object is NULL!"); + Assert.Fail(); + return; + } + + VerboseDebug($"Getting send method: {sendMethodName}"); var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + if (sendMethod == null) + { + Debug.LogError($"Send method for {sendMethodName} is NULL!"); + Assert.Fail(); + return; + } sendMethod.Invoke(senderObject, new object[] { i, b, f, s }); + VerboseDebug($"Getting verify method: {verifyMethodName}"); var verifyMethod = GetType().GetMethod(verifyMethodName); + if (verifyMethod == null) + { + Debug.LogError($"Verify method for {verifyMethodName} is NULL!"); + Assert.Fail(); + return; + } verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName, i, b, f, s }); } @@ -1344,11 +1359,33 @@ private void SendingNoOverrideWithParamsAndRpcParams(SendTo sendTo, ulong object var sendMethodName = $"DefaultTo{sendTo}WithParamsAndRpcParamsRpc"; var verifyMethodName = $"VerifySentTo{sendTo}WithParams"; + VerboseDebug("Get player object..."); var senderObject = GetPlayerObject(objectOwner, sender); + if (senderObject == null) + { + Debug.LogError($"Sender object is NULL!"); + Assert.Fail(); + return; + } + + VerboseDebug($"Getting send method: {sendMethodName}"); var sendMethod = senderObject.GetType().GetMethod(sendMethodName); + if (sendMethod == null) + { + Debug.LogError($"Send method for {sendMethodName} is NULL!"); + Assert.Fail(); + return; + } sendMethod.Invoke(senderObject, new object[] { i, b, f, s, new RpcParams() }); + VerboseDebug($"Getting verify method: {verifyMethodName}"); var verifyMethod = GetType().GetMethod(verifyMethodName); + if (verifyMethod == null) + { + Debug.LogError($"Verify method for {verifyMethodName} is NULL!"); + Assert.Fail(); + return; + } verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName, i, b, f, s }); } @@ -1383,7 +1420,7 @@ private void RunTestTypeB(TestTypes testType) { foreach (var defaultSendTo in sendToValues) { - UnityEngine.Debug.Log($"[{testType}][{defaultSendTo}]"); + VerboseDebug($"[{testType}][{defaultSendTo}]"); foreach (var overrideSendTo in sendToValues) { for (ulong objectOwner = 0; objectOwner <= numberOfClientsULong; objectOwner++) From 294364316d20141cf53a2b2afed1293574014865 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Thu, 14 May 2026 11:34:09 -0400 Subject: [PATCH 04/64] feat: Make custom driver construction smoother (#3980) * feat: Make custom driver construction smoother * Add PR number to CHANGELOG entries * Update package version --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 ++ .../UTP/INetworkStreamDriverConstructor.cs | 3 +-- .../UTP/NetworkMetricsPipelineStage.cs | 4 ---- .../Runtime/Transports/UTP/UnityTransport.cs | 22 +++++++++++++++---- com.unity.netcode.gameobjects/package.json | 4 ++-- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 9d1075d0b3..0d8fb3a2fb 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,9 +10,11 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added a new variant of `UnityTransport.GetDefaultPipelineConfigurations` that takes a reference to the created `NetworkDriver`. This will register all pipeline stages that `UnityTransport` requires, removing the need to manually register them in your own custom driver constructor. (#3980) ### Changed +- `NetworkMetricsPipelineStage` is now defined even when the multiplayer tools package is not installed, removing the need to guard its registration behind a version define when using a custom driver in `UnityTransport`. (#3980) ### Deprecated diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/INetworkStreamDriverConstructor.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/INetworkStreamDriverConstructor.cs index 8e07c0d3e4..c07c8c1484 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/INetworkStreamDriverConstructor.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/INetworkStreamDriverConstructor.cs @@ -36,9 +36,8 @@ namespace Unity.Netcode.Transports.UTP /// var settings = transport.GetDefaultNetworkSettings(); /// driver = NetworkDriver.Create(new IPCNetworkInterface(), settings); /// - /// driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); - /// /// transport.GetDefaultPipelineConfigurations( + /// ref driver, /// out var unreliableFragmentedPipelineStages, /// out var unreliableSequencedFragmentedPipelineStages, /// out var reliableSequencedPipelineStages); diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs index c602aef739..de1e7899b9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/NetworkMetricsPipelineStage.cs @@ -1,5 +1,3 @@ -#if MULTIPLAYER_TOOLS -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 using AOT; using Unity.Burst; using Unity.Collections.LowLevel.Unsafe; @@ -70,5 +68,3 @@ private static void InitializeConnection(byte* staticInstanceBuffer, int staticI } } } -#endif -#endif diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index b39fe2bc94..e392af403c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -657,6 +657,23 @@ public void GetDefaultPipelineConfigurations( reliableSequencedPipelineStages = new(reliableSequenced, Allocator.Temp); } + /// + /// Driver for which the pipeline configurations are being retrieved. + public void GetDefaultPipelineConfigurations( + ref NetworkDriver driver, + out NativeArray unreliableFragmentedPipelineStages, + out NativeArray unreliableSequencedFragmentedPipelineStages, + out NativeArray reliableSequencedPipelineStages) + { +#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 + driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); +#endif + GetDefaultPipelineConfigurations( + out unreliableFragmentedPipelineStages, + out unreliableSequencedFragmentedPipelineStages, + out reliableSequencedPipelineStages); + } + private NetworkPipeline SelectSendPipeline(NetworkDelivery delivery) { switch (delivery) @@ -1860,11 +1877,8 @@ public void CreateDriver( #endif } -#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 - driver.RegisterPipelineStage(new NetworkMetricsPipelineStage()); -#endif - GetDefaultPipelineConfigurations( + ref driver, out var unreliableFragmentedPipelineStages, out var unreliableSequencedFragmentedPipelineStages, out var reliableSequencedPipelineStages); diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 04492deaa0..bd77e48697 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": "2.11.3", + "version": "2.12.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", @@ -15,4 +15,4 @@ "path": "Samples~/Bootstrap" } ] -} \ No newline at end of file +} From 5d6a3c736e149d2ac3cc67c17df06af5ea937279 Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Fri, 15 May 2026 11:57:30 -0400 Subject: [PATCH 05/64] chore: Change codeowners for UTP (#3982) --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d54be2e86e..ad9486f7b4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -10,4 +10,4 @@ AssemblyInfo.cs @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa .github/ @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa .yamato/ @NoelStephensUnity @EmandM @Unity-Technologies/netcode-qa com.unity.netcode.gameobjects/Documentation*/ @jabbacakes -com.unity.netcode.gameobjects/Runtime/Transports/UTP/ @Unity-Technologies/multiplayer-workflows +com.unity.netcode.gameobjects/Runtime/Transports/UTP/ @simon-lemay-unity From 75c36f439be444d0b1b4780f2d66113d5f58a8a6 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 21 May 2026 15:54:37 -0400 Subject: [PATCH 06/64] chore: Add obsolete warnings to unused or invalid methods (#3987) * chore: Add obsolete warnings to unused or invalid methods * Update CHANGELOG * dotnet-fix * remove duplication of GetRootParentTransform * Remove unrelated change * Leave the public modifiers in place * Update xml doc --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 8 ++++++++ .../Editor/NetworkBehaviourEditor.cs | 15 +++++---------- .../Runtime/Core/NetworkManager.cs | 3 +++ .../NetworkVariable/Collections/NetworkList.cs | 10 ++-------- .../Runtime/Spawning/NetworkSpawnManager.cs | 1 + .../Manual/NestedNetworkTransforms/ChildMover.cs | 11 +---------- .../Assets/Tests/Runtime/RpcTestsAutomated.cs | 11 +++++------ 8 files changed, 26 insertions(+), 34 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0d8fb3a2fb..06ebf2c108 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 ### Deprecated +- Deprecated a number of methods that were no longer valid or being used. (#3987) ### Removed diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 57295be52e..ffea9115b7 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -97,17 +97,23 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem { foreach (var fieldDefinition in typeDefinition.Fields) { +#pragma warning disable CS0618 // Type or member is obsolete if (fieldDefinition.Name == nameof(NetworkManager.__rpc_func_table)) +#pragma warning restore CS0618 // Type or member is obsolete { fieldDefinition.IsPublic = true; } +#pragma warning disable CS0618 // Type or member is obsolete if (fieldDefinition.Name == nameof(NetworkManager.RpcReceiveHandler)) +#pragma warning restore CS0618 // Type or member is obsolete { fieldDefinition.IsPublic = true; } +#pragma warning disable CS0618 // Type or member is obsolete if (fieldDefinition.Name == nameof(NetworkManager.__rpc_name_table)) +#pragma warning restore CS0618 // Type or member is obsolete { fieldDefinition.IsPublic = true; } @@ -115,7 +121,9 @@ private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assem foreach (var nestedTypeDefinition in typeDefinition.NestedTypes) { +#pragma warning disable CS0618 // Type or member is obsolete if (nestedTypeDefinition.Name == nameof(NetworkManager.RpcReceiveHandler)) +#pragma warning restore CS0618 // Type or member is obsolete { nestedTypeDefinition.IsNestedPublic = true; } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs index 1312ff69e6..c2ce7da626 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs @@ -322,18 +322,13 @@ private void OnEnable() } /// - /// Recursively finds the root parent of a + /// Obsolete method to find root parent of a . + /// Use instead. /// /// The current we are inspecting for a parent /// the root parent for the first passed into the method - public static Transform GetRootParentTransform(Transform transform) - { - if (transform.parent == null || transform.parent == transform) - { - return transform; - } - return GetRootParentTransform(transform.parent); - } + [Obsolete("Use transform.root instead")] + public static Transform GetRootParentTransform(Transform transform) => transform.root; /// /// Used to determine if a GameObject has one or more NetworkBehaviours but @@ -358,7 +353,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 rootTransform = gameObject.transform.root; if (!rootTransform.TryGetComponent(out var networkManager)) { networkManager = rootTransform.GetComponentInChildren(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 8fc97dd84e..55fdd6dbf8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -44,12 +44,15 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `public` + [Obsolete("This field is no longer used and will be removed in a future version.")] internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters); // RuntimeAccessModifiersILPP will make this `public` + [Obsolete("This field is no longer used and will be removed in a future version.")] internal static readonly Dictionary __rpc_func_table = new Dictionary(); // RuntimeAccessModifiersILPP will make this `public` (legacy table should be removed in v3.x.x) + [Obsolete("This field is no longer used and will be removed in a future version.")] internal static readonly Dictionary __rpc_name_table = new Dictionary(); #pragma warning restore IDE1006 // restore naming rule violation check diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index aafeeb2ab4..9c0cb4bd83 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -683,14 +683,8 @@ private void HandleAddListEvent(NetworkListEvent listEvent) /// /// This method should not be used. It is left over from a previous interface /// - public int LastModifiedTick - { - get - { - // todo: implement proper network tick for NetworkList - return NetworkTickSystem.NoTick; - } - } + [Obsolete("This property is no longer used and will be removed in a future version.")] + public int LastModifiedTick => NetworkTickSystem.NoTick; /// /// Overridden implementation. diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 52e891f8e1..7a2f45b1d1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -448,6 +448,7 @@ private bool TryGetNetworkClient(ulong clientId, out NetworkClient networkClient /// /// not used /// not used + [Obsolete("This method is no longer used and will be removed in a future version.")] protected virtual void InternalOnOwnershipChanged(ulong perviousOwner, ulong newOwner) { diff --git a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs index f731d6fe95..efaceb4a38 100644 --- a/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs +++ b/testproject/Assets/Tests/Manual/NestedNetworkTransforms/ChildMover.cs @@ -45,20 +45,11 @@ public void PlayerIsMoving(float movementDirection) } } - private Transform GetRootParentTransform(Transform transform) - { - if (transform.parent != null) - { - return GetRootParentTransform(transform.parent); - } - return transform; - } - protected override void OnNetworkPostSpawn() { if (CanCommitToTransform) { - m_RootParentTransform = GetRootParentTransform(transform); + m_RootParentTransform = transform.root; if (RandomizeScale) { transform.localScale = transform.localScale * Random.Range(0.5f, 1.5f); diff --git a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs index 228bfbfdd5..1332aed20a 100644 --- a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs +++ b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs @@ -6,7 +6,6 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; -using Debug = UnityEngine.Debug; namespace TestProject.RuntimeTests { @@ -120,16 +119,16 @@ private IEnumerator AutomatedRpcTestsHandler(int numClients) Assert.IsFalse(m_TimedOut); // Log the output for visual confirmation (Acceptance Test for this test) that all RPC test types (tracked by counters) executed multiple times - Debug.Log("Final Host-Server Status Info:"); - Debug.Log(serverRpcTests.GetCurrentServerStatusInfo()); + VerboseDebug("Final Host-Server Status Info:"); + VerboseDebug(serverRpcTests.GetCurrentServerStatusInfo()); foreach (var rpcClientSideTest in clientRpcQueueManualTestInstsances) { - Debug.Log($"Final Client {rpcClientSideTest.NetworkManager.LocalClientId} Status Info:"); - Debug.Log(rpcClientSideTest.GetCurrentClientStatusInfo()); + VerboseDebug($"Final Client {rpcClientSideTest.NetworkManager.LocalClientId} Status Info:"); + VerboseDebug(rpcClientSideTest.GetCurrentClientStatusInfo()); } - Debug.Log($"Total frames updated = {Time.frameCount - startFrameCount} within {Time.realtimeSinceStartup - startTime} seconds."); + VerboseDebug($"Total frames updated = {Time.frameCount - startFrameCount} within {Time.realtimeSinceStartup - startTime} seconds."); } } } From 61417401ce4506288b115f22e8f3fc75f9e5b456 Mon Sep 17 00:00:00 2001 From: Netcode team bot Date: Wed, 27 May 2026 16:20:58 +0200 Subject: [PATCH 07/64] chore: Updated aspects of Netcode package in anticipation of v2.12.0 release (#3998) --- com.unity.netcode.gameobjects/CHANGELOG.md | 17 ++++++++++++++--- com.unity.netcode.gameobjects/package.json | 4 ++-- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 06ebf2c108..4a31c4ff55 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,15 +10,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added a new variant of `UnityTransport.GetDefaultPipelineConfigurations` that takes a reference to the created `NetworkDriver`. This will register all pipeline stages that `UnityTransport` requires, removing the need to manually register them in your own custom driver constructor. (#3980) ### Changed -- `NetworkMetricsPipelineStage` is now defined even when the multiplayer tools package is not installed, removing the need to guard its registration behind a version define when using a custom driver in `UnityTransport`. (#3980) ### Deprecated -- Deprecated a number of methods that were no longer valid or being used. (#3987) ### Removed @@ -32,6 +29,20 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [2.12.0] - 2026-05-24 + +### Added + +- Added a new variant of `UnityTransport.GetDefaultPipelineConfigurations` that takes a reference to the created `NetworkDriver`. This will register all pipeline stages that `UnityTransport` requires, removing the need to manually register them in your own custom driver constructor. (#3980) + +### Changed + +- `NetworkMetricsPipelineStage` is now defined even when the multiplayer tools package is not installed, removing the need to guard its registration behind a version define when using a custom driver in `UnityTransport`. (#3980) + +### Deprecated + +- Deprecated a number of methods that were no longer valid or being used. (#3987) + ### [2.11.2] - 2026-05-01 ### Fixed diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index bd77e48697..5400c16d26 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": "2.12.0", + "version": "2.12.1", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", @@ -15,4 +15,4 @@ "path": "Samples~/Bootstrap" } ] -} +} \ No newline at end of file From 7c992c261b8692e70581de93f02b9e10506061fa Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 29 May 2026 13:55:36 -0400 Subject: [PATCH 08/64] chore: Replace IsSceneObject with InScenePlaced bool (#4000) * chore: Replace IsSceneObject with InScenePlaced bool * Add XML doc * Update small files * Fix findobjects complaints * Fix tests * Turn off logs * ignore invalid scenes on the scene manager --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Editor/InScenePlacedProcessor.cs | 45 +++++++++ .../Editor/InScenePlacedProcessor.cs.meta | 2 + .../Editor/NetworkObjectEditor.cs | 12 +-- .../Runtime/Components/NetworkTransform.cs | 2 +- .../Connection/NetworkConnectionManager.cs | 6 +- .../Runtime/Core/FindObjects.cs | 94 ++++++++++++++++++- .../Runtime/Core/NetworkManager.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 47 ++++++---- .../DefaultSceneManagerHandler.cs | 2 +- .../SceneManagement/NetworkSceneHandle.cs | 6 +- .../SceneManagement/NetworkSceneManager.cs | 31 +++--- .../Runtime/SceneManagement/SceneEventData.cs | 48 +++++----- .../Runtime/Spawning/NetworkSpawnManager.cs | 69 ++++++-------- .../NetworkObjectOnSpawnTests.cs | 1 - .../NetworkObjectSpawnManyObjectsTests.cs | 1 - ...orkVariableBaseInitializesWhenPersisted.cs | 2 - .../Prefabs/NetworkPrefabOverrideTests.cs | 5 - .../IntegrationTestSceneHandler.cs | 50 +++------- .../TestHelpers/NetcodeIntegrationTest.cs | 1 - .../NetcodeIntegrationTestHelpers.cs | 3 - com.unity.netcode.gameobjects/package.json | 2 +- .../Tests/Runtime/NetworkManagerTests.cs | 2 +- .../AttachableBehaviourSceneLoadTests.cs | 2 +- .../ClientSynchronizationModeTests.cs | 61 +++++++++++- .../ClientSynchronizationValidationTest.cs | 4 +- .../NetworkSceneManagerDDOLTests.cs | 12 +-- .../NetworkSceneManagerEventDataPoolTest.cs | 6 +- ...NetworkSceneManagerPopulateInSceneTests.cs | 4 +- .../ParentDynamicUnderInScenePlaced.cs | 10 +- .../Tests/Runtime/PrefabExtendedTests.cs | 2 +- 31 files changed, 345 insertions(+), 190 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs create mode 100644 com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 4a31c4ff55..186d442d32 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 ### Deprecated +- Deprecated the nullable boolean `NetworkObject.IsSceneObject` and introduced `NetworkObject.InScenePlaced`. (#4000) ### Removed diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs new file mode 100644 index 0000000000..423ff4a65c --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -0,0 +1,45 @@ +using UnityEditor; +using UnityEditor.Build; +using UnityEditor.Build.Reporting; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Unity.Netcode.Editor +{ + /// + /// A that sets the property to true for all s in the scene. + /// Ensures that InScenePlaced is always true for all objects in the scene. + /// + /// + /// This will always run as the game enters the scene, + /// + internal class SetInScenePlaced : IProcessSceneWithReport + { + public int callbackOrder => 0; + public void OnProcessScene(Scene scene, BuildReport report) + { + foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) + { + networkObject.InScenePlaced = true; + } + } + } + + /// + /// An that sets the property to false for all s in prefabs. + /// Ensures that InScenePlaced is always false for all prefab objects. + /// This is important because when a prefab is instantiated in the scene, it should be treated as a dynamically spawned object. + /// + internal class InScenePlacedPrefabBuilder : AssetPostprocessor + { + public void OnPostprocessPrefab(GameObject root) + { + var networkObjects = root.GetComponentsInChildren(true); + foreach (var networkObject in networkObjects) + { + networkObject.InScenePlaced = false; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs.meta b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs.meta new file mode 100644 index 0000000000..784aa38575 --- /dev/null +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cda0fb70bdcd545a7983ca78f516bcff \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs index 04e0d1f698..0335896784 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs @@ -67,14 +67,10 @@ public override void OnInspectorGUI() EditorGUILayout.Toggle(nameof(NetworkObject.IsOwner), m_NetworkObject.IsOwner); EditorGUILayout.Toggle(nameof(NetworkObject.IsOwnedByServer), m_NetworkObject.IsOwnedByServer); EditorGUILayout.Toggle(nameof(NetworkObject.IsPlayerObject), m_NetworkObject.IsPlayerObject); - if (m_NetworkObject.IsSceneObject.HasValue) - { - EditorGUILayout.Toggle(nameof(NetworkObject.IsSceneObject), m_NetworkObject.IsSceneObject.Value); - } - else - { - EditorGUILayout.TextField(nameof(NetworkObject.IsSceneObject), "null"); - } +#pragma warning disable CS0618 // Type or member is obsolete + // TODO-3.x: Update name in 3.x branch + EditorGUILayout.Toggle(nameof(NetworkObject.IsSceneObject), m_NetworkObject.InScenePlaced); +#pragma warning restore CS0618 // Type or member is obsolete EditorGUILayout.Toggle(nameof(NetworkObject.DestroyWithScene), m_NetworkObject.DestroyWithScene); EditorGUILayout.TextField(nameof(NetworkObject.NetworkManager), m_NetworkObject.NetworkManager == null ? "null" : m_NetworkObject.NetworkManager.gameObject.name); GUI.enabled = guiEnabled; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 3ca9093b34..0259138b46 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2231,7 +2231,7 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // In-scene placed NetworkObjects parented under a GameObject with no // NetworkObject preserve their lossyScale when synchronizing. - if (parentNetworkObject == null && NetworkObject.IsSceneObject != false) + if (parentNetworkObject == null && NetworkObject.InScenePlaced) { hasParentNetworkObject = true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index aaeb67f4db..1da9ddb3bb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1184,7 +1184,11 @@ internal void CreateAndSpawnPlayer(ulong ownerId) return; } - networkObject.IsSceneObject = false; +#pragma warning disable CS0618 // Type or member is obsolete + // Obsolete with warning means we need the underlying behaviour to keep existing + // TODO: remove in the 3.x branch + networkObject.SetSceneObjectStatus(false); +#pragma warning restore CS0618 // Type or member is obsolete networkObject.NetworkManagerOwner = NetworkManager; networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs index a283cabe52..59329f8a81 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs @@ -1,7 +1,8 @@ -#if NGO_FINDOBJECTS_NOSORTING using System; -#endif +using System.Collections; +using System.Collections.Generic; using System.Runtime.CompilerServices; +using UnityEngine.SceneManagement; using Object = UnityEngine.Object; namespace Unity.Netcode @@ -11,14 +12,14 @@ namespace Unity.Netcode /// /// /// It is intentional that we do not include the UnityEngine namespace in order to avoid - /// over-complicatd define wrapping between versions that do or don't support FindObjectsSortMode. + /// over-complicated define wrapping between versions that do or don't support FindObjectsSortMode. /// internal static class FindObjects { /// /// Replaces to have one place where these changes are applied. /// - /// + /// The type of object to find. Must be a reference type derived from /// When true, inactive objects will be included. /// When true, the array returned will be sorted by identifier. /// Results as an of type T @@ -39,5 +40,90 @@ public static T[] ByType(bool includeInactive = false, bool orderByIdentifier #endif return results; } + + /// + /// Returns an enumerator that enumerates over all the components of a given type in a scene. + /// + /// The scene to use for searching + /// When true, inactive objects will be included. + /// Type of to get from the scene + /// a generator that yields successive NetworkObjects in the current scene + public static IEnumerable FromSceneByType(Scene scene, bool includeInactive) where T : UnityEngine.Component + { + return new ObjectsInSceneEnumerator(scene, includeInactive); + } + + /// + /// An Enumerator that enumerates over each component of type in the given scene. + /// + /// Type of to get from the scene + private struct ObjectsInSceneEnumerator : IEnumerable, IEnumerator where T : UnityEngine.Component + { + private readonly UnityEngine.GameObject[] m_RootObjects; + private int m_RootIndex; + private T[] m_CurrentChildObjects; + private int m_CurrentChildIndex; + + private readonly bool m_IncludeInactive; + + internal ObjectsInSceneEnumerator(Scene scene, bool includeInactive) + { + m_IncludeInactive = includeInactive; + + m_RootObjects = scene.GetRootGameObjects(); + m_RootIndex = 0; + m_CurrentChildObjects = null; + m_CurrentChildIndex = 0; + Current = null; + } + + public void Dispose() { } + + public bool MoveNext() + { + while (m_CurrentChildObjects == null && m_RootIndex < m_RootObjects.Length) + { + m_CurrentChildObjects = m_RootObjects[m_RootIndex].GetComponentsInChildren(m_IncludeInactive); + m_RootIndex++; + + if (m_CurrentChildObjects.Length == 0) + { + m_CurrentChildObjects = null; + } + } + + if (m_CurrentChildObjects != null && m_CurrentChildIndex < m_CurrentChildObjects.Length) + { + Current = m_CurrentChildObjects[m_CurrentChildIndex]; + m_CurrentChildIndex++; + + if (m_CurrentChildIndex >= m_CurrentChildObjects.Length) + { + m_CurrentChildIndex = 0; + m_CurrentChildObjects = null; + } + return true; + } + + Current = null; + return false; + } + + public void Reset() + { + m_RootIndex = 0; + m_CurrentChildObjects = null; + m_CurrentChildIndex = 0; + Current = null; + } + + object IEnumerator.Current => Current; + + public T Current { get; private set; } + + public IEnumerator GetEnumerator() => this; + + IEnumerator IEnumerable.GetEnumerator() => this; + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 55fdd6dbf8..2e7e3a7572 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -1644,7 +1644,7 @@ internal void ShutdownInternal() // place (i.e. sending any last state updates or the like). SpawnManager?.DespawnAndDestroyNetworkObjects(); - SpawnManager?.ServerResetShudownStateForSceneObjects(); + SpawnManager?.ServerResetShutdownStateForSceneObjects(); //// RpcTarget?.Dispose(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index d2f5aa5869..173d7fba8d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -291,7 +291,7 @@ internal void OnValidate() if (GlobalObjectIdHash != oldValue) { // Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed). - if (IsSceneObject.HasValue && IsSceneObject.Value) + if (InScenePlaced) { // Sanity check to make sure this is a scene placed object. if (globalId.identifierType != k_SceneObjectType) @@ -340,7 +340,12 @@ private void CheckForInScenePlaced() EditorUtility.SetDirty(this); } } - IsSceneObject = true; + +#pragma warning disable CS0618 // Type or member is obsolete + // Obsolete with warning means we need the underlying behaviour to keep existing + // TODO-3.x: remove in the 3.x branch + SetSceneObjectStatus(true); +#pragma warning restore CS0618 // Type or member is obsolete // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; @@ -1225,15 +1230,24 @@ private bool InternalHasAuthority() public bool IsSpawned { get; internal set; } /// - /// Gets if the object is a SceneObject, null if it's not yet spawned but is a scene object. + /// Gets if the object is a SceneObject. /// + [Obsolete("Use InScenePlaced instead")] public bool? IsSceneObject { get; internal set; } - //DANGOEXP TODO: Determine if we want to keep this + /// + /// True if this object is placed in a scene; false otherwise. + /// + [field: HideInInspector] + [field: SerializeField] + public bool InScenePlaced { get; internal set; } + /// /// Sets whether this NetworkObject was instantiated as part of a scene /// + /// Only use this when using custom scene loading /// When true, marks this as a scene-instantiated object; when false, marks it as runtime-instantiated + [Obsolete("SetSceneObjectStatus is now calculated during the build.")] public void SetSceneObjectStatus(bool isSceneObject = false) { IsSceneObject = isSceneObject; @@ -1457,7 +1471,7 @@ internal Scene SceneOrigin /// internal NetworkSceneHandle GetSceneOriginHandle() { - if (SceneOriginHandle.IsEmpty() && IsSpawned && IsSceneObject != false) + if (SceneOriginHandle.IsEmpty() && IsSpawned && InScenePlaced) { if (NetworkManager.LogLevel <= LogLevel.Error) { @@ -1622,7 +1636,7 @@ public void NetworkHide(ulong clientId) var message = new DestroyObjectMessage { NetworkObjectId = NetworkObjectId, - DestroyGameObject = !IsSceneObject.Value, + DestroyGameObject = !InScenePlaced, IsDistributedAuthority = NetworkManagerOwner.DistributedAuthorityMode, IsTargetedDestroy = NetworkManagerOwner.DistributedAuthorityMode, TargetClientId = clientId, // Just always populate this value whether we write it or not @@ -1750,7 +1764,7 @@ private void OnDestroy() var isStillValid = gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded; // If we're not the authority and everything is valid and dynamically spawned, then the destroy is not valid. - if (!isAuthorityDestroy && IsSceneObject == false && isStillValid) + if (!isAuthorityDestroy && !InScenePlaced && isStillValid) { if (networkManager.LogLevel <= LogLevel.Error) { @@ -1849,7 +1863,7 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), IsSceneObject.HasValue && IsSceneObject.Value, playerObject, ownerClientId, destroyWithScene)) + if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), InScenePlaced, playerObject, ownerClientId, destroyWithScene)) { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { @@ -2528,8 +2542,7 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa // 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) + if (transform.parent != null && !removeParent && !m_LatestParent.HasValue && InScenePlaced) { var parentNetworkObject = transform.parent.GetComponent(); @@ -3271,7 +3284,7 @@ internal SerializedObject Serialize(ulong targetClientId = NetworkManager.Server NetworkObjectId = NetworkObjectId, OwnerClientId = OwnerClientId, IsPlayerObject = IsPlayerObject, - IsSceneObject = IsSceneObject ?? true, + IsSceneObject = InScenePlaced, DestroyWithScene = DestroyWithScene, DontDestroyWithOwner = DontDestroyWithOwner, HasOwnershipFlags = NetworkManagerOwner.DistributedAuthorityMode, @@ -3456,7 +3469,7 @@ internal void SubscribeToActiveSceneForSynch() { if (ActiveSceneSynchronization) { - if (IsSceneObject.HasValue && !IsSceneObject.Value) + if (!InScenePlaced) { // Just in case it is a recycled NetworkObject, unsubscribe first SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; @@ -3473,7 +3486,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject, // or the NetworkManager is shutting down. - if (!IsSpawned || IsSceneObject != false || NetworkManagerOwner.ShutdownInProgress) + if (!IsSpawned || NetworkManagerOwner.ShutdownInProgress || InScenePlaced) { return; } @@ -3483,7 +3496,7 @@ private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate // and update their scene handles - if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null) + if (gameObject.scene != next && gameObject.transform.parent == null) { SceneManager.MoveGameObjectToScene(gameObject, next); SceneChangedUpdate(next); @@ -3571,7 +3584,7 @@ internal bool UpdateForSceneChanges() // the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed // NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle if (!SceneMigrationSynchronization || !IsSpawned || NetworkManagerOwner.ShutdownInProgress || - !NetworkManagerOwner.NetworkConfig.EnableSceneManagement || IsSceneObject != false || !gameObject) + !NetworkManagerOwner.NetworkConfig.EnableSceneManagement || InScenePlaced || !gameObject) { // Stop checking for a scene migration return false; @@ -3606,7 +3619,7 @@ internal uint CheckForGlobalObjectIdHashOverride() // If scene management is disabled and this is an in-scene placed NetworkObject then go ahead // and send the InScenePlacedSourcePrefab's GlobalObjectIdHash value (i.e. what to dynamically spawn) - if (!networkManager.NetworkConfig.EnableSceneManagement && IsSceneObject.Value && InScenePlacedSourceGlobalObjectIdHash != 0) + if (!networkManager.NetworkConfig.EnableSceneManagement && InScenePlaced && InScenePlacedSourceGlobalObjectIdHash != 0) { return InScenePlacedSourceGlobalObjectIdHash; } @@ -3614,7 +3627,7 @@ internal uint CheckForGlobalObjectIdHashOverride() // If the PrefabGlobalObjectIdHash is a non-zero value and the GlobalObjectIdHash value is // different from the PrefabGlobalObjectIdHash value, then the NetworkObject instance is // an override for the original network prefab (i.e. PrefabGlobalObjectIdHash) - if (!IsSceneObject.Value && GlobalObjectIdHash != PrefabGlobalObjectIdHash) + if (!InScenePlaced && GlobalObjectIdHash != PrefabGlobalObjectIdHash) { // If the PrefabGlobalObjectIdHash is already populated (i.e. InstantiateAndSpawn used), then return this if (PrefabGlobalObjectIdHash != 0) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 5a11a01b4a..6e3954b294 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -311,7 +311,7 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene) { // Only move dynamically spawned NetworkObjects with no parent as the children will follow - if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) + if (networkObject.gameObject.transform.parent == null && !networkObject.InScenePlaced) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneHandle.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneHandle.cs index e544eb1f24..f57414bcc5 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneHandle.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneHandle.cs @@ -34,7 +34,6 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade else { var reader = serializer.GetFastBufferReader(); - // DANGO-TODO Rust needs to be updated to either handle this ulong or to remove the scene store. #if SCENE_MANAGEMENT_SCENE_HANDLE_MUST_USE_ULONG reader.ReadValueSafe(out ulong rawData); m_Handle = SceneHandle.FromRawData(rawData); @@ -87,6 +86,11 @@ internal NetworkSceneHandle(int handle, bool asMock) public int GetRawData() => m_Handle; #endif + public override string ToString() + { + return m_Handle.ToString(); + } + #region Implicit conversions #if SCENE_MANAGEMENT_SCENE_HANDLE_AVAILABLE /// diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 2fbdf1fbe9..78bdc7204a 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -2234,7 +2234,7 @@ private void SynchronizeNetworkObjectScene() // This is only done for dynamically spawned NetworkObjects // Theoretically, a server could have NetworkObjects in a server-side only scene, if the client doesn't have that scene loaded // then skip it (it will reside in the currently active scene in this scenario on the client-side) - if (networkObject.IsSceneObject.Value == false && ServerSceneHandleToClientSceneHandle.ContainsKey(networkObject.NetworkSceneHandle)) + if (!networkObject.InScenePlaced && ServerSceneHandleToClientSceneHandle.ContainsKey(networkObject.NetworkSceneHandle)) { networkObject.SceneOriginHandle = ServerSceneHandleToClientSceneHandle[networkObject.NetworkSceneHandle]; @@ -2709,7 +2709,7 @@ internal void MoveObjectsToDontDestroyOnLoad() if (!networkObject.DestroyWithScene) { // Only move dynamically spawned NetworkObjects with no parent as the children will follow - if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) + if (networkObject.gameObject.transform.parent == null && !networkObject.InScenePlaced) { UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject); // When temporarily migrating to the DDOL, adjust the network and origin scene handles so no messages are generated @@ -2721,10 +2721,9 @@ internal void MoveObjectsToDontDestroyOnLoad() else if (networkObject.HasAuthority) { networkObject.SetIsDestroying(); - var isSceneObject = networkObject.IsSceneObject; // Only destroy non-scene placed NetworkObjects to avoid warnings about destroying in-scene placed NetworkObjects. // (MoveObjectsToDontDestroyOnLoad is only invoked during a scene event type of load and the load scene mode is single) - networkObject.Despawn(isSceneObject.HasValue && isSceneObject.Value == false); + networkObject.Despawn(!networkObject.InScenePlaced); } } } @@ -2745,19 +2744,27 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP { ScenePlacedObjects.Clear(); } - var networkObjects = FindObjects.ByType(); + var sceneHandle = sceneToFilterBy.handle; // 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 // During Client Side Synchronization: We add them on a FIFO basis, for each scene loaded without clearing, and then // at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects - foreach (var networkObjectInstance in networkObjects) + foreach (var networkObjectInstance in FindObjects.FromSceneByType(sceneToFilterBy, true)) { + if (!networkObjectInstance.InScenePlaced) + { + continue; + } + + if (networkObjectInstance.NetworkManagerOwner == null) + { + networkObjectInstance.NetworkManagerOwner = NetworkManager; + } + var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash; - var sceneHandle = networkObjectInstance.gameObject.scene.handle; - // We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes) - if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager || - networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle) + // We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per-scene basis (for additive scenes) + if (networkObjectInstance.NetworkManagerOwner == NetworkManager && networkObjectInstance.isActiveAndEnabled) { if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash)) { @@ -2795,7 +2802,7 @@ internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene) { // only move dynamically spawned network objects, with no parent as child objects will follow, // back into the currently active scene - if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) + if (networkObject.gameObject.transform.parent == null && !networkObject.InScenePlaced) { if (NetworkManager.DistributedAuthorityMode) { @@ -2879,7 +2886,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) } // Ignore in-scene placed NetworkObjects - if (networkObject.IsSceneObject != false) + if (networkObject.InScenePlaced) { // Really, this should ever happen but in case it does if (NetworkManager.LogLevel == LogLevel.Developer) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 0fff313574..0f41aad482 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -133,7 +133,6 @@ internal class SceneEventData : IDisposable private List m_NetworkObjectsSync = new List(); private List m_DespawnedInSceneObjectsSync = new List(); - private Dictionary> m_DespawnedInSceneObjects = new Dictionary>(); /// /// Server Side Re-Synchronization: @@ -369,13 +368,19 @@ internal void AddDespawnedInSceneNetworkObjects() { m_DespawnedInSceneObjectsSync.Clear(); // Find all active and non-active in-scene placed NetworkObjects - var inSceneNetworkObjects = FindObjects.ByType(true, true).Where((c) => c.NetworkManager == m_NetworkManager); - foreach (var sobj in inSceneNetworkObjects) + foreach (var scene in m_NetworkManager.SceneManager.ScenesLoaded.Values) { - if (sobj.IsSceneObject.HasValue && sobj.IsSceneObject.Value && !sobj.IsSpawned) + // Ignore invalid scenes + if (!scene.IsValid()) { - sobj.NetworkManagerOwner = m_NetworkManager; - m_DespawnedInSceneObjectsSync.Add(sobj); + continue; + } + foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) + { + if (networkObject.InScenePlaced && networkObject.NetworkManagerOwner == m_NetworkManager && !networkObject.IsSpawned) + { + m_DespawnedInSceneObjectsSync.Add(networkObject); + } } } } @@ -1009,7 +1014,6 @@ internal void WriteClientSynchronizationResults(FastBufferWriter writer) 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>(); @@ -1018,25 +1022,21 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() // We just need to get the scene InternalBuffer.ReadValueSafe(out NetworkSceneHandle networkSceneHandle); InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); - var sceneRelativeNetworkObjects = new Dictionary(); - if (!sceneCache.ContainsKey(networkSceneHandle)) + + // Check if we already have processed the objects in this scene + if (!sceneCache.TryGetValue(networkSceneHandle, out var sceneRelativeNetworkObjects)) { - if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(networkSceneHandle)) + // If we haven't already cached the objects in this scene, build the cache + sceneRelativeNetworkObjects = new Dictionary(); + if (m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle.TryGetValue(networkSceneHandle, out var localSceneHandle)) { - var localSceneHandle = m_NetworkManager.SceneManager.ServerSceneHandleToClientSceneHandle[networkSceneHandle]; - if (m_NetworkManager.SceneManager.ScenesLoaded.ContainsKey(localSceneHandle)) + if (m_NetworkManager.SceneManager.ScenesLoaded.TryGetValue(localSceneHandle, out var objectRelativeScene)) { - var objectRelativeScene = m_NetworkManager.SceneManager.ScenesLoaded[localSceneHandle]; - - // Find all active and non-active in-scene placed NetworkObjects - var inSceneNetworkObjects = FindObjects.ByType(true, true).Where((c) => - c.GetSceneOriginHandle() == localSceneHandle && (c.IsSceneObject != false)).ToList(); - - foreach (var inSceneObject in inSceneNetworkObjects) + foreach (var networkObject in FindObjects.FromSceneByType(objectRelativeScene, true)) { - if (!sceneRelativeNetworkObjects.ContainsKey(inSceneObject.GlobalObjectIdHash)) + if (networkObject.InScenePlaced) { - sceneRelativeNetworkObjects.Add(inSceneObject.GlobalObjectIdHash, inSceneObject); + sceneRelativeNetworkObjects.TryAdd(networkObject.GlobalObjectIdHash, networkObject); } } // Add this to a cache so we don't have to run this potentially multiple times (nothing will spawn or despawn during this time @@ -1052,10 +1052,6 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() 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.TryGetValue(globalObjectIdHash, out var despawnedObject)) @@ -1143,7 +1139,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) // Notify that all in-scene placed NetworkObjects have been spawned foreach (var networkObject in m_NetworkObjectsSync) { - if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value) + if (networkObject.IsSpawned && networkObject.InScenePlaced) { networkObject.InternalInSceneNetworkObjectsSpawned(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 7a2f45b1d1..46f52bc4f9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1320,11 +1320,15 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong return false; } - networkObject.IsSceneObject = sceneObject; +#pragma warning disable CS0618 // Type or member is obsolete + // Obsolete with warning means we need the underlying behaviour to keep existing + // TODO: remove in the 3.x branch + networkObject.SetSceneObjectStatus(sceneObject); +#pragma warning restore CS0618 // Type or member is obsolete // Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects // Note: Always check SceneOriginHandle directly at this specific location. - if (networkObject.IsSceneObject != false && networkObject.SceneOriginHandle.IsEmpty()) + if (networkObject.InScenePlaced && networkObject.SceneOriginHandle.IsEmpty()) { networkObject.SceneOrigin = networkObject.gameObject.scene; } @@ -1379,17 +1383,6 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.InvokeBehaviourNetworkSpawn(); - // propagate the IsSceneObject setting to child NetworkObjects - var children = networkObject.GetComponentsInChildren(); - foreach (var childObject in children) - { - // Do not propagate the in-scene object setting if a child was dynamically spawned. - if (childObject.IsSceneObject.HasValue && !childObject.IsSceneObject.Value) - { - continue; - } - childObject.IsSceneObject = sceneObject; - } // Only dynamically spawned NetworkObjects are allowed if (!sceneObject) @@ -1404,7 +1397,7 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set // then assign this to the PrefabGlobalObjectIdHash - if (networkObject.IsSceneObject.Value && networkObject.InScenePlacedSourceGlobalObjectIdHash != 0) + if (networkObject.InScenePlaced && networkObject.InScenePlacedSourceGlobalObjectIdHash != 0) { networkObject.PrefabGlobalObjectIdHash = networkObject.InScenePlacedSourceGlobalObjectIdHash; } @@ -1552,14 +1545,17 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa } // Makes scene objects ready to be reused - internal void ServerResetShudownStateForSceneObjects() + internal void ServerResetShutdownStateForSceneObjects() { - var networkObjects = FindObjects.ByType(orderByIdentifier: true).Where((c) => c.IsSceneObject != null && c.IsSceneObject == true); + var networkObjects = FindObjects.ByType(orderByIdentifier: true, includeInactive: true); foreach (var sobj in networkObjects) { + if (!sobj.InScenePlaced) + { + continue; + } sobj.IsSpawned = false; sobj.DestroyWithScene = false; - sobj.IsSceneObject = null; } } @@ -1575,7 +1571,7 @@ internal void ServerDestroySpawnedSceneObjects() foreach (var networkObject in spawnedObjects) { - if (networkObject.IsSceneObject != null && networkObject.IsSceneObject.Value && networkObject.DestroyWithScene + if (networkObject.InScenePlaced && networkObject.DestroyWithScene && networkObject.gameObject.scene != NetworkManager.SceneManager.DontDestroyOnLoadScene) { if (networkObject.IsSpawned && networkObject.HasAuthority) @@ -1619,7 +1615,7 @@ internal void DespawnAndDestroyNetworkObjects() { // If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene // is unloaded. Otherwise, despawn and destroy it. - var shouldDestroy = !(networkObject.IsSceneObject == null || (networkObject.IsSceneObject != null && networkObject.IsSceneObject.Value)); + var shouldDestroy = !networkObject.InScenePlaced; // If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed if (shouldDestroy) @@ -1635,7 +1631,7 @@ internal void DespawnAndDestroyNetworkObjects() // If the child is an in-scene placed NetworkObject then remove the child from the parent (which was dynamically spawned) // and set its parent to root - if (childObject.IsSceneObject != null && childObject.IsSceneObject.Value) + if (childObject.InScenePlaced) { childObject.TryRemoveParentCachedWorldPositionStays(); } @@ -1654,27 +1650,24 @@ internal void DestroySceneObjects() for (int i = 0; i < networkObjects.Length; i++) { - if (networkObjects[i].NetworkManager == NetworkManager) + if (networkObjects[i].NetworkManager == NetworkManager && networkObjects[i].InScenePlaced) { - if (networkObjects[i].IsSceneObject == null || networkObjects[i].IsSceneObject.Value == true) + if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) { - if (NetworkManager.PrefabHandler.ContainsHandler(networkObjects[i])) + if (SpawnedObjects.ContainsKey(networkObjects[i].NetworkObjectId)) { - if (SpawnedObjects.ContainsKey(networkObjects[i].NetworkObjectId)) - { - // This method invokes HandleNetworkPrefabDestroy, we only want to handle this once. - OnDespawnObject(networkObjects[i], false); - } - else // If not spawned, then just invoke the handler - { - NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); - } + // This method invokes HandleNetworkPrefabDestroy, we only want to handle this once. + OnDespawnObject(networkObjects[i], false); } - else + else // If not spawned, then just invoke the handler { - Object.Destroy(networkObjects[i].gameObject); + NetworkManager.PrefabHandler.HandleNetworkPrefabDestroy(networkObjects[i]); } } + else + { + Object.Destroy(networkObjects[i].gameObject); + } } } } @@ -1693,7 +1686,7 @@ internal void ServerSpawnSceneObjectsOnStartSweep() // This used to be two loops. // The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list. // Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true. - if (networkObject.IsSceneObject == null || (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value)) + if (networkObject.InScenePlaced) { var ownerId = networkObject.OwnerClientId; if (NetworkManager.DistributedAuthorityMode) @@ -1744,7 +1737,7 @@ internal void OnDespawnNonAuthorityObject([NotNull] NetworkObject networkObject, } } - if (networkObject.IsSceneObject == false) + if (!networkObject.InScenePlaced) { // If the object is not an in-scene placed NetworkObject, then we always destroy the object on the non-authority side destroyGameObject = true; @@ -1786,7 +1779,7 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro // DistributedAuthorityMode: All clients need to remove the parent locally due to mixed-authority hierarchies and race-conditions if (!NetworkManager.ShutdownInProgress && (NetworkManager.IsServer || distributedAuthority)) { - if (destroyGameObject && networkObject.IsSceneObject == true && !NetworkManager.SceneManager.IsSceneUnloading(networkObject)) + if (destroyGameObject && networkObject.InScenePlaced && !NetworkManager.SceneManager.IsSceneUnloading(networkObject)) { if (NetworkManager.LogLevel <= LogLevel.Normal) { @@ -2113,7 +2106,7 @@ internal void GetObjectDistribution(ulong clientId, ref Dictionary(); networkObject.IsSpawned = true; networkObject.SceneOriginHandle = default; - networkObject.IsSceneObject = false; // This validates invoking GetSceneOriginHandle will not throw an exception for a dynamically spawned NetworkObject // when the scene of origin hasn't been set. var sceneOriginHandle = networkObject.GetSceneOriginHandle(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs index cf786bd0c4..ad643d7ca7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -37,7 +37,6 @@ protected override void OnServerAndClientsCreated() var gameObject = new GameObject("TestObject"); var networkObject = gameObject.AddComponent(); NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); - networkObject.IsSceneObject = false; gameObject.AddComponent(); m_PrefabToSpawn = new NetworkPrefab() { Prefab = gameObject }; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs index 0245602fcd..6e8dc8cde9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableBaseInitializesWhenPersisted.cs @@ -26,7 +26,6 @@ protected override void OnOneTimeSetup() s_NetworkPrefab = new GameObject("PresistPrefab"); var networkObject = s_NetworkPrefab.AddComponent(); networkObject.GlobalObjectIdHash = 8888888; - networkObject.SetSceneObjectStatus(false); s_NetworkPrefab.AddComponent(); s_NetworkPrefab.AddComponent(); // Create enough prefab instance handlers to be re-used for all tests. @@ -374,7 +373,6 @@ public NetworkObject GetInstance() if (PrefabInstances.Count == 0) { instanceToReturn = Object.Instantiate(m_NetworkPrefab).GetComponent(); - instanceToReturn.SetSceneObjectStatus(false); instanceToReturn.gameObject.SetActive(true); } else diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs index 502146fa24..571ab30133 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabOverrideTests.cs @@ -174,11 +174,6 @@ protected override void OnServerAndClientsCreated() networkManager.NetworkConfig.Prefabs.Add(m_ClientSidePlayerPrefab); } - m_PrefabOverride.Prefab.GetComponent().IsSceneObject = false; - m_PrefabOverride.SourcePrefabToOverride.GetComponent().IsSceneObject = false; - m_PrefabOverride.OverridingTargetPrefab.GetComponent().IsSceneObject = false; - m_ClientSidePlayerPrefab.Prefab.GetComponent().IsSceneObject = false; - base.OnServerAndClientsCreated(); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/IntegrationTestSceneHandler.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/IntegrationTestSceneHandler.cs index 2a5c2c35f9..209caf300b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/IntegrationTestSceneHandler.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/IntegrationTestSceneHandler.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; using UnityEngine; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; @@ -141,47 +140,27 @@ private static void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadScen { SceneManager.sceneLoaded -= SceneManager_sceneLoaded; - ProcessInSceneObjects(scene, CurrentQueuedSceneJob.IntegrationTestSceneHandler.NetworkManager); + ProcessInSceneObjects(scene); CurrentQueuedSceneJob.JobType = QueuedSceneJob.JobTypes.Completed; } } /// - /// Handles some pre-spawn processing of in-scene placed NetworkObjects - /// to make sure the appropriate NetworkManagerOwner is assigned. It - /// also makes sure that each in-scene placed NetworkObject has an + /// Handles some pre-spawn processing of in-scene placed NetworkObjects. + /// Makes sure that each in-scene placed NetworkObject has an /// ObjectIdentifier component if one is not assigned to it or its /// children. /// /// the scenes that was just loaded - /// the relative NetworkManager - private static void ProcessInSceneObjects(Scene scene, NetworkManager networkManager) + private static void ProcessInSceneObjects(Scene scene) { - // Get all in-scene placed NeworkObjects that were instantiated when this scene loaded - var inSceneNetworkObjects = FindObjects.ByType(orderByIdentifier: true).Where((c) => c.IsSceneObject != false && c.GetSceneOriginHandle() == scene.handle); - foreach (var sobj in inSceneNetworkObjects) + // Get all in-scene placed NetworkObjects that were instantiated when this scene loaded + foreach (var sceneObject in FindObjects.FromSceneByType(scene, false)) { - ProcessInSceneObject(sobj, networkManager); - } - } - - /// - /// Assures to apply an ObjectNameIdentifier to all children - /// - private static void ProcessInSceneObject(NetworkObject networkObject, NetworkManager networkManager) - { - if (networkObject.GetComponent() == null) - { - networkObject.gameObject.AddComponent(); - var networkObjects = networkObject.gameObject.GetComponentsInChildren(); - foreach (var child in networkObjects) + if (sceneObject.InScenePlaced && sceneObject.GetComponent() == null) { - if (child == networkObject) - { - continue; - } - ProcessInSceneObject(child, networkManager); + sceneObject.gameObject.AddComponent(); } } } @@ -288,7 +267,7 @@ private void Sever_SceneLoaded(Scene scene, LoadSceneMode arg1) if (m_ServerSceneBeingLoaded == scene.name) { SceneManager.sceneLoaded -= Sever_SceneLoaded; - ProcessInSceneObjects(scene, NetworkManager); + ProcessInSceneObjects(scene); } } @@ -708,16 +687,11 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa { // Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects // are despawned. - var networkObjects = FindObjects.ByType(orderByIdentifier: true).Where((c) => c.IsSpawned); var distributedAuthority = networkManager.DistributedAuthorityMode; - foreach (var networkObject in networkObjects) + foreach (var networkObject in FindObjects.FromSceneByType(scene, false)) { - if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle)) + if (!networkObject.IsSpawned) { - if (networkObject != null) - { - VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Ignoring {networkObject.gameObject.name} because it isn't in scene {networkObject.gameObject.scene.name} "); - } continue; } @@ -750,7 +724,7 @@ public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkMa if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene) { // Only move dynamically spawned NetworkObjects with no parent as the children will follow - if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value) + if (networkObject.gameObject.transform.parent == null && !networkObject.InScenePlaced) { VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Moving {networkObject.gameObject.name} because it is in scene {networkObject.gameObject.scene.name} with DWS = {networkObject.DestroyWithScene}."); Object.DontDestroyOnLoad(networkObject.gameObject); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index a6f4a83275..b9c5623759 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -728,7 +728,6 @@ private void CreatePlayerPrefab() m_PlayerPrefab = new GameObject("Player"); OnPlayerPrefabGameObjectCreated(); NetworkObject networkObject = m_PlayerPrefab.AddComponent(); - networkObject.IsSceneObject = false; // Make it a prefab NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 32a0f734d7..99c7f47afc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -690,9 +690,6 @@ public static void MakeNetworkObjectTestPrefab(NetworkObject networkObject, uint networkObject.GlobalObjectIdHash = ++s_AutoIncrementGlobalObjectIdHashCounter; } - // Prevent object from being snapped up as a scene object - networkObject.IsSceneObject = false; - // To avoid issues with integration tests that forget to clean up, // this feature only works with NetcodeIntegrationTest derived classes if (IsNetcodeIntegrationTestRunning) diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 5400c16d26..d2b5daeecd 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": "2.12.1", + "version": "2.13.0", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", diff --git a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs index 411d7e6fec..ccf46cb61c 100644 --- a/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkManagerTests.cs @@ -155,7 +155,7 @@ public IEnumerator ValidateShutdown([Values] ShutdownChecks shutdownCheck) for (int i = spawnedObjects.Count - 1; i >= 0; i--) { var spawnedObject = spawnedObjects[i]; - if (spawnedObject.IsSceneObject != null && spawnedObject.IsSceneObject.Value) + if (spawnedObject.InScenePlaced) { spawnedObject.Despawn(); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/AttachableBehaviourSceneLoadTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/AttachableBehaviourSceneLoadTests.cs index 58d056d8d0..a3e31983e1 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/AttachableBehaviourSceneLoadTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/AttachableBehaviourSceneLoadTests.cs @@ -318,7 +318,7 @@ public IEnumerator AttachedUponSceneTransition([Values] bool detachOnDespawn) var persists = m_Persists == Persists.AttachableNode ? m_TargetInstance.NetworkObjectId : m_SourceInstance.NetworkObjectId; m_DoesNotPersistNetworkObjectIds.Add(doesNotPersist); persistedObjects.Add(networkManager, persists); - Debug.Log($"[{networkManager.name}] Spawned attachable and attached it."); + VerboseDebug($"[{networkManager.name}] Spawned attachable and attached it."); } // This is the actual validation point where the scene is unloaded and either the attachable or diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationModeTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationModeTests.cs index f69bec2d12..0da0940b00 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationModeTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationModeTests.cs @@ -50,6 +50,8 @@ public enum ActiveSceneStates private Dictionary> m_ClientScenesLoaded = new Dictionary>(); + private readonly List m_ObjectsInScenes = new(); + public ClientSynchronizationModeTests(ServerPreloadStates serverPreloadStates) { @@ -66,9 +68,9 @@ protected override IEnumerator OnSetup() { m_TempClientPreLoadedScenes.Clear(); m_ServerLoadedScenes.Clear(); + m_ObjectsInScenes.Clear(); if (m_ServerPreloadState == ServerPreloadStates.PreloadOnServer) { - SceneManager.sceneLoaded += SceneManager_sceneLoaded; yield return LoadScenesOnServer(); } yield return base.OnSetup(); @@ -78,6 +80,7 @@ private IEnumerator LoadScenesOnServer() { if (m_ServerPreloadState == ServerPreloadStates.PreloadOnServer) { + SceneManager.sceneLoaded += SceneManager_sceneLoaded; foreach (var sceneToLoad in m_TestScenes) { SceneManager.LoadSceneAsync(sceneToLoad, LoadSceneMode.Additive); @@ -88,12 +91,16 @@ private IEnumerator LoadScenesOnServer() } else { + m_ServerNetworkManager.SceneManager.OnSceneEvent += ServerSide_OnSceneEvent; + foreach (var sceneToLoad in m_TestScenes) { m_ServerNetworkManager.SceneManager.LoadScene(sceneToLoad, LoadSceneMode.Additive); yield return WaitForConditionOrTimeOut(() => SceneLoadedOnServer(sceneToLoad)); AssertOnTimeout($"[{m_ServerPreloadState}] Timed out waiting for scene {sceneToLoad} to be loaded!"); } + m_ServerNetworkManager.SceneManager.OnSceneEvent -= ServerSide_OnSceneEvent; + } } @@ -165,9 +172,21 @@ private bool AllScenesLoadedOnClients() return true; } + private void TrackObjectsInScene(Scene scene) + { + foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) + { + if (networkObject.InScenePlaced) + { + m_ObjectsInScenes.Add(networkObject); + } + } + } + private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { m_ServerLoadedScenes.Add(scene); + TrackObjectsInScene(scene); } @@ -203,7 +222,6 @@ public IEnumerator PreloadedScenesTest([Values] ClientPreloadStates clientPreloa // If we didn't preload the scenes, then load the scenes via NetworkSceneManager if (m_ServerPreloadState == ServerPreloadStates.NoPreloadOnServer) { - m_ServerNetworkManager.SceneManager.OnSceneEvent += ServerSide_OnSceneEvent; yield return LoadScenesOnServer(); } @@ -215,6 +233,18 @@ public IEnumerator PreloadedScenesTest([Values] ClientPreloadStates clientPreloa SceneManager.SetActiveScene(m_ServerLoadedScenes[2]); } + var authority = GetAuthorityNetworkManager(); + + foreach (var networkObject in m_ObjectsInScenes) + { + Assert.IsTrue(networkObject.IsSpawned, $"[Client-{authority.LocalClientId}] Server object {networkObject.name}, {networkObject.GlobalObjectIdHash} is not spawned!"); + Assert.AreEqual(networkObject.NetworkManagerOwner, authority, $"[Client-{authority.LocalClientId}] networkObject doesn't belong to the client"); + } + + var objToDisable = m_ObjectsInScenes[0]; + objToDisable.Despawn(false); + + // Late join some clients for (int i = 0; i < 1; i++) { @@ -223,6 +253,7 @@ public IEnumerator PreloadedScenesTest([Values] ClientPreloadStates clientPreloa if (clientPreloadStates == ClientPreloadStates.PreloadOnClient) { m_TempClientPreLoadedScenes.Clear(); + m_ObjectsInScenes.Clear(); SceneManager.sceneLoaded += PreLoadClient_SceneLoaded; foreach (var sceneToLoad in m_TestScenes) { @@ -232,17 +263,37 @@ public IEnumerator PreloadedScenesTest([Values] ClientPreloadStates clientPreloa SceneManager.sceneLoaded -= PreLoadClient_SceneLoaded; AssertOnTimeout($"[{clientPreloadStates}] Timed out waiting for client-side scenes to be preloaded!"); } - yield return CreateAndStartNewClient(); + + var newClient = CreateNewClient(); + yield return StartClient(newClient); AssertOnTimeout($"[Client Instance {i + 1}] Timed out waiting for client to start and connect!"); yield return WaitForConditionOrTimeOut(AllScenesLoadedOnClients); - AssertOnTimeout($"[Client-{m_ClientNetworkManagers[i].LocalClientId}] Timed out waiting for all scenes to be synchronized for new client!"); + AssertOnTimeout($"[Client-{newClient.LocalClientId}] Timed out waiting for all scenes to be synchronized for new client!"); + + if (clientPreloadStates == ClientPreloadStates.PreloadOnClient) + { + foreach (var networkObject in m_ObjectsInScenes) + { + if (networkObject.GlobalObjectIdHash == objToDisable.GlobalObjectIdHash) + { + Assert.IsFalse(networkObject.IsSpawned); + } + else + { + Assert.IsTrue(networkObject.IsSpawned, $"[Client-{newClient.LocalClientId}] Client side preloaded object {networkObject.name}, {networkObject.GlobalObjectIdHash} is not spawned!"); + } + Assert.AreEqual(networkObject.NetworkManagerOwner, newClient, $"[Client-{newClient.LocalClientId}] networkObject doesn't belong to the client"); + } + } } } + private void PreLoadClient_SceneLoaded(Scene scene, LoadSceneMode loadSceneMode) { m_TempClientPreLoadedScenes.Add(scene); + TrackObjectsInScene(scene); } private void ClientSide_OnSceneEvent(SceneEvent sceneEvent) @@ -273,6 +324,7 @@ private void ServerSide_OnSceneEvent(SceneEvent sceneEvent) case SceneEventType.LoadComplete: { m_ServerLoadedScenes.Add(sceneEvent.Scene); + TrackObjectsInScene(sceneEvent.Scene); break; } } @@ -283,6 +335,7 @@ protected override IEnumerator OnTearDown() SceneManager.sceneLoaded -= SceneManager_sceneLoaded; m_TempClientPreLoadedScenes.Clear(); m_ClientScenesLoaded.Clear(); + m_ObjectsInScenes.Clear(); return base.OnTearDown(); } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs index 32f88e9221..ea5dfc47ad 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/ClientSynchronizationValidationTest.cs @@ -110,7 +110,7 @@ public IEnumerator ClientVerifySceneBeforeLoading([Values] bool startClientBefor foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects) { var networkObject = spawnedObjectEntry.Value; - if (!networkObject.IsSceneObject.Value) + if (!networkObject.InScenePlaced) { continue; } @@ -138,7 +138,7 @@ public IEnumerator ClientVerifySceneBeforeLoading([Values] bool startClientBefor foreach (var spawnedObjectEntry in m_ServerNetworkManager.SpawnManager.SpawnedObjects) { var networkObject = spawnedObjectEntry.Value; - if (!networkObject.IsSceneObject.Value) + if (!networkObject.InScenePlaced) { continue; } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerDDOLTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerDDOLTests.cs index f38a311ea8..70ca7c24d2 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerDDOLTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerDDOLTests.cs @@ -94,8 +94,8 @@ public IEnumerator InSceneNetworkObjectState([Values(DefaultState.IsEnabled, Def [Values(NetworkObjectType.InScenePlaced, NetworkObjectType.DynamicallySpawned)] NetworkObjectType networkObjectType) { var waitForFullNetworkTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate); - var isActive = activeState == DefaultState.IsEnabled ? true : false; - var isInScene = networkObjectType == NetworkObjectType.InScenePlaced ? true : false; + var isActive = activeState == DefaultState.IsEnabled; + var isInScene = networkObjectType == NetworkObjectType.InScenePlaced; var objectInstance = Object.Instantiate(m_DDOL_ObjectToSpawn); var networkObject = objectInstance.GetComponent(); @@ -110,7 +110,7 @@ public IEnumerator InSceneNetworkObjectState([Values(DefaultState.IsEnabled, Def } // Sets whether we are in-scene or dynamically spawned NetworkObject - ddolBehaviour.SetInScene(isInScene); + networkObject.InScenePlaced = isInScene; networkObject.Spawn(); yield return waitForFullNetworkTick; @@ -148,12 +148,6 @@ public override void OnNetworkSpawn() NetworkObject.DestroyWithScene = false; base.OnNetworkSpawn(); } - - public void SetInScene(bool isInScene) - { - var networkObject = GetComponent(); - networkObject.IsSceneObject = isInScene; - } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs index 7c448830f6..107db75314 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerEventDataPoolTest.cs @@ -381,11 +381,11 @@ private bool CheckNetworkObjectsToSynchronizeSceneChanges(NetworkManager network m_ErrorMsg.Clear(); if (networkManager.SpawnManager.NetworkObjectsToSynchronizeSceneChanges.Count > 0) { - foreach (var entry in networkManager.SpawnManager.NetworkObjectsToSynchronizeSceneChanges) + foreach (var obj in networkManager.SpawnManager.NetworkObjectsToSynchronizeSceneChanges.Values) { - if (entry.Value.IsSceneObject.HasValue && entry.Value.IsSceneObject.Value) + if (obj.InScenePlaced) { - m_ErrorMsg.AppendLine($"{entry.Value.name} still exists within {nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)}!"); + m_ErrorMsg.AppendLine($"{obj.name} still exists within {nameof(NetworkSpawnManager.NetworkObjectsToSynchronizeSceneChanges)}!"); } } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs index bc3a24cb78..1d2302592e 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerPopulateInSceneTests.cs @@ -40,7 +40,7 @@ protected override void OnServerAndClientsCreated() // the scene is loaded (i.e. IsSceneObject is null) var inScenePrefab = CreateNetworkObjectPrefab("NewSceneObject"); var networkObject = inScenePrefab.GetComponent(); - networkObject.IsSceneObject = null; + networkObject.InScenePlaced = true; networkObject.NetworkManagerOwner = m_ServerNetworkManager; m_InSceneObjectList.Add(networkObject.GlobalObjectIdHash, inScenePrefab); @@ -49,7 +49,7 @@ protected override void OnServerAndClientsCreated() // unloading/reloading any scenes. inScenePrefab = CreateNetworkObjectPrefab("SetInSceneObject"); networkObject = inScenePrefab.GetComponent(); - networkObject.IsSceneObject = true; + networkObject.InScenePlaced = true; networkObject.NetworkManagerOwner = m_ServerNetworkManager; m_InSceneObjectList.Add(networkObject.GlobalObjectIdHash, inScenePrefab); } diff --git a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs index 3bf12e7011..2770ed4ce5 100644 --- a/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs +++ b/testproject/Assets/Tests/Runtime/ObjectParenting/ParentDynamicUnderInScenePlaced.cs @@ -74,7 +74,7 @@ private bool TestParentedAndNotInScenePlaced() { // Always assign m_FailedValidation to avoid possible null reference crashes. var serverPlayer = m_FailedValidation = m_ServerNetworkManager.LocalClient.PlayerObject; - if (serverPlayer.transform.parent == null || serverPlayer.IsSceneObject.Value == true) + if (serverPlayer.transform.parent == null || serverPlayer.InScenePlaced) { m_FailedValidation = serverPlayer; return false; @@ -83,7 +83,7 @@ private bool TestParentedAndNotInScenePlaced() foreach (var clientNetworkManager in m_ClientNetworkManagers) { var lateJoinPlayer = clientNetworkManager.LocalClient.PlayerObject; - if (lateJoinPlayer.transform.parent == null || lateJoinPlayer.IsSceneObject.Value == true) + if (lateJoinPlayer.transform.parent == null || lateJoinPlayer.InScenePlaced) { m_FailedValidation = lateJoinPlayer; return false; @@ -93,7 +93,7 @@ private bool TestParentedAndNotInScenePlaced() foreach (var dynamicallySpawned in ParentDynamicUnderInScenePlacedHelper.Instances) { var networkObject = dynamicallySpawned.Value; - if (networkObject.transform.parent == null || networkObject.IsSceneObject.Value == true) + if (networkObject.transform.parent == null || networkObject.InScenePlaced) { m_FailedValidation = networkObject; return false; @@ -124,7 +124,7 @@ public IEnumerator ParentUnderInSceneplaced() // Wait for the host-server's player to be parented under the in-scene placed NetworkObject yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); - AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); + AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.InScenePlaced}) | Was Parented ({m_FailedValidation.transform.position != null})"); m_TargetScenePlacedId = m_ServerNetworkManager.LocalClient.PlayerObject.transform.parent.GetComponent().NetworkObjectId; // Now dynamically spawn a NetworkObject to also test dynamically spawned NetworkObjects being parented @@ -141,7 +141,7 @@ public IEnumerator ParentUnderInSceneplaced() AssertOnTimeout($"[Client-{i + 1}] Failed to find in-scene placed NetworkObject or failed to parent under it!"); } yield return WaitForConditionOrTimeOut(TestParentedAndNotInScenePlaced); - AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.IsSceneObject.Value}) | Was Parented ({m_FailedValidation.transform.position != null})"); + AssertOnTimeout($"[{m_FailedValidation.name}] Failed validation! InScenePlaced ({m_FailedValidation.InScenePlaced}) | Was Parented ({m_FailedValidation.transform.position != null})"); } } diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index d0f7e294cb..909bb7585d 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -163,7 +163,7 @@ private bool ValidateAllClientsSpawnedObjects() var clientSpawnedObject = s_GlobalNetworkObjects[client.LocalClientId][spawnedObject.NetworkObjectId]; // When scene management is disabled, we match against the InScenePlacedSourceGlobalObjectIdHash for in-scene placed NetworkObjects - var spawnedObjectGlobalObjectIdHash = !m_SceneManagementEnabled && spawnedObject.IsSceneObject.Value ? spawnedObject.InScenePlacedSourceGlobalObjectIdHash : spawnedObject.GlobalObjectIdHash; + var spawnedObjectGlobalObjectIdHash = !m_SceneManagementEnabled && spawnedObject.InScenePlaced ? spawnedObject.InScenePlacedSourceGlobalObjectIdHash : spawnedObject.GlobalObjectIdHash; // Validate the GlobalObjectIdHash values match if (clientSpawnedObject.GlobalObjectIdHash != spawnedObjectGlobalObjectIdHash) { From 0a56f44042eee75638d210990682dbcf768e4331 Mon Sep 17 00:00:00 2001 From: Emma Date: Fri, 29 May 2026 17:35:41 -0400 Subject: [PATCH 09/64] fix: Add more detailed logging around RPC messages (#3994) * fix: Add more detailed logging around RPC messages --- .../Runtime/Logging/ContextualLogger.cs | 17 +- .../Runtime/Logging/LogBuilder.cs | 1 + .../Runtime/Logging/LogContext.cs | 76 -------- .../Runtime/Logging/LogContext.meta | 3 + .../Logging/LogContext/CollectionContext.cs | 25 +++ .../LogContext/CollectionContext.cs.meta | 3 + .../{ => LogContext}/GenericContext.cs | 23 ++- .../{ => LogContext}/GenericContext.cs.meta | 0 .../Runtime/Logging/LogContext/LogContext.cs | 169 ++++++++++++++++++ .../{ => LogContext}/LogContext.cs.meta | 0 .../LogContext/LogContextNetworkBehaviour.cs | 21 +++ .../LogContextNetworkBehaviour.cs.meta | 3 + .../LogContextNetworkManager.cs | 0 .../LogContextNetworkManager.cs.meta | 0 .../LogContext/LogContextNetworkObject.cs | 21 +++ .../LogContextNetworkObject.cs.meta | 3 + .../Runtime/Messaging/Messages/RpcMessages.cs | 95 +++++----- .../Tests/Runtime/Rpc/RpcInvocationTests.cs | 11 +- 18 files changed, 335 insertions(+), 136 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext.meta create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs.meta rename com.unity.netcode.gameobjects/Runtime/Logging/{ => LogContext}/GenericContext.cs (78%) rename com.unity.netcode.gameobjects/Runtime/Logging/{ => LogContext}/GenericContext.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs rename com.unity.netcode.gameobjects/Runtime/Logging/{ => LogContext}/LogContext.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs.meta rename com.unity.netcode.gameobjects/Runtime/Logging/{ => LogContext}/LogContextNetworkManager.cs (100%) rename com.unity.netcode.gameobjects/Runtime/Logging/{ => LogContext}/LogContextNetworkManager.cs.meta (100%) create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs create mode 100644 com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs index 006f919ce1..3141a2a3bf 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/ContextualLogger.cs @@ -60,7 +60,7 @@ public ContextualLogger(Object inspectorObject, [NotNull] NetworkManager network } /// Used for the NetworkLog - internal ContextualLogger(NetworkManager networkManager, bool useCompatibilityMode) + internal ContextualLogger(NetworkManager networkManager, bool useCompatibilityMode = false) { m_UseCompatibilityMode = useCompatibilityMode; m_ManagerContext = new LogContextNetworkManager(networkManager); @@ -89,7 +89,7 @@ internal DisposableContext AddDisposableInfo(string key, object value) [Conditional(k_CompilationCondition)] internal void RemoveInfo(string key) { - m_LoggerContext.ClearInfo(key); + m_LoggerContext.RemoveInfo(key); } [HideInCallstack] @@ -125,6 +125,19 @@ public void Exception(Exception exception) { Debug.unityLogger.LogException(exception, m_Object); } + [HideInCallstack] + public void Exception(Exception exception, Context context) + { + // Don't act if the LogLevel is higher than the level of this log + if (m_ManagerContext.LogLevel > context.Level) + { + return; + } + + var message = BuildLog(context); + Debug.unityLogger.LogException(new Exception(message, exception), context.RelevantObjectOverride ?? m_Object); + } + [HideInCallstack] private void Log(LogType logType, Context context) diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs index 0c59e10bd4..c8f8c1ba25 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogBuilder.cs @@ -31,6 +31,7 @@ public void AppendInfo(object key, object value) } public void Append(string value) => m_Builder.Append(value); + public void AppendLine(string value) => m_Builder.AppendLine(value); public string Build() => m_Builder.ToString(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs deleted file mode 100644 index 8c6cbd0bf1..0000000000 --- a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Runtime.CompilerServices; -using Object = UnityEngine.Object; - -namespace Unity.Netcode.Logging -{ - internal interface ILogContext - { - public void AppendTo(LogBuilder builder) - { - } - } - - internal struct Context : ILogContext - { - public readonly LogLevel Level; - private readonly string m_CallingFunction; - internal readonly string Message; - internal Object RelevantObjectOverride; - - private readonly GenericContext m_Other; - - public Context(LogLevel level, string msg, [CallerMemberName] string memberName = "") - { - Level = level; - Message = msg; - m_CallingFunction = memberName; - - m_Other = GenericContext.Create(); - RelevantObjectOverride = null; - } - - internal Context(LogLevel level, string msg, bool noCaller) - { - Level = level; - Message = msg; - m_CallingFunction = null; - - m_Other = GenericContext.Create(); - RelevantObjectOverride = null; - } - - public void AppendTo(LogBuilder builder) - { - // [CallingFunction] - if (!string.IsNullOrEmpty(m_CallingFunction)) - { - builder.AppendTag(m_CallingFunction); - } - - // [SomeContext][SomeName:SomeValue] - m_Other.AppendTo(builder); - - // Human-readable log message - builder.Append(" "); - builder.Append(Message); - } - - public Context AddInfo(object key, object value) - { - m_Other.StoreInfo(key, value); - return this; - } - - public Context AddTag(string msg) - { - m_Other.StoreTag(msg); - return this; - } - - public Context AddObject(Object obj) - { - RelevantObjectOverride = obj; - return this; - } - } -} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.meta new file mode 100644 index 0000000000..7c7f21c305 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1a57adef9bcf4768866c1e922dff4ad1 +timeCreated: 1779217887 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs new file mode 100644 index 0000000000..70c493950d --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; + +namespace Unity.Netcode.Logging +{ + internal delegate string LogCollectionBuilder(TItem item); + internal readonly struct CollectionContext : ILogContext + { + private readonly LogCollectionBuilder m_Delegate; + private readonly IEnumerable m_Collection; + + public CollectionContext(IEnumerable collection, LogCollectionBuilder builder) + { + m_Delegate = builder; + m_Collection = collection; + } + + public void AppendTo(LogBuilder builder) + { + foreach (var item in m_Collection) + { + builder.AppendLine(m_Delegate(item)); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs.meta new file mode 100644 index 0000000000..5323a6eda2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/CollectionContext.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f64de1e86c70433e96280f0c8af6e8f6 +timeCreated: 1779214517 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/GenericContext.cs similarity index 78% rename from com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs rename to com.unity.netcode.gameobjects/Runtime/Logging/LogContext/GenericContext.cs index 47d198f447..643ec30324 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/GenericContext.cs @@ -5,20 +5,20 @@ namespace Unity.Netcode.Logging { internal readonly struct GenericContext : ILogContext, IDisposable { - private readonly List m_Contexts; + private readonly List m_Tags; private readonly Dictionary m_Info; - private GenericContext(List contexts, Dictionary info) + private GenericContext(List tags, Dictionary info) { - m_Contexts = contexts; + m_Tags = tags; m_Info = info; } public void AppendTo(LogBuilder builder) { - if (m_Contexts != null) + if (m_Tags != null) { - foreach (var ctx in m_Contexts) + foreach (var ctx in m_Tags) { builder.AppendTag(ctx); } @@ -33,9 +33,9 @@ public void AppendTo(LogBuilder builder) } } - public void StoreTag(string msg) + public void StoreTag(string tag) { - m_Contexts.Add(msg); + m_Tags.Add(tag); } public void StoreInfo(object key, object value) @@ -43,11 +43,16 @@ public void StoreInfo(object key, object value) m_Info.Add(key, value); } - public void ClearInfo(object key) + public void RemoveInfo(object key) { m_Info?.Remove(key); } + public void RemoveTag(string tag) + { + m_Tags?.Remove(tag); + } + public void Dispose() { PreallocatedStore.Free(this); @@ -76,7 +81,7 @@ internal static GenericContext GetPreallocated() internal static void Free(GenericContext ctx) { - ctx.m_Contexts.Clear(); + ctx.m_Tags.Clear(); ctx.m_Info.Clear(); k_Preallocated.Enqueue(ctx); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/GenericContext.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Logging/GenericContext.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Logging/LogContext/GenericContext.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs new file mode 100644 index 0000000000..a36b6b91fb --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.Logging +{ + internal interface ILogContext + { + public void AppendTo(LogBuilder builder); + } + + internal struct Context : ILogContext, IDisposable + { + public readonly LogLevel Level; + private readonly string m_CallingFunction; + internal readonly string Message; + internal Object RelevantObjectOverride; + + private readonly GenericContext m_Other; + private List m_Prepend; + private List m_Postpend; + + public Context(LogLevel level, string msg, [CallerMemberName] string memberName = "") + { + Level = level; + Message = msg; + m_CallingFunction = memberName; + + m_Other = GenericContext.Create(); + RelevantObjectOverride = null; + m_Prepend = null; + m_Postpend = null; + } + + internal Context(LogLevel level, string msg, bool noCaller) + { + Level = level; + Message = msg; + m_CallingFunction = null; + + m_Other = GenericContext.Create(); + RelevantObjectOverride = null; + m_Prepend = null; + m_Postpend = null; + } + + public void AppendTo(LogBuilder builder) + { + // [CallingFunction] + if (!string.IsNullOrEmpty(m_CallingFunction)) + { + builder.AppendTag(m_CallingFunction); + } + + + // [SomeContext][SomeName:SomeValue] + m_Other.AppendTo(builder); + + if (m_Prepend != null) + { + foreach (var context in m_Prepend) + { + context.AppendTo(builder); + } + } + + // Human-readable log message + builder.Append(" "); + builder.Append(Message); + + if (m_Postpend != null) + { + foreach (var context in m_Postpend) + { + context.AppendTo(builder); + } + } + } + + public Context AddInfo(object key, object value) + { + m_Other.StoreInfo(key, value); + return this; + } + + public Context AddTag(string msg) + { + m_Other.StoreTag(msg); + return this; + } + + public Context AddObject(Object obj) + { + RelevantObjectOverride = obj; + return this; + } + + public Context AddNetworkObject(NetworkObject networkObject) + { + AddPrepend(new LogContextNetworkObject(networkObject)); + RelevantObjectOverride = networkObject; + return this; + } + + public Context AddNetworkBehaviour(NetworkBehaviour networkBehaviour) + { + AddPrepend(new LogContextNetworkBehaviour(networkBehaviour)); + RelevantObjectOverride = networkBehaviour; + return this; + } + + public Context AddCollection(IEnumerable collection, LogCollectionBuilder builder) + { + AddPostpend(new CollectionContext(collection, builder)); + return this; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddPrepend(ILogContext prepend) + { + if (m_Prepend == null) + { + m_Prepend = PreallocatedStore.GetPreallocated(); + } + m_Prepend.Add(prepend); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddPostpend(ILogContext postpend) + { + if (m_Postpend == null) + { + m_Postpend = PreallocatedStore.GetPreallocated(); + } + m_Postpend.Add(postpend); + } + + public void Dispose() + { + m_Other.Dispose(); + PreallocatedStore.Free(m_Prepend); + PreallocatedStore.Free(m_Postpend); + m_Prepend = null; + m_Postpend = null; + } + + private static class PreallocatedStore + { + private static readonly Queue> k_Preallocated = new(); + + internal static List GetPreallocated() + { + if (k_Preallocated.Count > 0) + { + k_Preallocated.Dequeue(); + } + + return new List(); + } + + internal static void Free(List collection) + { + collection.Clear(); + k_Preallocated.Enqueue(collection); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Logging/LogContext.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContext.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs new file mode 100644 index 0000000000..149fd001ff --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs @@ -0,0 +1,21 @@ +namespace Unity.Netcode.Logging +{ + internal readonly struct LogContextNetworkBehaviour : ILogContext + { + private readonly NetworkBehaviour m_NetworkBehaviour; + + public LogContextNetworkBehaviour(NetworkBehaviour networkBehaviour) + { + m_NetworkBehaviour = networkBehaviour; + } + + public void AppendTo(LogBuilder builder) + { + builder.AppendTag(m_NetworkBehaviour.gameObject.name); + if (m_NetworkBehaviour.IsSpawned) + { + builder.AppendInfo(nameof(NetworkBehaviour.NetworkBehaviourId), m_NetworkBehaviour.NetworkBehaviourId); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs.meta new file mode 100644 index 0000000000..a4f9cae7ed --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkBehaviour.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ce875bd203c24cf1b916741eac3a55f8 +timeCreated: 1779217913 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkManager.cs similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs rename to com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkManager.cs diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkManager.cs.meta similarity index 100% rename from com.unity.netcode.gameobjects/Runtime/Logging/LogContextNetworkManager.cs.meta rename to com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkManager.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs new file mode 100644 index 0000000000..d7feb201b1 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs @@ -0,0 +1,21 @@ +namespace Unity.Netcode.Logging +{ + internal readonly struct LogContextNetworkObject : ILogContext + { + private readonly NetworkObject m_NetworkObject; + + public LogContextNetworkObject(NetworkObject networkObject) + { + m_NetworkObject = networkObject; + } + + public void AppendTo(LogBuilder builder) + { + builder.AppendTag(m_NetworkObject.name); + if (m_NetworkObject.IsSpawned) + { + builder.AppendInfo(nameof(NetworkObject.NetworkObjectId), m_NetworkObject.NetworkObjectId); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs.meta b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs.meta new file mode 100644 index 0000000000..9c98d47b80 --- /dev/null +++ b/com.unity.netcode.gameobjects/Runtime/Logging/LogContext/LogContextNetworkObject.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: d6f400599d664e92a747913ca672e02e +timeCreated: 1779217114 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index c8be0c251d..843d875216 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -1,6 +1,6 @@ using System; using Unity.Collections; -using UnityEngine; +using Unity.Netcode.Logging; namespace Unity.Netcode { @@ -16,80 +16,79 @@ public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, string messageType) { + var networkManager = (NetworkManager)context.SystemOwner; 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)) + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject)) { + networkManager.Log.Info(new Context(LogLevel.Developer, $"Received RPC message for {nameof(NetworkObject)} that doesn't exist yet. Deferring the message.").AddInfo("SenderClientId", context.SenderId).AddInfo(nameof(NetworkObject.NetworkObjectId), metadata.NetworkObjectId).AddInfo(nameof(RpcMetadata.NetworkRpcMethodId), metadata.NetworkRpcMethodId)); networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, metadata.NetworkObjectId, reader, ref context, messageType); return false; } - var networkObject = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId]; - var networkBehaviour = networkManager.SpawnManager.SpawnedObjects[metadata.NetworkObjectId].GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); - if (networkBehaviour == null) - { - return false; - } - - if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId)) - { - return false; - } + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkBehaviourId); + ByteUnpacker.ReadValueBitPacked(reader, out metadata.NetworkRpcMethodId); payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) - networkBehaviour.TrackRpcMetricsReceive(ref metadata, ref context, reader.Length); -#endif return true; } public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload, ref __RpcParams rpcParams) { var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject)) { // If the NetworkObject no longer exists then just log a warning when developer mode logging is enabled and exit. // This can happen if NetworkObject is despawned and a client sends an RPC before receiving the despawn message. - if (networkManager.LogLevel == LogLevel.Developer) - { - NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs."); - } - + networkManager.Log.Warning(new Context(LogLevel.Developer, $"Received RPC message for {nameof(NetworkObject)} that doesn't exist yet. Deferring the message.").AddInfo(nameof(NetworkObject.NetworkObjectId), metadata.NetworkObjectId).AddInfo(nameof(RpcMetadata.NetworkRpcMethodId), metadata.NetworkRpcMethodId)); return; } var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); - try + if (networkBehaviour == null) + { + networkManager.Log.Error(new Context(LogLevel.Normal, $"Received RPC message for {nameof(NetworkBehaviour)} that doesn't exist. Dropping RPC message").AddNetworkObject(networkObject).AddInfo(nameof(NetworkBehaviour.NetworkBehaviourId), networkBehaviour.NetworkBehaviourId)); + return; + } + + var type = networkBehaviour.GetType(); + if (!NetworkBehaviour.__rpc_func_table.TryGetValue(type, out var rpcsForBehaviour) || !NetworkBehaviour.__rpc_permission_table.TryGetValue(type, out var permissionsTable)) + { + networkManager.Log.Error(new Context(LogLevel.Normal, $"Rpc table doesn't have RPCs registered for this {nameof(NetworkBehaviour)}. Dropping RPC message").AddNetworkObject(networkObject).AddInfo(nameof(NetworkBehaviour.NetworkBehaviourId), networkBehaviour.NetworkBehaviourId).AddInfo(nameof(NetworkBehaviour), type)); + return; + } + if (!rpcsForBehaviour.TryGetValue(metadata.NetworkRpcMethodId, out var receiveHandler) || !permissionsTable.TryGetValue(metadata.NetworkRpcMethodId, out var permission)) { - var permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId]; + networkManager.Log.Error(new Context(LogLevel.Normal, "Received RPC message for RPC receiver that doesn't exist. Dropping RPC message").AddNetworkBehaviour(networkBehaviour).AddInfo(nameof(RpcMetadata.NetworkRpcMethodId), metadata.NetworkRpcMethodId)); + return; + } - if ((permission == RpcInvokePermission.Server && rpcParams.SenderId != NetworkManager.ServerClientId) || - (permission == RpcInvokePermission.Owner && rpcParams.SenderId != networkObject.OwnerClientId)) - { - if (networkManager.LogLevel <= LogLevel.Developer) - { - NetworkLog.LogErrorServer($"Rpc message received from client-{rpcParams.SenderId} who does not have permission to perform this operation!"); - } - return; - } + if ((permission == RpcInvokePermission.Server && rpcParams.SenderId != NetworkManager.ServerClientId) || + (permission == RpcInvokePermission.Owner && rpcParams.SenderId != networkObject.OwnerClientId)) + { + networkManager.Log.ErrorServer(new Context(LogLevel.Normal, "Rpc message received from a client without permission to perform this operation!. Dropping RPC message").AddNetworkBehaviour(networkBehaviour)); + return; + } + +#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) + networkBehaviour.TrackRpcMetricsReceive(ref metadata, ref context, payload.Length); +#endif - NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); + try + { + receiveHandler(networkBehaviour, payload, rpcParams); } catch (Exception ex) { - Debug.LogException(new Exception($"Unhandled RPC exception!", ex)); - if (networkManager.LogLevel <= LogLevel.Developer) + networkManager.Log.Exception(ex, new Context(LogLevel.Error, "Unhandled RPC exception!").AddNetworkBehaviour(networkBehaviour)); + + var methodId = metadata.NetworkRpcMethodId; + networkManager.Log.Info(new Context(LogLevel.Developer, "RPC Table Contents").AddCollection(rpcsForBehaviour, entry => { - Debug.Log($"RPC Table Contents"); - foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()]) - { - var permission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId]; - Debug.Log($"{entry.Key} | {entry.Value.Method.Name} | {permission}"); - } - } + var invokePermission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][methodId]; + return $"{entry.Key} | {entry.Value.Method.Name} | {invokePermission}"; + })); } } } @@ -270,12 +269,12 @@ public void Handle(ref NetworkContext context) } catch (Exception ex) { - Debug.LogException(ex); + networkManager.Log.Exception(ex); } } else { - NetworkLog.LogErrorServer($"Received {nameof(ForwardServerRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!"); + networkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Received {nameof(ForwardServerRpcMessage)} when not the DAHost! Only DAHost may forward RPC messages!").AddInfo("SenderClientId", context.SenderId)); } ServerRpcMessage.ReadBuffer.Dispose(); ServerRpcMessage.WriteBuffer.Dispose(); @@ -351,7 +350,7 @@ public void Handle(ref NetworkContext context) } else { - NetworkLog.LogErrorServer($"Received {nameof(ForwardClientRpcMessage)} on client-{networkManager.LocalClientId}! Only DAHost may forward RPC messages!"); + networkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Received {nameof(ForwardClientRpcMessage)} when not the DAHost! Only DAHost may forward RPC messages!").AddInfo("SenderClientId", context.SenderId)); } ClientRpcMessage.WriteBuffer.Dispose(); ClientRpcMessage.ReadBuffer.Dispose(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs index 996021d425..613b81f5e5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs @@ -139,7 +139,16 @@ public IEnumerator RpcInvokePermissionSendingTests() [UnityTest] public IEnumerator RpcInvokePermissionReceivingTests() { - var firstClient = GetNonAuthorityNetworkManager(0); + NetworkManager firstClient = null; + foreach (var networkManager in m_NetworkManagers) + { + if (firstClient == null && !networkManager.IsServer && !networkManager.LocalClient.IsSessionOwner) + { + firstClient = networkManager; + } + + networkManager.LogLevel = LogLevel.Error; + } var spawnedObject = SpawnObject(m_Prefab, firstClient).GetComponent(); From 7d03b41b1152d819b9132dbea88e6576dcde1416 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Mon, 1 Jun 2026 14:29:15 +0200 Subject: [PATCH 10/64] refactor: Logging update (#4002) Update RigidbodyContactEventManager to use new logging system --- .../Runtime/Components/RigidbodyContactEventManager.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index bf54c4d964..63031d2a24 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Unity.Collections; using Unity.Jobs; +using Unity.Netcode.Logging; using Unity.Netcode.Runtime; using UnityEngine; @@ -105,14 +106,16 @@ private struct JobResultStruct private readonly Dictionary m_HandlerInfo = new Dictionary(); #endif + private ContextualLogger m_Log; private void OnEnable() { + m_Log = new ContextualLogger(this); m_ResultsArray = new NativeArray(16, Allocator.Persistent); Physics.ContactEvent += Physics_ContactEvent; if (Instance != null) { - NetworkLog.LogError($"[Invalid][Multiple Instances] Found more than one instance of {nameof(RigidbodyContactEventManager)}: {name} and {Instance.name}"); - NetworkLog.LogError($"[Disable][Additional Instance] Disabling {name} instance!"); + m_Log.Error(new Context(LogLevel.Error, $"Found more than one instance of {nameof(RigidbodyContactEventManager)}").AddTag("Invalid").AddTag("Multiple Instances").AddInfo("Instance 1", Instance.name).AddInfo("Instance 2", name)); + m_Log.Error(new Context(LogLevel.Error, $"Disabling instance: ").AddTag("Disable").AddTag("Additional Instance").AddInfo("Instance", name)); gameObject.SetActive(false); return; } From c48f54d94f9828564e4fa3b2ddeaabd0f78e4cd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Mon, 1 Jun 2026 23:09:12 +0200 Subject: [PATCH 11/64] fix: reset static fields for Fast Enter Play Mode (#3956) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: reset static fields for Fast Enter Play Mode support   - Added ResetStaticsOnLoad where possible   - Applied [AutoStaticsCleanup] to generic classes when applicable   - Promoted immutable statics to readonly or const   - Added OnDisable to deregister event subscriptions - chore: disable domain reload and remove treat warnings as errors in test project settings - test: add NetworkManager singleton reset test for Fast Enter Play Mode * fix: add missing message type registrations in MessageDelivery * chore: cleanup   - Remove unused m_ParentedChildren field and related loops in NetworkTransform   - Remove unused m_DespawnedInSceneObjects field in SceneEventData   - Replace static quaternion scratch buffer in QuaternionCompressor with local variables   - Consolidate LogSerializationOrder and EnableSerializationLogs into LogConfiguration - Update summary comments * docs: update CHANGELOG --------- Co-authored-by: Noel Stephens Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkAnimator.cs | 1 - .../Runtime/Components/NetworkTransform.cs | 51 ++++++------------- .../Components/QuaternionCompressor.cs | 48 +++++++---------- .../RigidbodyContactEventManager.cs | 4 +- .../Configuration/CommandLineOptions.cs | 43 +++++++++------- .../Runtime/Core/ComponentFactory.cs | 4 ++ .../Runtime/Core/NetworkBehaviour.cs | 2 - .../Runtime/Core/NetworkManager.cs | 19 ++++++- .../Runtime/Core/NetworkObject.cs | 6 +++ .../Runtime/Core/NetworkUpdateLoop.cs | 37 +++++++++----- .../Runtime/Logging/NetworkLog.cs | 10 +++- .../Messaging/DeferredMessageManager.cs | 4 ++ .../Runtime/Messaging/ILPPMessageProvider.cs | 4 ++ .../Runtime/Messaging/INetworkMessage.cs | 7 +-- .../Runtime/Messaging/MessageDelivery.cs | 29 +++++------ .../Messaging/NetworkMessageManager.cs | 16 +++--- .../Runtime/Metrics/NetworkMetrics.cs | 20 +++----- .../CollectionSerializationUtility.cs | 20 ++++---- .../NetworkVariableSerialization.cs | 2 + .../UserNetworkVariableSerialization.cs | 5 +- .../DefaultSceneManagerHandler.cs | 2 +- .../SceneManagement/NetworkSceneManager.cs | 15 +++++- .../Runtime/SceneManagement/SceneEventData.cs | 15 +++--- .../Runtime/Serialization/FastBufferWriter.cs | 8 +-- .../NetworkBehaviourReference.cs | 7 ++- .../Serialization/NetworkObjectReference.cs | 8 +-- .../SinglePlayer/SinglePlayerTransport.cs | 6 ++- .../Runtime/Transports/UTP/UnityTransport.cs | 21 ++++++-- .../TestHelpers/NetcodeIntegrationTest.cs | 2 - .../ProjectSettings/EditorSettings.asset | 2 +- .../ProjectSettings/ProjectSettings.asset | 3 +- 32 files changed, 238 insertions(+), 184 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 186d442d32..0a4aa4fde3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) ### Changed diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 7d82872890..3fb8166408 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -706,7 +706,6 @@ protected virtual bool OnIsServerAuthoritative() private int[] m_TransitionHash; private int[] m_AnimationHash; private float[] m_LayerWeights; - private static byte[] s_EmptyArray = new byte[] { }; private List m_ParametersToUpdate; private RpcParams m_RpcParams; private IGroupRpcTarget m_TargetGroup; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 0259138b46..18fd9cd88f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -24,6 +24,18 @@ public class NetworkTransform : NetworkBehaviour [HideInInspector] [SerializeField] internal bool NetworkTransformExpanded; + + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() + { + CurrentTick = 0; + TrackStateUpdateId = false; + AssignDefaultInterpolationType = false; + DefaultInterpolationType = default; + s_NetworkTickRegistration = new Dictionary(); + InterpolationBufferTickOffset = 0; + s_TickSynchPosition = 0; + } #endif internal enum Axis { X, Y, Z } @@ -1361,7 +1373,7 @@ public enum AuthorityModes /// /// When set each state update will contain a state identifier /// - internal static bool TrackStateUpdateId = false; + internal static bool TrackStateUpdateId; /// /// Enabled by default. @@ -2062,19 +2074,6 @@ private void TryCommitTransform(bool synchronize = false, bool settingState = fa childNetworkTransform.OnNetworkTick(true); } } - - // Synchronize any parented children with the parent's motion - foreach (var child in m_ParentedChildren) - { - // Synchronize any nested NetworkTransforms of the child with the parent's - foreach (var childNetworkTransform in child.NetworkTransforms) - { - if (childNetworkTransform.CanCommitToTransform) - { - childNetworkTransform.OnNetworkTick(true); - } - } - } } } } @@ -2221,13 +2220,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is // values are applied. var hasParentNetworkObject = false; - var parentNetworkObject = (NetworkObject)null; - // If the NetworkObject belonging to this NetworkTransform instance has a parent // (i.e. this handles nested NetworkTransforms under a parent at some layer above) if (NetworkObject.transform.parent != null) { - parentNetworkObject = NetworkObject.transform.parent.GetComponent(); + var parentNetworkObject = NetworkObject.transform.parent.GetComponent(); // In-scene placed NetworkObjects parented under a GameObject with no // NetworkObject preserve their lossyScale when synchronizing. @@ -3363,19 +3360,6 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf childNetworkTransform.OnNetworkTick(true); } } - - // Synchronize any parented children with the parent's motion - foreach (var child in m_ParentedChildren) - { - // Synchronize any nested NetworkTransforms of the child with the parent's - foreach (var childNetworkTransform in child.NetworkTransforms) - { - if (childNetworkTransform.CanCommitToTransform) - { - childNetworkTransform.OnNetworkTick(true); - } - } - } } // Provide notifications when the state has been updated @@ -3634,8 +3618,6 @@ internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManag /// public override void OnNetworkSpawn() { - m_ParentedChildren.Clear(); - Initialize(); if (CanCommitToTransform && !SwitchTransformSpaceWhenParented) @@ -3647,7 +3629,6 @@ public override void OnNetworkSpawn() private void CleanUpOnDestroyOrDespawn() { - m_ParentedChildren.Clear(); #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D var forUpdate = !m_UseRigidbodyForMotion; #else @@ -3861,7 +3842,6 @@ protected override void OnOwnershipChanged(ulong previous, ulong current) } internal bool IsNested; - private List m_ParentedChildren = new List(); private bool m_IsFirstNetworkTransform; @@ -4694,7 +4674,7 @@ internal static void UpdateNetworkTick(NetworkManager networkManager) /// The default value is 1 tick (plus the tick latency). When running on a local network, reducing this to 0 is recommended.
/// /// - public static int InterpolationBufferTickOffset = 0; + public static int InterpolationBufferTickOffset; internal static float GetTickLatency(NetworkManager networkManager) { if (networkManager.IsListening) @@ -4799,6 +4779,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager) } } } + private static int s_TickSynchPosition; private int m_NextTickSync; diff --git a/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs index f4d9b25dd2..3c338a2a84 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs @@ -4,7 +4,7 @@ namespace Unity.Netcode { /// - /// A Smallest Three Quaternion Compressor Implementation + /// The Smallest Three Quaternion Compressor Implementation /// /// /// Explanation of why "The smallest three": @@ -26,14 +26,14 @@ public static class QuaternionCompressor // We can further improve the encoding compression by dividing k_SqrtTwoOverTwo into 1.0f and multiplying that // by the precision mask (minor reduction of runtime calculations) - private const float k_CompressionEcodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask; + private const float k_CompressionEncodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask; // Used to shift the negative bit to the 10th bit position when compressing and encoding private const ushort k_ShiftNegativeBit = 9; // We can do the same for our decoding and decompression by dividing k_PrecisionMask into 1.0 and multiplying // that by k_SqrtTwoOverTwo (minor reduction of runtime calculations) - private const float k_DcompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding; + private const float k_DecompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding; // The sign bit position (10th bit) used when decompressing and decoding private const ushort k_NegShortBit = 0x200; @@ -42,9 +42,6 @@ public static class QuaternionCompressor private const ushort k_True = 1; private const ushort k_False = 0; - // Used to store the absolute value of the 4 quaternion elements - private static Quaternion s_QuatAbsValues = Quaternion.identity; - /// /// Compresses a Quaternion into an unsigned integer /// @@ -54,38 +51,31 @@ public static class QuaternionCompressor public static uint CompressQuaternion(ref Quaternion quaternion) { // Store off the absolute value for each Quaternion element - s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]); - s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]); - s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]); - s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]); + var quatAbsValue0 = Mathf.Abs(quaternion[0]); + var quatAbsValue1 = Mathf.Abs(quaternion[1]); + var quatAbsValue2 = Mathf.Abs(quaternion[2]); + var quatAbsValue3 = Mathf.Abs(quaternion[3]); // Get the largest element value of the quaternion to know what the remaining "Smallest Three" values are - var quatMax = Mathf.Max(s_QuatAbsValues[0], s_QuatAbsValues[1], s_QuatAbsValues[2], s_QuatAbsValues[3]); + var quatMax = Mathf.Max(quatAbsValue0, quatAbsValue1, quatAbsValue2, quatAbsValue3); - // Find the index of the largest element so we can skip that element while compressing and decompressing - var indexToSkip = (ushort)(s_QuatAbsValues[0] == quatMax ? 0 : s_QuatAbsValues[1] == quatMax ? 1 : s_QuatAbsValues[2] == quatMax ? 2 : 3); + // Find the index of the largest element, so we can skip that element while compressing and decompressing + var indexToSkip = (ushort)(quatAbsValue0 == quatMax ? 0 : quatAbsValue1 == quatMax ? 1 : quatAbsValue2 == quatMax ? 2 : 3); // Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion. - var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False); // Start with the index to skip which will be shifted to the highest two bits var compressed = (uint)indexToSkip; - // Step 1: Start with the first element - var currentIndex = 0; - - // Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4 - // Step 3: Get the sign of the element we are processing. If it is the not the same as the largest value's sign bit then we set the bit - // Step 4: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEcodingMask and round that result up - compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed; - currentIndex++; - // Repeat the last 3 steps for the remaining elements - compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed; - currentIndex++; - compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed; - currentIndex++; - compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed; + // Step 1: If we are on the index to skip, preserve the current compressed value, otherwise proceed to step 2 and 3 + // Step 2: Get the sign of the element we are processing. If it is not the same as the largest value's sign bit then we set the bit + // Step 3: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEncodingMask and round that result up + compressed = 0 != indexToSkip ? (compressed << 10) | (uint)((quaternion[0] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue0) : compressed; + // Repeat the 3 steps for the remaining elements + compressed = 1 != indexToSkip ? (compressed << 10) | (uint)((quaternion[1] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue1) : compressed; + compressed = 2 != indexToSkip ? (compressed << 10) | (uint)((quaternion[2] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue2) : compressed; + compressed = 3 != indexToSkip ? (compressed << 10) | (uint)((quaternion[3] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue3) : compressed; // Return the compress quaternion return compressed; @@ -111,7 +101,7 @@ public static void DecompressQuaternion(ref Quaternion quaternion, uint compress continue; } // Check the negative bit and multiply that result with the decompressed and decoded value - quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask); + quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DecompressionDecodingMask); sumOfSquaredMagnitudes += quaternion[i] * quaternion[i]; compressed = compressed >> 10; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs index 63031d2a24..3c94eae424 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs @@ -21,7 +21,7 @@ public struct ContactEventHandlerInfo ///
public bool ProvideNonRigidBodyContactEvents; /// - /// When set to true, the will prioritize invoking

+ /// When set to true, the will prioritize invoking
/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a is owned by the local client
/// will assure is only invoked on the authoritative side. ///
@@ -112,7 +112,7 @@ private void OnEnable() m_Log = new ContextualLogger(this); m_ResultsArray = new NativeArray(16, Allocator.Persistent); Physics.ContactEvent += Physics_ContactEvent; - if (Instance != null) + if (Instance != null && Instance != this) { m_Log.Error(new Context(LogLevel.Error, $"Found more than one instance of {nameof(RigidbodyContactEventManager)}").AddTag("Invalid").AddTag("Multiple Instances").AddInfo("Instance 1", Instance.name).AddInfo("Instance 2", name)); m_Log.Error(new Context(LogLevel.Error, $"Disabling instance: ").AddTag("Disable").AddTag("Additional Instance").AddInfo("Instance", name)); diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/CommandLineOptions.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/CommandLineOptions.cs index c6f9b4a222..67878b125a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/CommandLineOptions.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/CommandLineOptions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using UnityEngine; namespace Unity.Netcode { @@ -13,6 +12,7 @@ public class CommandLineOptions /// /// Command-line options singleton /// + [Obsolete("Not used anymore replaced by TryGetArg")] public static CommandLineOptions Instance { get @@ -30,34 +30,41 @@ private set } private static CommandLineOptions s_Instance; - [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] - private static void RuntimeInitializeOnLoad() => Instance = new CommandLineOptions(); - // Contains the current application instance domain's command line arguments - internal static List CommandLineArguments = new List(); - - // Invoked upon application start, after scene load - [RuntimeInitializeOnLoadMethod] - private static void ParseCommandLineArguments() - { - // Get all the command line arguments to be parsed later and/or modified - // prior to being parsed (for testing purposes). - CommandLineArguments = new List(Environment.GetCommandLineArgs()); - } + private static readonly List k_CommandLineArguments = new List(Environment.GetCommandLineArgs()); /// - /// Returns the value of an argument or null if there the argument is not present + /// Returns the value of an argument or null if the argument is not present /// /// The name of the argument /// Value of the command line argument passed in. + [Obsolete("Not used anymore replaced by TryGetArg")] public string GetArg(string arg) { - var argIndex = CommandLineArguments.IndexOf(arg); - if (argIndex >= 0 && argIndex < CommandLineArguments.Count - 1) + var argIndex = k_CommandLineArguments.IndexOf(arg); + if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1) { - return CommandLineArguments[argIndex + 1]; + return k_CommandLineArguments[argIndex + 1]; } return null; } + + /// + /// Returns true if the argument was found. + /// + /// The name of the argument to look up. + /// The argument's value, or if not found. + /// true if the argument was found; otherwise false. + public static bool TryGetArg(string arg, out string argValue) + { + var argIndex = k_CommandLineArguments.IndexOf(arg); + if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1) + { + argValue = k_CommandLineArguments[argIndex + 1]; + return true; + } + argValue = null; + return false; + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs b/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs index 52945c4e46..6733429ee2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs @@ -14,6 +14,10 @@ internal static class ComponentFactory internal delegate object CreateObjectDelegate(NetworkManager networkManager); private static Dictionary s_Delegates = new Dictionary(); +#if UNITY_EDITOR + [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() => s_Delegates = new Dictionary(); +#endif /// /// Instantiates an instance of a given interface diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 59f9e637b6..28ec5033b8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -1299,8 +1299,6 @@ internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false } } - internal static bool LogSentVariableUpdateMessage; - private bool CouldHaveDirtyNetworkVariables() { // TODO: There should be a better way by reading one dirty variable vs. 'n' diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 2e7e3a7572..4c7ca603db 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -21,6 +21,20 @@ namespace Unity.Netcode [HelpURL(HelpUrls.NetworkManager)] public class NetworkManager : MonoBehaviour, INetworkUpdateSystem { +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() + { + Singleton = null; + OnInstantiated = null; + OnDestroying = null; + OnSingletonReady = null; + OnNetworkManagerReset = null; + IsDistributedAuthority = false; + s_SerializedType = new List(); + DisableNotOptimizedSerializedType = false; + } +#endif /// /// Subscribe to this static event to get notifications when a instance has been instantiated. /// @@ -31,7 +45,6 @@ public class NetworkManager : MonoBehaviour, INetworkUpdateSystem /// public static event Action OnDestroying; - #if UNITY_EDITOR // Inspector view expand/collapse settings for this derived child class [HideInInspector] @@ -1800,6 +1813,10 @@ internal abstract class NetcodeAnalytics internal static ResetNetworkManagerDelegate OnNetworkManagerReset; + + /// + /// This is called by the Unity Editor reset button. See which is handled in "NetworkManagerHelper.cs". + /// private void Reset() { OnNetworkManagerReset?.Invoke(this); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 173d7fba8d..73cc6c7e2d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1741,6 +1741,8 @@ internal void SetIsDestroying() private void OnDestroy() { + SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; + // Apply the is destroying flag SetIsDestroying(); @@ -2520,6 +2522,10 @@ private void OnTransformParentChanged() // 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(); +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() => OrphanChildren = new HashSet(); +#endif internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false, bool enableNotification = true) { diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs index c48dea0333..5ac60e2cd4 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs @@ -70,19 +70,19 @@ public enum NetworkUpdateStage : byte ///
public static class NetworkUpdateLoop { - private static Dictionary> s_UpdateSystem_Sets; - private static Dictionary s_UpdateSystem_Arrays; - private const int k_UpdateSystem_InitialArrayCapacity = 1024; + private static Dictionary> s_UpdateSystemSets; + private static Dictionary s_UpdateSystemArrays; + private const int k_UpdateSystemInitialArrayCapacity = 1024; static NetworkUpdateLoop() { - s_UpdateSystem_Sets = new Dictionary>(); - s_UpdateSystem_Arrays = new Dictionary(); + s_UpdateSystemSets = new Dictionary>(); + s_UpdateSystemArrays = new Dictionary(); foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage))) { - s_UpdateSystem_Sets.Add(updateStage, new HashSet()); - s_UpdateSystem_Arrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystem_InitialArrayCapacity]); + s_UpdateSystemSets.Add(updateStage, new HashSet()); + s_UpdateSystemArrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystemInitialArrayCapacity]); } } @@ -105,19 +105,19 @@ public static void RegisterAllNetworkUpdates(this INetworkUpdateSystem updateSys /// The being registered for the implementation public static void RegisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update) { - var sysSet = s_UpdateSystem_Sets[updateStage]; + var sysSet = s_UpdateSystemSets[updateStage]; if (!sysSet.Contains(updateSystem)) { sysSet.Add(updateSystem); int setLen = sysSet.Count; - var sysArr = s_UpdateSystem_Arrays[updateStage]; + var sysArr = s_UpdateSystemArrays[updateStage]; int arrLen = sysArr.Length; if (setLen > arrLen) { // double capacity - sysArr = s_UpdateSystem_Arrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2]; + sysArr = s_UpdateSystemArrays[updateStage] = new INetworkUpdateSystem[arrLen *= 2]; } sysSet.CopyTo(sysArr); @@ -149,13 +149,13 @@ public static void UnregisterAllNetworkUpdates(this INetworkUpdateSystem updateS /// The to be deregistered from the implementation public static void UnregisterNetworkUpdate(this INetworkUpdateSystem updateSystem, NetworkUpdateStage updateStage = NetworkUpdateStage.Update) { - var sysSet = s_UpdateSystem_Sets[updateStage]; + var sysSet = s_UpdateSystemSets[updateStage]; if (sysSet.Contains(updateSystem)) { sysSet.Remove(updateSystem); int setLen = sysSet.Count; - var sysArr = s_UpdateSystem_Arrays[updateStage]; + var sysArr = s_UpdateSystemArrays[updateStage]; int arrLen = sysArr.Length; sysSet.CopyTo(sysArr); @@ -177,7 +177,7 @@ internal static void RunNetworkUpdateStage(NetworkUpdateStage updateStage) { UpdateStage = updateStage; - var sysArr = s_UpdateSystem_Arrays[updateStage]; + var sysArr = s_UpdateSystemArrays[updateStage]; int arrLen = sysArr.Length; for (int curIdx = 0; curIdx < arrLen; curIdx++) { @@ -291,6 +291,17 @@ public static PlayerLoopSystem CreateLoopSystem() [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void Initialize() { +#if UNITY_EDITOR + // Reset statics + s_UpdateSystemSets = new Dictionary>(); + s_UpdateSystemArrays = new Dictionary(); + foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage))) + { + s_UpdateSystemSets.Add(updateStage, new HashSet()); + s_UpdateSystemArrays.Add(updateStage, new INetworkUpdateSystem[k_UpdateSystemInitialArrayCapacity]); + } + UpdateStage = default; +#endif UnregisterLoopSystems(); RegisterLoopSystems(); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 29a092e77d..12fd9a60bb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -6,13 +6,19 @@ namespace Unity.Netcode { + /// + /// Log configuration containing : + /// - used in LogContextNetworkManager.cs + /// - used in SceneEventData.cs + /// internal struct LogConfiguration { internal bool LogNetworkManagerRole; + internal bool LogSerializationOrder; } /// - /// Helper class for logging + /// Helper class for logging. /// public static class NetworkLog { @@ -58,7 +64,7 @@ internal static void ConfigureIntegrationTestLogging(NetworkManager networkManag internal static void LogWarning(Context context) => s_Log.Warning(context); /// - /// Locally logs a error log with Netcode prefixing. + /// Locally logs an error log with Netcode prefixing. /// /// The message to log [HideInCallstack] diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs index f05d000fec..4d0cd8fec8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs @@ -96,6 +96,10 @@ public virtual unsafe void CleanupStaleTriggers() /// Used for testing purposes ///
internal static bool IncludeMessageType = true; +#if UNITY_EDITOR + [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() => IncludeMessageType = true; +#endif private string GetWarningMessage(IDeferredNetworkMessageManager.TriggerType triggerType, ulong key, TriggerInfo triggerInfo, float spawnTimeout) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index ec460cc821..21413a4fae 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -53,6 +53,10 @@ internal struct ILPPMessageProvider : INetworkMessageProvider // Enable this for integration tests that need no message types defined internal static bool IntegrationTestNoMessages; +#if UNITY_EDITOR + [UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() => IntegrationTestNoMessages = false; +#endif /// /// Returns a table of message type to NetworkMessageTypes enum value diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs index fe481d25f5..7417bd6b85 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/INetworkMessage.cs @@ -1,4 +1,3 @@ - namespace Unity.Netcode { /// @@ -45,8 +44,10 @@ internal interface INetworkMessage public int Version { get; } } - - internal static class MessageDeliveryType where T : INetworkMessage +#if UNITY_6000_6_OR_NEWER + [Scripting.LifecycleManagement.AutoStaticsCleanup] +#endif + internal static partial class MessageDeliveryType where T : INetworkMessage { internal static NetworkDelivery DefaultDelivery { get; private set; } internal static void Initialize() diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageDelivery.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageDelivery.cs index e8cf2ca6db..cc54d9d102 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/MessageDelivery.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/MessageDelivery.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Unity.Netcode; -using UnityEditor; using UnityEngine; internal static class MessageDelivery @@ -15,17 +14,21 @@ internal static class MessageDelivery /// when sending the message via public API. /// - Skip the time sync messages since it has always used unreliable network delivery. /// - private static HashSet s_SkipMessageTypes = new HashSet(){ + private static readonly HashSet k_SkipMessageTypes = new HashSet(){ NetworkMessageTypes.NamedMessage, NetworkMessageTypes.Unnamed}; - [RuntimeInitializeOnLoadMethod] + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void OnApplicationStart() { +#if UNITY_EDITOR + s_MessageToDelivery = new Dictionary(); + s_MessageToMessageType = new Dictionary(); +#endif UpdateMessageTypes(); } /// - /// FIrst pass at providing an easier path to configuring the network + /// First pass at providing an easier path to configuring the network /// delivery type for the message type. /// TODO: Once coalesces all reliable messages /// and/or organizes by a more unified order of operation tracking built into the @@ -40,7 +43,7 @@ private static void UpdateMessageTypes() foreach (var messageTypeObject in networkMessageTypes) { var messageType = (NetworkMessageTypes)messageTypeObject; - if (s_SkipMessageTypes.Contains(messageType)) + if (k_SkipMessageTypes.Contains(messageType)) { continue; } @@ -56,9 +59,13 @@ private static void UpdateMessageTypes() MessageDeliveryType.Initialize(); MessageDeliveryType.Initialize(); MessageDeliveryType.Initialize(); + MessageDeliveryType.Initialize(); + MessageDeliveryType.Initialize(); + MessageDeliveryType.Initialize(); MessageDeliveryType.Initialize(); MessageDeliveryType.Initialize(); MessageDeliveryType.Initialize(); + MessageDeliveryType.Initialize(); // RpcMessage.cs { MessageDeliveryType.Initialize(); @@ -71,18 +78,10 @@ private static void UpdateMessageTypes() MessageDeliveryType.Initialize(); } -#if UNITY_EDITOR - [InitializeOnLoadMethod] - [InitializeOnEnterPlayMode] - private static void OnEnterPlayMode() - { - UpdateMessageTypes(); - } -#endif internal static NetworkDelivery GetDelivery(Type type) { // Return the default if not registered or null - if (type == null || s_SkipMessageTypes.Contains(s_MessageToMessageType[type])) + if (type == null || k_SkipMessageTypes.Contains(s_MessageToMessageType[type])) { return NetworkDelivery.ReliableFragmentedSequenced; } @@ -91,7 +90,7 @@ internal static NetworkDelivery GetDelivery(Type type) internal static NetworkDelivery GetDelivery(NetworkMessageTypes messageType) { - if (s_SkipMessageTypes.Contains(messageType)) + if (k_SkipMessageTypes.Contains(messageType)) { throw new Exception($"{messageType} is not registered in the message type to network delivery map!"); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index c66aa62273..53714455c6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -34,9 +34,9 @@ public InvalidMessageStructureException(string issue) : base(issue) internal class NetworkMessageManager : IDisposable { public bool StopProcessing = false; - private static Type s_ConnectionApprovedType = typeof(ConnectionApprovedMessage); - private static Type s_ConnectionRequestType = typeof(ConnectionRequestMessage); - private static Type s_DisconnectReasonType = typeof(DisconnectReasonMessage); + private static readonly Type k_ConnectionApprovedType = typeof(ConnectionApprovedMessage); + private static readonly Type k_ConnectionRequestType = typeof(ConnectionRequestMessage); + private static readonly Type k_DisconnectReasonType = typeof(DisconnectReasonMessage); private struct ReceiveQueueItem { @@ -149,8 +149,6 @@ public NetworkMessageManager(INetworkMessageSender sender, object owner, INetwor } } - internal static bool EnableMessageOrderConsoleLog = false; - public void Dispose() { if (m_Disposed) @@ -549,7 +547,7 @@ internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = fals // 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 (messageType != s_ConnectionRequestType && messageType != s_ConnectionApprovedType && messageType != s_DisconnectReasonType && context.SenderId != manager.m_LocalClientId) + if (messageType != k_ConnectionRequestType && messageType != k_ConnectionApprovedType && messageType != k_DisconnectReasonType && context.SenderId != manager.m_LocalClientId) { messageVersion = manager.GetMessageVersion(messageType, context.SenderId, true); if (messageVersion < 0) @@ -603,7 +601,7 @@ internal int SendMessage(ref TMessageType messa 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) != s_ConnectionRequestType) + if (typeof(TMessageType) != k_ConnectionRequestType) { messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); if (messageVersion < 0) @@ -657,7 +655,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t // 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) != s_ConnectionRequestType) + if (typeof(TMessageType) != k_ConnectionRequestType) { var messageVersion = GetMessageVersion(typeof(TMessageType), clientIds[i]); if (messageVersion < 0) @@ -741,7 +739,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t // 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) != s_ConnectionRequestType) + if (typeof(TMessageType) != k_ConnectionRequestType) { messageVersion = GetMessageVersion(typeof(TMessageType), clientId); if (messageVersion < 0) diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs index b171932103..4bb7bb20a0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/NetworkMetrics.cs @@ -11,26 +11,22 @@ namespace Unity.Netcode internal class NetworkMetrics : INetworkMetrics { private const ulong k_MaxMetricsPerFrame = 1000L; - private static Dictionary s_SceneEventTypeNames; - private static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame"); + private static readonly Dictionary k_SceneEventTypeNames; + private static readonly ProfilerMarker k_FrameDispatch; static NetworkMetrics() { - s_SceneEventTypeNames = new Dictionary(); + k_SceneEventTypeNames = new Dictionary(); foreach (SceneEventType type in Enum.GetValues(typeof(SceneEventType))) { - s_SceneEventTypeNames[(uint)type] = type.ToString(); + k_SceneEventTypeNames[(uint)type] = type.ToString(); } + k_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame"); } private static string GetSceneEventTypeName(uint typeCode) { - if (!s_SceneEventTypeNames.TryGetValue(typeCode, out string name)) - { - name = "Unknown"; - } - - return name; + return k_SceneEventTypeNames.GetValueOrDefault(typeCode, "Unknown"); } private readonly Counter m_TransportBytesSent = new Counter(NetworkMetricTypes.TotalBytesSent.Id) @@ -511,9 +507,9 @@ public void UpdatePacketLoss(float packetLoss) public void DispatchFrame() { - s_FrameDispatch.Begin(); + k_FrameDispatch.Begin(); Dispatcher.Dispatch(); - s_FrameDispatch.End(); + k_FrameDispatch.End(); m_NumberOfMetricsThisFrame = 0; } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs index d9327f2441..7c1769d9d8 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/CollectionSerializationUtility.cs @@ -6,7 +6,7 @@ namespace Unity.Netcode { - internal static class CollectionSerializationUtility + internal static partial class CollectionSerializationUtility { public static void WriteNativeArrayDelta(FastBufferWriter writer, ref NativeArray value, ref NativeArray previousValue) where T : unmanaged { @@ -260,24 +260,24 @@ public static void ReadListDelta(FastBufferReader reader, ref List value) // so we're going to keep static lists that we can reuse in these methods. private static class ListCache { - private static List s_AddedList = new List(); - private static List s_RemovedList = new List(); - private static List s_ChangedList = new List(); + private static readonly List k_AddedList = new List(); + private static readonly List k_RemovedList = new List(); + private static readonly List k_ChangedList = new List(); public static List GetAddedList() { - s_AddedList.Clear(); - return s_AddedList; + k_AddedList.Clear(); + return k_AddedList; } public static List GetRemovedList() { - s_RemovedList.Clear(); - return s_RemovedList; + k_RemovedList.Clear(); + return k_RemovedList; } public static List GetChangedList() { - s_ChangedList.Clear(); - return s_ChangedList; + k_ChangedList.Clear(); + return k_ChangedList; } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs index 1a00d2c86a..0c5528ca66 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/NetworkVariableSerialization.cs @@ -12,6 +12,8 @@ namespace Unity.Netcode [Serializable] public static class NetworkVariableSerialization { + // This is all setup in ILPP (in the file NetworkBehaviorILPP), using the functions in TypedILPPInitializers. + // There is no need to reset statics here. internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); /// diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs index 3f2a64585c..fe6a7acbba 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/UserNetworkVariableSerialization.cs @@ -7,7 +7,10 @@ namespace Unity.Netcode /// users to tell NetworkVariable about those extension methods (or simply pass in a lambda) /// /// The type of value being serialized - public class UserNetworkVariableSerialization +#if UNITY_6000_6_OR_NEWER + [Scripting.LifecycleManagement.AutoStaticsCleanup] +#endif + public partial class UserNetworkVariableSerialization { /// /// The write value delegate handler definition diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 6e3954b294..76ea8feb04 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -206,7 +206,7 @@ public void PopulateLoadedScenes(ref Dictionary scene /// Unloads any scenes that have not been assigned. /// /// - public void UnloadUnassignedScenes(NetworkManager networkManager = null) + public void UnloadUnassignedScenes(NetworkManager networkManager) { var sceneManager = networkManager.SceneManager; SceneManager.sceneUnloaded += SceneManager_SceneUnloaded; diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 78bdc7204a..8c07fbd603 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -5,6 +5,7 @@ using Unity.Collections; using UnityEngine; using UnityEngine.SceneManagement; +using Debug = UnityEngine.Debug; namespace Unity.Netcode @@ -548,6 +549,15 @@ internal bool RemoveServerClientSceneHandle(NetworkSceneHandle serverHandle, Net /// not destroy temporary scene are moved into the active scene /// internal static bool IsSpawnedObjectsPendingInDontDestroyOnLoad; +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() + { + DisableReSynchronization = false; + IsSpawnedObjectsPendingInDontDestroyOnLoad = false; + SceneUnloadEventHandler.ResetInstances(); + } +#endif /// /// Client and Server: @@ -590,7 +600,7 @@ internal bool HasSceneAuthority() } /// - /// Handle NetworkSeneManager clean up + /// Handle NetworkSceneManager clean up /// public void Dispose() { @@ -1570,6 +1580,9 @@ public SceneEventProgressStatus LoadScene(string sceneName, LoadSceneMode loadSc internal class SceneUnloadEventHandler { private static Dictionary> s_Instances = new Dictionary>(); +#if UNITY_EDITOR + internal static void ResetInstances() => s_Instances = new Dictionary>(); +#endif internal static void RegisterScene(NetworkSceneManager networkSceneManager, Scene scene, LoadSceneMode loadSceneMode, AsyncOperation asyncOperation = null) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 0f41aad482..0ad4e96864 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -164,10 +164,10 @@ internal class SceneEventData : IDisposable /// we must distinguish which scene we are talking about when the server tells the client to unload a scene. /// The server will always communicate its local relative scene's handle and the client will determine its /// local relative handle from the table being built. - /// Look for usage to see where + /// Look for usage to see where /// entries are being added to or removed from the table /// - /// + /// /// internal void AddSceneToSynchronize(uint sceneHash, NetworkSceneHandle sceneHandle) { @@ -315,8 +315,6 @@ private void SortParentedNetworkObjects() } } - internal static bool LogSerializationOrder = false; - internal void AddSpawnedNetworkObjects() { m_NetworkObjectsSync.Clear(); @@ -352,7 +350,7 @@ private void SortObjectsToSync() // 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 (LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer) + if (NetworkLog.Config.LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer) { var messageBuilder = new StringBuilder(0xFFFF); messageBuilder.AppendLine("[Server-Side Client-Synchronization] NetworkObject serialization order:"); @@ -1122,7 +1120,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]"); LogArray(InternalBuffer.ToArray(), noStart, noStop, builder); } - // If we failed to deserialize the NetowrkObject then don't add null to the list + // If we failed to deserialize the NetworkObject then don't add null to the list if (spawnedNetworkObject != null) { if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) @@ -1152,7 +1150,10 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) catch (Exception ex) { UnityEngine.Debug.LogException(ex); - UnityEngine.Debug.Log(builder.ToString()); + if (EnableSerializationLogs) + { + UnityEngine.Debug.Log(builder.ToString()); + } } finally { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index bea537965e..ca39ea8c62 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -31,7 +31,7 @@ internal struct WriterHandle internal unsafe WriterHandle* Handle; - private static byte[] s_ByteArrayCache = new byte[65535]; + private static readonly byte[] k_ByteArrayCache = new byte[65535]; /// /// The current write position @@ -379,17 +379,17 @@ public unsafe byte[] ToArray() internal unsafe ArraySegment ToTempByteArray() { var length = Length; - if (length > s_ByteArrayCache.Length) + if (length > k_ByteArrayCache.Length) { return new ArraySegment(ToArray(), 0, length); } - fixed (byte* b = s_ByteArrayCache) + fixed (byte* b = k_ByteArrayCache) { UnsafeUtility.MemCpy(b, Handle->BufferPointer, length); } - return new ArraySegment(s_ByteArrayCache, 0, length); + return new ArraySegment(k_ByteArrayCache, 0, length); } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs index 19cecefc48..0b2e406e4f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkBehaviourReference.cs @@ -11,8 +11,7 @@ public struct NetworkBehaviourReference : INetworkSerializable, IEquatable /// Creates a new instance of the struct. @@ -24,7 +23,7 @@ public NetworkBehaviourReference(NetworkBehaviour networkBehaviour) if (networkBehaviour == null) { m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null); - m_NetworkBehaviourId = s_NullId; + m_NetworkBehaviourId = k_NullId; return; } if (networkBehaviour.NetworkObject == null) @@ -64,7 +63,7 @@ public bool TryGet(out T networkBehaviour, NetworkManager networkManager = nu [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null) { - if (networkBehaviourRef.m_NetworkBehaviourId == s_NullId) + if (networkBehaviourRef.m_NetworkBehaviourId == k_NullId) { return null; } diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs index 32ab0ed162..db5ed69ced 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/NetworkObjectReference.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode public struct NetworkObjectReference : INetworkSerializable, IEquatable { private ulong m_NetworkObjectId; - private static ulong s_NullId = ulong.MaxValue; + private const ulong k_NullId = ulong.MaxValue; /// /// The of the referenced . @@ -31,7 +31,7 @@ public NetworkObjectReference(NetworkObject networkObject) { if (networkObject == null) { - m_NetworkObjectId = s_NullId; + m_NetworkObjectId = k_NullId; return; } @@ -53,7 +53,7 @@ public NetworkObjectReference(GameObject gameObject) { if (gameObject == null) { - m_NetworkObjectId = s_NullId; + m_NetworkObjectId = k_NullId; return; } @@ -92,7 +92,7 @@ public bool TryGet(out NetworkObject networkObject, NetworkManager networkManage [MethodImpl(MethodImplOptions.AggressiveInlining)] private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null) { - if (networkObjectRef.m_NetworkObjectId == s_NullId) + if (networkObjectRef.m_NetworkObjectId == k_NullId) { return null; } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs index 7b2d6b1719..e94955618c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/SinglePlayer/SinglePlayerTransport.cs @@ -20,7 +20,7 @@ public class SinglePlayerTransport : NetworkTransport /// public override ulong ServerClientId { get; } = 0; - internal static string NotStartingAsHostErrorMessage = $"When using {nameof(SinglePlayerTransport)}, you must start a hosted session so both client and server are available locally."; + internal static readonly string NotStartingAsHostErrorMessage = $"When using {nameof(SinglePlayerTransport)}, you must start a hosted session so both client and server are available locally."; private struct MessageData { @@ -31,6 +31,10 @@ private struct MessageData } private static Dictionary> s_MessageQueue = new Dictionary>(); +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() => s_MessageQueue = new Dictionary>(); +#endif private ulong m_TransportId = 0; private NetworkManager m_NetworkManager; diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index e392af403c..fda0629b92 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -68,7 +68,7 @@ public enum ProtocolType // frame at 60 FPS. This will be a large over-estimation in any realistic scenario. private const int k_MaxReliableThroughput = (NetworkParameterConstants.MTU * 64 * 60) / 1000; // bytes per millisecond - private static ConnectionAddressData s_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, WebSocketPath = "/", ServerListenAddress = string.Empty }; + private static readonly ConnectionAddressData k_DefaultConnectionAddressData = new ConnectionAddressData { Address = "127.0.0.1", Port = 7777, WebSocketPath = "/", ServerListenAddress = string.Empty }; #pragma warning disable IDE1006 // Naming Styles /// @@ -308,7 +308,7 @@ public NetworkEndpoint ListenEndPoint /// This is where you can change IP Address, Port, or server's listen address. /// /// - public ConnectionAddressData ConnectionData = s_DefaultConnectionAddressData; + public ConnectionAddressData ConnectionData = k_DefaultConnectionAddressData; /// /// Parameters for the Network Simulator @@ -369,6 +369,19 @@ private struct PacketLossCache #endif internal static event Action TransportInitialized; internal static event Action TransportDisposed; +#if UNITY_EDITOR + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + private static void ResetStaticsOnLoad() + { + s_DriverConstructor = null; +#if UNITY_6000_2_OR_NEWER + OnDriverInitialized = null; + OnDisposingDriver = null; +#endif + TransportInitialized = null; + TransportDisposed = null; + } +#endif /// /// Provides access to the for this instance. @@ -856,7 +869,7 @@ private bool ParseCommandLineOptionsPort(out ushort port) return true; } #else - if (CommandLineOptions.Instance.GetArg(k_OverridePortArg) is string argValue) + if (CommandLineOptions.TryGetArg(k_OverridePortArg, out var argValue)) { port = (ushort)Convert.ChangeType(argValue, typeof(ushort)); return true; @@ -868,7 +881,7 @@ private bool ParseCommandLineOptionsPort(out ushort port) private bool ParseCommandLineOptionsAddress(out string ipValue) { - if (CommandLineOptions.Instance.GetArg(k_OverrideIpAddressArg) is string argValue) + if (CommandLineOptions.TryGetArg(k_OverrideIpAddressArg, out var argValue)) { ipValue = argValue; return true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index b9c5623759..f680b24338 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -2405,8 +2405,6 @@ public NetcodeIntegrationTest(HostOrServer hostOrServer) private void InitializeTestConfiguration(NetworkTopologyTypes networkTopologyType, HostOrServer? hostOrServer) { - NetworkMessageManager.EnableMessageOrderConsoleLog = false; - // Set m_NetworkTopologyType first because m_DistributedAuthority is calculated from it. m_NetworkTopologyType = networkTopologyType; diff --git a/testproject/ProjectSettings/EditorSettings.asset b/testproject/ProjectSettings/EditorSettings.asset index f92054474a..3ddd8f044f 100644 --- a/testproject/ProjectSettings/EditorSettings.asset +++ b/testproject/ProjectSettings/EditorSettings.asset @@ -23,7 +23,7 @@ EditorSettings: m_EnableTextureStreamingInEditMode: 1 m_EnableTextureStreamingInPlayMode: 1 m_AsyncShaderCompilation: 1 - m_EnterPlayModeOptionsEnabled: 0 + m_EnterPlayModeOptionsEnabled: 1 m_EnterPlayModeOptions: 3 m_ShowLightmapResolutionOverlay: 1 m_UseLegacyProbeSampleCount: 0 diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 3bb16ba357..176a6e2232 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -741,8 +741,7 @@ PlayerSettings: scriptingDefineSymbols: Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT additionalCompilerArguments: - Standalone: - - -warnaserror + Standalone: [] platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} From 7fd6ab9115da91275d94823fce25c4e397d7a836 Mon Sep 17 00:00:00 2001 From: mnachury-unity <120488254+mnachury-unity@users.noreply.github.com> Date: Tue, 2 Jun 2026 10:28:12 -0400 Subject: [PATCH 12/64] fix: end TransportConnect profiler marker on early returns in ConnectEventHandler (#4005) BeginSample was called at the top of ConnectEventHandler but two early return paths (duplicate server transport ID and duplicate client connect event) exited without calling EndSample, causing the 'Missing Profiler.EndSample' error surfaced by the MultipleConnectMessagesNoop playmode tests. --- .../Runtime/Connection/NetworkConnectionManager.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 1da9ddb3bb..abd63a8254 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -522,6 +522,9 @@ internal void ConnectEventHandler(ulong transportId) { NetworkLog.LogError($"[TransportApproval][Server] TransportId {transportId} is already connected to this server!"); } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + s_TransportConnect.End(); +#endif return; } @@ -536,6 +539,9 @@ internal void ConnectEventHandler(ulong transportId) { NetworkLog.LogError("[TransportApproval][Client] Client received a transport connection event after already connecting!"); } +#if DEVELOPMENT_BUILD || UNITY_EDITOR + s_TransportConnect.End(); +#endif return; } From 8c78fc31cedbf69db527f286b3253b7a49d5d848 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Fri, 5 Jun 2026 15:55:02 +0200 Subject: [PATCH 13/64] refactor: NetworkAnimator cleanup and code quality improvements (#4001) refactor: cleanup and code quality improvements in NetworkAnimator - Add readonly to fields where applicable - Narrow internal fields and methods to private where applicable - Fix naming casing (TransitionStateinfo, m_DestinationStateToTransitioninfo, field prefixes) - Replace ContainsKey + Add/index lookups with TryAdd/TryGetValue - Remove unused variables and dead code - Improve XML summaries and comment clarity --------- Co-authored-by: Emma --- .../Runtime/Components/NetworkAnimator.cs | 175 ++++++++---------- 1 file changed, 73 insertions(+), 102 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 3fb8166408..246d6b8484 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -14,8 +14,8 @@ namespace Unity.Netcode.Components { internal class NetworkAnimatorStateChangeHandler : INetworkUpdateSystem { - private NetworkAnimator m_NetworkAnimator; - private bool m_IsServer; + private readonly NetworkAnimator m_NetworkAnimator; + private readonly bool m_IsServer; /// /// This removes sending RPCs from within RPCs when the @@ -132,14 +132,14 @@ private struct AnimationUpdate public NetworkAnimator.AnimationMessage AnimationMessage; } - private List m_SendAnimationUpdates = new List(); + private readonly List m_SendAnimationUpdates = new List(); /// /// Invoked when a server needs to forwarding an update to the animation state /// internal void SendAnimationUpdate(NetworkAnimator.AnimationMessage animationMessage, RpcParams rpcParams = default) { - m_SendAnimationUpdates.Add(new AnimationUpdate() { RpcParams = rpcParams, AnimationMessage = animationMessage }); + m_SendAnimationUpdates.Add(new AnimationUpdate { RpcParams = rpcParams, AnimationMessage = animationMessage }); } private struct ParameterUpdate @@ -148,17 +148,17 @@ private struct ParameterUpdate public NetworkAnimator.ParametersUpdateMessage ParametersUpdateMessage; } - private List m_SendParameterUpdates = new List(); + private readonly List m_SendParameterUpdates = new List(); /// /// Invoked when a server needs to forwarding an update to the parameter state /// internal void SendParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage, RpcParams rpcParams = default) { - m_SendParameterUpdates.Add(new ParameterUpdate() { RpcParams = rpcParams, ParametersUpdateMessage = parametersUpdateMessage }); + m_SendParameterUpdates.Add(new ParameterUpdate { RpcParams = rpcParams, ParametersUpdateMessage = parametersUpdateMessage }); } - private List m_ProcessParameterUpdates = new List(); + private readonly List m_ProcessParameterUpdates = new List(); internal void ProcessParameterUpdate(NetworkAnimator.ParametersUpdateMessage parametersUpdateMessage) { m_ProcessParameterUpdates.Add(parametersUpdateMessage); @@ -171,31 +171,31 @@ private struct TriggerUpdate public NetworkAnimator.AnimationTriggerMessage AnimationTriggerMessage; } - private List m_SendTriggerUpdates = new List(); + private readonly List m_SendTriggerUpdates = new List(); /// /// Invoked when a server needs to forward an update to a Trigger state /// internal void QueueTriggerUpdateToClient(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage, RpcParams clientRpcParams = default) { - m_SendTriggerUpdates.Add(new TriggerUpdate() { RpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); + m_SendTriggerUpdates.Add(new TriggerUpdate { RpcParams = clientRpcParams, AnimationTriggerMessage = animationTriggerMessage }); } internal void QueueTriggerUpdateToServer(NetworkAnimator.AnimationTriggerMessage animationTriggerMessage) { - m_SendTriggerUpdates.Add(new TriggerUpdate() { AnimationTriggerMessage = animationTriggerMessage, SendToServer = true }); + m_SendTriggerUpdates.Add(new TriggerUpdate { AnimationTriggerMessage = animationTriggerMessage, SendToServer = true }); } internal void DeregisterUpdate() { - NetworkUpdateLoop.UnregisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); + this.UnregisterNetworkUpdate(NetworkUpdateStage.PreUpdate); } internal NetworkAnimatorStateChangeHandler(NetworkAnimator networkAnimator) { m_NetworkAnimator = networkAnimator; m_IsServer = networkAnimator.LocalNetworkManager.IsServer; - NetworkUpdateLoop.RegisterNetworkUpdate(this, NetworkUpdateStage.PreUpdate); + this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate); } } @@ -213,7 +213,7 @@ public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver #endif [Serializable] - internal class TransitionStateinfo + internal class TransitionStateInfo { public bool IsCrossFadeExit; public int Layer; @@ -260,11 +260,8 @@ public enum AuthorityModes /// public Animator Animator { - get { return m_Animator; } - set - { - m_Animator = value; - } + get => m_Animator; + set => m_Animator = value; } /// @@ -272,11 +269,11 @@ public Animator Animator /// [HideInInspector] [SerializeField] - internal List TransitionStateInfoList; + 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>(); + private readonly Dictionary> m_DestinationStateToTransitionInfo = new Dictionary>(); // Named differently to avoid serialization conflicts with NetworkBehaviour internal NetworkManager LocalNetworkManager; @@ -284,21 +281,15 @@ public Animator Animator internal bool DistributedAuthorityMode; /// - /// Builds the m_DestinationStateToTransitioninfo lookup table + /// Builds the 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); - } + m_DestinationStateToTransitionInfo.TryAdd(entry.Layer, new Dictionary()); + var destinationStateTransitionInfo = m_DestinationStateToTransitionInfo[entry.Layer]; + destinationStateTransitionInfo.TryAdd(entry.DestinationState, entry); } } @@ -323,14 +314,14 @@ internal class AnimatorParametersListContainer [SerializeField] internal AnimatorParametersListContainer AnimatorParameterEntries; - internal Dictionary AnimatorParameterEntryTable = new Dictionary(); + private readonly Dictionary m_AnimatorParameterEntryTable = new Dictionary(); #if UNITY_EDITOR [HideInInspector] [SerializeField] internal bool AnimatorParametersExpanded; - internal Dictionary ParameterToNameLookup = new Dictionary(); + private readonly Dictionary m_ParameterToNameLookup = new Dictionary(); private void ParseStateMachineStates(int layerIndex, ref AnimatorController animatorController, ref AnimatorStateMachine stateMachine) { @@ -371,7 +362,7 @@ private void ParseStateMachineStates(int layerIndex, ref AnimatorController anim } else if (transition.destinationState != null) { - var transitionInfo = new TransitionStateinfo() + var transitionInfo = new TransitionStateInfo() { Layer = layerIndex, OriginatingState = animatorState.nameHash, @@ -408,7 +399,7 @@ private void BuildTransitionStateInfoList() return; } - TransitionStateInfoList = new List(); + TransitionStateInfoList = new List(); var animControllerType = m_Animator.runtimeAnimatorController.GetType(); var animatorController = (AnimatorController)null; @@ -433,7 +424,7 @@ private void BuildTransitionStateInfoList() } } - internal void ProcessParameterEntries() + private void ProcessParameterEntries() { if (!Animator) { @@ -462,18 +453,18 @@ internal void ProcessParameterEntries() var parameters = animatorController.parameters; var parametersToRemove = new List(); - ParameterToNameLookup.Clear(); + m_ParameterToNameLookup.Clear(); foreach (var parameter in parameters) { - ParameterToNameLookup.Add(parameter.nameHash, parameter); + m_ParameterToNameLookup.Add(parameter.nameHash, parameter); } // Rebuild the parameter entry table for the inspector view - AnimatorParameterEntryTable.Clear(); + m_AnimatorParameterEntryTable.Clear(); foreach (var parameterEntry in AnimatorParameterEntries.ParameterEntries) { // Check for removed parameters. - if (!ParameterToNameLookup.ContainsKey(parameterEntry.NameHash)) + if (!m_ParameterToNameLookup.ContainsKey(parameterEntry.NameHash)) { parametersToRemove.Add(parameterEntry); // Skip this removed entry @@ -481,12 +472,9 @@ internal void ProcessParameterEntries() } // Build the list of known parameters - if (!AnimatorParameterEntryTable.ContainsKey(parameterEntry.NameHash)) - { - AnimatorParameterEntryTable.Add(parameterEntry.NameHash, parameterEntry); - } + m_AnimatorParameterEntryTable.TryAdd(parameterEntry.NameHash, parameterEntry); - var parameter = ParameterToNameLookup[parameterEntry.NameHash]; + var parameter = m_ParameterToNameLookup[parameterEntry.NameHash]; parameterEntry.name = parameter.name; parameterEntry.ParameterType = parameter.type; } @@ -498,9 +486,9 @@ internal void ProcessParameterEntries() } // Update any newly added parameters - foreach (var parameterLookUp in ParameterToNameLookup) + foreach (var parameterLookUp in m_ParameterToNameLookup) { - if (!AnimatorParameterEntryTable.ContainsKey(parameterLookUp.Value.nameHash)) + if (!m_AnimatorParameterEntryTable.ContainsKey(parameterLookUp.Value.nameHash)) { var animatorParameterEntry = new AnimatorParameterEntry() { @@ -510,7 +498,7 @@ internal void ProcessParameterEntries() Synchronize = true, }; AnimatorParameterEntries.ParameterEntries.Add(animatorParameterEntry); - AnimatorParameterEntryTable.Add(parameterLookUp.Value.nameHash, animatorParameterEntry); + m_AnimatorParameterEntryTable.Add(parameterLookUp.Value.nameHash, animatorParameterEntry); } } } @@ -602,7 +590,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade serializer.SerializeValue(ref NormalizedTime); serializer.SerializeValue(ref Weight); - // Cross fading includes the duration of the cross fade. + // Cross-fading includes the duration of the cross-fade. if (CrossFade) { serializer.SerializeValue(ref Duration); @@ -680,7 +668,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade /// Determines whether the is or based on the field. /// Optionally, you can still derive from and override the method. /// - /// or + /// True if this is server authoritative, false otherwise public bool IsServerAuthoritative() { return OnIsServerAuthoritative(); @@ -787,7 +775,7 @@ protected virtual void Awake() foreach (var parameterEntry in AnimatorParameterEntries.ParameterEntries) { - AnimatorParameterEntryTable.TryAdd(parameterEntry.NameHash, parameterEntry); + m_AnimatorParameterEntryTable.TryAdd(parameterEntry.NameHash, parameterEntry); } int layers = m_Animator.layerCount; @@ -813,10 +801,7 @@ protected virtual void Awake() // AnimationMessage. m_AnimationMessage.AnimationStates.Add(new AnimationState()); float layerWeightNow = m_Animator.GetLayerWeight(layer); - if (layerWeightNow != m_LayerWeights[layer]) - { - m_LayerWeights[layer] = layerWeightNow; - } + m_LayerWeights[layer] = layerWeightNow; } // The total initialization size calculated for the m_ParameterWriter write buffer. @@ -834,9 +819,9 @@ protected virtual void Awake() { var parameter = parameters[i]; var synchronizeParameter = true; - if (AnimatorParameterEntryTable.ContainsKey(parameter.nameHash)) + if (m_AnimatorParameterEntryTable.TryGetValue(parameter.nameHash, out var entry)) { - synchronizeParameter = AnimatorParameterEntryTable[parameter.nameHash].Synchronize; + synchronizeParameter = entry.Synchronize; } var cacheParam = new AnimatorParamCache @@ -862,8 +847,6 @@ protected virtual void Awake() var valueBool = m_Animator.GetBool(cacheParam.Hash); UnsafeUtility.WriteArrayElement(cacheParam.Value, 0, valueBool); break; - default: - break; } } @@ -985,12 +968,12 @@ private void WriteSynchronizationData(ref BufferSerializer serializer) whe var normalizedTime = synchronizationStateInfo.normalizedTime; var isInTransition = m_Animator.IsInTransition(layer); - // Grab one of the available AnimationState entries so we can fill it with the current + // 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 + // handled by cross-fading between the late joining client's current layer's AnimationState // and the transition's destination AnimationState. if (isInTransition) { @@ -1012,16 +995,14 @@ private void WriteSynchronizationData(ref BufferSerializer serializer) whe } 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)) + // Check if this transition can be synchronized using cross-fading + if (m_DestinationStateToTransitionInfo.TryGetValue(layer, out var layerTransitions)) { - if (m_DestinationStateToTransitioninfo[layer].ContainsKey(nextState.shortNameHash)) + if (layerTransitions.TryGetValue(nextState.shortNameHash, out var transitionInfo)) { - var destinationInfo = m_DestinationStateToTransitioninfo[layer][nextState.shortNameHash]; - stateHash = destinationInfo.OriginatingState; - // Set the destination state to cross fade to from the originating state - animationState.DestinationStateHash = destinationInfo.DestinationState; + stateHash = transitionInfo.OriginatingState; + // Set the destination state to cross-fade to from the originating state + animationState.DestinationStateHash = transitionInfo.DestinationState; } } } @@ -1069,7 +1050,7 @@ protected override void OnSynchronize(ref BufferSerializer serializer) /// /// Checks for animation state changes in: /// -Layer weights - /// -Cross fades + /// -Cross-fades /// -Transitions /// -Layer AnimationStates /// @@ -1103,7 +1084,7 @@ private void CheckForStateChange(int layer) { m_TransitionHash[layer] = nt.fullPathHash; m_AnimationHash[layer] = 0; - // Next state is the destination state for cross fade + // Next state is the destination state for cross-fade animState.DestinationStateHash = nt.fullPathHash; animState.CrossFade = true; animState.Transition = true; @@ -1112,12 +1093,13 @@ private void CheckForStateChange(int layer) stateChangeDetected = true; //Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})"); } - // If we are not transitioned into the "any state" and the animator transition isn't a full path hash (layer to layer) and our pre-built destination state to transition does not contain the - // current layer (i.e. transitioning into a state from another layer) =or= we do contain the layer and the layer contains state to transition to is contained within our pre-built destination - // state then we can handle this transition as a non-cross fade state transition between layers. - // Otherwise, if we don't enter into this then this is a "trigger transition to some state that is now being transitioned back to the Idle state via trigger" or "Dual Triggers" IDLE<-->State. - else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer] && (!m_DestinationStateToTransitioninfo.ContainsKey(layer) || - (m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash)))) + // Handle as a non-cross-fade transition when: + // - not an "any state" transition and this is a new transition on this layer + // - the layer is either absent from the lookup table (cross-layer transition) or its destination state is present + // Skipping this block means we are in a "dual trigger" scenario where a trigger transitions + // to a state that is immediately transitioned back via another trigger (e.g. IDLE <--> State). + else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer] && (!m_DestinationStateToTransitionInfo.TryGetValue(layer, out var layerTransitions) || + layerTransitions.ContainsKey(nt.fullPathHash))) { // first time in this transition for this layer m_TransitionHash[layer] = tt.fullPathHash; @@ -1127,7 +1109,7 @@ private void CheckForStateChange(int layer) animState.CrossFade = false; animState.Transition = true; animState.NormalizedTime = tt.normalizedTime; - if (m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash)) + if (layerTransitions != null) { animState.DestinationStateHash = nt.fullPathHash; } @@ -1188,9 +1170,6 @@ internal void CheckForAnimatorChanges() // 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); - var totalSpeed = st.speed * st.speedMultiplier; - var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f; CheckForStateChange(layer); } @@ -1336,8 +1315,8 @@ private unsafe bool CheckParametersChanged() } /// - /// Writes all of the Animator's parameters - /// This uses the m_ParametersToUpdate list to write out only + /// Writes all the Animator's parameters + /// This uses the list to write out only /// the parameters that have changed /// private unsafe void WriteParameters(ref FastBufferWriter writer) @@ -1367,7 +1346,7 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) BytePacker.WriteValuePacked(writer, (uint)valueInt); } } - else // Note: Triggers are treated like boolean values + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) { var valueBool = m_Animator.GetBool(hash); @@ -1452,7 +1431,7 @@ internal unsafe void UpdateParameters(ref ParametersUpdateMessage parametersUpda /// /// Applies the AnimationState state to the Animator /// - internal void UpdateAnimationState(AnimationState animationState) + private void UpdateAnimationState(AnimationState animationState) { // Handle updating layer weights first. if (animationState.Layer < m_LayerWeights.Length) @@ -1474,21 +1453,17 @@ internal void UpdateAnimationState(AnimationState animationState) // If it is a transition, then we are synchronizing transitions in progress when a client late joins if (animationState.Transition && !animationState.CrossFade) { - // 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)) + // At this point all entries in the lookup table should be valid. + // Look up the transition info for this layer and destination state. + if (m_DestinationStateToTransitionInfo.TryGetValue(animationState.Layer, out var layerTransitions)) { - // Verify the inner-table has the destination AnimationState name hash - if (m_DestinationStateToTransitioninfo[animationState.Layer].ContainsKey(animationState.DestinationStateHash)) + if (layerTransitions.TryGetValue(animationState.DestinationStateHash, out var transitionInfo)) { - // Make sure we are on the originating/starting state we are going to cross fade into + // 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); + // 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(transitionInfo.DestinationState, transitionInfo.TransitionDuration, transitionInfo.Layer, 0.0f, animationState.NormalizedTime); } else if (LocalNetworkManager.LogLevel == LogLevel.Developer) { @@ -1525,7 +1500,7 @@ internal void UpdateAnimationState(AnimationState animationState) /// The server sets its local parameters and then forwards the message to the remaining clients /// [Rpc(SendTo.Server, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] - private unsafe void SendServerParametersUpdateRpc(ParametersUpdateMessage parametersUpdate, RpcParams rpcParams = default) + private void SendServerParametersUpdateRpc(ParametersUpdateMessage parametersUpdate, RpcParams rpcParams = default) { if (IsServerAuthoritative()) { @@ -1707,9 +1682,6 @@ internal void SendServerAnimTriggerRpc(AnimationTriggerMessage animationTriggerM } } - /// - /// See above - /// private void InternalSetTrigger(int hash, bool isSet = true) { m_Animator.SetBool(hash, isSet); @@ -1720,6 +1692,7 @@ private void InternalSetTrigger(int hash, bool isSet = true) /// to forward a trigger to a client /// /// the payload containing the trigger data to apply + /// Defined as it's used to send the RPC to be invoked on this client [Rpc(SendTo.NotAuthority, AllowTargetOverride = true, InvokePermission = RpcInvokePermission.Owner)] internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage, RpcParams rpcParams = default) { @@ -1731,7 +1704,7 @@ internal void SendAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage /// a trigger to a client /// /// the payload containing the trigger data to apply - /// unused + /// used to send the RPC to be invoked on this client [Rpc(SendTo.NotServer, AllowTargetOverride = true)] internal void SendClientAnimTriggerRpc(AnimationTriggerMessage animationTriggerMessage, RpcParams rpcParams = default) { @@ -1774,13 +1747,11 @@ public void SetTrigger(int hash, bool setTrigger = true) { if (IsServer) { - /// as to why we queue m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animTriggerMessage); InternalSetTrigger(hash, setTrigger); } else { - /// as to why we queue m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToServer(animTriggerMessage); if (!IsServerAuthoritative()) { From 67e3e1ca3edfa42b8d59ebb85aff012bf89487f2 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 9 Jun 2026 13:30:37 -0500 Subject: [PATCH 14/64] chore: replace DEVELOPMENT_BUILD || UNITY_EDITOR with DEBUG (#4006) * update Replacing DEVELOPMENT_BUILD || UNITY_EDITOR with DEBUG. Replacing niche DEVELOPMENT_BUILD uses with DEBUG. * update Removing extra files that didn't need to be included in this PR. * update Adding changelog entry * update Missed the codegen assembly. * update Upgrading our CI ubuntu image to: `package-ci/ubuntu-22.04:v4.86.0`. * Revert "update" This reverts commit 2b010d8e01d23acfe0ca18ef7f4f8a8f5c1e61ce. * update bumping ubuntu to use b1.large (segmentation fault was due to running out of memory). * Revert "update" This reverts commit bd1f00452e854b6be92d923eddb44189f1d2764e. * Reapply "update" This reverts commit 6a434bf2f6aa8b31b28e049eae662161102abf85. * update Switching PR triggers to use trunk as opposed to the pinned trunk. * Revert "update" This reverts commit 5ce1ef4245907ff7a6258f9f5f91e61a23439781. * update Moving pinned trunk up a bit. --- .yamato/project.metafile | 6 +-- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../CodeGen/RuntimeAccessModifiersILPP.cs | 2 +- .../Connection/NetworkConnectionManager.cs | 22 ++++----- .../Runtime/Core/NetworkBehaviour.cs | 12 ++--- .../Runtime/Core/NetworkBehaviourUpdater.cs | 6 +-- .../Runtime/Core/NetworkManager.cs | 6 +-- .../Runtime/Messaging/CustomMessageManager.cs | 2 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 2 +- .../Messaging/NetworkMessageManager.cs | 2 +- .../Messaging/RpcTargets/BaseRpcTarget.cs | 2 +- .../RpcTargets/LocalSendRpcTarget.cs | 2 +- .../RpcTargets/ProxyRpcTargetGroup.cs | 2 +- .../Messaging/RpcTargets/ServerRpcTarget.cs | 2 +- .../Runtime/Serialization/BitReader.cs | 12 ++--- .../Runtime/Serialization/BitWriter.cs | 12 ++--- .../Runtime/Serialization/FastBufferReader.cs | 34 ++++++------- .../Runtime/Serialization/FastBufferWriter.cs | 36 +++++++------- .../Runtime/Timing/NetworkTickSystem.cs | 6 +-- .../Runtime/Timing/NetworkTimeSystem.cs | 6 +-- .../Runtime/Transports/UTP/UnityTransport.cs | 2 +- .../SceneTransitioningBase1.unity | 48 ++++++++++++++++--- 22 files changed, 130 insertions(+), 95 deletions(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index acc0c1d405..f4802d6ff0 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -47,7 +47,7 @@ test_platforms: type: Unity::VM image: package-ci/ubuntu-22.04:v4.85.0 flavor: b1.large - smaller_flavor: b1.medium + smaller_flavor: b1.large larger_flavor: b1.xlarge standalone: StandaloneLinux64 - name: win @@ -181,10 +181,10 @@ validation_editors: - 6000.4 - 6000.5 - trunk - - a4ce83e807ca9aff8394d1cc07341168dc49df03 + - f1298548e194f35ff7dfa6fee699d4464ab3919c minimal: - 6000.0 -pinnedTrunk: a4ce83e807ca9aff8394d1cc07341168dc49df03 +pinnedTrunk: f1298548e194f35ff7dfa6fee699d4464ab3919c # Scripting backends used by Standalone RunTimeTests--------------------------------------------------- diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0a4aa4fde3..3493b13666 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 ### Changed +- Changed replaced define usages of `DEVELOPMENT_BUILD || UNITY_EDITOR` and a few niche uses of `DEVELOPMENT_BUILD` with `DEBUG`. (#4006) ### Deprecated diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index ffea9115b7..18c73ed737 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -159,7 +159,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { fieldDefinition.IsFamilyOrAssembly = true; } -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table)) { fieldDefinition.IsFamilyOrAssembly = true; diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index abd63a8254..5a8724374c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -96,7 +96,7 @@ public struct ConnectionEventData /// public sealed class NetworkConnectionManager { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private static ProfilerMarker s_TransportPollMarker = new ProfilerMarker($"{nameof(NetworkManager)}.TransportPoll"); private static ProfilerMarker s_TransportConnect = new ProfilerMarker($"{nameof(NetworkManager)}.TransportConnect"); private static ProfilerMarker s_HandleIncomingData = new ProfilerMarker($"{nameof(NetworkManager)}.{nameof(NetworkMessageManager.HandleIncomingData)}"); @@ -438,7 +438,7 @@ private ulong GetServerTransportId() internal void PollAndHandleNetworkEvents() { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportPollMarker.Begin(); #endif NetworkEvent networkEvent; @@ -453,7 +453,7 @@ internal void PollAndHandleNetworkEvents() // Only do another iteration if: there are no more messages AND (there is no limit to max events or we have processed less than the maximum) } while (NetworkManager.IsListening && networkEvent != NetworkEvent.Nothing); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportPollMarker.End(); #endif } @@ -501,7 +501,7 @@ internal void HandleNetworkEvent(NetworkEvent networkEvent, ulong transportClien /// internal void ConnectEventHandler(ulong transportId) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportConnect.Begin(); #endif // Assumptions: @@ -522,7 +522,7 @@ internal void ConnectEventHandler(ulong transportId) { NetworkLog.LogError($"[TransportApproval][Server] TransportId {transportId} is already connected to this server!"); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportConnect.End(); #endif return; @@ -539,7 +539,7 @@ internal void ConnectEventHandler(ulong transportId) { NetworkLog.LogError("[TransportApproval][Client] Client received a transport connection event after already connecting!"); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportConnect.End(); #endif return; @@ -577,7 +577,7 @@ internal void ConnectEventHandler(ulong transportId) StartClientApprovalCoroutine(clientId); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportConnect.End(); #endif } @@ -587,7 +587,7 @@ internal void ConnectEventHandler(ulong transportId) /// internal void DataEventHandler(ulong transportClientId, ref ArraySegment payload, float receiveTime) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_HandleIncomingData.Begin(); #endif var (clientId, isConnectedClient) = TransportIdToClientId(transportClientId); @@ -596,7 +596,7 @@ internal void DataEventHandler(ulong transportClientId, ref ArraySegment p MessageManager.HandleIncomingData(clientId, payload, receiveTime); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_HandleIncomingData.End(); #endif } @@ -639,7 +639,7 @@ internal void DisconnectEventHandler(ulong transportClientId) return; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportDisconnect.Begin(); #endif @@ -698,7 +698,7 @@ internal void DisconnectEventHandler(ulong transportClientId) NetworkManager.Shutdown(true); } } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_TransportDisconnect.End(); #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs index 28ec5033b8..16b6d3be62 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs @@ -43,7 +43,7 @@ public abstract class NetworkBehaviour : MonoBehaviour internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); internal static readonly Dictionary> __rpc_permission_table = new Dictionary>(); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) // RuntimeAccessModifiersILPP will make this `public` internal static readonly Dictionary> __rpc_name_table = new Dictionary>(); #endif @@ -145,7 +145,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth bufferWriter.Dispose(); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) TrackRpcMetricsSend(ref serverRpcMessage, rpcMethodId, rpcWriteSize); #endif } @@ -269,7 +269,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth } bufferWriter.Dispose(); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) if (!ValidateRpcMessageMetrics(GetType())) { return; @@ -1002,12 +1002,12 @@ internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMeth var rpcType = GetType(); __rpc_func_table[rpcType][hash] = handler; __rpc_permission_table[rpcType][hash] = permission; -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) __rpc_name_table[rpcType][hash] = rpcMethodName; #endif } -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) [MethodImpl(MethodImplOptions.AggressiveInlining)] private bool ValidateRpcMessageMetrics(Type type) { @@ -1125,7 +1125,7 @@ internal void InitializeVariables() { __rpc_func_table[GetType()] = new Dictionary(); __rpc_permission_table[GetType()] = new Dictionary(); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) __rpc_name_table[GetType()] = new Dictionary(); #endif __initializeRpcs(); diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs index 4f12bca033..871c8afb7d 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviourUpdater.cs @@ -27,7 +27,7 @@ public class NetworkBehaviourUpdater /// private HashSet m_PendingDirtyNetworkObjects = new HashSet(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}"); #endif @@ -190,7 +190,7 @@ internal void ProcessDirtyObject(NetworkObject networkObject, bool forceSend) /// Refer to the definition. internal void NetworkBehaviourUpdate(bool forceSend = false) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_NetworkBehaviourUpdate.Begin(); #endif try @@ -214,7 +214,7 @@ internal void NetworkBehaviourUpdate(bool forceSend = false) } finally { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_NetworkBehaviourUpdate.End(); #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs index 4c7ca603db..453f10b88f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs @@ -70,7 +70,7 @@ private static void ResetStaticsOnLoad() #pragma warning restore IDE1006 // restore naming rule violation check -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private static List s_SerializedType = new List(); // This is used to control the serialized type not optimized messaging for integration test purposes internal static bool DisableNotOptimizedSerializedType; @@ -1176,7 +1176,7 @@ public int MaximumFragmentedMessageSize internal void Initialize(bool server) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (!DisableNotOptimizedSerializedType) { s_SerializedType.Clear(); @@ -1239,7 +1239,7 @@ internal void Initialize(bool server) MessageManager.Hook(new NetworkManagerHooks(this)); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (NetworkConfig.NetworkProfilingMetrics) { MessageManager.Hook(new ProfilingHooks()); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs index 15e30205f8..58a1f93470 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/CustomMessageManager.cs @@ -434,7 +434,7 @@ public void SendNamedMessage(string messageName, IReadOnlyList clientIds, /// Exception thrown in case validation fails private unsafe void ValidateMessageSize(FastBufferWriter messageStream, NetworkDelivery networkDelivery, bool isNamed) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG var maxNonFragmentedSize = m_NetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize() - sizeof(NetworkBatchHeader); if (isNamed) { diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index 843d875216..b7f1320788 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -71,7 +71,7 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, return; } -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) networkBehaviour.TrackRpcMetricsReceive(ref metadata, ref context, payload.Length); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 53714455c6..343cec9961 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -849,7 +849,7 @@ internal unsafe void ProcessSendQueues() } queueItem.Writer.Seek(0); -#if UNITY_EDITOR || DEVELOPMENT_BUILD +#if DEBUG // Skipping the Verify and sneaking the write mark in because we know it's fine. queueItem.Writer.Handle->AllowedWriteMark = sizeof(NetworkBatchHeader); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs index a1e12a5310..a1b5b5d8aa 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/BaseRpcTarget.cs @@ -53,7 +53,7 @@ protected void CheckLockBeforeDispose() private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery) { var size = behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) // Send to a specific client behaviour.TrackRpcMetricsSend(clientId, ref message, size); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs index 386af8816f..4afa621d11 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/LocalSendRpcTarget.cs @@ -46,7 +46,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, message.Handle(ref context); length = tempBuffer.Length; } -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) // Local invocation sends to self behaviour.TrackRpcMetricsSend(m_NetworkManager.LocalClientId, ref message, length); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs index 8dd2e744eb..40321b3742 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ProxyRpcTargetGroup.cs @@ -24,7 +24,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, } var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message }; var size = behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) foreach (var clientId in TargetClientIds) { behaviour.TrackRpcMetricsSend(clientId, ref message, size); diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs index 63380ce40c..7382b41153 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/ServerRpcTarget.cs @@ -36,7 +36,7 @@ internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, using var tempBuffer = new FastBufferReader(message.WriteBuffer, Allocator.None); message.ReadBuffer = tempBuffer; message.Handle(ref context); -#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) +#if MULTIPLAYER_TOOLS && (DEBUG || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE) // Local invocation sends to self behaviour.TrackRpcMetricsSend(m_NetworkManager.LocalClientId, ref message, tempBuffer.Length); #endif diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs index dcb30a92dd..ab8c9bc5ce 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs @@ -15,7 +15,7 @@ public ref struct BitReader private readonly unsafe byte* m_BufferPointer; private readonly int m_Position; private int m_BitPosition; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private int m_AllowedBitwiseReadMark; #endif @@ -39,7 +39,7 @@ internal unsafe BitReader(FastBufferReader reader) m_BufferPointer = m_Reader.Handle->BufferPointer + m_Reader.Handle->Position; m_Position = m_Reader.Handle->Position; m_BitPosition = 0; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_AllowedBitwiseReadMark = (m_Reader.Handle->AllowedReadMark - m_Position) * k_BitsPerByte; #endif } @@ -81,7 +81,7 @@ public unsafe bool TryBeginReadBits(uint bitCount) { return false; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_AllowedBitwiseReadMark = (int)newBitPosition; #endif return true; @@ -94,7 +94,7 @@ public unsafe bool TryBeginReadBits(uint bitCount) /// Amount of bits to read public unsafe void ReadBits(out ulong value, uint bitCount) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (bitCount > 64) { throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); @@ -136,7 +136,7 @@ public unsafe void ReadBits(out ulong value, uint bitCount) /// Amount of bits to read. public void ReadBits(out byte value, uint bitCount) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG int checkPos = (int)(m_BitPosition + bitCount); if (checkPos > m_AllowedBitwiseReadMark) { @@ -153,7 +153,7 @@ public void ReadBits(out byte value, uint bitCount) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBit(out bool bit) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG int checkPos = (m_BitPosition + 1); if (checkPos > m_AllowedBitwiseReadMark) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs index 0e3ccfb7e3..7b38bcadc9 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitWriter.cs @@ -15,7 +15,7 @@ public ref struct BitWriter private unsafe byte* m_BufferPointer; private readonly int m_Position; private int m_BitPosition; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private int m_AllowedBitwiseWriteMark; #endif private const int k_BitsPerByte = 8; @@ -37,7 +37,7 @@ internal unsafe BitWriter(FastBufferWriter writer) m_BufferPointer = writer.Handle->BufferPointer + writer.Handle->Position; m_Position = writer.Handle->Position; m_BitPosition = 0; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_AllowedBitwiseWriteMark = (m_Writer.Handle->AllowedWriteMark - m_Writer.Handle->Position) * k_BitsPerByte; #endif } @@ -97,7 +97,7 @@ public unsafe bool TryBeginWriteBits(int bitCount) return false; } } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG m_AllowedBitwiseWriteMark = newBitPosition; #endif return true; @@ -110,7 +110,7 @@ public unsafe bool TryBeginWriteBits(int bitCount) /// Amount of bits to write public unsafe void WriteBits(ulong value, uint bitCount) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (bitCount > 64) { throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot write more than 64 bits from a 64-bit value!"); @@ -153,7 +153,7 @@ public unsafe void WriteBits(ulong value, uint bitCount) /// Amount of bits to write. public void WriteBits(byte value, uint bitCount) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG int checkPos = (int)(m_BitPosition + bitCount); if (checkPos > m_AllowedBitwiseWriteMark) { @@ -174,7 +174,7 @@ public void WriteBits(byte value, uint bitCount) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBit(bool bit) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG int checkPos = (m_BitPosition + 1); if (checkPos > m_AllowedBitwiseWriteMark) { diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index d0dfdca7cd..d420ccd28e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -20,7 +20,7 @@ internal struct ReaderHandle internal int Position; internal int Length; internal Allocator Allocator; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG internal int AllowedReadMark; internal bool InBitwiseContext; #endif @@ -55,7 +55,7 @@ public unsafe int Length internal unsafe void CommitBitwiseReads(int amount) { Handle->Position += amount; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->InBitwiseContext = false; #endif } @@ -83,7 +83,7 @@ internal unsafe void CommitBitwiseReads(int amount) // When we dispose, we are really only interested in disposing Allocator.Persistent and Allocator.TempJob // as disposing Allocator.Temp and Allocator.None would do nothing. Therefore, make sure we dispose the readerHandle with the right Allocator label readerHandle->Allocator = copyAllocator == Allocator.None ? internalAllocator : copyAllocator; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG readerHandle->AllowedReadMark = 0; readerHandle->InBitwiseContext = false; #endif @@ -321,7 +321,7 @@ public unsafe void Seek(int where) [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void MarkBytesRead(int amount) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -343,7 +343,7 @@ internal unsafe void MarkBytesRead(int amount) /// A BitReader public unsafe BitReader EnterBitwiseContext() { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->InBitwiseContext = true; #endif return new BitReader(this); @@ -366,7 +366,7 @@ public unsafe BitReader EnterBitwiseContext() [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginRead(int bytes) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -377,7 +377,7 @@ public unsafe bool TryBeginRead(int bytes) { return false; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->AllowedReadMark = Handle->Position + bytes; #endif return true; @@ -401,7 +401,7 @@ public unsafe bool TryBeginRead(int bytes) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginReadValue(in T value) where T : unmanaged { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -413,7 +413,7 @@ public unsafe bool TryBeginReadValue(in T value) where T : unmanaged { return false; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->AllowedReadMark = Handle->Position + len; #endif return true; @@ -429,7 +429,7 @@ public unsafe bool TryBeginReadValue(in T value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe bool TryBeginReadInternal(int bytes) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -440,7 +440,7 @@ internal unsafe bool TryBeginReadInternal(int bytes) { return false; } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->Position + bytes > Handle->AllowedReadMark) { Handle->AllowedReadMark = Handle->Position + bytes; @@ -602,7 +602,7 @@ public unsafe void ReadValue(out string s, bool oneByteChars = false) /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -679,7 +679,7 @@ private void ReadLength(out int length) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -707,7 +707,7 @@ public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetB [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadByte(out byte value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -731,7 +731,7 @@ public unsafe void ReadByte(out byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadByteSafe(out byte value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -755,7 +755,7 @@ public unsafe void ReadByteSafe(out byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytes(byte* value, int size, int offset = 0) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -782,7 +782,7 @@ public unsafe void ReadBytes(byte* value, int size, int offset = 0) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void ReadBytesSafe(byte* value, int size, int offset = 0) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs index ca39ea8c62..343822fe32 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferWriter.cs @@ -23,7 +23,7 @@ internal struct WriterHandle internal int MaxCapacity; internal Allocator Allocator; internal bool BufferGrew; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG internal int AllowedWriteMark; internal bool InBitwiseContext; #endif @@ -79,7 +79,7 @@ public unsafe int Length internal unsafe void CommitBitwiseWrites(int amount) { Handle->Position += amount; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->InBitwiseContext = false; #endif } @@ -97,7 +97,7 @@ public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) // If the buffer grows, a new buffer will be allocated and the handle pointer pointed at the new location... // The original buffer won't be deallocated until the writer is destroyed since it's part of the handle allocation. Handle = (WriterHandle*)UnsafeUtility.Malloc(sizeof(WriterHandle) + size, UnsafeUtility.AlignOf(), allocator); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG UnsafeUtility.MemSet(Handle, 0, sizeof(WriterHandle) + size); #endif Handle->BufferPointer = (byte*)(Handle + 1); @@ -107,7 +107,7 @@ public unsafe FastBufferWriter(int size, Allocator allocator, int maxSize = -1) Handle->Allocator = allocator; Handle->MaxCapacity = maxSize < size ? size : maxSize; Handle->BufferGrew = false; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->AllowedWriteMark = 0; Handle->InBitwiseContext = false; #endif @@ -185,7 +185,7 @@ public unsafe void Truncate(int where = -1) /// A BitWriter public unsafe BitWriter EnterBitwiseContext() { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->InBitwiseContext = true; #endif return new BitWriter(this); @@ -201,7 +201,7 @@ internal unsafe void Grow(int additionalSizeRequired) var newSize = Math.Min(desiredSize, Handle->MaxCapacity); byte* newBuffer = (byte*)UnsafeUtility.Malloc(newSize, UnsafeUtility.AlignOf(), Handle->Allocator); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG UnsafeUtility.MemSet(newBuffer, 0, newSize); #endif UnsafeUtility.MemCpy(newBuffer, Handle->BufferPointer, Length); @@ -232,7 +232,7 @@ internal unsafe void Grow(int additionalSizeRequired) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWrite(int bytes) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -255,7 +255,7 @@ public unsafe bool TryBeginWrite(int bytes) return false; } } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->AllowedWriteMark = Handle->Position + bytes; #endif return true; @@ -280,7 +280,7 @@ public unsafe bool TryBeginWrite(int bytes) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -304,7 +304,7 @@ public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged return false; } } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG Handle->AllowedWriteMark = Handle->Position + len; #endif return true; @@ -320,7 +320,7 @@ public unsafe bool TryBeginWriteValue(in T value) where T : unmanaged [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe bool TryBeginWriteInternal(int bytes) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -343,7 +343,7 @@ public unsafe bool TryBeginWriteInternal(int bytes) return false; } } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->Position + bytes > Handle->AllowedWriteMark) { Handle->AllowedWriteMark = Handle->Position + bytes; @@ -523,7 +523,7 @@ public unsafe void WriteValue(string s, bool oneByteChars = false) /// Whether or not to use one byte per character. This will only allow ASCII public unsafe void WriteValueSafe(string s, bool oneByteChars = false) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -618,7 +618,7 @@ public static unsafe int GetWriteSize(NativeList array, int count = -1, in [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -644,7 +644,7 @@ public unsafe void WritePartialValue(T value, int bytesToWrite, int offsetByt [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteByte(byte value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -668,7 +668,7 @@ public unsafe void WriteByte(byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteByteSafe(byte value) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -692,7 +692,7 @@ public unsafe void WriteByteSafe(byte value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytes(byte* value, int size, int offset = 0) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( @@ -719,7 +719,7 @@ public unsafe void WriteBytes(byte* value, int size, int offset = 0) [MethodImpl(MethodImplOptions.AggressiveInlining)] public unsafe void WriteBytesSafe(byte* value, int size, int offset = 0) { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG if (Handle->InBitwiseContext) { throw new InvalidOperationException( diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs index be03c1b478..821f727ed3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTickSystem.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode [Serializable] public class NetworkTickSystem { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private static ProfilerMarker s_Tick = new ProfilerMarker($"{nameof(NetworkTickSystem)}.Tick"); #endif @@ -97,11 +97,11 @@ public void UpdateTick(double localTimeSec, double serverTimeSec) LocalTime = new NetworkTime(TickRate, i); ServerTime = new NetworkTime(TickRate, i - localToServerDifference); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_Tick.Begin(); #endif Tick?.Invoke(); -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_Tick.End(); #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs index 16f4173f24..a967052a16 100644 --- a/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs +++ b/com.unity.netcode.gameobjects/Runtime/Timing/NetworkTimeSystem.cs @@ -34,7 +34,7 @@ public class NetworkTimeSystem /// private const double k_DefaultAdjustmentRatio = 0.01d; -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG private static ProfilerMarker s_SyncTime = new ProfilerMarker($"{nameof(NetworkManager)}.SyncTime"); #endif private double m_PreviousTimeSec; @@ -182,7 +182,7 @@ internal void UpdateTime() /// private void OnTickSyncTime() { -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_SyncTime.Begin(); #endif @@ -196,7 +196,7 @@ private void OnTickSyncTime() m_ConnectionManager.SendMessage(ref message, m_NetworkDelivery, m_ConnectionManager.ConnectedClientIds); } -#if DEVELOPMENT_BUILD || UNITY_EDITOR +#if DEBUG s_SyncTime.End(); #endif } diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index fda0629b92..cac615f394 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -1,7 +1,7 @@ // 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) +#if UNITY_EDITOR || (DEBUG && !UNITY_MP_TOOLS_NETSIM_DISABLED_IN_DEVELOP) || (!DEBUG && UNITY_MP_TOOLS_NETSIM_ENABLED_IN_RELEASE) #define UNITY_MP_TOOLS_NETSIM_IMPLEMENTATION_ENABLED #endif diff --git a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity index bab567858b..e1dfeb1a4d 100644 --- a/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity +++ b/testproject/Assets/Tests/Manual/SceneTransitioningAdditive/SceneTransitioningBase1.unity @@ -246,6 +246,7 @@ MonoBehaviour: Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + k__BackingField: 0 ActiveSceneSynchronization: 0 SceneMigrationSynchronization: 1 SpawnWithObservers: 1 @@ -278,14 +279,14 @@ Light: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 37242881} m_Enabled: 1 - serializedVersion: 11 + serializedVersion: 13 m_Type: 1 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_CookieSize2D: {x: 10, y: 10} m_Shadows: m_Type: 2 m_Resolution: -1 @@ -330,7 +331,7 @@ Light: m_UseBoundingSphereOverride: 0 m_UseViewFrustumForShadowCasterCull: 1 m_ForceVisible: 0 - m_ShadowRadius: 0 + m_ShapeRadius: 0 m_ShadowAngle: 0 m_LightUnit: 1 m_LuxAtDistance: 1 @@ -646,6 +647,8 @@ MeshRenderer: m_RayTracingAccelStructBuildFlagsOverride: 0 m_RayTracingAccelStructBuildFlags: 1 m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -667,9 +670,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &57392473 MeshFilter: @@ -979,6 +984,7 @@ Canvas: m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 m_VertexColorAlwaysGammaSpace: 0 + m_UseReflectionProbes: 0 m_AdditionalShaderChannelsFlag: 0 m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 @@ -1157,6 +1163,7 @@ Canvas: m_OverridePixelPerfect: 0 m_SortingBucketNormalizedSize: 0 m_VertexColorAlwaysGammaSpace: 0 + m_UseReflectionProbes: 0 m_AdditionalShaderChannelsFlag: 0 m_UpdateRectTransformForStandalone: 0 m_SortingLayerID: 0 @@ -1408,6 +1415,8 @@ MeshRenderer: m_RayTracingAccelStructBuildFlagsOverride: 0 m_RayTracingAccelStructBuildFlags: 1 m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -1429,9 +1438,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &336568648 MeshFilter: @@ -2549,7 +2560,7 @@ LightingSettings: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_Name: - serializedVersion: 9 + serializedVersion: 10 m_EnableBakedLightmaps: 1 m_EnableRealtimeLightmaps: 0 m_RealtimeEnvironmentLighting: 1 @@ -2564,6 +2575,12 @@ LightingSettings: m_BakeResolution: 40 m_Padding: 2 m_LightmapCompression: 3 + m_LightmapPackingMode: 1 + m_LightmapPackingMethod: 0 + m_XAtlasPackingAttempts: 16384 + m_XAtlasBruteForce: 0 + m_XAtlasBlockAlign: 0 + m_XAtlasRepackUnderutilizedLightmaps: 1 m_AO: 0 m_AOMaxDistance: 1 m_CompAOExponent: 1 @@ -2876,11 +2893,12 @@ MonoBehaviour: NetworkIdRecycleDelay: 120 RpcHashSize: 0 LoadSceneTimeOut: 120 - SpawnTimeout: 1 + SpawnTimeout: 10 EnableNetworkLogs: 1 NetworkTopology: 0 UseCMBService: 0 AutoSpawnPlayerPrefabClientSide: 1 + NetworkMessageMetrics: 1 NetworkProfilingMetrics: 1 OldPrefabList: - Override: 0 @@ -2934,7 +2952,7 @@ MonoBehaviour: SourceHashToOverride: 0 OverridingTargetPrefab: {fileID: 0} RunInBackground: 1 - LogLevel: 1 + LogLevel: 0 --- !u!4 &1024114720 Transform: m_ObjectHideFlags: 0 @@ -3000,7 +3018,9 @@ MonoBehaviour: ConnectionData: Address: 127.0.0.1 Port: 7777 - ServerListenAddress: + WebSocketPath: / + ServerListenAddress: 127.0.0.1 + ClientBindPort: 0 DebugSimulator: PacketDelayMS: 0 PacketJitterMS: 0 @@ -3088,6 +3108,7 @@ MonoBehaviour: Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + k__BackingField: 0 ActiveSceneSynchronization: 0 SceneMigrationSynchronization: 1 SpawnWithObservers: 1 @@ -3399,6 +3420,8 @@ MeshRenderer: m_RayTracingAccelStructBuildFlagsOverride: 0 m_RayTracingAccelStructBuildFlags: 1 m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -3420,9 +3443,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1336081254 MeshFilter: @@ -5318,6 +5343,8 @@ MeshRenderer: m_RayTracingAccelStructBuildFlagsOverride: 0 m_RayTracingAccelStructBuildFlags: 1 m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -5339,9 +5366,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &1857685346 MeshFilter: @@ -5765,6 +5794,8 @@ MeshRenderer: m_RayTracingAccelStructBuildFlagsOverride: 0 m_RayTracingAccelStructBuildFlags: 1 m_SmallMeshCulling: 1 + m_ForceMeshLod: -1 + m_MeshLodSelectionBias: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -5786,9 +5817,11 @@ MeshRenderer: m_AutoUVMaxDistance: 0.5 m_AutoUVMaxAngle: 89 m_LightmapParameters: {fileID: 0} + m_GlobalIlluminationMeshLod: 0 m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_MaskInteraction: 0 m_AdditionalVertexStreams: {fileID: 0} --- !u!33 &2028091271 MeshFilter: @@ -6134,6 +6167,7 @@ MonoBehaviour: Ownership: 1 AlwaysReplicateAsRoot: 0 SynchronizeTransform: 1 + k__BackingField: 0 ActiveSceneSynchronization: 0 SceneMigrationSynchronization: 1 SpawnWithObservers: 1 From 7215237b763069b1c69945ac4ce80df6849a1ccc Mon Sep 17 00:00:00 2001 From: Netcode team bot Date: Sun, 21 Jun 2026 16:07:40 +0200 Subject: [PATCH 15/64] chore: Updated aspects of Netcode package in anticipation of v2.13.0 release (#4033) --- com.unity.netcode.gameobjects/CHANGELOG.md | 17 ++++++++++++++--- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3493b13666..1f7af6d540 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,15 +10,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) ### Changed -- Changed replaced define usages of `DEVELOPMENT_BUILD || UNITY_EDITOR` and a few niche uses of `DEVELOPMENT_BUILD` with `DEBUG`. (#4006) ### Deprecated -- Deprecated the nullable boolean `NetworkObject.IsSceneObject` and introduced `NetworkObject.InScenePlaced`. (#4000) ### Removed @@ -32,6 +29,20 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [2.13.0] - 2026-06-21 + +### Added + +- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) + +### Changed + +- Changed replaced define usages of `DEVELOPMENT_BUILD || UNITY_EDITOR` and a few niche uses of `DEVELOPMENT_BUILD` with `DEBUG`. (#4006) + +### Deprecated + +- Deprecated the nullable boolean `NetworkObject.IsSceneObject` and introduced `NetworkObject.InScenePlaced`. (#4000) + ## [2.12.0] - 2026-05-24 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index d2b5daeecd..074afe6e47 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": "2.13.0", + "version": "2.13.1", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From b776c5446373a4ea39d5ff16d9c2cadf86cbd697 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sun, 21 Jun 2026 10:19:06 -0500 Subject: [PATCH 16/64] fix: NetworkRigidbodyBase 2D rigid body issues (#4012) * fix Fixing issue where Rigidbody2D was not applying rotation correctly. (4009) Fixing issue where `NetworkRigidbodyBase` was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (3924) * style Removing static using directive (not used). * update Missed the return... * test Did a bit of an overhaul on the original NetworkRigidbodyTest. It should now run with distributed authority tests and includes a 2D rigidbody in the tests as well. * stye Removing trailing space on comment * test Updating the NetworkRigidbodyTest to do a 2nd spawn pass that swaps the session owner and non-session owner clients to validate it works when a non-session authority client is the spawn authority. * update Removing Rigidbody tests no longer used and already covered by `NetworkRigidbodyTests`. Moving `RigidbodyContactEventManagerTests` into its own file. --- .../Components/NetworkRigidBodyBase.cs | 62 +- .../Runtime/Physics/NetworkRigidbody2DTest.cs | 80 --- .../Physics/NetworkRigidbody2DTest.cs.meta | 11 - .../Runtime/Physics/NetworkRigidbodyTest.cs | 589 +++++------------- .../RigidbodyContactEventManagerTests.cs | 403 ++++++++++++ .../RigidbodyContactEventManagerTests.cs.meta | 2 + testproject/Assets/Tests/Runtime/Physics.meta | 8 - ...etworkRigidbody2DCntChangeOwnershipTest.cs | 95 --- ...kRigidbody2DCntChangeOwnershipTest.cs.meta | 11 - .../Physics/NetworkRigidbody2DCntTest.cs | 82 --- .../Physics/NetworkRigidbody2DCntTest.cs.meta | 11 - .../NetworkRigidbodyCntChangeOwnershipTest.cs | 95 --- ...orkRigidbodyCntChangeOwnershipTest.cs.meta | 11 - .../Physics/NetworkRigidbodyCntTest.cs | 82 --- .../Physics/NetworkRigidbodyCntTest.cs.meta | 11 - 15 files changed, 613 insertions(+), 940 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/Physics.meta delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs delete mode 100644 testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 0d0de9afa8..4094e084a8 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -2,6 +2,7 @@ using System.Runtime.CompilerServices; using UnityEngine; + namespace Unity.Netcode.Components { /// @@ -571,7 +572,7 @@ private void InternalMoveRotation2D(Quaternion rotation) { var quaternion = Quaternion.identity; var angles = quaternion.eulerAngles; - angles.z = m_InternalRigidbody2D.rotation; + angles.z = rotation.z; quaternion.eulerAngles = angles; m_InternalRigidbody2D.MoveRotation(quaternion); } @@ -845,6 +846,28 @@ public void SetIsKinematic(bool isKinematic) PostSetIsKinematic(); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private bool HasInterpolationTypeSet(InterpolationTypes interpolationType) + { +#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + if (m_IsRigidbody2D) + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; + } + else + { + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; + } +#endif +#if COM_UNITY_MODULES_PHYSICS && !COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; +#endif +#if !COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; +#endif + } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void PostSetIsKinematic() { @@ -853,26 +876,29 @@ private void PostSetIsKinematic() { return; } + if (UseRigidBodyForMotion) { - // Only if the NetworkTransform is set to interpolate do we need to check for extrapolation - if (NetworkTransform.Interpolate && m_OriginalInterpolation == InterpolationTypes.Extrapolate) + // Exit early if the original interpolation type is not set to extrapolate or NetworkTransform interpolate is disabled + if (m_OriginalInterpolation != InterpolationTypes.Extrapolate || !NetworkTransform.Interpolate) { - if (IsKinematic()) - { - // If not already set to interpolate then set the Rigidbody to interpolate - if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate) - { - // Sleep until the next fixed update when switching from extrapolation to interpolation - SleepRigidbody(); - SetInterpolation(InterpolationTypes.Interpolate); - } - } - else - { - // Switch it back to the original interpolation if non-kinematic (doesn't require sleep). - SetInterpolation(m_OriginalInterpolation); - } + return; + } + + // Otherwise, if this is the active physics object + if (!IsKinematic()) + { + // switch it back to the original interpolation and exit early + SetInterpolation(m_OriginalInterpolation); + return; + } + + // If the Rigidbody or Rigidbody2D is currently configured to extrapolate + if (HasInterpolationTypeSet(InterpolationTypes.Extrapolate)) + { + // sleep until the next fixed update when switching from extrapolation to interpolation + SleepRigidbody(); + SetInterpolation(InterpolationTypes.Interpolate); } } else diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs deleted file mode 100644 index 089eeee542..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs +++ /dev/null @@ -1,80 +0,0 @@ -#if COM_UNITY_MODULES_PHYSICS2D -using System.Collections; -using NUnit.Framework; -using Unity.Netcode.Components; -using UnityEngine; -using UnityEngine.TestTools; -using Unity.Netcode.TestHelpers.Runtime; - -namespace Unity.Netcode.RuntimeTests -{ - internal class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase - { - public override bool Kinematic => false; - } - - internal class NetworkRigidbody2DKinematicTest : NetworkRigidbody2DTestBase - { - public override bool Kinematic => true; - } - - public abstract class NetworkRigidbody2DTestBase : NetcodeIntegrationTest - { - protected override int NumberOfClients => 1; - - public abstract bool Kinematic { get; } - - protected override void OnCreatePlayerPrefab() - { - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.AddComponent(); - m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation2D.Interpolate; - m_PlayerPrefab.GetComponent().isKinematic = Kinematic; - } - - /// - /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. - /// - /// - [UnityTest] - public IEnumerator TestRigidbodyKinematicEnableDisable() - { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); - var serverPlayer = serverClientPlayerResult.Result.gameObject; - - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); - var clientPlayer = clientClientPlayerResult.Result.gameObject; - - Assert.IsNotNull(serverPlayer); - Assert.IsNotNull(clientPlayer); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - // server rigidbody has authority and should have a kinematic mode of false - Assert.True(serverPlayer.GetComponent().isKinematic == Kinematic); - Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, serverPlayer.GetComponent().interpolation); - - // client rigidbody has no authority and should have a kinematic mode of true - Assert.True(clientPlayer.GetComponent().isKinematic); - Assert.AreEqual(RigidbodyInterpolation2D.None, clientPlayer.GetComponent().interpolation); - - // despawn the server player, (but keep it around on the server) - serverPlayer.GetComponent().Despawn(false); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - // This should equal Kinematic - Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic); - - yield return WaitForTicks(m_ServerNetworkManager, 5); - - Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. - } - } -} -#endif // COM_UNITY_MODULES_PHYSICS2D diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta deleted file mode 100644 index 48faa513f7..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f33b2f298cbb7b248b2a76ba48ee1d53 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 4509797369..1a7d768831 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -1,7 +1,6 @@ #if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D using System.Collections; using System.Collections.Generic; -using System.Text; using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; @@ -10,487 +9,227 @@ namespace Unity.Netcode.RuntimeTests { - [TestFixture(RigidbodyInterpolation.Interpolate, true, true)] // This should be allowed under all condistions when using Rigidbody motion - [TestFixture(RigidbodyInterpolation.Extrapolate, true, true)] // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation - [TestFixture(RigidbodyInterpolation.Extrapolate, false, true)] // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation - [TestFixture(RigidbodyInterpolation.Interpolate, true, false)] // This should not allow kinematic instances to have Rigidbody interpolation enabled - [TestFixture(RigidbodyInterpolation.Interpolate, false, false)] // Testing that rigid body interpolation remains the same if NT interpolate is disabled + [TestFixture(HostOrServer.Server)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] internal class NetworkRigidbodyTest : NetcodeIntegrationTest { protected override int NumberOfClients => 1; - private bool m_NetworkTransformInterpolate; - private bool m_UseRigidBodyForMotion; - private RigidbodyInterpolation m_RigidbodyInterpolation; - public NetworkRigidbodyTest(RigidbodyInterpolation rigidbodyInterpolation, bool networkTransformInterpolate, bool useRigidbodyForMotion) - { - m_RigidbodyInterpolation = rigidbodyInterpolation; - m_NetworkTransformInterpolate = networkTransformInterpolate; - m_UseRigidBodyForMotion = useRigidbodyForMotion; - } - - protected override void OnCreatePlayerPrefab() - { - var networkTransform = m_PlayerPrefab.AddComponent(); - networkTransform.Interpolate = m_NetworkTransformInterpolate; - var rigidbody = m_PlayerPrefab.AddComponent(); - rigidbody.interpolation = m_RigidbodyInterpolation; - var networkRigidbody = m_PlayerPrefab.AddComponent(); - networkRigidbody.UseRigidBodyForMotion = m_UseRigidBodyForMotion; - } - - /// - /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. - /// - /// - [UnityTest] - public IEnumerator TestRigidbodyKinematicEnableDisable() - { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerInstance = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject; - - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientPlayerInstance = m_ClientNetworkManagers[0].LocalClient.PlayerObject; - - Assert.IsNotNull(serverClientPlayerInstance, $"{nameof(serverClientPlayerInstance)} is null!"); - Assert.IsNotNull(clientPlayerInstance, $"{nameof(clientPlayerInstance)} is null!"); - - var serverClientInstanceRigidBody = serverClientPlayerInstance.GetComponent(); - var clientRigidBody = clientPlayerInstance.GetComponent(); - - if (m_UseRigidBodyForMotion) - { - var interpolateCompareNonAuthoritative = m_NetworkTransformInterpolate ? RigidbodyInterpolation.Interpolate : m_RigidbodyInterpolation; - - // Server authoritative NT should yield non-kinematic mode for the server-side player instance - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - - // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. - Assert.AreEqual(m_RigidbodyInterpolation, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {m_RigidbodyInterpolation}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - - // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used - Assert.AreEqual(interpolateCompareNonAuthoritative, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {interpolateCompareNonAuthoritative}!"); - } - else + private List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)> m_TestConfigurations = + new List<(RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion)>() { - // server rigidbody has authority and should not be kinematic - Assert.False(serverClientInstanceRigidBody.isKinematic, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is kinematic!"); - Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverClientInstanceRigidBody.interpolation, $"[Server-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {serverClientInstanceRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - - // Server authoritative NT should yield kinematic mode for the client-side player instance - Assert.True(clientRigidBody.isKinematic, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic!"); - - // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation - if (!m_NetworkTransformInterpolate) - { - Assert.AreEqual(RigidbodyInterpolation.Interpolate, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.Interpolate)}!"); - } - else - { - Assert.AreEqual(RigidbodyInterpolation.None, clientRigidBody.interpolation, $"[Client-Side] Client-{m_ClientNetworkManagers[0].LocalClientId} " + - $"player's {nameof(Rigidbody)}'s interpolation is {clientRigidBody.interpolation} and not {nameof(RigidbodyInterpolation.None)}!"); - } - } - - // despawn the server player (but keep it around on the server) - serverClientPlayerInstance.Despawn(false); - - yield return WaitForConditionOrTimeOut(() => !serverClientPlayerInstance.IsSpawned && !clientPlayerInstance.IsSpawned); - AssertOnTimeout("Timed out waiting for client player to despawn on both server and client!"); - - // When despawned, we should always be kinematic (i.e. don't apply physics when despawned) - Assert.True(serverClientInstanceRigidBody.isKinematic, $"[Server-Side][Despawned] Client-{m_ClientNetworkManagers[0].LocalClientId} player's {nameof(Rigidbody)} is not kinematic when despawned!"); - Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!"); - } - } - - internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo - { - public ContactEventHandlerInfo GetContactEventHandlerInfo() - { - var contactEventHandlerInfo = new ContactEventHandlerInfo() - { - HasContactEventPriority = IsOwner, - ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value, + (RigidbodyInterpolation.Interpolate, true, true), // This should be allowed under all condistions when using Rigidbody motion + (RigidbodyInterpolation.Extrapolate, true, true), // This should not allow extrapolation on non-auth instances when using Rigidbody motion & NT interpolation + (RigidbodyInterpolation.Extrapolate, false, true), // This should allow extrapolation on non-auth instances when using Rigidbody & NT has no interpolation + (RigidbodyInterpolation.Interpolate, true, false), // This should not allow kinematic instances to have Rigidbody interpolation enabled + (RigidbodyInterpolation.Interpolate, false, false) // Testing that rigidbody interpolation remains the same if NT interpolate is disabled }; - return contactEventHandlerInfo; - } - - protected override void OnRegisterForContactEvents(bool isRegistering) - { - RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); - } - } + /// + /// The current test configuration applied to the current test running. + /// + private (RigidbodyInterpolation interpolationType, bool enableInterpolation, bool useRigidbodyForMotion) m_CurrentConfiguration; - internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler - { - public static Vector3 SessionOwnerSpawnPoint; - public static Vector3 ClientSpawnPoint; - public static bool VerboseDebug; - public enum HelperStates + public NetworkRigidbodyTest(HostOrServer hostOrServer) : base(hostOrServer) { - None, - MoveForward, } - private HelperStates m_HelperState; - - public void SetHelperState(HelperStates state) - { - m_HelperState = state; - if (!m_NetworkRigidbody.IsKinematic()) - { - m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; - m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; - } - m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None; - if (!m_NetworkRigidbody.IsKinematic()) - { - m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; - m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; - } + /// + /// Base prefab for and + /// + private GameObject m_RigidbodyPrefab; + private NetworkTransform m_3DNetworkTransform; + private Rigidbody m_PrefabRigidbody; + private NetworkRigidbody m_PrefabNetworkRigidbody; + private NetworkObject m_3DAuthorityInstance; - } + /// + /// Base prefab for and + /// + private GameObject m_Rigidbody2DPrefab; + private NetworkTransform m_2DNetworkTransform; + private Rigidbody2D m_PrefabRigidbody2D; + private NetworkRigidbody2D m_PrefabNetworkRigidbody2D; + private NetworkObject m_2DAuthorityInstance; - protected struct ContactEventInfo + protected override void OnServerAndClientsCreated() { - public ulong EventId; - public Vector3 AveragedCollisionNormal; - public Rigidbody CollidingBody; - public Vector3 ContactPoint; - } + m_RigidbodyPrefab = CreateNetworkObjectPrefab("RBTest"); + m_3DNetworkTransform = m_RigidbodyPrefab.AddComponent(); + m_PrefabRigidbody = m_RigidbodyPrefab.AddComponent(); + m_PrefabNetworkRigidbody = m_RigidbodyPrefab.AddComponent(); - protected List m_ContactEvents = new List(); + m_Rigidbody2DPrefab = CreateNetworkObjectPrefab("RB2DTest"); + m_2DNetworkTransform = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); + m_PrefabNetworkRigidbody2D = m_Rigidbody2DPrefab.AddComponent(); - protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); - - protected NetworkRigidbody m_NetworkRigidbody; - public ContactEventTransformHelper Target; - - public bool HasContactEvents() - { - return m_ContactEvents.Count > 0; + base.OnServerAndClientsCreated(); } - public Rigidbody GetRigidbody() + private string m_ConfigHeader; + private void ApplyCurrentTestConfiguration() { - return m_NetworkRigidbody.Rigidbody; - } - - public bool HadContactWith(ContactEventTransformHelper otherObject) - { - if (otherObject == null) - { - return false; - } - foreach (var contactEvent in m_ContactEvents) - { - if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody) - { - return true; - } - } - return false; - } + // Configure both 3D and 2D versions based on the current test configuration + m_3DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody.interpolation = m_CurrentConfiguration.interpolationType; + m_PrefabNetworkRigidbody.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; + m_2DNetworkTransform.Interpolate = m_CurrentConfiguration.enableInterpolation; + m_PrefabRigidbody2D.interpolation = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + m_PrefabNetworkRigidbody2D.UseRigidBodyForMotion = m_CurrentConfiguration.useRigidbodyForMotion; - protected virtual void CheckToStopMoving() - { - SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward); + // Build a header used in assert messages + m_ConfigHeader = $"[{m_CurrentConfiguration.interpolationType}][Interpolate: {m_CurrentConfiguration.enableInterpolation}][RB-Motion: {m_CurrentConfiguration.useRigidbodyForMotion}]"; } - public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default) + /// + /// Iterates through the to validate various + /// Rigidbody interpolation settings and kinematic states for authority and non-authority + /// instances. + /// + [UnityTest] + public IEnumerator TestRigidbodyKinematicEnableDisable() { - if (Target == null) + foreach (var configuration in m_TestConfigurations) { - return; - } + m_CurrentConfiguration = configuration; + ApplyCurrentTestConfiguration(); - if (collidingBody != null) - { - Log($">>>>>>> contact event with {collidingBody.name}!"); - } - else - { - Log($">>>>>>> contact event with non-rigidbody!"); - } + // Host, Server, DAHost/Session-owner are spawn authority + yield return RunTestConfiguration(); - m_ContactEvents.Add(new ContactEventInfo() - { - EventId = eventId, - AveragedCollisionNormal = averagedCollisionNormal, - CollidingBody = collidingBody, - ContactPoint = contactPoint, - }); - CheckToStopMoving(); - } - - private void SetInitialPositionClientServer() - { - if (IsServer) - { - if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer) - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } - else + // When using distributed authority, swap the session owner with + // the non-session owner client as being the spawn authority. + if (m_DistributedAuthority) { - transform.position = SessionOwnerSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + yield return RunTestConfiguration(true); } } - else - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } } - private void SetInitialPositionDistributedAuthority() - { - if (HasAuthority) + /// + /// Validates the current applied test configuration. + /// + private IEnumerator RunTestConfiguration(bool swapAuthority = false) + { + // The authority is the "spawn authority". + // Distributed authority runs this a second time with a non-session owner client being the + // spawn authority to validate that scenario works correctly. + var authority = !swapAuthority ? GetAuthorityNetworkManager() : GetNonAuthorityNetworkManager(); + var nonAuthority = !swapAuthority ? GetNonAuthorityNetworkManager() : GetAuthorityNetworkManager(); + + // Spawn instances of both the 3D and 2D prefabs configured for the current test. + m_3DAuthorityInstance = SpawnObject(m_RigidbodyPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_3DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_3DAuthorityInstance.name} on all clients!"); + + m_2DAuthorityInstance = SpawnObject(m_Rigidbody2DPrefab, authority).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(m_2DAuthorityInstance); + AssertOnTimeout($"Failed to spawn {m_2DAuthorityInstance.name} on all clients!"); + + // Test 3D Rigidbody + #region 3D Rigidbody validation + var authorityRigidbody = m_3DAuthorityInstance.GetComponent(); + var nonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_3DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody = nonAuthorityInstance.GetComponent(); + var authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_3DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody.isKinematic, $"{authorityHeader} is kinematic!"); + + var nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody.isKinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative = RigidbodyInterpolation.None; + + if (m_CurrentConfiguration.useRigidbodyForMotion) { - if (IsSessionOwner) - { - transform.position = SessionOwnerSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; - } - else - { - transform.position = ClientSpawnPoint; - m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; - } - } - } - - public override void OnNetworkSpawn() - { - m_NetworkRigidbody = GetComponent(); + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(m_CurrentConfiguration.interpolationType, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); - m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15; - m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10; + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.Interpolate : m_CurrentConfiguration.interpolationType; - if (NetworkManager.DistributedAuthorityMode) - { - SetInitialPositionDistributedAuthority(); } else { - SetInitialPositionClientServer(); - } - if (IsLocalPlayer) - { - RegisterForContactEvents(true); - } - else - { - m_NetworkRigidbody.Rigidbody.detectCollisions = false; - } - base.OnNetworkSpawn(); - } - - protected virtual void OnRegisterForContactEvents(bool isRegistering) - { - RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); - } - - public void RegisterForContactEvents(bool isRegistering) - { - OnRegisterForContactEvents(isRegistering); - } - - private void FixedUpdate() - { - if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward) - { - return; - } - var distance = Vector3.Distance(Target.transform.position, transform.position); - var moveAmount = Mathf.Max(1.2f, distance); - // Head towards our target - var dir = (Target.transform.position - transform.position).normalized; - var deltaMove = dir * moveAmount * Time.fixedDeltaTime; - m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove); - + Assert.AreEqual(RigidbodyInterpolation.Interpolate, authorityRigidbody.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody.interpolation} " + + $"and not {RigidbodyInterpolation.Interpolate}!"); - Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}"); - } - - protected void Log(string msg) - { - if (VerboseDebug) - { - Debug.Log($"Client-{OwnerClientId} {msg}"); + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + interpolateCompareNonAuthoritative = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation.None : RigidbodyInterpolation.Interpolate; } - } - } - - [TestFixture(HostOrServer.Host, ContactEventTypes.Default)] - [TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)] - [TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)] - [TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)] - internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation - { - protected override int NumberOfClients => 1; - - private GameObject m_RigidbodyContactEventManager; - public enum ContactEventTypes - { - Default, - WithInfo - } + Assert.AreEqual(interpolateCompareNonAuthoritative, nonAuthorityRigidbody.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion - private ContactEventTypes m_ContactEventType; - private StringBuilder m_ErrorLogger = new StringBuilder(); + // Test 2D Rigidbody + #region 2D Rigidbody validation + var authorityRigidbody2D = m_2DAuthorityInstance.GetComponent(); + var nonAuthorityInstance2D = nonAuthority.SpawnManager.SpawnedObjects[m_2DAuthorityInstance.NetworkObjectId]; + var nonAuthorityRigidbody2D = nonAuthorityInstance2D.GetComponent(); - public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer) - { - m_ContactEventType = contactEventType; - } + authorityHeader = $"{m_ConfigHeader}[Authority] Client-{authority.LocalClientId}'s instance of {m_2DAuthorityInstance.name}"; + // The authority instance should always be non-kinematic + Assert.False(authorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{authorityHeader} is kinematic!"); - protected override void OnCreatePlayerPrefab() - { - ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3); - ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4); - if (m_ContactEventType == ContactEventTypes.Default) + nonAuthorityHeader = $"{m_ConfigHeader}[Non-Authority] Client-{nonAuthority.LocalClientId}'s instance of {nonAuthorityInstance.name}"; + // Non-authority instances should always be kinematic + Assert.True(nonAuthorityRigidbody2D.bodyType == RigidbodyType2D.Kinematic, $"{nonAuthorityHeader} is not kinematic!"); + var interpolateCompareNonAuthoritative2D = RigidbodyInterpolation2D.None; + var configInterpolation2D = m_CurrentConfiguration.interpolationType == RigidbodyInterpolation.Interpolate ? RigidbodyInterpolation2D.Interpolate : RigidbodyInterpolation2D.Extrapolate; + if (m_CurrentConfiguration.useRigidbodyForMotion) { - var helper = m_PlayerPrefab.AddComponent(); - helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + // The authoritative instance can be None, Interpolate, or Extrapolate for the Rigidbody interpolation settings. + Assert.AreEqual(configInterpolation2D, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {m_CurrentConfiguration.interpolationType}!"); + + // When using Rigidbody motion, authoritative and non-authoritative Rigidbody interpolation settings should be preserved (except when extrapolation is used + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.Interpolate : configInterpolation2D; } else { - var helperWithInfo = m_PlayerPrefab.AddComponent(); - helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner; - } - - var rigidbody = m_PlayerPrefab.AddComponent(); - rigidbody.useGravity = false; - rigidbody.isKinematic = true; - rigidbody.mass = 5.0f; - rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous; - var sphereCollider = m_PlayerPrefab.AddComponent(); - sphereCollider.radius = 0.5f; - sphereCollider.providesContacts = true; - - var networkRigidbody = m_PlayerPrefab.AddComponent(); - networkRigidbody.UseRigidBodyForMotion = true; - networkRigidbody.AutoUpdateKinematicState = false; - - m_RigidbodyContactEventManager = new GameObject(); - m_RigidbodyContactEventManager.AddComponent(); - } - + Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, authorityRigidbody2D.interpolation, $"{authorityHeader} interpolation is {authorityRigidbody2D.interpolation} " + + $"and not {RigidbodyInterpolation2D.Interpolate}!"); - - private bool PlayersSpawnedInRightLocation() - { - var authority = GetAuthorityNetworkManager(); - var nonAuthority = GetNonAuthorityNetworkManager(); - - var position = authority.LocalClient.PlayerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); - return false; - } - - position = nonAuthority.LocalClient.PlayerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); - return false; - } - var playerObject = (NetworkObject)null; - if (!authority.SpawnManager.SpawnedObjects.ContainsKey(nonAuthority.LocalClient.PlayerObject.NetworkObjectId)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} cannot find a local spawned instance of Client-{nonAuthority.LocalClientId}'s player object!"); - return false; + // client rigidbody has no authority with NT interpolation disabled should allow Rigidbody interpolation + interpolateCompareNonAuthoritative2D = m_CurrentConfiguration.enableInterpolation ? RigidbodyInterpolation2D.None : RigidbodyInterpolation2D.Interpolate; } - playerObject = authority.SpawnManager.SpawnedObjects[nonAuthority.LocalClient.PlayerObject.NetworkObjectId]; - position = playerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) - { - m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); - return false; - } + Assert.AreEqual(interpolateCompareNonAuthoritative2D, nonAuthorityRigidbody2D.interpolation, $"{nonAuthorityHeader} interpolation is {nonAuthorityRigidbody2D.interpolation} " + + $"and not {interpolateCompareNonAuthoritative}!"); + #endregion - if (!nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(authority.LocalClient.PlayerObject.NetworkObjectId)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} cannot find a local spawned instance of Client-{authority.LocalClientId}'s player object!"); - return false; - } - playerObject = nonAuthority.SpawnManager.SpawnedObjects[authority.LocalClient.PlayerObject.NetworkObjectId]; - position = playerObject.transform.position; - if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position)) - { - m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); - return false; - } - return true; + var spawnedInstances = new List() { m_3DAuthorityInstance, m_2DAuthorityInstance }; + m_3DAuthorityInstance.Despawn(); + m_2DAuthorityInstance.Despawn(); + yield return WaitForDespawnedOnAllOrTimeOut(spawnedInstances); + AssertOnTimeout($"Failed to de-spawn instances on all clients!"); + m_3DAuthorityInstance = null; + m_2DAuthorityInstance = null; } - - [UnityTest] - public IEnumerator TestContactEvents() + /// + /// Handle clean up in case of a failed test + /// + protected override IEnumerator OnTearDown() { - ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug; - - m_PlayerPrefab.SetActive(false); - m_ErrorLogger.Clear(); - // Validate all instances are spawned in the right location - yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation); - AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}"); - m_ErrorLogger.Clear(); - - var authority = GetAuthorityNetworkManager(); - var nonAuthority = GetNonAuthorityNetworkManager(); + // If either of these are not null then we most likely failed and didn't cleanup. - var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.LocalClient.PlayerObject.GetComponent() : - authority.LocalClient.PlayerObject.GetComponent(); - var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.LocalClient.PlayerObject.GetComponent() : - nonAuthority.LocalClient.PlayerObject.GetComponent(); - - // Get both players to point towards each other - sessionOwnerPlayer.Target = clientPlayer; - clientPlayer.Target = sessionOwnerPlayer; - - sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); - clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); - - - yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer)); - AssertOnTimeout("Timed out waiting for a player to collide with another player!"); - - clientPlayer.RegisterForContactEvents(false); - sessionOwnerPlayer.RegisterForContactEvents(false); - var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent() : - authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent(); - otherPlayer.RegisterForContactEvents(false); - otherPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() : - nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent(); - otherPlayer.RegisterForContactEvents(false); - - Object.Destroy(m_RigidbodyContactEventManager); - m_RigidbodyContactEventManager = null; - } + // Clean-up m_3DAuthorityInstance + if (m_3DAuthorityInstance) + { + Object.Destroy(m_3DAuthorityInstance); + m_3DAuthorityInstance = null; + } - protected override IEnumerator OnTearDown() - { - // In case of a test failure - if (m_RigidbodyContactEventManager) + // Clean-up m_2DAuthorityInstance + if (m_2DAuthorityInstance) { - Object.Destroy(m_RigidbodyContactEventManager); - m_RigidbodyContactEventManager = null; + Object.Destroy(m_2DAuthorityInstance); + m_2DAuthorityInstance = null; } return base.OnTearDown(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs new file mode 100644 index 0000000000..51aaa5ba56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs @@ -0,0 +1,403 @@ +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +using System.Collections; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + + +namespace Unity.Netcode.RuntimeTests +{ + + + [TestFixture(HostOrServer.Host, ContactEventTypes.Default)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)] + [TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)] + [TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)] + internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 1; + + private GameObject m_RigidbodyContactEventManager; + + public enum ContactEventTypes + { + Default, + WithInfo + } + + private ContactEventTypes m_ContactEventType; + private StringBuilder m_ErrorLogger = new StringBuilder(); + + public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer) + { + m_ContactEventType = contactEventType; + } + + protected override void OnCreatePlayerPrefab() + { + ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3); + ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4); + if (m_ContactEventType == ContactEventTypes.Default) + { + var helper = m_PlayerPrefab.AddComponent(); + helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + else + { + var helperWithInfo = m_PlayerPrefab.AddComponent(); + helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + } + + var rigidbody = m_PlayerPrefab.AddComponent(); + rigidbody.useGravity = false; + rigidbody.isKinematic = true; + rigidbody.mass = 5.0f; + rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous; + var sphereCollider = m_PlayerPrefab.AddComponent(); + sphereCollider.radius = 0.5f; + sphereCollider.providesContacts = true; + + var networkRigidbody = m_PlayerPrefab.AddComponent(); + networkRigidbody.UseRigidBodyForMotion = true; + networkRigidbody.AutoUpdateKinematicState = false; + + m_RigidbodyContactEventManager = new GameObject(); + m_RigidbodyContactEventManager.AddComponent(); + } + + + + private bool PlayersSpawnedInRightLocation() + { + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var position = authority.LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + + position = nonAuthority.LocalClient.PlayerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + var playerObject = (NetworkObject)null; + if (!authority.SpawnManager.SpawnedObjects.ContainsKey(nonAuthority.LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} cannot find a local spawned instance of Client-{nonAuthority.LocalClientId}'s player object!"); + return false; + } + playerObject = authority.SpawnManager.SpawnedObjects[nonAuthority.LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + + if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position)) + { + m_ErrorLogger.AppendLine($"Client-{authority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!"); + return false; + } + + if (!nonAuthority.SpawnManager.SpawnedObjects.ContainsKey(authority.LocalClient.PlayerObject.NetworkObjectId)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} cannot find a local spawned instance of Client-{authority.LocalClientId}'s player object!"); + return false; + } + playerObject = nonAuthority.SpawnManager.SpawnedObjects[authority.LocalClient.PlayerObject.NetworkObjectId]; + position = playerObject.transform.position; + if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position)) + { + m_ErrorLogger.AppendLine($"Client-{nonAuthority.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!"); + return false; + } + return true; + } + + + [UnityTest] + public IEnumerator TestContactEvents() + { + ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug; + + m_PlayerPrefab.SetActive(false); + m_ErrorLogger.Clear(); + // Validate all instances are spawned in the right location + yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation); + AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}"); + m_ErrorLogger.Clear(); + + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.LocalClient.PlayerObject.GetComponent() : + authority.LocalClient.PlayerObject.GetComponent(); + var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.LocalClient.PlayerObject.GetComponent() : + nonAuthority.LocalClient.PlayerObject.GetComponent(); + + // Get both players to point towards each other + sessionOwnerPlayer.Target = clientPlayer; + clientPlayer.Target = sessionOwnerPlayer; + + sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward); + + + yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer)); + AssertOnTimeout("Timed out waiting for a player to collide with another player!"); + + clientPlayer.RegisterForContactEvents(false); + sessionOwnerPlayer.RegisterForContactEvents(false); + var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent() : + authority.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + otherPlayer = m_ContactEventType == ContactEventTypes.Default ? nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent() : + nonAuthority.SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent(); + otherPlayer.RegisterForContactEvents(false); + + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + protected override IEnumerator OnTearDown() + { + // In case of a test failure + if (m_RigidbodyContactEventManager) + { + Object.Destroy(m_RigidbodyContactEventManager); + m_RigidbodyContactEventManager = null; + } + + return base.OnTearDown(); + } + } + + #region Test helper classes + internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo + { + public ContactEventHandlerInfo GetContactEventHandlerInfo() + { + var contactEventHandlerInfo = new ContactEventHandlerInfo() + { + HasContactEventPriority = IsOwner, + ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value, + }; + return contactEventHandlerInfo; + } + + protected override void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + } + + internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler + { + public static Vector3 SessionOwnerSpawnPoint; + public static Vector3 ClientSpawnPoint; + public static bool VerboseDebug; + public enum HelperStates + { + None, + MoveForward, + } + + private HelperStates m_HelperState; + + public void SetHelperState(HelperStates state) + { + m_HelperState = state; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None; + if (!m_NetworkRigidbody.IsKinematic()) + { + m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero; + m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero; + } + + } + + protected struct ContactEventInfo + { + public ulong EventId; + public Vector3 AveragedCollisionNormal; + public Rigidbody CollidingBody; + public Vector3 ContactPoint; + } + + protected List m_ContactEvents = new List(); + + protected NetworkVariable m_EnableNonRigidbodyContacts = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + protected NetworkRigidbody m_NetworkRigidbody; + public ContactEventTransformHelper Target; + + public bool HasContactEvents() + { + return m_ContactEvents.Count > 0; + } + + public Rigidbody GetRigidbody() + { + return m_NetworkRigidbody.Rigidbody; + } + + public bool HadContactWith(ContactEventTransformHelper otherObject) + { + if (otherObject == null) + { + return false; + } + foreach (var contactEvent in m_ContactEvents) + { + if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody) + { + return true; + } + } + return false; + } + + protected virtual void CheckToStopMoving() + { + SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward); + } + + public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default) + { + if (Target == null) + { + return; + } + + if (collidingBody != null) + { + Log($">>>>>>> contact event with {collidingBody.name}!"); + } + else + { + Log($">>>>>>> contact event with non-rigidbody!"); + } + + m_ContactEvents.Add(new ContactEventInfo() + { + EventId = eventId, + AveragedCollisionNormal = averagedCollisionNormal, + CollidingBody = collidingBody, + ContactPoint = contactPoint, + }); + CheckToStopMoving(); + } + + private void SetInitialPositionClientServer() + { + if (IsServer) + { + if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer) + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + else + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + + private void SetInitialPositionDistributedAuthority() + { + if (HasAuthority) + { + if (IsSessionOwner) + { + transform.position = SessionOwnerSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint; + } + else + { + transform.position = ClientSpawnPoint; + m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint; + } + } + } + + public override void OnNetworkSpawn() + { + m_NetworkRigidbody = GetComponent(); + + m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15; + m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10; + + if (NetworkManager.DistributedAuthorityMode) + { + SetInitialPositionDistributedAuthority(); + } + else + { + SetInitialPositionClientServer(); + } + if (IsLocalPlayer) + { + RegisterForContactEvents(true); + } + else + { + m_NetworkRigidbody.Rigidbody.detectCollisions = false; + } + base.OnNetworkSpawn(); + } + + protected virtual void OnRegisterForContactEvents(bool isRegistering) + { + RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering); + } + + public void RegisterForContactEvents(bool isRegistering) + { + OnRegisterForContactEvents(isRegistering); + } + + private void FixedUpdate() + { + if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward) + { + return; + } + var distance = Vector3.Distance(Target.transform.position, transform.position); + var moveAmount = Mathf.Max(1.2f, distance); + // Head towards our target + var dir = (Target.transform.position - transform.position).normalized; + var deltaMove = dir * moveAmount * Time.fixedDeltaTime; + m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove); + + + Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}"); + } + + protected void Log(string msg) + { + if (VerboseDebug) + { + Debug.Log($"Client-{OwnerClientId} {msg}"); + } + } + } + #endregion +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta new file mode 100644 index 0000000000..687995a482 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/RigidbodyContactEventManagerTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 6d002e232abdb5049aa3c4f5cc36292d \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/Physics.meta b/testproject/Assets/Tests/Runtime/Physics.meta deleted file mode 100644 index de0808f1a7..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: efc90b4d4d76f7a48ad1cf726a3a193e -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs deleted file mode 100644 index 4f3c651cb3..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbody2DDynamicCntChangeOwnershipTest : NetworkRigidbody2DCntChangeOwnershipTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbody2DKinematicCntChangeOwnershipTest : NetworkRigidbody2DCntChangeOwnershipTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbody2DCntChangeOwnershipTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// -// // give server ownership over the player -// -// serverPlayer.GetComponent().ChangeOwnership(NetworkManager.ServerClientId); -// -// yield return null; -// yield return null; -// -// // server should now be able to commit to transform -// TestKinematicSetCorrectly(serverPlayer, clientPlayer); -// -// // return ownership to client -// serverPlayer.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); -// yield return null; -// yield return null; -// -// // client should again be able to commit -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// } -// -// -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta deleted file mode 100644 index b1c3736256..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntChangeOwnershipTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: a51c7f7b93f04e3499089963d9bbb254 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs deleted file mode 100644 index eac6d42d7e..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody2D. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbody2DDynamicCntTest : NetworkRigidbody2DCntTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbody2DKinematicCntTest : NetworkRigidbody2DCntTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbody2DCntTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// // despawn the server player -// serverPlayer.GetComponent().Despawn(false); -// -// yield return null; -// yield return null; -// Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. -// } -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta deleted file mode 100644 index f0d4cff600..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbody2DCntTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 5ae5587c06cba7a46b93ee6774741c0d -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs deleted file mode 100644 index 2e45162f52..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs +++ /dev/null @@ -1,95 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbodyDynamicCntChangeOwnershipTest : NetworkRigidbodyCntChangeOwnershipTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbodyKinematicCntChangeOwnershipTest : NetworkRigidbodyCntChangeOwnershipTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbodyCntChangeOwnershipTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// -// // give server ownership over the player -// -// serverPlayer.GetComponent().ChangeOwnership(NetworkManager.ServerClientId); -// -// yield return null; -// yield return null; -// -// // server should now be able to commit to transform -// TestKinematicSetCorrectly(serverPlayer, clientPlayer); -// -// // return ownership to client -// serverPlayer.GetComponent().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); -// yield return null; -// yield return null; -// -// // client should again be able to commit -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// } -// -// -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta deleted file mode 100644 index 3eb4f88f5b..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntChangeOwnershipTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 941008c8040f03c44bd835d24b073260 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs deleted file mode 100644 index 72f0c72b96..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs +++ /dev/null @@ -1,82 +0,0 @@ -// using System.Collections; -// using NUnit.Framework; -// using Unity.Netcode.Components; -// using Unity.Netcode.Samples; -// using UnityEngine; -// using UnityEngine.TestTools; -// -// // Tests for ClientNetworkTransform (CNT) + NetworkRigidbody. This test is in TestProject because it needs access to ClientNetworkTransform -// namespace Unity.Netcode.RuntimeTests -// { -// public class NetworkRigidbodyDynamicCntTest : NetworkRigidbodyCntTestBase -// { -// public override bool Kinematic => false; -// } -// -// public class NetworkRigidbodyKinematicCntTest : NetworkRigidbodyCntTestBase -// { -// public override bool Kinematic => true; -// } -// -// public abstract class NetworkRigidbodyCntTestBase : NetcodeIntegrationTest -// { -// protected override int NumberOfClients => 1; -// -// public abstract bool Kinematic { get; } -// -// [UnitySetUp] -// public override IEnumerator Setup() -// { -// yield return StartSomeClientsAndServerWithPlayers(true, NumberOfClients, playerPrefab => -// { -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.AddComponent(); -// playerPrefab.GetComponent().isKinematic = Kinematic; -// }); -// } -// /// -// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. -// /// -// /// -// [UnityTest] -// public IEnumerator TestRigidbodyKinematicEnableDisable() -// { -// // This is the *SERVER VERSION* of the *CLIENT PLAYER* -// var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield returnNetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult); -// var serverPlayer = serverClientPlayerResult.Result.gameObject; -// -// // This is the *CLIENT VERSION* of the *CLIENT PLAYER* -// var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); -// yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult); -// var clientPlayer = clientClientPlayerResult.Result.gameObject; -// -// Assert.IsNotNull(serverPlayer); -// Assert.IsNotNull(clientPlayer); -// -// int waitFor = Time.frameCount + 2; -// yield return new WaitUntil(() => Time.frameCount >= waitFor); -// -// TestKinematicSetCorrectly(clientPlayer, serverPlayer); -// -// // despawn the server player -// serverPlayer.GetComponent().Despawn(false); -// -// yield return null; -// yield return null; -// -// Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. -// } -// -// private void TestKinematicSetCorrectly(GameObject canCommitPlayer, GameObject canNotCommitPlayer) -// { -// -// // can commit player has authority and should have a kinematic mode of false (or true in case body was already kinematic). -// Assert.True(canCommitPlayer.GetComponent().isKinematic == Kinematic); -// -// // can not commit player has no authority and should have a kinematic mode of true -// Assert.True(canNotCommitPlayer.GetComponent().isKinematic); -// } -// } -// } diff --git a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta b/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta deleted file mode 100644 index cea97efc01..0000000000 --- a/testproject/Assets/Tests/Runtime/Physics/NetworkRigidbodyCntTest.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: e89712f9406aaa84d85508fa07d97655 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From dcd8147281f2c7aabb7601e16fb9dcff9aa11c58 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Wed, 24 Jun 2026 19:13:27 +0200 Subject: [PATCH 17/64] ci: CI deps update and changelog correction (#4034) * changelog correction * Wrench and deps update and CI regeneration * corrected editors * Addressed Standards check complains --- .yamato/project.metafile | 8 +- .yamato/wrench/api-validation-jobs.yml | 4 +- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 258 +++++++++++++--- .yamato/wrench/promotion-jobs.yml | 80 ++++- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 286 +++++++++++++++--- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 10 +- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 16 +- .../Editor/NetworkManagerHelper.cs | 8 +- .../BufferedLinearInterpolator.cs | 44 +-- .../Runtime/Components/NetworkAnimator.cs | 58 ++-- .../Runtime/Components/NetworkTransform.cs | 38 +-- .../Connection/NetworkConnectionManager.cs | 20 +- .../Runtime/Core/NetworkObject.cs | 54 ++-- .../Messaging/Messages/CreateObjectMessage.cs | 8 +- .../DefaultSceneManagerHandler.cs | 10 +- .../Runtime/Spawning/NetworkPrefabHandler.cs | 8 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 8 +- .../NetworkClientAndPlayerObjectTests.cs | 10 +- .../Tests/Runtime/NetworkShowHideTests.cs | 8 +- .../NetworkTransformNonAuthorityTests.cs | 12 +- .../Tests/Manual/Scripts/NetworkPrefabPool.cs | 10 +- .../Scripts/NetworkPrefabPoolAdditive.cs | 10 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 8 +- 27 files changed, 720 insertions(+), 266 deletions(-) diff --git a/.yamato/project.metafile b/.yamato/project.metafile index f4802d6ff0..6db694d80c 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -24,7 +24,7 @@ small_agent_platform: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.86.0 flavor: b1.small @@ -39,13 +39,13 @@ test_platforms: default: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.86.0 flavor: b1.large standalone: StandaloneLinux64 desktop: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.85.0 + image: package-ci/ubuntu-22.04:v4.86.0 flavor: b1.large smaller_flavor: b1.large larger_flavor: b1.xlarge @@ -178,8 +178,8 @@ validation_editors: all: - 6000.0 - 6000.3 - - 6000.4 - 6000.5 + - 6000.6 - trunk - f1298548e194f35ff7dfa6fee699d4464ab3919c minimal: diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 6976c10973..1b9106d777 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -60,8 +60,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index 06c750a127..fec8ec8847 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -29,5 +29,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 445f507a73..1029bfeb24 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -22,12 +22,15 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_macos13 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_macos13arm - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_win10 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_macos13arm + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_ubuntu2204 + - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -82,10 +85,10 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -140,10 +143,10 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -199,10 +202,10 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -257,10 +260,10 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -315,10 +318,10 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -374,10 +377,10 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (MacOS). preview_apv_-_6000_4_-_macos13: @@ -432,10 +435,10 @@ preview_apv_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (Ubuntu). preview_apv_-_6000_4_-_ubuntu2204: @@ -490,10 +493,10 @@ preview_apv_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.4 manifest (Windows). preview_apv_-_6000_4_-_win10: @@ -549,10 +552,10 @@ preview_apv_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (MacOS). preview_apv_-_6000_5_-_macos13: @@ -607,10 +610,10 @@ preview_apv_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). preview_apv_-_6000_5_-_ubuntu2204: @@ -665,10 +668,10 @@ preview_apv_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.5 manifest (Windows). preview_apv_-_6000_5_-_win10: @@ -724,18 +727,19 @@ preview_apv_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (MacOS). -preview_apv_-_6000_6_-_macos13: - name: Preview APV - 6000.6 - macos13 +preview_apv_-_6000_6_-_macos13arm: + name: Preview APV - 6000.6 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip - command: 7z x -aoa wrench-localapv.zip @@ -744,7 +748,7 @@ preview_apv_-_6000_6_-_macos13: - 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 + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast --arch arm64 timeout: 10 retries: 3 - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ @@ -782,10 +786,10 @@ preview_apv_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). preview_apv_-_6000_6_-_ubuntu2204: @@ -802,7 +806,7 @@ preview_apv_-_6000_6_-_ubuntu2204: - 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 + - command: unity-downloader-cli -u 6000.6/staging -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ @@ -840,10 +844,10 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Functional tests for dependents found in the latest 6000.6 manifest (Windows). preview_apv_-_6000_6_-_win10: @@ -861,7 +865,7 @@ preview_apv_-_6000_6_-_win10: - 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 + - command: unity-downloader-cli -u 6000.6/staging -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ @@ -899,8 +903,184 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + +# Functional tests for dependents found in the latest 6000.7 manifest (MacOS). +preview_apv_-_6000_7_-_macos13arm: + name: Preview APV - 6000.7 - macos13arm + agent: + image: package-ci/macos-13-arm64:v4 + type: Unity::VM::osx + flavor: m1.mac + model: M1 + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 --arch arm64 + timeout: 10 + retries: 3 + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.7 --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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + +# Functional tests for dependents found in the latest 6000.7 manifest (Ubuntu). +preview_apv_-_6000_7_-_ubuntu2204: + name: Preview APV - 6000.7 - ubuntu2204 + 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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.7 --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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + +# Functional tests for dependents found in the latest 6000.7 manifest (Windows). +preview_apv_-_6000_7_-_win10: + name: Preview APV - 6000.7 - win10 + agent: + image: package-ci/win10:v4 + 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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.7 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/editor_manifest_validator.py --version=6000.7 --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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 7fd22a018d..929cce45a2 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -151,15 +151,15 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.5-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.6-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 specific_options: @@ -181,12 +181,42 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-win10 + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-win10 + unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -333,15 +363,15 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.5-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13 + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: ignore_artifact: true pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.6-macos13 + location: results/pvp/validate-netcode.gameobjects-6000.6-macos13arm unzip: true UTR: - location: results/UTR/validate-netcode.gameobjects-6000.6-macos13 + location: results/UTR/validate-netcode.gameobjects-6000.6-macos13arm unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204 specific_options: @@ -363,11 +393,41 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 + unzip: true + - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 + specific_options: + packages: + ignore_artifact: true + pvp-results: + location: results/pvp/validate-netcode.gameobjects-6000.7-win10 + unzip: true + UTR: + location: results/UTR/validate-netcode.gameobjects-6000.7-win10 + unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index b9eba06e54..34d5f02642 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 84a92fcd75..9e05a163af 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -69,10 +69,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -139,10 +139,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -209,10 +209,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -279,10 +279,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -349,10 +349,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -419,10 +419,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -489,10 +489,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -559,10 +559,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -629,10 +629,10 @@ validate_-_netcode_gameobjects_-_6000_4_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -699,10 +699,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -769,10 +769,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -839,26 +839,27 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - macos13 (6000.6 - MacOS). -validate_-_netcode_gameobjects_-_6000_6_-_macos13: - name: Validate - netcode.gameobjects - 6000.6 - macos13 +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.6 - macos13arm (6000.6 - MacOS). +validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: + name: Validate - netcode.gameobjects - 6000.6 - macos13arm agent: - image: package-ci/macos-13:v4 + image: package-ci/macos-13-arm64:v4 type: Unity::VM::osx - flavor: b1.xlarge + flavor: m1.mac + model: M1 commands: - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast --arch arm64 timeout: 20 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 @@ -909,10 +910,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -928,7 +929,7 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: - 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 + - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast timeout: 20 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 @@ -979,10 +980,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects @@ -998,6 +999,217 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: - 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 6000.6/staging -c editor --path .Editor --fast + timeout: 20 + 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 + - 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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - macos13arm (6000.7 - MacOS). +validate_-_netcode_gameobjects_-_6000_7_-_macos13arm: + name: Validate - netcode.gameobjects - 6000.7 - macos13arm + agent: + image: package-ci/macos-13-arm64:v4 + type: Unity::VM::osx + flavor: m1.mac + model: M1 + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 --arch arm64 + timeout: 20 + 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 + - 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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - ubuntu2204 (6000.7 - Ubuntu). +validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204: + name: Validate - netcode.gameobjects - 6000.7 - ubuntu2204 + 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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: 20 + 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 + - 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: + UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ + UNITY_LICENSING_SERVER_DELETE_NUL: 0 + UNITY_LICENSING_SERVER_DELETE_ULF: 0 + UNITY_LICENSING_SERVER_TOOLSET: pro + UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + metadata: + Job Maintainers: '#rm-packageworks' + Wrench: 3.2.0.0 + labels: + - Packages:netcode.gameobjects + +# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - win10 (6000.7 - Windows). +validate_-_netcode_gameobjects_-_6000_7_-_win10: + name: Validate - netcode.gameobjects - 6000.7 - win10 + agent: + image: package-ci/win10:v4 + type: Unity::VM + flavor: b1.large + commands: + - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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: 20 retries: 3 @@ -1049,10 +1261,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 2.9.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.0.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 2.9.0.0 + Wrench: 3.2.0.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 9aa8ecfde1..585d0dfb22 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "2.9.0.0", + "wrench_version": "3.2.0.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index 452a22131a..ef0ba3d3bc 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 1f7af6d540..3f6df83f11 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,7 +21,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - +- Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) +- Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) ### Security @@ -34,6 +35,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added - Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956) +- RPC messages now log any time they are not processed. (#3994) ### Changed diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 04a72d7c9e..797197ec86 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2152,14 +2152,14 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere } else #endif - if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") - { - typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); - } - else - { - typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); - } + if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); + } + else + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); + } if (typeMethod != null) { methodRef = m_MainModule.ImportReference(typeMethod); diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index 5c7e554baf..a3224e6471 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -196,10 +196,10 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool return isParented; } else // If we are no longer a child, then we can remove ourself from this list - if (transform.root == gameObject.transform) - { - s_LastKnownNetworkManagerParents.Remove(networkManager); - } + if (transform.root == gameObject.transform) + { + s_LastKnownNetworkManagerParents.Remove(networkManager); + } } if (!EditorApplication.isUpdating && isParented) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 24eb580402..294cd9989f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -468,19 +468,19 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime } } else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (m_BufferQueue.Count == 0) - { - // When the delta between the time sent and the current tick latency time-window is greater than the max delta time - // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), - // we will want to reset the interpolator with the current known value. This prevents the next received state update's - // time to be calculated against the last calculated time which if there is an extended period of time between the two - // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then - // starts moving again). - if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) + if (m_BufferQueue.Count == 0) { - InterpolateState.Reset(InterpolateState.CurrentValue); + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } } - } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -594,19 +594,19 @@ public T Update(float deltaTime, double renderTime, double serverTime) InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) - { - // When the delta between the time sent and the current tick latency time-window is greater than the max delta time - // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), - // we will want to reset the interpolator with the current known value. This prevents the next received state update's - // time to be calculated against the last calculated time which if there is an extended period of time between the two - // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then - // starts moving again). - if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. + if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) { - InterpolateState.Reset(InterpolateState.CurrentValue); + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. + { + InterpolateState.Reset(InterpolateState.CurrentValue); + } } - } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index 246d6b8484..f87eee73ab 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -1181,25 +1181,25 @@ internal void CheckForAnimatorChanges() SendAnimStateRpc(m_AnimationMessage); } else - if (!IsServer && IsOwner) - { - SendServerAnimStateRpc(m_AnimationMessage); - } - else - { - // Just notify all remote clients and not the local server - m_TargetGroup.Clear(); - foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds) + if (!IsServer && IsOwner) + { + SendServerAnimStateRpc(m_AnimationMessage); + } + else { - if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) + // Just notify all remote clients and not the local server + m_TargetGroup.Clear(); + foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds) { - continue; + if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) + { + continue; + } + m_TargetGroup.Add(clientId); } - m_TargetGroup.Add(clientId); + m_RpcParams.Send.Target = m_TargetGroup.Target; + SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams); } - m_RpcParams.Send.Target = m_TargetGroup.Target; - SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams); - } } } @@ -1347,24 +1347,24 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) } } else - if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) - { - var valueBool = m_Animator.GetBool(hash); - fixed (void* value = cacheValue.Value) + if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) { - UnsafeUtility.WriteArrayElement(value, 0, valueBool); - BytePacker.WriteValuePacked(writer, valueBool); + var valueBool = m_Animator.GetBool(hash); + fixed (void* value = cacheValue.Value) + { + UnsafeUtility.WriteArrayElement(value, 0, valueBool); + BytePacker.WriteValuePacked(writer, valueBool); + } } - } - else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) - { - var valueFloat = m_Animator.GetFloat(hash); - fixed (void* value = cacheValue.Value) + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) { - UnsafeUtility.WriteArrayElement(value, 0, valueFloat); - BytePacker.WriteValuePacked(writer, valueFloat); + var valueFloat = m_Animator.GetFloat(hash); + fixed (void* value = cacheValue.Value) + { + UnsafeUtility.WriteArrayElement(value, 0, valueFloat); + BytePacker.WriteValuePacked(writer, valueFloat); + } } - } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 18fd9cd88f..63b43544bd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2516,23 +2516,23 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is } } else // Just apply the full local scale when synchronizing - if (SynchronizeScale) - { - var localScale = CachedTransform.localScale; - if (!UseHalfFloatPrecision) + if (SynchronizeScale) { + var localScale = CachedTransform.localScale; + if (!UseHalfFloatPrecision) + { - networkState.ScaleX = localScale.x; - networkState.ScaleY = localScale.y; - networkState.ScaleZ = localScale.z; - } - else - { - networkState.Scale = localScale; + networkState.ScaleX = localScale.x; + networkState.ScaleY = localScale.y; + networkState.ScaleZ = localScale.z; + } + else + { + networkState.Scale = localScale; + } + flagStates.MarkChanged(AxialType.Scale, true); + isScaleDirty = true; } - flagStates.MarkChanged(AxialType.Scale, true); - isScaleDirty = true; - } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; if (isDirty) @@ -3489,11 +3489,11 @@ private void NonAuthorityFinalizeSynchronization() } } else // Otherwise, just run through standard synchronization of this instance - if (!CanCommitToTransform) - { - ApplySynchronization(); - InternalInitialization(); - } + if (!CanCommitToTransform) + { + ApplySynchronization(); + InternalInitialization(); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index 5a8724374c..cc44bd8783 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -968,18 +968,18 @@ internal void ProcessClientsToDisconnect() return (true, playerPrefabHash.Value); } else - if (NetworkManager.NetworkConfig.PlayerPrefab != null) - { - var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); - if (networkObject != null) + if (NetworkManager.NetworkConfig.PlayerPrefab != null) { - return (true, networkObject.GlobalObjectIdHash); - } - else - { - NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!")); + var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); + if (networkObject != null) + { + return (true, networkObject.GlobalObjectIdHash); + } + else + { + NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!")); + } } - } return (false, 0); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 73cc6c7e2d..5008ee6075 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2564,25 +2564,25 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa 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 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); + 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 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 - SetCachedParent(parentNetworkObject.transform); + // Set the cached parent + SetCachedParent(parentNetworkObject.transform); - return true; - } + return true; + } } // If we are removing the parent or our latest parent is not set, then remove the parent. @@ -3548,16 +3548,16 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } else // Otherwise, the client did not find the client to server scene handle - if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) - { - // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject - // into, but that scenario seemed very edge case and under most instances a user should be notified that this - // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to - // the server-side too. - NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " + - $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + - $"has no associated server side (network) scene handle!"); - } + if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) + { + // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject + // into, but that scenario seemed very edge case and under most instances a user should be notified that this + // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to + // the server-side too. + NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " + + $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + + $"has no associated server side (network) scene handle!"); + } OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index cf518a6216..b8f8af78fb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -190,10 +190,10 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende NetworkLog.LogErrorServer($"[{nameof(CreateObjectMessage)}][Duplicate-Broadcast] Detected duplicated object creation for {serializedObject.NetworkObjectId}!"); } else // Trap to make sure the owner is not receiving any messages it sent - if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) - { - NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{serializedObject.NetworkObjectId}!"); - } + if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) + { + NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{serializedObject.NetworkObjectId}!"); + } } else { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index 76ea8feb04..dba918a679 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -360,13 +360,13 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load return; } else // Warn users if they are changing this after there are clients already connected and synchronized - if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) - { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) { - NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) + { + NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); + } } - } // For additive client synchronization, we take into consideration scenes // already loaded. diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index c8aab939f4..ece7561161 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -342,10 +342,10 @@ internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance) } } else // Otherwise the NetworkObject is the source NetworkPrefab - if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) - { - prefabInstanceHandler.Destroy(networkObjectInstance); - } + if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) + { + prefabInstanceHandler.Destroy(networkObjectInstance); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 46f52bc4f9..449ca04100 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1820,10 +1820,10 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro } } else - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); - } + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 5697833bbc..461d85a757 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -247,11 +247,11 @@ private bool ValidateAllPlayerObjects() success = false; } else - if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) - { - m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); - success = false; - } + if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 6c09212a18..43f6eac7de 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -489,10 +489,10 @@ private bool Object1IsNotVisibileToClient() } } else - if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) - { - m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); - } + if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) + { + m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); + } } return m_ErrorLog.Length == 0; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs index e5fb5a4ba9..5705d21942 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs @@ -419,12 +419,12 @@ private bool ShouldSyncAxis(bool first, bool second, bool lastValue) return false; } else - if (!first && !second) - { - // If both are disabled, then make the - // last one enabled. - return true; - } + if (!first && !second) + { + // If both are disabled, then make the + // last one enabled. + return true; + } } return Random.Range(start, 100) >= 50 ? false : true; } diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs index 7821d2af27..5b045b1855 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs @@ -497,11 +497,11 @@ public void UpdateSpawnsPerSecond() StartSpawningBoxes(); } else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) - { - m_IsSpawningObjects = false; - StopCoroutine(SpawnObjects()); - } + if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + { + m_IsSpawningObjects = false; + StopCoroutine(SpawnObjects()); + } } } diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs index f714d937de..0c4992cbd9 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs @@ -343,11 +343,11 @@ public void UpdateSpawnsPerSecond() StartSpawningBoxes(); } else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) - { - m_IsSpawningObjects = false; - InternalStopCoroutine(); - } + if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + { + m_IsSpawningObjects = false; + InternalStopCoroutine(); + } } diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 474c9dabbe..dc2493ec28 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -528,10 +528,10 @@ private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, } } else - if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) - { - return false; - } + if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) + { + return false; + } } return true; } From f7ca6184928a958bcd5a7692ff6cd8015b904e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Mon, 29 Jun 2026 20:32:30 +0200 Subject: [PATCH 18/64] chore: clean up SceneEventData internal fields and methods visibility (#4029) - Made list fields readonly - Reduced method visibility from internal to private where applicable - Replaced indexed for loops with foreach and inlined out variable declarations - Replaced ContainsKey + indexer with TryGetValue - Removed unused totalBytes variable --- .../Runtime/SceneManagement/SceneEventData.cs | 185 ++++++++---------- 1 file changed, 78 insertions(+), 107 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 0ad4e96864..4840944117 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -130,21 +130,21 @@ internal class SceneEventData : IDisposable /// was synchronizing (if so server will send another message back to the client informing the client of NetworkObjects to remove) /// spawned during an initial synchronization. /// - private List m_NetworkObjectsSync = new List(); + private readonly List m_NetworkObjectsSync = new List(); - private List m_DespawnedInSceneObjectsSync = new List(); + private readonly List m_DespawnedInSceneObjectsSync = new List(); /// /// Server Side Re-Synchronization: /// If there happens to be NetworkObjects in the final Event_Sync_Complete message that are no longer spawned, /// the server will compile a list and send back an Event_ReSync message to the client. /// - private List m_NetworkObjectsToBeRemoved = new List(); + private readonly List m_NetworkObjectsToBeRemoved = new List(); private bool m_HasInternalBuffer; - internal FastBufferReader InternalBuffer; + private FastBufferReader m_InternalBuffer; - private NetworkManager m_NetworkManager; + private readonly NetworkManager m_NetworkManager; internal List ClientsCompleted; internal List ClientsTimedOut; @@ -391,12 +391,12 @@ internal void AddDespawnedInSceneNetworkObjects() /// internal void AddNetworkObjectForSynch(uint sceneIndex, NetworkObject networkObject) { - if (!m_SceneNetworkObjects.ContainsKey(sceneIndex)) + if (!m_SceneNetworkObjects.TryGetValue(sceneIndex, out var sceneNetworkObject)) { - m_SceneNetworkObjects.Add(sceneIndex, new List()); + sceneNetworkObject = new List(); + m_SceneNetworkObjects.Add(sceneIndex, sceneNetworkObject); } - - m_SceneNetworkObjects[sceneIndex].Add(networkObject); + sceneNetworkObject.Add(networkObject); } /// @@ -571,7 +571,7 @@ internal void Serialize(FastBufferWriter writer) private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) { - writer.WriteBytesSafe(InternalBuffer.GetUnsafePtrAtCurrentPosition(), InternalBuffer.Length); + writer.WriteBytesSafe(m_InternalBuffer.GetUnsafePtrAtCurrentPosition(), m_InternalBuffer.Length); } /// @@ -579,9 +579,9 @@ private unsafe void CopyInternalBuffer(ref FastBufferWriter writer) /// Called at the end of a event once the scene is loaded and scene placed NetworkObjects /// have been locally spawned /// - internal void WriteSceneSynchronizationData(FastBufferWriter writer) + private void WriteSceneSynchronizationData(FastBufferWriter writer) { - var builder = (StringBuilder)null; + StringBuilder builder = null; if (EnableSerializationLogs) { builder = new StringBuilder(); @@ -604,11 +604,10 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) return; } - // Size Place Holder -- Start + // Size Placeholder -- Start // !!NOTE!!: Since this is a placeholder to be set after we know how much we have written, // for stream offset purposes this MUST not be a packed value! writer.WriteValueSafe(0); - int totalBytes = 0; // Write the number of NetworkObjects we are serializing writer.WriteValueSafe(m_NetworkObjectsSync.Count); @@ -619,16 +618,14 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // Serialize all NetworkObjects that are spawned - for (var i = 0; i < m_NetworkObjectsSync.Count; ++i) + foreach (var networkObject in m_NetworkObjectsSync) { - var networkObject = m_NetworkObjectsSync[i]; var noStart = writer.Position; // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. - var serializedObject = m_NetworkObjectsSync[i].Serialize(TargetClientId, distributedAuthority); + var serializedObject = networkObject.Serialize(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); var noStop = writer.Position; - totalBytes += noStop - noStart; if (EnableSerializationLogs) { var offStart = noStart - (positionStart + sizeof(int)); @@ -645,16 +642,13 @@ internal void WriteSceneSynchronizationData(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) + foreach (var despawnedInSceneObject in m_DespawnedInSceneObjectsSync) { - var noStart = writer.Position; - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); - var noStop = writer.Position; - totalBytes += noStop - noStart; + writer.WriteValueSafe(despawnedInSceneObject.GetSceneOriginHandle()); + writer.WriteValueSafe(despawnedInSceneObject.GlobalObjectIdHash); } - // Size Place Holder -- End + // Size Placeholder -- End var positionEnd = writer.Position; var bytesWritten = (uint)(positionEnd - (positionStart + sizeof(uint))); writer.Seek(positionStart); @@ -673,12 +667,12 @@ internal void WriteSceneSynchronizationData(FastBufferWriter writer) /// have been locally spawned /// Maximum number of objects that could theoretically be synchronized is 65536 /// - internal void SerializeScenePlacedObjects(FastBufferWriter writer) + private void SerializeScenePlacedObjects(FastBufferWriter writer) { - var numberOfObjects = (ushort)0; + ushort numberOfObjects = 0; var headPosition = writer.Position; - // Write our count place holder (must not be packed!) + // Write our count placeholder (must not be packed!) writer.WriteValueSafe((ushort)0); var distributedAuthority = m_NetworkManager.DistributedAuthorityMode; // If distributed authority mode and sending to the service, then ignore observers @@ -701,10 +695,10 @@ internal void SerializeScenePlacedObjects(FastBufferWriter writer) SortObjectsToSync(); // Serialize the sorted objects to sync. - foreach (var objectToSycn in m_NetworkObjectsSync) + foreach (var objectToSync in m_NetworkObjectsSync) { // Serialize the NetworkObject - var serializedObject = objectToSycn.Serialize(TargetClientId, distributedAuthority); + var serializedObject = objectToSync.Serialize(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); numberOfObjects++; } @@ -712,10 +706,10 @@ 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) + foreach (var despawnedInSceneObject in m_DespawnedInSceneObjectsSync) { - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle()); - writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash); + writer.WriteValueSafe(despawnedInSceneObject.GetSceneOriginHandle()); + writer.WriteValueSafe(despawnedInSceneObject.GlobalObjectIdHash); } var tailPosition = writer.Position; @@ -802,7 +796,7 @@ internal void Deserialize(FastBufferReader reader) // be processed once we are done loading. m_HasInternalBuffer = true; // We use Allocator.Persistent since scene loading could take longer than 4 frames - InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, reader.Length - reader.Position); + m_InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, reader.Length - reader.Position); } break; } @@ -828,7 +822,7 @@ internal void Deserialize(FastBufferReader reader) /// into the internal buffer to be used throughout the synchronization process. /// /// - internal void CopySceneSynchronizationData(FastBufferReader reader) + private void CopySceneSynchronizationData(FastBufferReader reader) { m_NetworkObjectsSync.Clear(); reader.ReadValueSafe(out uint[] scenesToSynchronize); @@ -850,10 +844,10 @@ internal void CopySceneSynchronizationData(FastBufferReader reader) m_HasInternalBuffer = true; // We use Allocator.Persistent since scene synchronization will most likely take longer than 4 frames - InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); + m_InternalBuffer = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.Persistent, sizeToCopy); if (EnableSerializationLogs) { - LogArray(InternalBuffer.ToArray()); + LogArray(m_InternalBuffer.ToArray()); } } } @@ -868,12 +862,12 @@ internal void DeserializeScenePlacedObjects() try { // is not packed! - InternalBuffer.ReadValueSafe(out ushort newObjectsCount); + m_InternalBuffer.ReadValueSafe(out ushort newObjectsCount); var sceneObjects = new List(); for (ushort i = 0; i < newObjectsCount; i++) { var serializedObject = new NetworkObject.SerializedObject(); - serializedObject.Deserialize(InternalBuffer); + serializedObject.Deserialize(m_InternalBuffer); if (serializedObject.IsSceneObject) { @@ -881,7 +875,7 @@ internal void DeserializeScenePlacedObjects() m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var networkObject = NetworkObject.Deserialize(serializedObject, InternalBuffer, m_NetworkManager); + var networkObject = NetworkObject.Deserialize(serializedObject, m_InternalBuffer, m_NetworkManager); if (serializedObject.IsSceneObject) { @@ -900,7 +894,7 @@ internal void DeserializeScenePlacedObjects() } finally { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } @@ -912,7 +906,7 @@ internal void DeserializeScenePlacedObjects() /// client handles any returned values by the server. /// /// - internal void ReadClientReSynchronizationData(FastBufferReader reader) + private void ReadClientReSynchronizationData(FastBufferReader reader) { reader.ReadValueSafe(out uint[] networkObjectsToRemove); @@ -949,7 +943,7 @@ internal void ReadClientReSynchronizationData(FastBufferReader reader) /// the server will compile a list and send back an Event_ReSync message to the client. /// /// - internal void WriteClientReSynchronizationData(FastBufferWriter writer) + private void WriteClientReSynchronizationData(FastBufferWriter writer) { //Write how many objects need to be removed writer.WriteValueSafe(m_NetworkObjectsToBeRemoved.ToArray()); @@ -963,7 +957,7 @@ internal void WriteClientReSynchronizationData(FastBufferWriter writer) /// internal bool ClientNeedsReSynchronization() { - return (m_NetworkObjectsToBeRemoved.Count > 0); + return m_NetworkObjectsToBeRemoved.Count > 0; } /// @@ -973,7 +967,7 @@ internal bool ClientNeedsReSynchronization() /// have since been despawned. /// /// - internal void CheckClientSynchronizationResults(FastBufferReader reader) + private void CheckClientSynchronizationResults(FastBufferReader reader) { m_NetworkObjectsToBeRemoved.Clear(); reader.ReadValueSafe(out uint networkObjectIdCount); @@ -990,12 +984,12 @@ internal void CheckClientSynchronizationResults(FastBufferReader reader) /// /// Client Side: /// During the deserialization process of the servers Event_Sync, the client builds a list of - /// all NetworkObjectIds that were spawned. Upon responding to the server with the Event_Sync_Complete + /// all NetworkObjectIds that were spawned. Upon responding to the server with the Event_Sync_Complete, /// this list is included for the server to review over and determine if the client needs a minor resynchronization /// of NetworkObjects that might have been despawned while the client was processing the Event_Sync. /// /// - internal void WriteClientSynchronizationResults(FastBufferWriter writer) + private void WriteClientSynchronizationResults(FastBufferWriter writer) { //Write how many objects were spawned writer.WriteValueSafe((uint)m_NetworkObjectsSync.Count); @@ -1012,14 +1006,14 @@ internal void WriteClientSynchronizationResults(FastBufferWriter writer) private void DeserializeDespawnedInScenePlacedNetworkObjects() { // Process all de-spawned in-scene NetworkObjects for this network session - InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); + m_InternalBuffer.ReadValueSafe(out int despawnedObjectsCount); var sceneCache = new Dictionary>(); for (int i = 0; i < despawnedObjectsCount; i++) { // We just need to get the scene - InternalBuffer.ReadValueSafe(out NetworkSceneHandle networkSceneHandle); - InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); + m_InternalBuffer.ReadValueSafe(out NetworkSceneHandle networkSceneHandle); + m_InternalBuffer.ReadValueSafe(out uint globalObjectIdHash); // Check if we already have processed the objects in this scene if (!sceneCache.TryGetValue(networkSceneHandle, out var sceneRelativeNetworkObjects)) @@ -1060,15 +1054,10 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() // 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 despawnedObject.InvokeBehaviourNetworkDespawn(); - if (!m_NetworkManager.SceneManager.ScenePlacedObjects.ContainsKey(globalObjectIdHash)) - { - m_NetworkManager.SceneManager.ScenePlacedObjects.Add(globalObjectIdHash, new Dictionary()); - } - if (!m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].ContainsKey(despawnedObject.GetSceneOriginHandle())) - { - m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].Add(despawnedObject.GetSceneOriginHandle(), despawnedObject); - } + m_NetworkManager.SceneManager.ScenePlacedObjects.TryAdd(globalObjectIdHash, new Dictionary()); + + m_NetworkManager.SceneManager.ScenePlacedObjects[globalObjectIdHash].TryAdd(despawnedObject.GetSceneOriginHandle(), despawnedObject); } else { @@ -1086,7 +1075,7 @@ private void DeserializeDespawnedInScenePlacedNetworkObjects() /// internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { - var builder = (StringBuilder)null; + StringBuilder builder = null; if (EnableSerializationLogs) { builder = new StringBuilder(); @@ -1095,30 +1084,30 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) try { // Process all spawned NetworkObjects for this network session - InternalBuffer.ReadValueSafe(out int newObjectsCount); + m_InternalBuffer.ReadValueSafe(out int newObjectsCount); if (EnableSerializationLogs) { - builder.AppendLine($"[Read][Synchronize Objects][WPos: {InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); + builder.AppendLine($"[Read][Synchronize Objects][WPos: {m_InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:"); } for (int i = 0; i < newObjectsCount; i++) { - var noStart = InternalBuffer.Position; + var noStart = m_InternalBuffer.Position; var serializedObject = new NetworkObject.SerializedObject(); - serializedObject.Deserialize(InternalBuffer); + serializedObject.Deserialize(m_InternalBuffer); // If the sceneObject is in-scene placed, then set the scene being synchronized if (serializedObject.IsSceneObject) { m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var spawnedNetworkObject = NetworkObject.Deserialize(serializedObject, InternalBuffer, networkManager); + var spawnedNetworkObject = NetworkObject.Deserialize(serializedObject, m_InternalBuffer, networkManager); - var noStop = InternalBuffer.Position; + var noStop = m_InternalBuffer.Position; if (EnableSerializationLogs) { builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]"); - LogArray(InternalBuffer.ToArray(), noStart, noStop, builder); + LogArray(m_InternalBuffer.ToArray(), noStart, noStop, builder); } // If we failed to deserialize the NetworkObject then don't add null to the list if (spawnedNetworkObject != null) @@ -1157,7 +1146,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) } finally { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } @@ -1166,7 +1155,7 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) /// Writes the all clients loaded or unloaded completed and timed out lists /// /// - internal void WriteSceneEventProgressDone(FastBufferWriter writer) + private void WriteSceneEventProgressDone(FastBufferWriter writer) { writer.WriteValueSafe((ushort)ClientsCompleted.Count); foreach (var clientId in ClientsCompleted) @@ -1185,7 +1174,7 @@ internal void WriteSceneEventProgressDone(FastBufferWriter writer) /// Reads the all clients loaded or unloaded completed and timed out lists /// /// - internal void ReadSceneEventProgressDone(FastBufferReader reader) + private void ReadSceneEventProgressDone(FastBufferReader reader) { reader.ReadValueSafe(out ushort completedCount); ClientsCompleted = new List(); @@ -1227,7 +1216,7 @@ private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) { - if (!sceneManager.ObjectsMigratedIntoNewScene[sceneHandleObjects.Key].ContainsKey(ownerId)) + if (!sceneHandleObjects.Value.ContainsKey(ownerId)) { throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!"); } @@ -1251,40 +1240,31 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) var sceneManager = m_NetworkManager.SceneManager; var spawnManager = m_NetworkManager.SpawnManager; - var numberOfScenes = 0; - NetworkSceneHandle sceneHandle; - var objectCount = 0; - var networkObjectId = (ulong)0; - - var ownerID = (ulong)0; - reader.ReadValueSafe(out ownerID); + reader.ReadValueSafe(out ulong ownerID); m_OwnerId = ownerID; - reader.ReadValueSafe(out numberOfScenes); + reader.ReadValueSafe(out int numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { - reader.ReadValueSafe(out sceneHandle); + reader.ReadValueSafe(out NetworkSceneHandle sceneHandle); if (!sceneManager.ObjectsMigratedIntoNewScene.TryGetValue(sceneHandle, out var migratedObjects)) { migratedObjects = new Dictionary>(); sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, migratedObjects); } - if (!migratedObjects.ContainsKey(ownerID)) - { - migratedObjects.Add(ownerID, new List()); - } + migratedObjects.TryAdd(ownerID, new List()); - reader.ReadValueSafe(out objectCount); + reader.ReadValueSafe(out int objectCount); for (int j = 0; j < objectCount; j++) { - reader.ReadValueSafe(out networkObjectId); - if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + reader.ReadValueSafe(out ulong networkObjectId); + if (!spawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) { NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!"); continue; } - var networkObject = spawnManager.SpawnedObjects[networkObjectId]; + // Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed migratedObjects[ownerID].Add(networkObject); } @@ -1301,15 +1281,8 @@ private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader) private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) { var sceneManager = m_NetworkManager.SceneManager; - var spawnManager = m_NetworkManager.SpawnManager; - var ownerId = (ulong)0; - var numberOfScenes = 0; - NetworkSceneHandle sceneHandle; - var objectCount = 0; - var networkObjectId = (ulong)0; - - reader.ReadValueSafe(out ownerId); + reader.ReadValueSafe(out ulong ownerId); var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent() { @@ -1317,17 +1290,16 @@ private void DeferObjectsMovedIntoNewScene(FastBufferReader reader) ObjectsMigratedTable = new Dictionary>(), }; - - reader.ReadValueSafe(out numberOfScenes); + reader.ReadValueSafe(out int numberOfScenes); for (int i = 0; i < numberOfScenes; i++) { - reader.ReadValueSafe(out sceneHandle); + reader.ReadValueSafe(out NetworkSceneHandle sceneHandle); var objectsMigrated = new List(); deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, objectsMigrated); - reader.ReadValueSafe(out objectCount); + reader.ReadValueSafe(out int objectCount); for (int j = 0; j < objectCount; j++) { - reader.ReadValueSafe(out networkObjectId); + reader.ReadValueSafe(out ulong networkObjectId); objectsMigrated.Add(networkObjectId); } } @@ -1351,10 +1323,9 @@ internal void ProcessDeferredObjectSceneChangedEvents() migratedObjects = new Dictionary>(); sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, migratedObjects); } - if (!migratedObjects.ContainsKey(objectsMovedEvent.OwnerId)) - { - migratedObjects.Add(objectsMovedEvent.OwnerId, new List()); - } + + migratedObjects.TryAdd(objectsMovedEvent.OwnerId, new List()); + var objectList = migratedObjects[objectsMovedEvent.OwnerId]; foreach (var objectId in keyEntry.Value) { @@ -1364,9 +1335,9 @@ internal void ProcessDeferredObjectSceneChangedEvents() continue; } - if (!migratedObjects[objectsMovedEvent.OwnerId].Contains(networkObject)) + if (!objectList.Contains(networkObject)) { - migratedObjects[objectsMovedEvent.OwnerId].Add(networkObject); + objectList.Add(networkObject); } } } @@ -1382,7 +1353,7 @@ public void Dispose() { if (m_HasInternalBuffer) { - InternalBuffer.Dispose(); + m_InternalBuffer.Dispose(); m_HasInternalBuffer = false; } } From e6ecb65732033b377e13d9db868a1933a1fd0cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Noellie=20V=C3=A9lez?= Date: Tue, 30 Jun 2026 23:36:42 +0200 Subject: [PATCH 19/64] chore: cache HasAuthority to avoid recomputing it on every access (#4026) refactor: precalculate HasAuthority into cached m_HasAuthority field - Replaced InternalHasAuthority() with private m_HasAuthority field - Set m_HasAuthority on Spawn (before SpawnInternal), cleared on despawn, updated on ownership change - Replaced all internal HasAuthority call sites with direct m_HasAuthority field access - Updated public HasAuthority property to return cached value when spawned - Moved network object setup from NetworkSpawnManager.SpawnNetworkObjectLocallyCommon to NetworkObject.SetupOnSpawn * fix vettng test --------- Co-authored-by: Emma --- .../Runtime/Core/NetworkObject.cs | 195 ++++++++++++------ .../Runtime/Logging/NetworkLog.cs | 8 + .../Runtime/Spawning/NetworkSpawnManager.cs | 118 ++--------- .../DistributedAuthorityCodecTests.cs | 61 ------ .../NetworkObjectOnSpawnTests.cs | 28 +-- .../ProjectSettings/ProjectSettings.asset | 2 +- 6 files changed, 165 insertions(+), 247 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 5008ee6075..3423c05a07 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -416,7 +416,7 @@ public void DeferDespawn(int tickOffset, bool destroy = true) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.LogLevel <= LogLevel.Error) { @@ -633,7 +633,7 @@ public bool SetOwnershipLock(bool lockOwnership = true) } // If we don't have authority exit early - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManager.LogLevel <= LogLevel.Error) { @@ -909,7 +909,7 @@ internal void OwnershipRequest(ulong clientRequestingOwnership) // This action is always authorized as long as the client still has authority. // We need to pass in that this is a request approval ownership change. - NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, HasAuthority, true); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, clientRequestingOwnership, m_HasAuthority, true); } else { @@ -1155,14 +1155,9 @@ public bool HasOwnershipStatus(OwnershipStatus status) /// /// When in client-server mode, authority should is not considered the same as ownership. /// - public bool HasAuthority => InternalHasAuthority(); + public bool HasAuthority => IsSpawned ? m_HasAuthority : !NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private bool InternalHasAuthority() - { - var networkManager = NetworkManager; - return networkManager.DistributedAuthorityMode ? OwnerClientId == networkManager.LocalClientId : networkManager.IsServer; - } + private bool m_HasAuthority; /// /// The NetworkManager that owns this NetworkObject. @@ -1448,10 +1443,7 @@ public bool IsNetworkVisibleTo(ulong clientId) /// internal Scene SceneOrigin { - get - { - return m_SceneOrigin; - } + get => m_SceneOrigin; set { @@ -1471,13 +1463,8 @@ internal Scene SceneOrigin /// internal NetworkSceneHandle GetSceneOriginHandle() { - if (SceneOriginHandle.IsEmpty() && IsSpawned && InScenePlaced) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"{nameof(GetSceneOriginHandle)} called when {nameof(SceneOriginHandle)} is still zero but the {nameof(NetworkObject)} is already spawned!"); - } - } + NetworkLog.InternalAssert(!(IsSpawned && InScenePlaced && SceneOriginHandle.IsEmpty()), $"Spawned in scene placed NetworkObject {name} should always have a valid SceneOriginHandle"); + return !SceneOriginHandle.IsEmpty() ? SceneOriginHandle : gameObject.scene.handle; } @@ -1506,7 +1493,7 @@ public void NetworkShow(ulong clientId) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.DistributedAuthorityMode) { @@ -1601,7 +1588,7 @@ public void NetworkHide(ulong clientId) return; } - if (!HasAuthority) + if (!m_HasAuthority) { if (NetworkManagerOwner.DistributedAuthorityMode) { @@ -1760,7 +1747,7 @@ private void OnDestroy() { // An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject // was marked as destroy pending scene event (which means the destroy with scene property was set). - var isAuthorityDestroy = HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; + var isAuthorityDestroy = m_HasAuthority || NetworkManager.DAHost || DestroyPendingSceneEvent; // If the NetworkObject's GameObject is still valid and the scene is still valid and loaded, then we are still valid var isStillValid = gameObject != null && gameObject.scene.IsValid() && gameObject.scene.isLoaded; @@ -2005,8 +1992,7 @@ public NetworkObject InstantiateAndSpawn(NetworkManager networkManager, ulong ow /// Should the object be destroyed when the scene is changed public void Spawn(bool destroyWithScene = false) { - var networkManager = NetworkManager; - var clientId = networkManager.DistributedAuthorityMode ? networkManager.LocalClientId : NetworkManager.ServerClientId; + var clientId = NetworkManager.DistributedAuthorityMode ? NetworkManager.LocalClientId : NetworkManager.ServerClientId; SpawnInternal(destroyWithScene, clientId, false); } @@ -2058,10 +2044,60 @@ public void Despawn(bool destroy = true) NetworkManagerOwner.SpawnManager.DespawnObject(this, destroy); } + internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClientId, bool destroyWithScene) + { + NetworkObjectId = networkId; + IsPlayerObject = isPlayerObject; + OwnerClientId = ownerClientId; + // When spawned, previous owner is always the first assigned owner + PreviousOwnerId = ownerClientId; + m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; + IsSpawned = true; + + // If this is the player, and the client is the owner, then lock ownership by default + if (NetworkManagerOwner.DistributedAuthorityMode && NetworkManagerOwner.LocalClientId == ownerClientId && isPlayerObject) + { + AddOwnershipExtended(OwnershipStatusExtended.Locked); + } + + if (IsSpawnAuthority) + { + SetupObservers(); + } + + /* + * Setup scene related settings + */ + DestroyWithScene = InScenePlaced || destroyWithScene; + if (InScenePlaced) + { + // Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects + // Note: Always check SceneOriginHandle directly at this specific location. + if (SceneOriginHandle.IsEmpty()) + { + SceneOrigin = gameObject.scene; + } + + // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set + // then assign this to the PrefabGlobalObjectIdHash + if (InScenePlacedSourceGlobalObjectIdHash != 0) + { + PrefabGlobalObjectIdHash = InScenePlacedSourceGlobalObjectIdHash; + } + } + else if (ActiveSceneSynchronization) + { + // Just in case it is a recycled NetworkObject, unsubscribe first + SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; + SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged; + } + } + internal void ResetOnDespawn() { // Always clear out the observers list when despawned Observers.Clear(); + m_HasAuthority = false; IsSpawnAuthority = false; IsSpawned = false; DeferredDespawnTick = 0; @@ -2069,6 +2105,62 @@ internal void ResetOnDespawn() RemoveOwnershipExtended(OwnershipStatusExtended.Locked | OwnershipStatusExtended.Requested); } + internal void SetupObservers() + { + NetworkLog.InternalAssert(IsSpawnAuthority, "This function should only be called on the authority."); + + if (!SpawnWithObservers) + { + if (NetworkManagerOwner.DistributedAuthorityMode) + { + // Always add the owner/authority in DA mode even if SpawnWithObservers is false + // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) + AddObserver(OwnerClientId); + } + + return; + } + + // If running as a server only, then make sure to always add the server's client identifier + if (NetworkManagerOwner.IsServer && !NetworkManagerOwner.IsHost) + { + AddObserver(NetworkManager.ServerClientId); + } + + // If SpawnWithObservers is set, + // then add all connected clients as observers + foreach (var clientId in NetworkManagerOwner.ConnectedClientsIds) + { + // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. + if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId)) + { + continue; + } + AddObserver(clientId); + } + + // Intentionally checking as opposed to just assigning in order to generate notification. + if (!Observers.Contains(OwnerClientId)) + { + // The owner only needs to always be included in DA mode. + if (NetworkManagerOwner.DistributedAuthorityMode) + { + if (NetworkManager.LogLevel <= LogLevel.Error) + { + NetworkLog.LogError($"Client-{OwnerClientId} is the owner of {name} but is not an observer! Adding owner as an observer!"); + } + AddObserver(OwnerClientId); + } + else + { + if (NetworkManager.LogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"Client-{OwnerClientId} is the owner of {name} but is not an observer! This may cause issues"); + } + } + } + } + /// /// Removes all ownership of an object from any client. Can only be called from server /// @@ -2099,7 +2191,7 @@ public void ChangeOwnership(ulong newOwnerClientId) } return; } - NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, HasAuthority); + NetworkManagerOwner.SpawnManager.ChangeOwnership(this, newOwnerClientId, m_HasAuthority); } /// @@ -2108,20 +2200,18 @@ public void ChangeOwnership(ulong newOwnerClientId) /// internal void InvokeBehaviourOnOwnershipChanged(ulong originalOwnerClientId, ulong newOwnerClientId) { - if (!IsSpawned) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]"); - } - return; - } + NetworkLog.InternalAssert(IsSpawned, "[{name}][Attempted behavior invoke on ownership changed before {nameof(NetworkObject)} was spawned]"); var distributedAuthorityMode = NetworkManagerOwner.DistributedAuthorityMode; var isServer = NetworkManagerOwner.IsServer; var isPreviousOwner = originalOwnerClientId == NetworkManagerOwner.LocalClientId; var isNewOwner = newOwnerClientId == NetworkManagerOwner.LocalClientId; + if (distributedAuthorityMode) + { + m_HasAuthority = isNewOwner; + } + if (distributedAuthorityMode || isPreviousOwner) { NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, originalOwnerClientId, true); @@ -2334,7 +2424,7 @@ 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); + var isAuthority = m_HasAuthority || (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. @@ -2409,7 +2499,7 @@ private void OnTransformParentChanged() // With distributed authority, we need to track "valid authoritative" parenting changes. // So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change". - var isParentingAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); + var isParentingAuthority = m_HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner); // If we are spawned and don't have authority; reset the parent back to the cached parent and exit if (!isParentingAuthority) { @@ -2663,8 +2753,6 @@ internal void InvokeBehaviourNetworkPreSpawn() internal void InvokeBehaviourNetworkSpawn() { - NetworkManagerOwner.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); - // Always invoke all InternalOnNetworkSpawn methods on each child NetworkBehaviour // ** before ** invoking OnNetworkSpawn. // This assures all NetworkVariables and RPC related tables have been initialized @@ -3465,29 +3553,13 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, return networkObject; } - /// - /// Subscribes to changes in the currently active scene - /// - /// - /// Only for dynamically spawned NetworkObjects - /// - internal void SubscribeToActiveSceneForSynch() - { - if (ActiveSceneSynchronization) - { - if (!InScenePlaced) - { - // Just in case it is a recycled NetworkObject, unsubscribe first - SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged; - SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged; - } - } - } - /// /// If AutoSynchActiveScene is enabled, then this is the callback that handles updating /// a NetworkObject's scene information. /// + /// + /// Should only be used for dynamically spawned NetworkObjects + /// private void CurrentlyActiveSceneChanged(Scene current, Scene next) { // Early exit if the NetworkObject is not spawned, is an in-scene placed NetworkObject, @@ -3526,15 +3598,14 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) return; } - var isAuthority = HasAuthority; SceneOriginHandle = scene.handle; // non-authority needs to update the NetworkSceneHandle - if (!isAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) + if (!m_HasAuthority && NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle)) { NetworkSceneHandle = NetworkManagerOwner.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle]; } - else if (isAuthority) + else if (m_HasAuthority) { // Since the authority is the source of truth for the NetworkSceneHandle, // the NetworkSceneHandle is the same as the SceneOriginHandle. @@ -3561,7 +3632,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes - if (isAuthority && notify && !transform.parent) + if (m_HasAuthority && notify && !transform.parent) { NetworkManagerOwner.SceneManager.NotifyNetworkObjectSceneChanged(this); } diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index 12fd9a60bb..df08bee61f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -1,8 +1,10 @@ +using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using Unity.Netcode.Logging; using UnityEngine; +using UnityEngine.Assertions; namespace Unity.Netcode { @@ -158,5 +160,11 @@ private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkMana return true; } + [HideInCallstack] + [Conditional("NETCODE_INTERNAL")] + internal static void InternalAssert(bool condition, string message) + { + Assert.IsTrue(condition, message); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 449ca04100..38711e155c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1137,48 +1137,13 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n networkObject.NetworkManagerOwner = NetworkManager; networkObject.InvokeBehaviourNetworkPreSpawn(); - // DANGO-TODO: It would be nice to allow users to specify which clients are observers prior to spawning - // For now, this is the best place I could find to add all connected clients as observers for newly - // instantiated and spawned NetworkObjects on the authoritative side. - if (NetworkManager.DistributedAuthorityMode) + if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject) { - if (NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject) - { - networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; - networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; - } - - // Always add the owner/authority even if SpawnWithObservers is false - // (authority should not take into consideration networkObject.CheckObjectVisibility when SpawnWithObservers is false) - if (!networkObject.SpawnWithObservers) - { - networkObject.AddObserver(ownerClientId); - } - else - { - foreach (var clientId in NetworkManager.ConnectionManager.ConnectedClientIds) - { - // If SpawnWithObservers is enabled, then authority does take networkObject.CheckObjectVisibility into consideration - if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility.Invoke(clientId)) - { - continue; - } - networkObject.AddObserver(clientId); - } - - // Sanity check to make sure the owner is always included - // Intentionally checking as opposed to just assigning in order to generate notification. - if (!networkObject.Observers.Contains(ownerClientId)) - { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"Client-{ownerClientId} is the owner of {networkObject.name} but is not an observer! Adding owner, but there is a bug in observer synchronization!"); - } - networkObject.AddObserver(ownerClientId); - } - } + networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; + networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; } + if (!SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene)) { if (NetworkManager.LogLevel <= LogLevel.Error) @@ -1295,9 +1260,10 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize } /// - /// Handles the all the final setup and spawning needed for + /// Handles all the final setup and spawning needed for spawning a NetworkObject locally. /// - /// boolean indicating whether the spawn succeeded. Internal dev note: THIS IS A CATCH FOR OURSELVES. DON'T PULL OUT + /// boolean indicating whether the spawn succeeded. + // Internal dev note: THIS IS A CATCH FOR OURSELVES. DON'T PULL OUT internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene) { // TODO: Replace the following checks with internal Netcode asserts @@ -1311,7 +1277,7 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong return false; } - if (networkId == default) + if (networkId == 0) { if (NetworkManager.LogLevel <= LogLevel.Error) { @@ -1326,82 +1292,23 @@ internal bool SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkObject.SetSceneObjectStatus(sceneObject); #pragma warning restore CS0618 // Type or member is obsolete - // Always check to make sure our scene of origin is properly set for in-scene placed NetworkObjects - // Note: Always check SceneOriginHandle directly at this specific location. - if (networkObject.InScenePlaced && networkObject.SceneOriginHandle.IsEmpty()) - { - networkObject.SceneOrigin = networkObject.gameObject.scene; - } - - networkObject.NetworkObjectId = networkId; - - networkObject.DestroyWithScene = sceneObject || destroyWithScene; - - networkObject.IsPlayerObject = playerObject; + networkObject.SetupOnSpawn(networkId, playerObject, ownerClientId, destroyWithScene); - networkObject.OwnerClientId = ownerClientId; - - // When spawned, previous owner is always the first assigned owner - networkObject.PreviousOwnerId = ownerClientId; - - // If this the player and the client is the owner, then lock ownership by default - if (NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClientId == ownerClientId && playerObject) - { - networkObject.AddOwnershipExtended(NetworkObject.OwnershipStatusExtended.Locked); - } - - networkObject.IsSpawned = true; SpawnedObjects.Add(networkObject.NetworkObjectId, networkObject); SpawnedObjectsList.Add(networkObject); - - // If we are not running in DA mode, this is the server, and the NetworkObject has SpawnWithObservers set, - // then add all connected clients as observers - if (!NetworkManager.DistributedAuthorityMode && NetworkManager.IsServer && networkObject.SpawnWithObservers) - { - // If running as a server only, then make sure to always add the server's client identifier - if (!NetworkManager.IsHost) - { - networkObject.AddObserver(NetworkManager.LocalClientId); - } - - // Add client observers - for (int i = 0; i < NetworkManager.ConnectedClientsIds.Count; i++) - { - // If CheckObjectVisibility has a callback, then allow that method determine who the observers are. - if (networkObject.CheckObjectVisibility != null && !networkObject.CheckObjectVisibility(NetworkManager.ConnectedClientsIds[i])) - { - continue; - } - networkObject.AddObserver(NetworkManager.ConnectedClientsIds[i]); - } - } - networkObject.ApplyNetworkParenting(); + NetworkObject.CheckOrphanChildren(); AddNetworkObjectToSceneChangedUpdates(networkObject); - + UpdateOwnershipTable(networkObject, ownerClientId); networkObject.InvokeBehaviourNetworkSpawn(); - - // Only dynamically spawned NetworkObjects are allowed - if (!sceneObject) - { - networkObject.SubscribeToActiveSceneForSynch(); - } - if (networkObject.IsPlayerObject) { UpdateNetworkClientPlayer(networkObject); } - // If we are an in-scene placed NetworkObject and our InScenePlacedSourceGlobalObjectIdHash is set - // then assign this to the PrefabGlobalObjectIdHash - if (networkObject.InScenePlaced && networkObject.InScenePlacedSourceGlobalObjectIdHash != 0) - { - networkObject.PrefabGlobalObjectIdHash = networkObject.InScenePlacedSourceGlobalObjectIdHash; - } - return true; } @@ -1554,8 +1461,7 @@ internal void ServerResetShutdownStateForSceneObjects() { continue; } - sobj.IsSpawned = false; - sobj.DestroyWithScene = false; + sobj.ResetOnDespawn(); } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs index b6157324bc..fb88b9ada8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DistributedAuthorityCodecTests.cs @@ -76,11 +76,6 @@ internal class TestNetworkComponent : NetworkBehaviour { public NetworkList MyNetworkList = new NetworkList(new List { 1, 2, 3 }); public NetworkVariable MyNetworkVar = new NetworkVariable(3); - - [Rpc(SendTo.Authority)] - public void TestAuthorityRpc(byte[] _) - { - } } protected override void OnOneTimeSetup() @@ -157,62 +152,6 @@ protected override IEnumerator OnStartedServerAndClients() m_Client.MessageManager.Hook(m_ClientCodecHook); } - [UnityTest] - public IEnumerator AuthorityRpc() - { - var player = m_Client.LocalClient.PlayerObject; - player.OwnerClientId = m_Client.LocalClientId + 1; - - var networkComponent = player.GetComponent(); - networkComponent.UpdateNetworkProperties(); - networkComponent.TestAuthorityRpc(new byte[] { 1, 2, 3, 4 }); - - // Universal Rpcs are sent as a ProxyMessage (which contains an RpcMessage) - yield return m_ClientCodecHook.WaitForMessageReceived(); - } - - [UnityTest] - public IEnumerator ChangeOwnership() - { - var message = new ChangeOwnershipMessage - { - DistributedAuthorityMode = true, - NetworkObjectId = 100, - OwnerClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator ClientConnected() - { - var message = new ClientConnectedMessage() - { - ClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator ClientDisconnected() - { - var message = new ClientDisconnectedMessage() - { - ClientId = 2, - }; - - yield return SendMessage(ref message); - } - - [UnityTest] - public IEnumerator CreateObject() - { - SpawnObject(m_SpawnObject, m_Client); - yield return m_ClientCodecHook.WaitForMessageReceived(); - } - [UnityTest] public IEnumerator DestroyObject() { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 0cdb31c11e..5b5583f31d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -341,7 +341,17 @@ bool HasConditionBeenMet() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns!"); //----------- step 2 check spawn and destroy again - authorityInstance.GetComponent().Spawn(); + var authorityObject = authorityInstance.GetComponent(); + authorityObject.Spawn(); + + // This validates invoking GetSceneOriginHandle will not throw an exception for a dynamically spawned NetworkObject + // when the scene of origin hasn't been set. + var sceneOriginHandle = authorityObject.GetSceneOriginHandle(); + + // This validates that GetSceneOriginHandle will return the GameObject's scene handle that should be the currently active scene + var activeSceneHandle = SceneManager.GetActiveScene().handle; + Assert.IsTrue(sceneOriginHandle == activeSceneHandle, $"{nameof(NetworkObject)} should have returned the active scene handle of {activeSceneHandle} but returned {sceneOriginHandle}"); + // wait a tick yield return s_DefaultWaitForTick; // check spawned again on server this is 2 because we are reusing the object which was already spawned once. @@ -366,22 +376,6 @@ bool HasConditionBeenMet() Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns! (2nd pass)"); } - [Test] - public void DynamicallySpawnedNoSceneOriginException() - { - var gameObject = new GameObject(); - var networkObject = gameObject.AddComponent(); - networkObject.IsSpawned = true; - networkObject.SceneOriginHandle = default; - // This validates invoking GetSceneOriginHandle will not throw an exception for a dynamically spawned NetworkObject - // when the scene of origin hasn't been set. - var sceneOriginHandle = networkObject.GetSceneOriginHandle(); - - // This validates that GetSceneOriginHandle will return the GameObject's scene handle that should be the currently active scene - var activeSceneHandle = SceneManager.GetActiveScene().handle; - Assert.IsTrue(sceneOriginHandle == activeSceneHandle, $"{nameof(NetworkObject)} should have returned the active scene handle of {activeSceneHandle} but returned {sceneOriginHandle}"); - } - private class TrackOnSpawnFunctions : NetworkBehaviour { public int OnNetworkSpawnCalledCount { get; private set; } diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset index 176a6e2232..61fc950e7c 100644 --- a/testproject/ProjectSettings/ProjectSettings.asset +++ b/testproject/ProjectSettings/ProjectSettings.asset @@ -739,7 +739,7 @@ PlayerSettings: webWasm2023: 0 webEnableSubmoduleStrippingCompatibility: 0 scriptingDefineSymbols: - Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT + Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT;NETCODE_INTERNAL additionalCompilerArguments: Standalone: [] platformArchitecture: {} From 780080303343742052a5cd29c3d15259d9911673 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 1 Jul 2026 08:08:59 -0500 Subject: [PATCH 20/64] fix: fastbufferreader string deserialization int overflow (#4052) * fix This fixes an edge case scenario with string reading where if user code has caused the character count to have been already read prior to attempting to read a string value (safely or unsafely) and then reading the string can result in the signed integer size to roll over to a negative value and thus causing the reader to attempt to read into restricted memory outside of the application domain which results in the editor crashing. The fix catches this scenario and throws an overflow exception prior to attempting to read into negative memory space relative to the application domain. * test This test validates the fix. * update Adding changelog entry. * update Making the reader use the already calculated readSize. * test Updating test to unsafely read a string under the same condition. * fix Fixing an issue where the unsafe string read needs to create an empty string based on the character count and not the byte count. * refactor Based on Paolo's suggestion on combining the string size, in bytes, validation script to a single in-lined method shared between the safe and unsafe string read methods. Also combined the actual reading of the string data into a single in-lined method. * style Removing the auto-added (and not used) UnityEngine.UIElements using directive. * style updated comments and renamed CheckIfValidStringLength to ValidateStringByteCount. * test Removing the added 3 bytes used for earlier debugging purposes. * style spelling/typo fix. whitespace after sentence in comment fix. * refactor Based on Emma's suggestion, removing the `SizeOfLengthField` method and `TryBeginReadInternal` check within the ReadValueSafe (string) method and replacing that with `ReadLengthSafe`. * Update com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs Co-authored-by: Emma * update Based on suggestion from Emma, removing InBitwiseContext check since this is done in both ReadLengthSafe and TryBeginReadInternal. --------- Co-authored-by: Emma --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Serialization/FastBufferReader.cs | 108 ++++++++++-------- .../Serialization/FastBufferReaderTests.cs | 44 +++++++ 3 files changed, 108 insertions(+), 46 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 3f6df83f11..0c5e47d1a0 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -21,6 +21,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed + +- Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) - Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index d420ccd28e..d132f775fd 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -564,14 +564,36 @@ public void ReadNetworkSerializableInPlace(ref T value) where T : INetworkSer } /// - /// Reads a string - /// NOTE: ALLOCATES + /// Validates the string's total byte count based on whether we are + /// using one or two byte characters. + /// + /// + /// Will throw an overflow exception if the size is greater than . + /// + /// Character count + /// If false(default) 2 byte characters and if true 1 byte characters + /// total size in bytes to read + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private int ValidateStringByteCount(int length, bool oneByteChars) + { + var readSize = oneByteChars ? length : length * sizeof(char); + if (int.MaxValue < (uint)readSize) + { + throw new OverflowException($"Invalid reader position detected when trying to read a string of size {(uint)readSize}! This can result from an error in the serialization. Ensure deserialization exactly matches what was serialized!"); + } + return readSize; + } + + /// + /// Commonly shared string read method between . /// - /// Stores the read string - /// Whether or not to use one byte per character. This will only allow ASCII - public unsafe void ReadValue(out string s, bool oneByteChars = false) + /// The output of the string read. + /// The number of characters in the string. + /// If false(default) 2 byte characters and if true 1 byte characters. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private unsafe void ReadString(out string s, int length, bool oneByteChars) { - ReadLength(out int length); s = "".PadRight(length); int target = s.Length; fixed (char* native = s) @@ -592,56 +614,50 @@ public unsafe void ReadValue(out string s, bool oneByteChars = false) } /// - /// Reads a string. - /// NOTE: ALLOCATES - /// - /// "Safe" version - automatically performs bounds checking. Less efficient than bounds checking - /// for multiple reads at once by calling TryBeginRead. + /// Reads a string without bounds checking. + /// NOTE: This method ALLOCATES memory. /// + /// + /// This is the un-safe string read which requires invoking prior to invoking this method.
+ /// Using one byte characters only allows ASCII characters. + ///
/// Stores the read string - /// Whether or not to use one byte per character. This will only allow ASCII - public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) + /// If false(default) 2 byte characters and if true 1 byte characters. + public unsafe void ReadValue(out string s, bool oneByteChars = false) { -#if DEBUG - if (Handle->InBitwiseContext) - { - throw new InvalidOperationException( - "Cannot use BufferReader in bytewise mode while in a bitwise context."); - } -#endif + ReadLength(out int length); - if (!TryBeginReadInternal(SizeOfLengthField())) - { - throw new OverflowException("Reading past the end of the buffer"); - } + // Validate the string's byte count based on the character count. + ValidateStringByteCount(length, oneByteChars); - ReadLength(out int length); + // Read the string + ReadString(out s, length, oneByteChars); + } + + /// + /// Reads a string after it performs bounds checking automatically. + /// NOTE: This method ALLOCATES memory. + /// + /// + /// This is the safe string read which invokes prior to reading the string.
+ /// Using one byte characters only allows ASCII characters. + ///
+ /// The string re the read string + /// If false(default) 2 byte characters and if true 1 byte characters. + public unsafe void ReadValueSafe(out string s, bool oneByteChars = false) + { + ReadLengthSafe(out int length); - if (!TryBeginReadInternal(length * (oneByteChars ? 1 : sizeof(char)))) + // Validate the string's byte count based on the character count and if it is valid begin reading based on the returned + // byte count. + if (!TryBeginReadInternal(ValidateStringByteCount(length, oneByteChars))) { throw new OverflowException("Reading past the end of the buffer"); } - s = "".PadRight(length); - int target = s.Length; - fixed (char* native = s) - { - if (oneByteChars) - { - for (int i = 0; i < target; ++i) - { - ReadByte(out byte b); - native[i] = (char)b; - } - } - else - { - ReadBytes((byte*)native, target * sizeof(char)); - } - } - } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static int SizeOfLengthField() => sizeof(uint); + // Read the string + ReadString(out s, length, oneByteChars); + } [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ReadLengthSafe(out uint length) => ReadUnmanagedSafe(out length); diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index 85aabbb52e..d38c10e0a7 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -924,6 +924,50 @@ public void GivenFastBufferWriterContainingValue_WhenReadingString_ValueMatchesW } } + /// + /// This validates that catches a potential + /// scenario where the string's character count value has already been read + /// due to an error within user script and the resultant character count + /// multiplied times 2 (when using 2 bytes vs 1) causes the length to roll over + /// to a negative value which, in turn, causes the reader to attempt to read + /// into restricted memory and causes the editor to crash. + /// + [Test] + public void ReadingStringAfterStringLengthHasAlreadyBeenRead([Values] bool isSafeRead) + { + // This was an issue uncovered in UUM-145752 that resulted + // in the below text to result in a length that when using + // 2 bytes per character would cause the skewed size to roll + // over into a negative value causing the editor to crash + // when it attempted to read a large negative offset value. + string valueToTest = "true"; + + var serializedValueSize = FastBufferWriter.GetWriteSize(valueToTest); + + using var writer = new FastBufferWriter(serializedValueSize, Allocator.Temp); + writer.WriteValueSafe(valueToTest); + + using var reader = new FastBufferReader(writer, Allocator.Temp); + + // Read the value of the character count before trying to read the string + // This mocks user code having read too far into a stream causing the position to be skewed such that + // the string reader reads the some of the bytes for the actual text as the length. + reader.ReadByteSafe(out byte count); + Assert.True(count == valueToTest.Length, $"Count ({count}) is not the expected size of {valueToTest.Length}!"); + if (isSafeRead) + { + // This should throw an overflow exception but should not crash the editor. + Assert.Throws(() => reader.ReadValueSafe(out string valueRead)); + } + else + { + // Assume user does a pre-calculation of the size to be read: + Assert.IsTrue(reader.TryBeginRead(count), "Reader denied read permission"); + + // This should throw an overflow exception but should not crash the editor. + Assert.Throws(() => reader.ReadValue(out string valueRead)); + } + } [TestCase(1, 0)] [TestCase(2, 0)] From 10e01eb7222d6b5804201721694e18737e6ce71a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Sat, 4 Jul 2026 13:32:54 +0200 Subject: [PATCH 21/64] fix: remove dead API doc links to internal scene event types (#4064) fix: remove dead API doc links to internal scene event types (UUM-131558) The NetworkSceneManager class summary and SceneEventDelegate XML docs referenced the internal types SceneEventMessage and SceneEventData via , which generate hyperlinks in the DocFX-generated API docs. Because those types are internal they have no public documentation pages, so the links resolved to missing pages. Convert the descriptive class-summary references to inline code () and drop the internal SceneEventData entry from the SceneEventDelegate 'See also' list (a user cannot reference an internal type), keeping the public SceneEvent link. --- .../Runtime/SceneManagement/NetworkSceneManager.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index 8c07fbd603..a28baf1f94 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -143,7 +143,7 @@ public class SceneEvent /// /// Main class for managing network scenes when is enabled. - /// Uses the message to communicate between the server and client(s) + /// Uses the SceneEventMessage message to communicate SceneEventData between the server and client(s) /// [Serializable] public class NetworkSceneManager : IDisposable @@ -162,8 +162,7 @@ public class NetworkSceneManager : IDisposable /// /// The delegate callback definition for scene event notifications.
/// See also:
- ///
- /// + /// ///
/// SceneEvent which contains information about the scene event, including type, progress, and scene details public delegate void SceneEventDelegate(SceneEvent sceneEvent); From 0dce182b1ab312b90f244c7e12c45719d67d3ff3 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 6 Jul 2026 19:03:27 -0400 Subject: [PATCH 22/64] fix: Serialization docs (#4007) * fix: Serialization docs * Apply suggestions from code review Co-authored-by: Amy Reeve * Apply suggestion from @jabbacakes Co-authored-by: Amy Reeve * small changes * Fix pvp exceptions * Fix pvp errors * Fix formatting * Use RuntimeInitializeOnLoad * fix the tests * Fix tests v2 electric boogaloo * Add details in README files --------- Co-authored-by: Amy Reeve --- .../Documentation~/TableOfContents.md | 10 +- .../advanced-topics/bufferserializer.md | 7 + .../advanced-topics/custom-serialization.md | 114 ++++----- .../fastbufferwriter-fastbufferreader.md | 7 + .../advanced-topics/message-system/rpc.md | 1 + .../serialization/inetworkserializable.md | 45 ++-- .../inetworkserializebymemcpy.md | 7 +- .../networkobject-serialization.md | 18 +- .../serialization/serialization-arrays.md | 40 +-- .../serialization/serialization-overview.md | 36 +++ .../basics/custom-networkvariables.md | 179 ++----------- .../Documentation~/basics/networkvariable.md | 92 +------ .../Documentation~/serialization.md | 7 +- .../TypedSerializerImplementations.cs | 30 +++ .../Editor/DocumentationCodeSamples.meta | 3 + .../Editor/DocumentationCodeSamples/README.md | 31 +++ .../DocumentationCodeSamples/README.md.meta | 7 + .../Serialization.meta | 3 + .../SerializationCustomization.cs | 114 +++++++++ .../SerializationCustomization.cs.meta | 3 + .../Runtime/DocumentationCodeSamples.meta | 8 + .../NetworkVariable.meta | 8 + .../NetworkVariable/CustomNetworkVariable.cs | 239 ++++++++++++++++++ .../CustomNetworkVariable.cs.meta | 2 + .../CustomSerializationDocsTests.cs | 213 ++++++++++++++++ .../CustomSerializationDocsTests.cs.meta | 3 + .../DocumentationCodeSamples/README.md | 33 +++ .../DocumentationCodeSamples/README.md.meta | 7 + 28 files changed, 887 insertions(+), 380 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index e78e7192f6..47bd50c6bc 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -70,15 +70,17 @@ * [Network update loop reference](advanced-topics/network-update-loop-system/network-update-loop-reference.md) * [Network time and ticks](advanced-topics/networktime-ticks.md) * [Serialization](serialization.md) + * [Serialization overview](advanced-topics/serialization/serialization-overview.md) * [C# primitives](advanced-topics/serialization/cprimitives.md) * [Unity primitives](advanced-topics/serialization/unity-primitives.md) * [Enum types](advanced-topics/serialization/enum-types.md) * [Arrays](advanced-topics/serialization/serialization-arrays.md) - * [INetworkSerializable](advanced-topics/serialization/inetworkserializable.md) - * [INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md) - * [Custom serialization](advanced-topics/custom-serialization.md) - * [NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md) + * [Customize serializable types with INetworkSerializable](advanced-topics/serialization/inetworkserializable.md) + * [Serialize unmanaged structs with INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md) + * [NetworkObject and NetworkBehaviour serialization](advanced-topics/serialization/networkobject-serialization.md) * [FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md) + * [BufferSerializer](advanced-topics/bufferserializer.md) + * [Custom serialization](advanced-topics/custom-serialization.md) * [Scene management](scene-management.md) * [Scene management overview](basics/scenemanagement/scene-management-overview.md) * [Integrated management](integrated-management.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md index 22375bccf5..0f5df51f15 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/bufferserializer.md @@ -1,5 +1,8 @@ # BufferSerializer +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to use `BufferSerializer`. + `BufferSerializer` is the bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. It wraps [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md) to provide high performance serialization, but has a couple of differences to make it more user-friendly: - Rather than writing separate methods for serializing and deserializing, `BufferSerializer` allows writing a single method that can handle both operations, which reduces the possibility of a mismatch between the two @@ -15,3 +18,7 @@ However, when those downsides are unreasonable, `BufferSerializer - For performance, you can use `PreCheck(int amount)` followed by `SerializeValuePreChecked()` to perform bounds checking for multiple fields at once. - For both performance and bandwidth usage, you can obtain the wrapped underlying reader/writer via `serializer.GetFastBufferReader()` when `serializer.IsReader` is `true`, and `serializer.GetFastBufferWriter()` when `serializer.IsWriter` is `true`. These provide micro-performance improvements by removing a level of indirection, and also give you a type you can use with `BytePacker` and `ByteUnpacker`. + +## Serializing custom types + +`BufferSerializer` can be extended via extension methods to handle serializing custom types. Refer to [customizing `BufferSerializer`](./custom-serialization.md#bufferserializer) for instructions on how to do this. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md index 7a04e2f123..b7b23e4afc 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md @@ -1,93 +1,71 @@ # Custom serialization -Netcode uses a default serialization pipeline when using `RPC`s, `NetworkVariable`s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to customize serialization. -`` -Custom Types => Built In Types => INetworkSerializable -`` +Netcode for GameObjects provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. -That is, when Netcode first gets hold of a type, it will check for any custom types that the user have registered for serialization, after that it will check if it's a built in type, such as a Vector3, float etc. These are handled by default. If not, it will check if the type inherits `INetworkSerializable`, if it does, it will call it's write methods. +Netcode for GameObjects supports custom serialization of unsupported types, including those you haven't defined yourself, such as third-party .NET types. You can also use custom serialization to override how an existing supported type is serialized. -By default, any type that satisfies the `unmanaged` generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, etc) as well as any structs that has only these basic types. +Custom serialization is implemented slightly differently for RPCs and NetworkVariables. The examples on this page provide different ways to serialization a custom health struct. -With this flow, you can provide support for serializing any unsupported types, and with the API provided, it can even be done with types that you haven't defined yourself, those who are behind a 3rd party wall, such as .NET types. However, the way custom serialization is implemented for RPCs and NetworkVariables is slightly different. +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#HealthStruct)] -### Serialize a type in a Remote Procedure Call (RPC) +## FastBufferReader and FastBufferWriter -> [!NOTE] -> From versioln 1.7.0 Remote Procedure Calls (RPCs) can also use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when RPCs serialize the type. Unity selects the RPC flow if you implement both the RPC and Network variable flows. When a type is used by both NetworkVariables and RPCs you can use the NetworkVariable flow to lower maintenance requirements. +[`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md) are the main serialization tools in Netcode for GameObjects. To register serialization for a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`. `FastBufferReader` and `FastBufferWriter` can read and write primitive types, and you can extend this functionality to serialize your custom type. -To register a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()`: +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#FastBuffer)] -```csharp -// Tells the Netcode how to serialize and deserialize Url in the future. -// The class name doesn't matter here. -public static class SerializationExtensions -{ - public static void ReadValueSafe(this FastBufferReader reader, out Url url) - { - reader.ReadValueSafe(out string val); - url = new Url(val); - } - - public static void WriteValueSafe(this FastBufferWriter writer, in Url url) - { - writer.WriteValueSafe(url.Value); - } -} -``` +You may also need to add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()` if you want to serialize without [bounds checking](./fastbufferwriter-fastbufferreader.md#bounds-checking) -The code generation for RPCs will automatically pick up and use these functions, and they'll become available via `FastBufferWriter` and `FastBufferReader` directly. +## BufferSerializer -You can also optionally use the same method to add support for `BufferSerializer.SerializeValue()`, if you wish, which will make this type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types: +You can also add custom serialization support to the bi-directional [`BufferSerializer`](./bufferserializer.md). This makes your custom type readily available within [`INetworkSerializable`](serialization/inetworkserializable.md) types and in the [`NetworkBehaviour.OnSynchronize()` method](../components/core/networkbehaviour-synchronize.md#prespawn-synchronization-with-onsynchronize): -```csharp -// The class name doesn't matter here. -public static class SerializationExtensions -{ - public static void SerializeValue(this BufferSerializer serializer, ref Url url) where TReaderWriter: IReaderWriter - { - if (serializer.IsReader) - { - url = new Url(); - } - serializer.SerializeValue(ref url.Value); - } -} -``` +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs#BufferSerializer)] -Additionally, you can also add extensions for `FastBufferReader.ReadValue()`, `FastBufferWriter.WriteValue()`, and `BufferSerializer.SerializeValuePreChecked()` to provide more optimal implementations for manual serialization using `FastBufferReader.TryBeginRead()`, `FastBufferWriter.TryBeginWrite()`, and `BufferSerializer.PreCheck()`, respectively. However, none of these will be used for serializing RPCs - only `ReadValueSafe` and `WriteValueSafe` are used. +## Remote procedure call (RPCs) -### For NetworkVariable +> [!NOTE] +> RPCs can use the Network Variable flow, but NetworkVariables can't use the RPC flow. The RPC flow is more efficient when only RPCs need to serialize the type. When a type is used by both NetworkVariables and RPCs, you can implement just the NetworkVariable flow to lower maintenance requirements. Unity will select the RPC flow for RPCs if you have implemented both flows. -`NetworkVariable` goes through a slightly different pipeline than `RPC`s and relies on a different process for determining how to serialize its types. As a result, making a custom type available to the `RPC` pipeline doesn't automatically make it available to the `NetworkVariable` pipeline, and vice-versa. The same method can be used for both, but currently, `NetworkVariable` requires an additional runtime step to make it aware of the methods. +To serialize a custom type, or override an already handled type, you need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). -To add custom serialization support in `NetworkVariable`, follow the steps from the "For RPCs" section to write extension methods for `FastBufferReader` and `FastBufferWriter`; then, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized) add the following: +The code generation for RPCs will automatically pick up and use these functions, as they'll become available via `FastBufferWriter` and `FastBufferReader` directly. -```csharp -UserNetworkVariableSerialization.WriteValue = SerializationExtensions.WriteValueSafe; -UserNetworkVariableSerialization.ReadValue = SerializationExtensions.ReadValueSafe; -``` +## NetworkVariable + +Implementing [`INetworkSerializable`](./serialization/inetworkserializable.md) is the cleanest and most straightforward way to customize the serialization of a type within a [`NetworkVariable`](../basics/networkvariable.md). `UserNetworkVariableSerialization` provides runtime configuration to further override serialization of a type. -You can also use lambda expressions here: +First you will need to create extension methods for `FastBufferReader.ReadValueSafe()` and `FastBufferWriter.WriteValueSafe()` as [outlined above](#fastbufferreader-and-fastbufferwriter). + +Secondly, somewhere in your application startup (before any `NetworkVariable`s using the affected types will be serialized), add the following: ```csharp -UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in Url url) => -{ - writer.WriteValueSafe(url.Value); -}; - -UserNetworkVariableSerialization.ReadValue = (FastBufferReader reader, out Url url) -{ - reader.ReadValueSafe(out string val); - url = new Url(val); -}; +UserNetworkVariableSerialization.WriteValue = SerializationExtensions.WriteValueSafe; +UserNetworkVariableSerialization.ReadValue = SerializationExtensions.ReadValueSafe; +UserNetworkVariableSerialization.DuplicateValue = (in Health value, ref Health duplicatedValue) => duplicatedValue = value; ``` -When you create an extension method in `NetworkVariable` you need to implement the following values: +`DuplicateValue` should return a complete deep copy of the value that `NetworkVariable` compares to a previous value, which is used to check whether the value has changed. `DuplicateValue` avoids re-serializing it over the network every frame when it hasn't changed. + +> [!NOTE] +> `WriteValue`, `ReadValue` and `DuplicateValue` all need to be defined to customize your serialization. + +> [!NOTE] +> `WriteValue` and `ReadValue` will not be used if a type implements `INetworkSerializable` or [`INetworkSerializeByMemcpy`](./serialization/inetworkserializebymemcpy.md). + +### Serializing delta updates + +Reading and writing a value provides the minimal amount of `NetworkVariable` functionality. This will synchronize your whole type whenever any value within the type value changes. To support sending delta updates rather than a full update whenever your type has changed, implement the following functions: + +- `WriteDelta` +- `ReadDelta` + +> [!NOTE] +> Both `WriteDelta` and `ReadDelta` need to be defined for either to be used. -- `WriteValue` -- `ReadValue` -- `DuplicateValue` +Here is a full implementation of a custom type with the methods needed for `UserNetworkVariableSerialization` -`DuplicateValue` returns a complete deep copy of the value that `NetworkVariable` compares to a previous value to check whether or not that values has changed. This avoids reserializing it over the network every frame when it hasn't changed. +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/NetworkVariableSerialization.cs#HealthExample)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md index 1ac9b0f9d3..3f8327fc17 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md @@ -1,5 +1,8 @@ # FastBufferWriter and FastBufferReader +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning about `FastBufferWriter` and `FastBufferReader`. + The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. There's a trade-off of CPU usage vs bandwidth in using this: Writing individual fields is slower (especially when it includes operations on unaligned memory), but allows the buffer to be filled more efficiently, both because it avoids padding for alignment in structs, and because it allows you to use `BytePacker.WriteValuePacked()`/`ByteUnpacker.ReadValuePacked()` and `BytePacker.WriteValueBitPacked()`/`ByteUnpacker.ReadValueBitPacked()`. The difference between these two is that the BitPacked variants pack more efficiently, but they reduce the valid range of values. See the section below for details on packing. @@ -156,3 +159,7 @@ Packing values is done using the utility classes `BytePacker` and `ByteUnpacker` | uint | 30 bits (0 to 1,073,741,824) | | long | 60 bits + sign bit (-1,152,921,504,606,846,976 to 1,152,921,504,606,846,975) | | ulong | 61 bits (0 to 2,305,843,009,213,693,952) | + +## Serializing custom types + +`FastBufferReader` and `FastBufferWriter` can be extended via extension methods to handle serializing custom types. Refer to [customizing `FastBufferReader` and `FastBufferWriter`](./custom-serialization.md#fastbufferreader-and-fastbufferwriter) for instructions on how to do this. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md index 1fb5c3dcc4..b46a1b8b91 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md @@ -328,3 +328,4 @@ void Update() ## Additional resources * [RPC parameters](rpc-params.md) +* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpc) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md index 36d83416ba..2733a0e373 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md @@ -1,9 +1,14 @@ -# INetworkSerializable +# Customize serializable types with INetworkSerializable -You can use the `INetworkSerializable` interface to define custom serializable types. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. + +You can use the `INetworkSerializable` interface to define custom serializable types. This interface has one function: `NetworkSerialize(BufferSerializer serializer)`, which ingests a bi-directional [`BufferSerializer`](../bufferserializer.md) that you can use to implement bi-directional custom serialization. + +`INetworkSerializable` can be implemented on both managed and unmanaged types, although serializing managed types isn't recommended because it can reduce your game's performance. ```csharp -struct MyComplexStruct : INetworkSerializable +struct SpawnPoint : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -18,24 +23,20 @@ struct MyComplexStruct : INetworkSerializable } ``` -Types implementing `INetworkSerializable` are supported by `NetworkSerializer`, `RPC` s and `NetworkVariable` s. +Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). ```csharp - [Rpc(SendTo.Server)] -void MyServerRpc(MyComplexStruct myStruct) { /* ... */ } +void SpawnAtPointRpc(SpawnPoint spawnPoint) { /* ... */ } -void Update() +void DoSpawnHere() { - if (Input.GetKeyDown(KeyCode.P)) + var spawnPoint = new SpawnPoint { - MyServerRpc( - new MyComplexStruct - { - Position = transform.position, - Rotation = transform.rotation - }); // Client -> Server - } + Position = transform.position, + Rotation = transform.rotation + }; + SpawnAtPointRpc(spawnPoint); // Client -> Server } ``` @@ -59,7 +60,7 @@ The following example explores a more advanced use case. ```csharp -public struct MyMoveStruct : INetworkSerializable +public struct SpawnWithMovement : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -125,7 +126,7 @@ Review the following example: ```csharp -public struct MyStructA : INetworkSerializable +public struct SpawnPoint : INetworkSerializable { public Vector3 Position; public Quaternion Rotation; @@ -137,17 +138,17 @@ public struct MyStructA : INetworkSerializable } } -public struct MyStructB : INetworkSerializable +public struct SpawnInfo : INetworkSerializable { public int SomeNumber; public string SomeText; - public MyStructA StructA; + public SpawnPoint SpawnPoint; void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref SomeNumber); serializer.SerializeValue(ref SomeText); - StructA.NetworkSerialize(serializer); + SpawnPoint.NetworkSerialize(serializer); } } ``` @@ -167,7 +168,7 @@ While you can have nested `INetworkSerializable` implementations (an `INetworkSe ```csharp /// This isn't supported. -public struct MyStructB : MyStructA +public struct SpawnInfo : SpawnPoint { public int SomeNumber; public string SomeText; @@ -232,4 +233,4 @@ Then declare this network variable like so: ```csharp NetworkVariable myVar = new NetworkVariable(); -``` \ No newline at end of file +``` diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md index 7a684dbaba..212bfbf339 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md @@ -1,4 +1,7 @@ -# INetworkSerializeByMemcpy +# Serialize unmanaged structs with INetworkSerializeByMemcpy + +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. The `INetworkSerializeByMemcpy` interface is used to mark an unmanaged struct type as being trivially serializable over the network by directly copying the whole struct, byte-for-byte, as it appears in memory, into and out of the buffer. This can offer some benefits for performance compared to serializing one field at a time, especially if the struct has many fields in it, but it may be less efficient from a bandwidth-usage perspective, as fields will often be padded for memory alignment and you won't be able to "pack" any of the fields to optimize for space usage. @@ -14,7 +17,7 @@ public struct MyStruct : INetworkSerializeByMemcpy } ``` -If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both `RPC`s and `NetworkVariables`, as well as in other contexts such as `BufferSerializer<>`, `FastBufferReader`, and `FastBufferWriter`. +If you have a type you wish to serialize that you know is compatible with this method of serialization, but don't have access to modify the struct to add this interface, you can wrap your values in `ForceNetworkSerializeByMemcpy` to enable it to be serialized this way. This works in both [`RPC`s](../message-system/rpc.md) and [`NetworkVariable`s](../../basics/networkvariable.md), as well as in other contexts such as with [`BufferSerializer<>`](../bufferserializer.md) or [`FastBufferReader`, and `FastBufferWriter`](../fastbufferwriter-fastbufferreader.md). ```csharp public NetworkVariable> GuidVar; diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md index 9ca778028d..466d545b4b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md @@ -1,14 +1,18 @@ -# NetworkObject and NetworkBehaviour +# NetworkObject and NetworkBehaviour serialization -`GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. + +`GameObject`, [`NetworkObject`](../../components/core/networkobject.md) and [`NetworkBehaviour`](../../components/core/networkbehaviour.md) aren't serializable types so they can't be used in [`RPC`s](../message-system/rpc.md) or [`NetworkVariable`s](../../basics/networkvariable.md) by default. -There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. +There are two convenience wrappers which can be used to send a reference to a `NetworkObject` or a `NetworkBehaviour` over an `RPC` or in a `NetworkVariable`. ## NetworkObjectReference -`NetworkObjectReference` can be used to serialize a reference to a NetworkObject. It can only be used on already spawned `NetworkObjects`. +`NetworkObjectReference` can be used to serialize a reference to a `NetworkObject`. It can only be used on already spawned `NetworkObject`s. Here is an example of using NetworkObject reference to send a target NetworkObject over an RPC: + ```csharp public class Weapon : NetworkBehaviour { @@ -36,6 +40,7 @@ public class Weapon : NetworkBehaviour ### Implicit Operators There are also implicit operators which convert from/to `NetworkObject/GameObject` which can be used to simplify code. For instance the above example can also be written in the following way: + ```csharp public class Weapon : NetworkBehaviour { @@ -51,6 +56,7 @@ public class Weapon : NetworkBehaviour } } ``` + > [!NOTE] > The implicit conversion to NetworkObject / GameObject will result in `Null` if the reference can't be found. @@ -85,6 +91,6 @@ public class Weapon : NetworkBehaviour ## How NetworkObjectReference & NetworkBehaviourReference work -`NetworkObjectReference` and `NetworkBehaviourReference` are convenience wrappers which serialize the id of a NetworkObject when being sent and on the receiving end retrieve the corresponding ` ` with that id. `NetworkBehaviourReference` sends an additional index which is used to find the right NetworkBehaviour on the NetworkObject. +`NetworkObjectReference` and `NetworkBehaviourReference` are convenience wrappers which serialize the id of a `NetworkObject` when being sent and on the receiving end retrieve the corresponding `NetworkObject` with that id. `NetworkBehaviourReference` sends an additional index which is used to find the right `NetworkBehaviour` on the `NetworkObject`. -Both of them are structs implementing the `INetworkSerializable` interface. +Both of them are structs implementing the [`INetworkSerializable`](./inetworkserializable.md) interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md index c347f4c13e..5681e5952c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md @@ -1,6 +1,9 @@ # Arrays and native containers -Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize arrays and native containers. + +Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as custom types, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. ## Performance considerations @@ -17,37 +20,6 @@ Using built-in primitive types is fairly straightforward: void HelloServerRpc(int[] scores, Color[] colors) { /* ... */ } ``` -## INetworkSerializable implementation example - -There are many ways to handle sending an array of managed types. The following example is a simple `string` container class that implements `INetworkSerializable` and can be used as an array of "StringContainers": - -```csharp -[Rpc(SendTo.ClientsAndHost)] -void SendMessagesClientRpc(StringContainer[] messages) -{ - foreach (var stringContainer in stringContainers) - { - Debug.Log($"{stringContainer.SomeText}"); - } -} - -public class StringContainer : INetworkSerializable -{ - public string SomeText; - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter - { - if (serializer.IsWriter) - { - serializer.GetFastBufferWriter().WriteValueSafe(SomeText); - } - else - { - serializer.GetFastBufferReader().ReadValueSafe(out SomeText); - } - } -} -``` - ## Native containers Netcode for GameObjects supports `NativeArray` and `NativeList` native containers with built-in serialization, RPCs, and NetworkVariables. However, you can't nest either of these containers without causing a crash. @@ -77,3 +49,7 @@ To serialize a `NativeList` container, you must: > [!NOTE] > When using `NativeLists` within `INetworkSerializable`, the list `ref` value must be a valid, initialized `NativeList`. > NetworkVariables are similar that the value must be initialized before it can receive updates. For example, `public NetworkVariable> ByteListVar = new NetworkVariable>{Value = new NativeList(Allocator.Persistent)};`. RPCs do this automatically. + +## Generic collections + +For performance reasons, Netcode for GameObjects does not have built-in serialization code for C# generic collections. However, `NetworkVariable` does support [synchronizing generic collection types](../../basics/networkvariable.md#using-collections-with-networkvariables). diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md new file mode 100644 index 0000000000..fb52e42773 --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md @@ -0,0 +1,36 @@ +# Serialization overview + +Netcode for GameObjects uses a default serialization pipeline when using [`RPC`](../../rpc-landing.md)'s, [`NetworkVariable`](../../networkvariables-landing.md)s, or any other Netcode-related tasks that require serialization. The serialization pipeline looks like this: + +`` +Custom types => Built-in types => INetworkSerializable +`` + +When Netcode for GameObjects first receives a type, it checks for any custom types that you have registered for serialization, then it checks if it's a built-in type, such as a Vector3 or a float. These are handled by default. Otherwise, it checks if the type inherits [`INetworkSerializable`](inetworkserializable.md), and if it does, it calls its write methods. + +By default, any type that satisfies the unmanaged generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, for example), as well as any structs that contain only these basic types. + +Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md). These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. + +`FastBufferWriter` and `FastBufferReader` also contain the functions `FastBufferWriter.WriteNetworkSerializable()` and `FastBufferReader.ReadNetworkSerializable` for writing and reading values that use the `INetworkSerializable` interface. + +## Built-in serialization + +* [C# primitives](./cprimitives.md) +* [Unity primitives](./unity-primitives.md) +* [Enum types](./enum-types.md) +* [Arrays](./serialization-arrays.md) +* [Collections](../../basics/networkvariable.md#using-collections-with-networkvariables) + +## Custom serialization + +* [INetworkSerializable](./inetworkserializable.md) +* [INetworkSerializeByMemcpy](./inetworkserializebymemcpy.md) +* [Customizing serialization](../custom-serialization.md) +* [Custom NetworkVariable implementations](../../basics/custom-networkvariables.md) + +## Additional resources + +* [NetworkObject serialization](./networkobject-serialization.md) +* [FastBufferWriter and FastBufferReader](../fastbufferwriter-fastbufferreader.md) +* [BufferSerializer](../bufferserializer.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md index c2c1f97286..de160d26d2 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md @@ -2,6 +2,9 @@ In addition to the standard [`NetworkVariable`s](networkvariable.md) available in Netcode for GameObjects, you can also create custom `NetworkVariable`s for advanced implementations. The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, you can create your own custom implementation. +> [!NOTE] +> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. + To create your own `NetworkVariableBase`-derived container, you should: 1. Create a class deriving from `NetworkVariableBase`. @@ -16,11 +19,14 @@ To create your own `NetworkVariableBase`-derived container, you should: The way you read and write `NetworkVariable`s changes depending on the type you use. -* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the `NetworkVariable` value. -* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the `NetworkVariable` value. This process can save bandwidth but adds CPU processing time. +* Known, non-generic types can use [`FastBufferWriter` and `FastBufferReader`](../advanced-topics/fastbufferwriter-fastbufferreader.md) to serialize the `NetworkVariable` value. +* Integer types: This type gives you the option to use [`BytePacker` and `ByteUnpacker`](../advanced-topics/fastbufferwriter-fastbufferreader.md#packing) to compress the `NetworkVariable` value. This process can save bandwidth but adds CPU processing time. * Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. +* Types implementing [`INetworkSerializable`](../advanced-topics/serialization/inetworkserializable.md) can also be serialized using `FastBufferWriter` and `FastBufferReader`. + +Read more about [custom serialization](../advanced-topics/custom-serialization.md) for more approaches to customizing serialization. ### Serialize a hard-coded type @@ -66,175 +72,22 @@ For dynamically-allocated types with a value that isn't `null` (for example, man You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. -The type you use must be serializable according to the [support types list above](#supported-types). Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. +The type you use must be serializable according to the [supported types list](./networkvariable.md#supported-types). Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. -## Custom NetworkVariable example +> [!NOTE] +> These attributes won't generate delta serialization. If you would like to customize how deltas are sent when a custom value has partially changed, refer to [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#networkvariable). + +## Custom NetworkVariableBase example This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: - ```csharp - /// Using MyCustomNetworkVariable within a NetworkBehaviour - public class TestMyCustomNetworkVariable : NetworkBehaviour - { - public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable(); - public MyCustomGenericNetworkVariable CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable(); - public override void OnNetworkSpawn() - { - if (IsServer) - { - for (int i = 0; i < 4; i++) - { - var someData = new SomeData(); - someData.SomeFloatData = (float)i; - someData.SomeIntData = i; - someData.SomeListOfValues.Add((ulong)i + 1000000); - someData.SomeListOfValues.Add((ulong)i + 2000000); - someData.SomeListOfValues.Add((ulong)i + 3000000); - CustomNetworkVariable.SomeDataToSynchronize.Add(someData); - CustomNetworkVariable.SetDirty(true); - - CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i); - CustomGenericNetworkVariable.SetDirty(true); - } - } - } - } - - /// Bare minimum example of NetworkVariableBase derived class - [Serializable] - public class MyCustomNetworkVariable : NetworkVariableBase - { - /// Managed list of class instances - public List SomeDataToSynchronize = new List(); - - /// - /// Writes the complete state of the variable to the writer - /// - /// The stream to write the state to - public override void WriteField(FastBufferWriter writer) - { - // Serialize the data we need to synchronize - writer.WriteValueSafe(SomeDataToSynchronize.Count); - foreach (var dataEntry in SomeDataToSynchronize) - { - writer.WriteValueSafe(dataEntry.SomeIntData); - writer.WriteValueSafe(dataEntry.SomeFloatData); - writer.WriteValueSafe(dataEntry.SomeListOfValues.Count); - foreach (var valueItem in dataEntry.SomeListOfValues) - { - writer.WriteValueSafe(valueItem); - } - } - } - - /// - /// Reads the complete state from the reader and applies it - /// - /// The stream to read the state from - public override void ReadField(FastBufferReader reader) - { - // De-Serialize the data being synchronized - var itemsToUpdate = (int)0; - reader.ReadValueSafe(out itemsToUpdate); - SomeDataToSynchronize.Clear(); - for (int i = 0; i < itemsToUpdate; i++) - { - var newEntry = new SomeData(); - reader.ReadValueSafe(out newEntry.SomeIntData); - reader.ReadValueSafe(out newEntry.SomeFloatData); - var itemsCount = (int)0; - var tempValue = (ulong)0; - reader.ReadValueSafe(out itemsCount); - newEntry.SomeListOfValues.Clear(); - for (int j = 0; j < itemsCount; j++) - { - reader.ReadValueSafe(out tempValue); - newEntry.SomeListOfValues.Add(tempValue); - } - SomeDataToSynchronize.Add(newEntry); - } - } - - public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) - { - // Do nothing for this example - } - - public override void WriteDelta(FastBufferWriter writer) - { - // Do nothing for this example - } - } - - /// Bare minimum example of generic NetworkVariableBase derived class - [Serializable] - [GenerateSerializationForGenericParameter(0)] - public class MyCustomGenericNetworkVariable : NetworkVariableBase - { - /// Managed list of class instances - public List SomeDataToSynchronize = new List(); - - /// - /// Writes the complete state of the variable to the writer - /// - /// The stream to write the state to - public override void WriteField(FastBufferWriter writer) - { - // Serialize the data we need to synchronize - writer.WriteValueSafe(SomeDataToSynchronize.Count); - for (var i = 0; i < SomeDataToSynchronize.Count; ++i) - { - var dataEntry = SomeDataToSynchronize[i]; - // NetworkVariableSerialization is used for serializing generic types - NetworkVariableSerialization.Write(writer, ref dataEntry); - } - } - - /// - /// Reads the complete state from the reader and applies it - /// - /// The stream to read the state from - public override void ReadField(FastBufferReader reader) - { - // De-Serialize the data being synchronized - var itemsToUpdate = (int)0; - reader.ReadValueSafe(out itemsToUpdate); - SomeDataToSynchronize.Clear(); - for (int i = 0; i < itemsToUpdate; i++) - { - T newEntry = default; - // NetworkVariableSerialization is used for serializing generic types - NetworkVariableSerialization.Read(reader, ref newEntry); - SomeDataToSynchronize.Add(newEntry); - } - } - - public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) - { - // Do nothing for this example - } - - public override void WriteDelta(FastBufferWriter writer) - { - // Do nothing for this example - } - } - - /// Example managed class used as the item type in the - /// MyCustomNetworkVariable.SomeDataToSynchronize list - [Serializable] - public class SomeData - { - public int SomeIntData = default; - public float SomeFloatData = default; - public List SomeListOfValues = new List(); - } - ``` +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs#TestMyCustomNetworkVariable)] While the above example isn't the recommended way to synchronize a list where the number or order of elements in the list often changes, it's an example of how you can define your own rules using `NetworkVariableBase`. You can test the code above by: -- Using the above code with a project that includes Netcode for GameObjects v1.0 (or higher). + +- Using the above code with a project that includes Netcode for GameObjects. - Adding the `TestMyCustomNetworkVariable` component to an in-scene placed NetworkObject. - Creating a stand alone build and running that as a host or server. - Running the same scene within the Editor and connecting as a client. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md index df9b3bb506..cfdd037143 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/networkvariable.md @@ -552,7 +552,7 @@ public class PlayerState : NetworkBehaviour ## Complex types -Almost all of the examples on this page have been focused around numeric [value types](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-types). Netcode for GameObjects also supports complex types and can support both unmanaged types *and* managed types (although avoiding managed types where possible will improve your game's performance). +Any type that implements the [`INetworkSerializable`](../advanced-topics/serialization/inetworkserializable.md) interface can be used inside a `NetworkVariable`. To synchronize a custom type that is not supported by the default serialization provided by Netcode for GameObjects, implement this interface to allow it to be used inside a `NetworkVariable`. ### Synchronizing complex types example @@ -582,7 +582,7 @@ public class PlayerState : NetworkBehaviour void Awake() { //NetworkList can't be initialized at declaration time like NetworkVariable. It must be initialized in Awake instead. - //If you do initialize at declaration, you will run into Memmory leak errors. + //If you do initialize at declaration, you will run into Memory leak errors. TeamAreaWeaponBoosters = new NetworkList(); } @@ -707,88 +707,12 @@ public struct AreaWeaponBooster : INetworkSerializable, System.IEquatable [!NOTE] -> The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, we encourage you to create your own custom implementation. - -To create your own `NetworkVariableBase` derived container, you should: - -- Create a class deriving from `NetworkVariableBase`. - -- Assure the the following methods are overridden: - - `void WriteField(FastBufferWriter writer)` - - `void ReadField(FastBufferReader reader)` - - `void WriteDelta(FastBufferWriter writer)` - - `void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)` -- Depdending upon your custom `NetworkVariableBase` container, you might look at `NetworkVariable` or `NetworkList` to see how those two examples were implemented. - - - -#### NetworkVariableSerialization<T> - -The way you read and write network variables changes depending on the type you use. - -* Known, non-generic types: Use `FastBufferReader.ReadValue` to read from and `FastBufferWriter.WriteValue` to write to the network variable value. -* Integer types: This type gives you the option to use `BytePacker` and `ByteUnpacker` to compress the network variable value. This process can save bandwidth but adds CPU processing time. -* Generic types: Use serializers that Unity generates based on types discovered during a compile-time code generation process. This means you need to tell Unity's code generation algorithm which types to generate serializers for. To tell Unity which types to serialize, use the following methods: - * Use `GenerateSerializationForTypeAttribute` to serialize hard-coded types. - * Use `GenerateSerializationForGenericParameterAttribute` to serialize generic types. - To learn how to use these methods, refer to [Network variable serialization](#network-variable-serialization). - -##### Tell Unity to serialize a hard-coded type -The following code example uses `GenerateSerializationForTypeAttribute` to generate serialization for a specific hard-coded type: -```csharp -[GenerateSerializationForType(typeof(Foo))] -public class MyNetworkVariableTypeUsingFoo : NetworkVariableBase {} -``` - -You can call a type that you know the name of with the `FastBufferReader` or `FastBufferWriter` methods. These methods don't work for Generic types because the name of the type is unknown. -##### Tell Unity to serialize a generic type -The following code example uses `GenerateSerializationForGenericParameterAttribute` to generate serialization for a specific Generic parameter in your `NetworkVariable` type: -```csharp -[GenerateSerializationForGenericParameter(0)] -public class MyNetworkVariableType : NetworkVariableBase {} -``` - -This attribute accepts an integer that indicates which parameter in the type to generate serialization for. This value is 0-indexed, which means that the first type is 0, the second type is 1, and so on. -The following code example places the attribute more than once on one class to generate serialization for multiple types, in this case,`TFirstType` and `TSecondType: - -```csharp -[GenerateSerializationForGenericParameter(0)] -[GenerateSerializationForGenericParameter(1)] -public class MyNetworkVariableType : NetworkVariableBase {} -``` - - -The `GenerateSerializationForGenericParameterAttribute` and `GenerateSerializationForTypeAttribute` attributes make Unity's code generation create the following methods: - -```csharp -NetworkVariableSerialization.Write(FastBufferWriter writer, ref T value); -NetworkVariableSerialization.Read(FastBufferWriter writer, ref T value); -NetworkVariableSerialization.Duplicate(in T value, ref T duplicatedValue); -NetworkVariableSerialization.AreEqual(in T a, in T b); -``` - -For dynamically allocated types with a value that isn't `null` (for example, managed types and collections like NativeArray and NativeList) call `Read` to read the value in the existing object and write data into it directy (in-place). This avoids more allocations. - -You can use `AreEqual` to determine if a value is different from the value that `Duplicate` cached. This avoids sending the same value multiple times. You can also use the previous value that `Duplicate` cached to calculate deltas to use in `ReadDelta` and `WriteDelta`. - -The type you use must be serializable according to the "Supported Types" list above. Each type needs its own serializer instantiated, so this step tells the codegen which types to create serializers for. - -> [!NOTE] Unity's code generator assumes that all `NetworkVariable` types exist as fields inside NetworkBehaviour types. This means that Unity only inspects fields inside NetworkBehaviour types to identify the types to create serializers for. - - ### Custom NetworkVariable Example - -This example shows a custom `NetworkVariable` type to help you understand how you might implement such a type. In the current version of Netcode for GameObjects, this example is possible without using a custom `NetworkVariable` type; however, for more complex situations that aren't natively supported, this basic example should help inform you of how to approach the implementation: - -Looking at the read and write segments of code within `AreaWeaponBooster.NetworkSerialize`, the nested complex type property `ApplyWeaponBooster` handles its own serialization and de-serialization. The `ApplyWeaponBooster`'s implemented `NetworkSerialize` method serializes and deserializes any `AreaWeaponBooster` type property. This design approach can help reduce code replication while providing a more modular foundation to build even more complex, nested types. - +Further information on customizing the serialization of complex types can be found in [custom serialization](../advanced-topics/custom-serialization.md#networkvariable). ## Strings -While `NetworkVariable` does support managed `INetworkSerializable` types, strings aren't in the list of supported types. This is because strings in C# are immutable types, preventing them from being deserialized in-place, so every update to a `NetworkVariable` would cause a Garbage Collected allocation to create the new string, which may lead to performance problems. +While `NetworkVariable` does support managed `INetworkSerializable` types, strings aren't in the [list of supported types](#supported-types). This is because strings in C# are immutable types, preventing them from being deserialized in-place, so every update to a `NetworkVariable` would cause a Garbage Collected allocation to create the new string, which may lead to performance problems. -While it's technically possible to support strings using custom serialization through `UserNetworkVariableSerialization`, it isn't recommended to do so due to the performance implications that come with it. Instead, we recommend using one of the `Unity.Collections.FixedString` value types. In the below example, we used a `FixedString128Bytes` as the `NetworkVariable` value type. On the server side, it changes the string value each time you press the space bar on the server or host instance. Joining clients will be synchronized with the current value applied on the server side, and each time you hit the space bar on the server side, the client synchronizes with the changed string. +While it's technically possible to support strings using custom serialization through [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#networkvariable), it isn't recommended to do so due to the performance implications that come with it. Instead, it's recommended to use one of the `Unity.Collections.FixedString` value types. In the below example, a `FixedString128Bytes` is the `NetworkVariable` value type. On the server side, it changes the string value each time you press the space bar on the server or host instance. Joining clients will be synchronized with the current value applied on the server side, and each time you hit the space bar on the server side, the client synchronizes with the changed string. > [!NOTE] > `NetworkVariable` won't serialize the entire 128 bytes each time the `Value` is changed. Only the number of bytes that are actually used to store the string value will be sent, no matter which size of `FixedString` you use. @@ -855,3 +779,9 @@ public class TestFixedString : NetworkBehaviour > [!NOTE] > The above example uses a pre-set list of strings to cycle through for example purposes only. If you have a predefined set of text strings as part of your actual design then you would not want to use a FixedString to handle synchronizing the changes to `m_TextString`. Instead, you would want to use a `uint` for the type `T` where the `uint` was the index of the string message to apply to `m_TextString`. + +### Delta updates + +To save bandwidth, `NetworkVariables` can send delta updates. A delta is a compact description of what changed since the last sync. By default, [collection types](#using-collections-with-networkvariables) all support sending delta updates. This means adding a single item to a large list doesn't need to send the entire list over the network. For complex types, it is often worth considering whether sending deltas will improve bandwidth. Delta serialization can be configured via [`UserNetworkVariableSerialization`](../advanced-topics/custom-serialization.md#serializing-delta-updates). + +When a NetworkObject is spawned or a late-joining client first sees it, every `NetworkVariable` will be serialized in full. This allows game clients to start receiving deltas from a known state. diff --git a/com.unity.netcode.gameobjects/Documentation~/serialization.md b/com.unity.netcode.gameobjects/Documentation~/serialization.md index fb39fad334..4dfd16f484 100644 --- a/com.unity.netcode.gameobjects/Documentation~/serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/serialization.md @@ -4,11 +4,14 @@ Netcode for GameObjects has built-in serialization support for C# and Unity prim | **Topic** | **Description** | | :------------------------------ | :------------------------------- | +| **[Serialization overview](advanced-topics/serialization/serialization-overview.md)** | An overview of how Netcode for GameObjects handles serialization. | | **[C# primitives](advanced-topics/serialization/cprimitives.md)** | C# primitive types are serialized by built-in serialization code. These types include `bool`, `char`, `sbyte`, `byte`, `short`, `ushort`, `int`, `uint`, `long`, `ulong`, `float`, `double`, and `string`. | | **[Unity primitives](advanced-topics/serialization/unity-primitives.md)** | Unity Primitive `Color`, `Color32`, `Vector2`, `Vector3`, `Vector4`, `Quaternion`, `Ray`, `Ray2D` types will be serialized by built-in serialization code. | | **[Enum types](advanced-topics/serialization/enum-types.md)** | A user-defined enum type will be serialized by built-in serialization code (with underlying integer type). | | **[Arrays](advanced-topics/serialization/serialization-arrays.md)** | Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](advanced-topics/serialization/cprimitives.md), like `int[]`, and [Unity primitive types](advanced-topics/serialization/unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as `string[]`, need to be handled using a container class or structure that implements the [`INetworkSerializable`](advanced-topics/serialization/inetworkserializable.md) interface. | -| **[INetworkSerializable](advanced-topics/serialization/inetworkserializable.md)** | You can use the `INetworkSerializable` interface to define custom serializable types. | +| **[Customize serializable types with INetworkSerializable](advanced-topics/serialization/inetworkserializable.md)** | You can use the `INetworkSerializable` interface to define custom serializable types. | +| **[Serialize unmanaged structs with INetworkSerializeByMemcpy](advanced-topics/serialization/inetworkserializebymemcpy.md)** | You can use the `INetworkSerializeByMemcpy` interface to mark an unmanaged struct type as being trivially serializable. | | **[Custom serialization](advanced-topics/custom-serialization.md)** | Create custom serialization types. | | **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. | -| **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | \ No newline at end of file +| **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | +| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. | diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs index e1242378dd..00dd2978b6 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Serialization/TypedSerializerImplementations.cs @@ -243,11 +243,23 @@ public void Read(FastBufferReader reader, ref T value) public void WriteDelta(FastBufferWriter writer, ref T value, ref T previousValue) { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.WriteDelta(writer, value, previousValue); + return; + } + Write(writer, ref value); } public void ReadDelta(FastBufferReader reader, ref T value) { + if (UserNetworkVariableSerialization.WriteDelta != null && UserNetworkVariableSerialization.ReadDelta != null) + { + UserNetworkVariableSerialization.ReadDelta(reader, ref value); + return; + } + Read(reader, ref value); } @@ -258,6 +270,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + duplicatedValue = value; } } @@ -959,6 +977,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + duplicatedValue = value; } } @@ -1128,6 +1152,12 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { + if (UserNetworkVariableSerialization.DuplicateValue != null) + { + UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); + return; + } + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); var refValue = value; Write(writer, ref refValue); diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta new file mode 100644 index 0000000000..bc0cce73b4 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0f959eedfa8244038f30beb1f00b8cf0 +timeCreated: 1780100790 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md new file mode 100644 index 0000000000..33ca58ffcc --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md @@ -0,0 +1,31 @@ +# Code documentation + +Use this folder for any code snippets that only need to compile. + +Any code snippets that you want to run tests on should be put in [Runtime tests](../../Runtime/Documentation/README.md). + +To embed code in documentation, use the following tag + +```md +[!code-cs[](../../Tests/Editor/DocumentationCodeSamples/.cs#SomeRegionName)] +``` + +With the code formatted like this + +```cs +namespace DocumentationCodeSamples +{ + internal MyTestClass + { + #region SomeRegionName + // All the code in this region block will be embedded without indentation in the docs. + #endregion + + [Test] + public void TestOfDocumentationCode() + { + ... + } + } +} +``` diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta new file mode 100644 index 0000000000..d43f352993 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a6afff0520856465a9f94b9a5bcebf24 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta new file mode 100644 index 0000000000..677d24fb22 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9371809eac694274ad78a6e7faf27e69 +timeCreated: 1780674489 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs new file mode 100644 index 0000000000..3f70a15cb7 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs @@ -0,0 +1,114 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Netcode; + +namespace DocumentationCodeSamples +{ + #region HealthStruct + /// Container for storing health data for a player or item. + public struct Health + { + /// + /// The maximum health that this player or item can have. + /// This is unlikely to change often. + /// + public uint MaxHealth; + + /// + /// The current level of health that this player or item has. + /// This is likely to change regularly. + /// + public int CurrentHealth; + } + #endregion + + #region FastBuffer + /// Tells the Netcode how to serialize and deserialize our custom type. + // The class name doesn't matter here. + public static class FastBufferExtensions + { + /// + /// Extension method to override the serialization for a custom type. + /// + /// Buffer to write values into. + /// The type to customize or override. + public static void WriteValueSafe(this FastBufferWriter writer, in Health health) + { + writer.WriteValueSafe(health.MaxHealth); + writer.WriteValueSafe(health.CurrentHealth); + } + + /// + /// Extension method to override the de-serialization for a custom type. + /// + /// Buffer to read values from. + /// The type to customize or override. + public static void ReadValueSafe(this FastBufferReader reader, out Health health) + { + reader.ReadValueSafe(out uint max); + reader.ReadValueSafe(out int current); + health = new Health { MaxHealth = max, CurrentHealth = current }; + } + } + #endregion + + #region BufferSerializer + /// Tells the how to serialize and deserialize our custom type. + // The class name doesn't matter here. + public static class BufferSerializerExtensions + { + /// + /// Extension method to override bi-directional serialization for a custom type. + /// + /// Bi-directional serial + /// The type to customize or override. + /// Boilerplate syntax to enable the bi-directional serialization. + public static void SerializeValue(this BufferSerializer serializer, ref Health health) where TReaderWriter : IReaderWriter + { + // Because the BufferSerializer already knows how to read and write the primitive types + // We can use the existing BufferSerializer serialization. + serializer.SerializeValue(ref health.MaxHealth); + serializer.SerializeValue(ref health.CurrentHealth); + } + } + #endregion + + internal class TestSerializationDocs + { + [Test] + public void TestFastBufferSerialization() + { + var healthToTest = new Health { MaxHealth = 123, CurrentHealth = 89 }; + var expected = healthToTest; + + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); + writer.WriteValueSafe(healthToTest); + + using var reader = new FastBufferReader(writer, Allocator.None); + reader.ReadValueSafe(out Health readHealth); + + Assert.AreEqual(expected.MaxHealth, readHealth.MaxHealth); + Assert.AreEqual(expected.CurrentHealth, readHealth.CurrentHealth); + } + + [Test] + public void TestBufferSerializerSerialization() + { + var healthToTest = new Health { MaxHealth = 456, CurrentHealth = 78 }; + var expected = healthToTest; + + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); + var bufferWriter = new BufferSerializer(new BufferSerializerWriter(writer)); + bufferWriter.SerializeValue(ref healthToTest); + + using var tempReader = new FastBufferReader(bufferWriter.GetFastBufferWriter(), Allocator.None); + var bufferReader = new BufferSerializer(new BufferSerializerReader(tempReader)); + var readHealth = new Health(); + bufferReader.SerializeValue(ref readHealth); + + Assert.AreEqual(expected.MaxHealth, readHealth.MaxHealth); + Assert.AreEqual(expected.CurrentHealth, readHealth.CurrentHealth); + + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta new file mode 100644 index 0000000000..a39141e43d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/DocumentationCodeSamples/Serialization/SerializationCustomization.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 08949d7abc1249418192e44999279f89 +timeCreated: 1780101010 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta new file mode 100644 index 0000000000..aea9d686c3 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e017ce40eb7744e03bc0a83fa18ddcc3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta new file mode 100644 index 0000000000..6fc7d1074d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 446ef307887e3446f896c899f43af442 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs new file mode 100644 index 0000000000..74fb7fe157 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs @@ -0,0 +1,239 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + #region TestMyCustomNetworkVariable + // Using MyCustomNetworkVariable within a NetworkBehaviour + internal class TestMyCustomNetworkVariable : NetworkBehaviour + { + public MyCustomNetworkVariable CustomNetworkVariable = new MyCustomNetworkVariable(); + public MyCustomGenericNetworkVariable CustomGenericNetworkVariable = new MyCustomGenericNetworkVariable(); + + public override void OnNetworkSpawn() + { + if (IsServer) + { + for (int i = 0; i < 4; i++) + { + var someData = new SomeData + { + SomeFloatData = i, + SomeIntData = i + }; + someData.SomeListOfValues.Add((ulong)i + 1000000); + someData.SomeListOfValues.Add((ulong)i + 2000000); + someData.SomeListOfValues.Add((ulong)i + 3000000); + CustomNetworkVariable.SomeDataToSynchronize.Add(someData); + CustomNetworkVariable.SetDirty(true); + + CustomGenericNetworkVariable.SomeDataToSynchronize.Add(i); + CustomGenericNetworkVariable.SetDirty(true); + } + } + } + } + + /// + /// Bare minimum example of NetworkVariableBase derived class + /// + [Serializable] + public class MyCustomNetworkVariable : NetworkVariableBase + { + /// + /// Managed list of class instances + /// + internal List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + foreach (var dataEntry in SomeDataToSynchronize) + { + writer.WriteValueSafe(dataEntry.SomeIntData); + writer.WriteValueSafe(dataEntry.SomeFloatData); + writer.WriteValueSafe(dataEntry.SomeListOfValues.Count); + foreach (var valueItem in dataEntry.SomeListOfValues) + { + writer.WriteValueSafe(valueItem); + } + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = 0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + var newEntry = new SomeData(); + reader.ReadValueSafe(out newEntry.SomeIntData); + reader.ReadValueSafe(out newEntry.SomeFloatData); + var itemsCount = 0; + var tempValue = (ulong)0; + reader.ReadValueSafe(out itemsCount); + newEntry.SomeListOfValues.Clear(); + for (int j = 0; j < itemsCount; j++) + { + reader.ReadValueSafe(out tempValue); + newEntry.SomeListOfValues.Add(tempValue); + } + + SomeDataToSynchronize.Add(newEntry); + } + } + + /// + /// Used to write partial updates rather than synchronizing the full state on every change. + /// + /// The stream to write the state to + public override void WriteDelta(FastBufferWriter writer) + { + // Not implemented for this example, instead we can write the field + WriteField(writer); + } + + /// + /// Used to read partial updates rather than synchronizing the full state on every change. + /// + /// + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Not implemented for this example, instead we can read the field + ReadField(reader); + } + + } + + /// + /// Bare minimum example of generic NetworkVariableBase derived class + /// + /// Generic type marker + [Serializable] + [GenerateSerializationForGenericParameter(0)] + public class MyCustomGenericNetworkVariable : NetworkVariableBase + { + /// Managed list of class instances + public List SomeDataToSynchronize = new List(); + + /// + /// Writes the complete state of the variable to the writer + /// + /// The stream to write the state to + public override void WriteField(FastBufferWriter writer) + { + // Serialize the data we need to synchronize + writer.WriteValueSafe(SomeDataToSynchronize.Count); + for (var i = 0; i < SomeDataToSynchronize.Count; ++i) + { + var dataEntry = SomeDataToSynchronize[i]; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Write(writer, ref dataEntry); + } + } + + /// + /// Reads the complete state from the reader and applies it + /// + /// The stream to read the state from + public override void ReadField(FastBufferReader reader) + { + // De-Serialize the data being synchronized + var itemsToUpdate = 0; + reader.ReadValueSafe(out itemsToUpdate); + SomeDataToSynchronize.Clear(); + for (int i = 0; i < itemsToUpdate; i++) + { + T newEntry = default; + // NetworkVariableSerialization is used for serializing generic types + NetworkVariableSerialization.Read(reader, ref newEntry); + SomeDataToSynchronize.Add(newEntry); + } + } + + /// + /// Used to write partial updates rather than synchronizing the full state on every change. + /// + /// The stream to write the state to + public override void WriteDelta(FastBufferWriter writer) + { + // Not implemented for this example, instead we can write the field + WriteField(writer); + } + + /// + /// Used to read partial updates rather than synchronizing the full state on every change. + /// + /// + public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) + { + // Not implemented for this example, instead we can read the field + ReadField(reader); + } + } + + [Serializable] + internal class SomeData + { + public int SomeIntData = default; + public float SomeFloatData = default; + public List SomeListOfValues = new List(); + } + #endregion + + + internal class CustomNetworkVariableTest : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab(nameof(TestMyCustomNetworkVariable)); + m_PrefabToSpawn.AddComponent(); + } + + /// + /// Validates when the authority applies a value during spawn or + /// post spawn of a newly instantiated and spawned object the value is set by the time non-authority + /// instances invoke . + /// + [UnityTest] + public IEnumerator CustomNetworkVariableCodeWorks() + { + var authority = GetAuthorityNetworkManager(); + var authorityObject = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + var authorityBehaviour = authorityObject.GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(authorityObject.NetworkObjectId); + AssertOnTimeout("Failed to spawn network object"); + + foreach (var networkManager in m_NetworkManagers) + { + Assert.True(networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityObject.NetworkObjectId, out var localObject)); + var testBehaviour = localObject.GetComponent(); + Assert.NotNull(testBehaviour); + Assert.AreEqual(authorityBehaviour.CustomNetworkVariable.SomeDataToSynchronize.Count, testBehaviour.CustomNetworkVariable.SomeDataToSynchronize.Count, $"[Client-{networkManager.LocalClientId}] Incorrect length found for {nameof(MyCustomNetworkVariable)}"); + Assert.AreEqual(authorityBehaviour.CustomGenericNetworkVariable.SomeDataToSynchronize, testBehaviour.CustomGenericNetworkVariable.SomeDataToSynchronize, $"[Client-{networkManager.LocalClientId}] Incorrect length found for {nameof(MyCustomNetworkVariable)}"); + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta new file mode 100644 index 0000000000..5f186a3e56 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomNetworkVariable.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: a4617887a8b0d46be9babcc146fcfd20 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs new file mode 100644 index 0000000000..1e6096b187 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs @@ -0,0 +1,213 @@ +using System.Collections; +using System.Text; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + internal static class FastBufferExtensions + { + internal static void WriteValueSafe(this FastBufferWriter writer, in CustomSerializationDocsTests.Health health) + { + writer.WriteValueSafe(health.MaxHealth); + writer.WriteValueSafe(health.CurrentHealth); + } + + internal static void ReadValueSafe(this FastBufferReader reader, out CustomSerializationDocsTests.Health health) + { + reader.ReadValueSafe(out uint max); + reader.ReadValueSafe(out int current); + health = new CustomSerializationDocsTests.Health { MaxHealth = max, CurrentHealth = current }; + } + } + + + internal class CustomSerializationDocsTests : NetcodeIntegrationTest + { + #region HealthExample + public struct Health + { + public uint MaxHealth; + public int CurrentHealth; + + // Register our custom serialization on load + [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] + public static void RegisterHealthSerialization() + { + // You can reuse the FastBufferWriter and FastBufferReader extension methods we wrote above + UserNetworkVariableSerialization.WriteValue = FastBufferExtensions.WriteValueSafe; + UserNetworkVariableSerialization.ReadValue = FastBufferExtensions.ReadValueSafe; + + // Here is where you register your custom delta handling. + UserNetworkVariableSerialization.WriteDelta = WriteDelta; + UserNetworkVariableSerialization.ReadDelta = ReadDelta; + + // You can also use lambda expressions to register functions + UserNetworkVariableSerialization.DuplicateValue = (in Health value, ref Health duplicatedValue) => { duplicatedValue = value; }; + } + + // We can use an enum to indicate which field has changed for the delta change. + // This lets us save bandwidth when only one value has changed. + // In the case of Health, we expect CurrentHealth to change much more often than MaxHealth + // Implementing WriteDelta saves us bandwidth on not sending the MaxHealth every time CurrentHealth changes. + private enum ChangeType : byte + { + MaxHealth, + CurrentHealth, + All, + } + + public static void WriteDelta(FastBufferWriter writer, in Health value, in Health previousValue) + { + if (value.MaxHealth == previousValue.MaxHealth && value.CurrentHealth != previousValue.CurrentHealth) + { + // If only our CurrentHealth has changed, we can send the CurrentHealth enum with only the updated CurrentHealth value + writer.WriteValueSafe(ChangeType.CurrentHealth); + writer.WriteValueSafe(value.CurrentHealth); + } + else if (value.CurrentHealth == previousValue.CurrentHealth && value.MaxHealth != previousValue.MaxHealth) + { + // If only our MaxHealth has changed, we can send the MaxHealth enum with only the updated MaxHealth value + writer.WriteValueSafe(ChangeType.MaxHealth); + writer.WriteValueSafe(value.MaxHealth); + } + else + { + // If both values have changed, we need to serialize both values. + writer.WriteValueSafe(ChangeType.All); + writer.WriteValueSafe(value.MaxHealth); + writer.WriteValueSafe(value.CurrentHealth); + } + } + + public static void ReadDelta(FastBufferReader reader, ref Health value) + { + // First we read what type of change we've received + reader.ReadValueSafe(out ChangeType changeType); + + // Then we read the data in our delta message, based on what type of change we've received. + switch (changeType) + { + case ChangeType.CurrentHealth: + { + reader.ReadValueSafe(out value.CurrentHealth); + break; + } + case ChangeType.MaxHealth: + { + reader.ReadValueSafe(out value.MaxHealth); + break; + } + case ChangeType.All: + { + reader.ReadValueSafe(out value.MaxHealth); + reader.ReadValueSafe(out value.CurrentHealth); + break; + } + } + } + } + #endregion + + internal class TestHealthBehaviour : NetworkBehaviour + { + internal readonly NetworkVariable HealthVar = new(); + + internal Health ReceivedFromRpc; + + [Rpc(SendTo.Everyone)] + public void SendHealthRpc(Health health) + { + ReceivedFromRpc = health; + } + } + + protected override int NumberOfClients => 1; + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab(nameof(TestHealthBehaviour)); + m_PrefabToSpawn.AddComponent(); + } + + private Health m_ExpectedHealth; + private ulong m_NetworkObjectIdToTest; + + private bool m_TestingRpc; + + private bool ValidateAllAreEqual(StringBuilder errorLog) + { + foreach (var networkManager in m_NetworkManagers) + { + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(m_NetworkObjectIdToTest, out NetworkObject localInstance)) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] SpawnedObject not found!"); + return false; + } + + var healthInstance = localInstance.GetComponent(); + if (healthInstance == null) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] Health instance is null!"); + return false; + } + + var received = m_TestingRpc ? healthInstance.ReceivedFromRpc : healthInstance.HealthVar.Value; + if (m_ExpectedHealth.MaxHealth != received.MaxHealth) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] MaxHealth values don't match! Expected {m_ExpectedHealth.MaxHealth}, Received {received.MaxHealth}"); + return false; + } + + if (m_ExpectedHealth.CurrentHealth != received.CurrentHealth) + { + errorLog.Append($"[Client-{networkManager.LocalClientId}] CurrentHealth values don't match! Expected {m_ExpectedHealth.CurrentHealth}, Received {received.CurrentHealth}"); + return false; + } + } + + return true; + } + + [UnityTest] + public IEnumerator TestHealthCode() + { + var authority = GetAuthorityNetworkManager(); + var authorityInstance = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + m_NetworkObjectIdToTest = authorityInstance.NetworkObjectId; + + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance.NetworkObjectId); + AssertOnTimeout("Failed to spawn network object"); + + var healthToTest = new Health { MaxHealth = 456, CurrentHealth = 23 }; + m_ExpectedHealth = healthToTest; + m_TestingRpc = true; + + authorityInstance.SendHealthRpc(healthToTest); + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("RPC send failed"); + + m_TestingRpc = false; + healthToTest = new Health { MaxHealth = 123, CurrentHealth = 45 }; + m_ExpectedHealth = healthToTest; + + authorityInstance.HealthVar.Value = healthToTest; + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("NetworkVariable assignment failed"); + + var current = authorityInstance.HealthVar.Value; + current.CurrentHealth -= 10; + authorityInstance.HealthVar.Value = current; + + m_ExpectedHealth = current; + + yield return WaitForConditionOrTimeOut(ValidateAllAreEqual); + AssertOnTimeout("NetworkVariable update failed"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta new file mode 100644 index 0000000000..1588a4d111 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 22820b0916e341ddbcadc50030dc5f60 +timeCreated: 1780100433 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md new file mode 100644 index 0000000000..92cd488e8e --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md @@ -0,0 +1,33 @@ +# Code documentation + +Use this folder for any code examples that we want to test to ensure the runtime functionality isn't broken + +Any code snippets that are small enough that you only want to check that they compile should be in [Editor tests](../../Editor/Documentation/README.md). + +To embed code in documentation, use the following tag + +```md +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/.cs#SomeRegionName)] +``` + +With the code formatted like this + +```cs +namespace DocumentationCodeSamples +{ + internal MyTestClass : NetcodeIntegrationTest + { + #region SomeRegionName + // All the code in this region block will be embedded without indentation in the docs. + #endregion + + protected override int NumberOfClients => 1; + + [UnityTest] + public IEnumerator TestOfDocumentationCode() + { + ... + } + } +} +``` diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta new file mode 100644 index 0000000000..4f45c6f105 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8a8cfc9d633474efb95052169e9fcb50 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From 413c5a392cbaadd1ebdd5c87b901d0d83d4f32b6 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 6 Jul 2026 20:02:41 -0400 Subject: [PATCH 23/64] fix: NullReferenceExceptions in spawn path (#4067) * fix: NullReferenceExceptions in spawn path * Update NetcodeIntegrationTest * Update CHANGELOG.md * put back serialization code --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Connection/NetworkConnectionManager.cs | 2 +- .../Runtime/Core/NetworkObject.cs | 25 ++- .../Runtime/Messaging/ILPPMessageProvider.cs | 1 + .../Messages/ConnectionApprovedMessage.cs | 4 +- .../Messaging/Messages/CreateObjectMessage.cs | 59 ++--- .../Runtime/SceneManagement/SceneEventData.cs | 21 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 10 +- .../NetworkObjectSynchronizationTests.cs | 204 +++++++----------- ...etworkPrefabHandlerSynchronizationTests.cs | 88 ++++++++ ...kPrefabHandlerSynchronizationTests.cs.meta | 3 + .../TestHelpers/NetcodeIntegrationTest.cs | 2 +- 12 files changed, 234 insertions(+), 186 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 0c5e47d1a0..5374068ba0 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) - Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) - Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index cc44bd8783..dd22801412 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -1218,7 +1218,7 @@ internal void ApprovedPlayerSpawn(ulong clientId, uint playerPrefabHash) var message = new CreateObjectMessage { - ObjectInfo = ConnectedClients[clientId].PlayerObject.Serialize(clientPair.Key), + ObjectInfo = ConnectedClients[clientId].PlayerObject.SerializeSpawnedObject(clientPair.Key), IncludesSerializedObject = true, }; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 3423c05a07..6a9397e48e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Text; @@ -3369,7 +3370,12 @@ internal void SynchronizeNetworkBehaviours(ref BufferSerializer serializer } } - internal SerializedObject Serialize(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) + /// + /// Creates a on an authority client. + /// Used to synchronize state to a non-authority client. + /// + /// This function is the authority mirror of + internal SerializedObject SerializeSpawnedObject(ulong targetClientId = NetworkManager.ServerClientId, bool syncObservers = false) { var obj = new SerializedObject { @@ -3444,15 +3450,17 @@ internal SerializedObject Serialize(ulong targetClientId = NetworkManager.Server } /// - /// Used to deserialize a serialized which occurs - /// when the client is approved or during a scene transition + /// Does a non-authority local spawn of a given . + /// This occurs when the client is approved, a new object is spawned by an authority, or during a scene transition. /// - /// Deserialized scene object data - /// FastBufferReader for the NetworkVariable data - /// NetworkManager instance + /// This function is the non-authority mirror of + /// Deserialized data received from the authority for this + /// FastBufferReader for any additional data sent with this object on spawn. + /// NetworkManager instance. /// will be true if invoked by CreateObjectMessage /// The deserialized NetworkObject or null if deserialization failed - internal static NetworkObject Deserialize(in SerializedObject serializedObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) + [return: MaybeNull] + internal static NetworkObject DeserializeAndSpawnObject(in SerializedObject serializedObject, FastBufferReader reader, NetworkManager networkManager, bool invokedByMessage = false) { var endOfSynchronizationData = reader.Position + serializedObject.SynchronizationDataSize; @@ -3479,7 +3487,8 @@ internal static NetworkObject Deserialize(in SerializedObject serializedObject, { if (networkManager.LogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning($"[{networkObject.name}][Deserialize][{nameof(NetworkBehaviour)}Synchronization][Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!"); + var networkObjectName = networkObject != null ? networkObject.name : "null"; + NetworkLog.LogWarning($"[{networkObjectName}][Deserialize][{nameof(NetworkBehaviour)}Synchronization][Size mismatch] Expected: {endOfSynchronizationData} Currently At: {reader.Position}!"); } reader.Seek(endOfSynchronizationData); } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs index 21413a4fae..8d8379dc05 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/ILPPMessageProvider.cs @@ -148,6 +148,7 @@ internal static Dictionary GetMessageTypesMap() [InitializeOnLoadMethod] public static void NotifyOnPlayStateChange() { + EditorApplication.playModeStateChanged -= OnPlayModeStateChanged; EditorApplication.playModeStateChanged += OnPlayModeStateChanged; } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 17669a335f..2a7eee8be7 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -160,7 +160,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) { sobj.AddObserver(OwnerClientId); // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. - var serializedObject = sobj.Serialize(OwnerClientId, IsDistributedAuthority); + var serializedObject = sobj.SerializeSpawnedObject(OwnerClientId, IsDistributedAuthority); serializedObject.Serialize(writer); ++sceneObjectCount; } @@ -344,7 +344,7 @@ public void Handle(ref NetworkContext context) { var serializedObject = new NetworkObject.SerializedObject(); serializedObject.Deserialize(m_ReceivedSceneObjectData); - NetworkObject.Deserialize(serializedObject, m_ReceivedSceneObjectData, networkManager); + NetworkObject.DeserializeAndSpawnObject(serializedObject, m_ReceivedSceneObjectData, networkManager); } if (networkManager.DistributedAuthorityMode && networkManager.AutoSpawnPlayerPrefabClientSide) diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs index b8f8af78fb..a5f3039251 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Runtime.CompilerServices; +using Unity.Netcode.Logging; namespace Unity.Netcode { @@ -171,7 +172,12 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende { if (!networkManager.DistributedAuthorityMode) { - networkObject = NetworkObject.Deserialize(serializedObject, networkVariableData, networkManager); + networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, networkVariableData, networkManager); + if (networkObject == null) + { + networkManager.Log.ErrorServer(new Context(LogLevel.Developer, $"Failed to deserialize {nameof(NetworkObject)}.").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash).AddInfo(nameof(NetworkObject.NetworkObjectId), serializedObject.NetworkObjectId)); + return; + } } else { @@ -179,25 +185,27 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende var hasNewObserverIdList = newObserverIds != null && newObserverIds.Length > 0; // Depending upon visibility of the NetworkObject and the client in question, it could be that // this client already has visibility of this NetworkObject - if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId)) + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(serializedObject.NetworkObjectId, out networkObject)) { - // If so, then just get the local instance - networkObject = networkManager.SpawnManager.SpawnedObjects[serializedObject.NetworkObjectId]; - // This should not happen, logging error just in case if (hasNewObserverIdList && newObserverIds.Contains(networkManager.LocalClientId)) { NetworkLog.LogErrorServer($"[{nameof(CreateObjectMessage)}][Duplicate-Broadcast] Detected duplicated object creation for {serializedObject.NetworkObjectId}!"); } - else // Trap to make sure the owner is not receiving any messages it sent - if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) - { - NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{serializedObject.NetworkObjectId}!"); - } + // Trap to make sure the owner is not receiving any messages it sent + else if (networkManager.CMBServiceConnection && networkManager.LocalClientId == networkObject.OwnerClientId) + { + NetworkLog.LogWarning($"[{nameof(CreateObjectMessage)}][Client-{networkManager.LocalClientId}][Duplicate-CreateObjectMessage][Client Is Owner] Detected duplicated object creation for {networkObject.name}-{serializedObject.NetworkObjectId}!"); + } } else { - networkObject = NetworkObject.Deserialize(serializedObject, networkVariableData, networkManager, true); + networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, networkVariableData, networkManager, true); + if (networkObject == null) + { + networkManager.Log.ErrorServer(new Context(LogLevel.Developer, $"Failed to deserialize {nameof(NetworkObject)}.").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash).AddInfo(nameof(NetworkObject.NetworkObjectId), serializedObject.NetworkObjectId)); + return; + } } // DA - NGO CMB SERVICE NOTES: @@ -214,27 +222,6 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende // Mock CMB Service and forward to all clients if (networkManager.DAHost) { - // DA - NGO CMB SERVICE NOTES: - // (*** See above notes fist ***) - // If it is a player object freshly spawning and one or more clients all connect at the exact same time (i.e. received on effectively - // the same frame), then we need to check the observers list to make sure all players are visible upon first spawning. At a later date, - // for area of interest we will need to have some form of follow up "observer update" message to cull out players not within each - // player's AOI. - if (networkObject.IsPlayerObject && hasNewObserverIdList && clientList.Count != observerIds.Length) - { - // For same-frame newly spawned players that might not be aware of all other players, update the player's observer - // list. - observerIds = clientList.ToArray(); - } - - var createObjectMessage = new CreateObjectMessage() - { - ObjectInfo = serializedObject, - m_ReceivedNetworkVariableData = networkVariableData, - ObserverIds = hasObserverIdList ? observerIds : null, - NetworkObjectId = networkObject.NetworkObjectId, - IncludesSerializedObject = true, - }; foreach (var clientId in clientList) { // DA - NGO CMB SERVICE NOTES: @@ -250,16 +237,12 @@ internal static void CreateObject(ref NetworkManager networkManager, ulong sende // If this included a list of new observers and the targeted clientId is one of the observers, then send the serialized data. // Otherwise, the targeted clientId has already has visibility (i.e. it is already spawned) and so just send the updated // observers list to that client's instance. - createObjectMessage.IncludesSerializedObject = hasNewObserverIdList && newObserverIds.Contains(clientId); - networkManager.SpawnManager.SendSpawnCallForObject(clientId, networkObject); } } } - if (networkObject != null) - { - networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); - } + + networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, messageSize); } catch (System.Exception ex) { diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 4840944117..2575bd6bfd 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -622,7 +622,7 @@ private void WriteSceneSynchronizationData(FastBufferWriter writer) { var noStart = writer.Position; // In distributed authority mode, we send the currently known observers of each NetworkObject to the client being synchronized. - var serializedObject = networkObject.Serialize(TargetClientId, distributedAuthority); + var serializedObject = networkObject.SerializeSpawnedObject(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); var noStop = writer.Position; @@ -698,7 +698,7 @@ private void SerializeScenePlacedObjects(FastBufferWriter writer) foreach (var objectToSync in m_NetworkObjectsSync) { // Serialize the NetworkObject - var serializedObject = objectToSync.Serialize(TargetClientId, distributedAuthority); + var serializedObject = objectToSync.SerializeSpawnedObject(TargetClientId, distributedAuthority); serializedObject.Serialize(writer); numberOfObjects++; } @@ -875,9 +875,9 @@ internal void DeserializeScenePlacedObjects() m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var networkObject = NetworkObject.Deserialize(serializedObject, m_InternalBuffer, m_NetworkManager); + var networkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, m_InternalBuffer, m_NetworkManager); - if (serializedObject.IsSceneObject) + if (serializedObject.IsSceneObject && networkObject != null) { sceneObjects.Add(networkObject); } @@ -1101,7 +1101,11 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) { m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle); } - var spawnedNetworkObject = NetworkObject.Deserialize(serializedObject, m_InternalBuffer, networkManager); + var spawnedNetworkObject = NetworkObject.DeserializeAndSpawnObject(serializedObject, m_InternalBuffer, networkManager); + if (spawnedNetworkObject == null) + { + continue; + } var noStop = m_InternalBuffer.Position; if (EnableSerializationLogs) @@ -1110,12 +1114,9 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager) LogArray(m_InternalBuffer.ToArray(), noStart, noStop, builder); } // If we failed to deserialize the NetworkObject then don't add null to the list - if (spawnedNetworkObject != null) + if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) { - if (!m_NetworkObjectsSync.Contains(spawnedNetworkObject)) - { - m_NetworkObjectsSync.Add(spawnedNetworkObject); - } + m_NetworkObjectsSync.Add(spawnedNetworkObject); } } if (EnableSerializationLogs) diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 38711e155c..2f8ae30d03 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1165,13 +1165,13 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n /// /// Only spawn non-authority instances should invoke this. /// This is invoked to instantiate an authority spawned , and - /// is only invoked by: + /// is only invoked by: /// /// - /// IMPORTANT: Pre spawn methods need to be invoked from within . + /// IMPORTANT: Pre spawn methods need to be invoked from within . /// /// boolean indicating whether the spawn succeeded - internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serializedObject, out NetworkObject networkObject, FastBufferReader reader, bool destroyWithScene) + internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serializedObject, [MaybeNullWhen(false)] out NetworkObject networkObject, FastBufferReader reader, bool destroyWithScene) { if (SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId)) { @@ -1368,7 +1368,7 @@ internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject } var message = new CreateObjectMessage { - ObjectInfo = networkObject.Serialize(clientId, NetworkManager.DistributedAuthorityMode), + ObjectInfo = networkObject.SerializeSpawnedObject(clientId, NetworkManager.DistributedAuthorityMode), IncludesSerializedObject = true, UpdateObservers = NetworkManager.DistributedAuthorityMode, ObserverIds = NetworkManager.DistributedAuthorityMode ? networkObject.Observers.ToArray() : null, @@ -1394,7 +1394,7 @@ internal void SendSpawnCallForObserverUpdate(ulong[] newObservers, NetworkObject var message = new CreateObjectMessage { - ObjectInfo = networkObject.Serialize(), + ObjectInfo = networkObject.SerializeSpawnedObject(), ObserverIds = networkObject.Observers.ToArray(), NewObserverIds = newObservers.ToArray(), IncludesSerializedObject = true, diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 6224c6c627..7535e4ffce 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -8,7 +8,6 @@ namespace Unity.Netcode.RuntimeTests { - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-14172 [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.DAHost)] [TestFixture(VariableLengthSafety.DisableNetVarSafety, HostOrServer.Host)] @@ -28,12 +27,6 @@ internal class NetworkObjectSynchronizationTests : NetcodeIntegrationTest private LogLevel m_CurrentLogLevel; - // TODO: [CmbServiceTests] Adapt to run with the service - protected override bool UseCMBService() - { - return false; - } - public enum VariableLengthSafety { DisableNetVarSafety, @@ -57,15 +50,15 @@ protected override void OnCreatePlayerPrefab() protected override void OnServerAndClientsCreated() { - + var authority = GetAuthorityNetworkManager(); // Set the NetworkVariable Safety Check setting - m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; + authority.NetworkConfig.EnsureNetworkVariableLengthSafety = m_VariableLengthSafety == VariableLengthSafety.EnabledNetVarSafety; // Ignore the errors generated during this test (they are expected) - m_ServerNetworkManager.LogLevel = LogLevel.Nothing; + authority.LogLevel = LogLevel.Nothing; // Disable forcing the same prefabs to avoid failed connections - m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; + authority.NetworkConfig.ForceSamePrefabs = false; // Create the valid network prefab m_NetworkPrefab = CreateNetworkObjectPrefab("ValidObject"); @@ -104,8 +97,9 @@ protected override void OnNewClientCreated(NetworkManager networkManager) public IEnumerator NetworkObjectDeserializationFailure() { m_CurrentLogLevel = LogLevel.Nothing; - var validSpawnedNetworkObjects = new List(); + var authoritySpawnedNetworkObjects = new List(); NetworkBehaviourWithNetworkVariables.ResetSpawnCount(); + var authority = GetAuthorityNetworkManager(); // Spawn NetworkObjects on the server side with half of them being the // invalid network prefabs to simulate NetworkObject synchronization failure @@ -113,47 +107,29 @@ public IEnumerator NetworkObjectDeserializationFailure() { if (i % 2 == 0) { - SpawnObject(m_InValidNetworkPrefab, m_ServerNetworkManager); + SpawnObject(m_InValidNetworkPrefab, authority); } else { // Keep track of the prefabs that should successfully spawn on the client side - validSpawnedNetworkObjects.Add(SpawnObject(m_NetworkPrefab, m_ServerNetworkManager)); + var instance = SpawnObject(m_NetworkPrefab, authority); + authoritySpawnedNetworkObjects.Add(instance.GetComponent()); } } // Assure the server-side spawned all NetworkObjects - yield return WaitForConditionOrTimeOut(() => NetworkBehaviourWithNetworkVariables.ServerSpawnCount == k_NumberToSpawn); + yield return WaitForConditionOrTimeOut(() => NetworkBehaviourWithNetworkVariables.AuthoritySpawnCount == k_NumberToSpawn); // Now spawn and connect a client that will fail to spawn half of the NetworkObjects spawned - yield return CreateAndStartNewClient(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); if (m_UseHost) { - var delayCounter = 0; - while (m_ClientNetworkManagers.Length == 0) - { - delayCounter++; - Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); - yield return s_DefaultWaitForTick; - } - delayCounter = 0; - while (!m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_ClientNetworkManagers[0].LocalClientId)) - { - delayCounter++; - if (delayCounter >= 30) - { - VerboseDebug("Trap!"); - } - Assert.True(delayCounter < 30, "TimeOut waiting for client to spawn!"); - yield return s_DefaultWaitForTick; - } - - - 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(); + var serverSideClientPlayerComponent = m_PlayerNetworkObjects[authority.LocalClientId][newClient.LocalClientId].GetComponent(); + var serverSideHostPlayerComponent = authority.LocalClient.PlayerObject.GetComponent(); + var clientSidePlayerComponent = newClient.LocalClient.PlayerObject.GetComponent(); + var clientSideHostPlayerComponent = m_PlayerNetworkObjects[newClient.LocalClientId][authority.LocalClientId].GetComponent(); var modeText = m_DistributedAuthority ? "owner" : "server"; // 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, @@ -192,16 +168,15 @@ public IEnumerator NetworkObjectDeserializationFailure() 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 secondClient = CreateNewClient(); + yield return StartClient(secondClient); - var clientSide1PlayerComponent = m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent(); - var clientSide2Player1Clone = m_PlayerNetworkObjects[2][clientSide1PlayerComponent.OwnerClientId].GetComponent(); + var clientSide1PlayerComponent = newClient.LocalClient.PlayerObject.GetComponent(); + var clientSide2Player1Clone = m_PlayerNetworkObjects[secondClient.LocalClientId][clientSide1PlayerComponent.OwnerClientId].GetComponent(); var clientOneId = clientSide1PlayerComponent.OwnerClientId; - var clientSide2PlayerComponent = m_ClientNetworkManagers[1].LocalClient.PlayerObject.GetComponent(); - var clientSide1Player2Clone = m_PlayerNetworkObjects[1][clientSide2PlayerComponent.OwnerClientId].GetComponent(); + var clientSide2PlayerComponent = secondClient.LocalClient.PlayerObject.GetComponent(); + var clientSide1Player2Clone = m_PlayerNetworkObjects[newClient.LocalClientId][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 @@ -244,75 +219,57 @@ public IEnumerator NetworkObjectDeserializationFailure() } } - // DANGO-TODO: This scenario is only possible to do if we add a DA-Server to mock the CMB Service or we integrate the CMB Service AND we have updated NetworkVariable permissions - // to only allow the service to write. For now, we will skip this validation for distributed authority - if (!m_DistributedAuthority) + // Now validate all of the NetworkVariable values match to assure everything synchronized properly + foreach (var spawnedObject in authoritySpawnedNetworkObjects) { - // Now validate all of the NetworkVariable values match to assure everything synchronized properly - foreach (var spawnedObject in validSpawnedNetworkObjects) + foreach (var networkManager in m_NetworkManagers) { - foreach (var clientNetworkManager in m_ClientNetworkManagers) + if (networkManager == authority) { - //Validate that the connected client has spawned all of the instances that shouldn't have failed. - var clientSideNetworkObjects = s_GlobalNetworkObjects[clientNetworkManager.LocalClientId]; + continue; + } + //Validate that the connected client has spawned all of the instances that shouldn't have failed. + var clientSideNetworkObjects = s_GlobalNetworkObjects[networkManager.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})!"); + Assert.IsTrue(NetworkBehaviourWithNetworkVariables.NonAuthoritySpawnCount[networkManager.LocalClientId] == authoritySpawnedNetworkObjects.Count, $"Client-{networkManager.LocalClientId} spawned " + + $"({NetworkBehaviourWithNetworkVariables.NonAuthoritySpawnCount}) {nameof(NetworkObject)}s but the expected number of {nameof(NetworkObject)}s should have been ({authoritySpawnedNetworkObjects.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}"); + Assert.IsTrue(clientSideNetworkObjects.ContainsKey(spawnedObject.NetworkObjectId), $"Failed to find valid spawned {nameof(NetworkObject)} on the client-side with a " + + $"{nameof(NetworkObject.NetworkObjectId)} of {spawnedObject.NetworkObjectId}"); - var clientSideObject = clientSideNetworkObjects[spawnedNetworkObject.NetworkObjectId]; - Assert.IsTrue(clientSideObject.NetworkManager == clientNetworkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); + var clientSideObject = clientSideNetworkObjects[spawnedObject.NetworkObjectId]; + Assert.IsTrue(clientSideObject.NetworkManager == networkManager, $"Client-side object {clientSideObject}'s {nameof(NetworkManager)} is not valid!"); - ValidateNetworkBehaviourWithNetworkVariables(spawnedNetworkObject, clientSideObject); - } + ValidateNetworkBehaviourWithNetworkVariables(spawnedObject, clientSideObject); } } } - private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject serverSideNetworkObject, NetworkObject clientSideNetworkObject) + private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject authorityNetworkObject, NetworkObject nonAuthorityNetworkObject) { - var serverSideComponent = serverSideNetworkObject.GetComponent(); - var clientSideComponent = clientSideNetworkObject.GetComponent(); + var authorityComponent = authorityNetworkObject.GetComponent(); + var nonAuthorityComponent = nonAuthorityNetworkObject.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})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData1.Count == nonAuthorityComponent.NetworkVariableData1.Count, $"[{authorityComponent.name}:{netVarName1}] Server side {nameof(NetworkList)} " + + $"count ({authorityComponent.NetworkVariableData1.Count}) does not match the client side {nameof(NetworkList)} count ({nonAuthorityComponent.NetworkVariableData1.Count})!"); - for (int i = 0; i < serverSideComponent.NetworkVariableData1.Count; i++) + for (int i = 0; i < authorityComponent.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(authorityComponent.NetworkVariableData1[i] == nonAuthorityComponent.NetworkVariableData1[i], $"[{authorityComponent.name}:{netVarName1}][Index:{i}] Server side instance value " + + $"({authorityComponent.NetworkVariableData1[i]}) does not match the client side instance value ({nonAuthorityComponent.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})!"); - } - - - private bool ClientSpawnedNetworkObjects(List spawnedObjectList) - { - var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; - - foreach (var spawnedObject in spawnedObjectList) - { - var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); - if (!clientSideNetworkObjects.ContainsKey(serverSideSpawnedNetworkObject.NetworkObjectId)) - { - return false; - } - } - return true; + Assert.IsTrue(authorityComponent.NetworkVariableData2.Value == nonAuthorityComponent.NetworkVariableData2.Value, $"[{authorityComponent.name}:{netVarName2}] Server side instance value ({authorityComponent.NetworkVariableData2.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData2.Value})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData3.Value == nonAuthorityComponent.NetworkVariableData3.Value, $"[{authorityComponent.name}:{netVarName3}] Server side instance value ({authorityComponent.NetworkVariableData3.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData3.Value})!"); + Assert.IsTrue(authorityComponent.NetworkVariableData4.Value == nonAuthorityComponent.NetworkVariableData4.Value, $"[{authorityComponent.name}:{netVarName4}] Server side instance value ({authorityComponent.NetworkVariableData4.Value}) " + + $"does not match the client side instance value ({nonAuthorityComponent.NetworkVariableData4.Value})!"); } /// @@ -322,7 +279,8 @@ private bool ClientSpawnedNetworkObjects(List spawnedObjectList) [UnityTest] public IEnumerator NetworkBehaviourSynchronization() { - m_ServerNetworkManager.LogLevel = LogLevel.Normal; + var authority = GetAuthorityNetworkManager(); + authority.LogLevel = LogLevel.Normal; m_CurrentLogLevel = LogLevel.Normal; NetworkBehaviourSynchronizeFailureComponent.ResetBehaviour(); @@ -331,28 +289,29 @@ public IEnumerator NetworkBehaviourSynchronization() // 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 synchronizationObject = SpawnObject(m_SynchronizationPrefab, authority); 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(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); // 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]; - yield return WaitForConditionOrTimeOut(() => ClientSpawnedNetworkObjects(spawnedObjectList)); + var clientSideNetworkObjects = s_GlobalNetworkObjects[newClient.LocalClientId]; + yield return WaitForSpawnedOnAllOrTimeOut(clientSideNetworkObjects.Values); AssertOnTimeout($"Timed out waiting for newly joined client to spawn all NetworkObjects!"); foreach (var spawnedObject in spawnedObjectList) { - var serverSideSpawnedNetworkObject = spawnedObject.GetComponent(); - var clientSideObject = clientSideNetworkObjects[serverSideSpawnedNetworkObject.NetworkObjectId]; - var clientSideSpawnedNetworkObject = clientSideObject.GetComponent(); + var authorityObject = spawnedObject.GetComponent(); + var nonAuthorityObject = clientSideNetworkObjects[authorityObject.NetworkObjectId]; + var clientSideSpawnedNetworkObject = nonAuthorityObject.GetComponent(); - ValidateNetworkBehaviourWithNetworkVariables(serverSideSpawnedNetworkObject, clientSideSpawnedNetworkObject); + ValidateNetworkBehaviourWithNetworkVariables(authorityObject, clientSideSpawnedNetworkObject); } } @@ -362,12 +321,14 @@ public IEnumerator NetworkBehaviourSynchronization() [UnityTest] public IEnumerator NetworkBehaviourOnSynchronize() { - var serverSideInstance = SpawnObject(m_OnSynchronizePrefab, m_ServerNetworkManager).GetComponent(); + var authority = GetAuthorityNetworkManager(); + var serverSideInstance = SpawnObject(m_OnSynchronizePrefab, authority).GetComponent(); // Now spawn and connect a client that will have custom serialized data applied during the client synchronization process. - yield return CreateAndStartNewClient(); + var newClient = CreateNewClient(); + yield return StartClient(newClient); - var clientSideNetworkObjects = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId]; + var clientSideNetworkObjects = s_GlobalNetworkObjects[newClient.LocalClientId]; var clientSideInstance = clientSideNetworkObjects[serverSideInstance.NetworkObjectId].GetComponent(); // Validate the values match @@ -386,13 +347,13 @@ public IEnumerator NetworkBehaviourOnSynchronize() /// internal class NetworkBehaviourWithNetworkVariables : NetworkBehaviour { - public static int ServerSpawnCount { get; internal set; } - public static readonly Dictionary ClientSpawnCount = new Dictionary(); + public static int AuthoritySpawnCount { get; internal set; } + public static readonly Dictionary NonAuthoritySpawnCount = new Dictionary(); public static void ResetSpawnCount() { - ServerSpawnCount = 0; - ClientSpawnCount.Clear(); + AuthoritySpawnCount = 0; + NonAuthoritySpawnCount.Clear(); } private const uint k_MinDataBlocks = 1; @@ -424,15 +385,15 @@ public override void OnNetworkSpawn() { if (IsServer) { - ServerSpawnCount++; + AuthoritySpawnCount++; } else { - if (!ClientSpawnCount.ContainsKey(NetworkManager.LocalClientId)) + if (!NonAuthoritySpawnCount.ContainsKey(NetworkManager.LocalClientId)) { - ClientSpawnCount.Add(NetworkManager.LocalClientId, 0); + NonAuthoritySpawnCount.Add(NetworkManager.LocalClientId, 0); } - ClientSpawnCount[NetworkManager.LocalClientId]++; + NonAuthoritySpawnCount[NetworkManager.LocalClientId]++; } base.OnNetworkSpawn(); @@ -496,8 +457,8 @@ public override void OnNetworkSpawn() internal class NetworkBehaviourSynchronizeFailureComponent : NetworkBehaviour { public static int NumberOfFailureTypes { get; internal set; } - public static int ServerSpawnCount { get; internal set; } - public static int ClientSpawnCount { get; internal set; } + public static int AuthoritySpawnCount { get; internal set; } + public static int NonAuthoritySpawnCount { get; internal set; } private static FailureTypes s_FailureType = FailureTypes.None; @@ -513,8 +474,8 @@ public enum FailureTypes public static void ResetBehaviour() { - ServerSpawnCount = 0; - ClientSpawnCount = 0; + AuthoritySpawnCount = 0; + NonAuthoritySpawnCount = 0; s_FailureType = FailureTypes.None; NumberOfFailureTypes = System.Enum.GetValues(typeof(FailureTypes)).Length; } @@ -595,6 +556,7 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } case FailureTypes.DontReadAnything: { + Debug.Log("Don't read anything is being run"); // Don't read anything break; } @@ -641,14 +603,14 @@ private void Awake() public override void OnNetworkSpawn() { - if (IsServer) + if (HasAuthority) { - ServerSpawnCount++; + AuthoritySpawnCount++; m_MyCustomData.GenerateData((ushort)Random.Range(1, 512)); } else { - ClientSpawnCount++; + NonAuthoritySpawnCount++; } base.OnNetworkSpawn(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs new file mode 100644 index 0000000000..d135f29b91 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -0,0 +1,88 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class NetworkPrefabHandlerSynchronizationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + public NetworkPrefabHandlerSynchronizationTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private GameObject m_ValidPrefab; + private GameObject m_ClientSideValidPrefab; + private GameObject m_ClientSideExceptionPrefab; + + protected override void OnServerAndClientsCreated() + { + m_ValidPrefab = CreateNetworkObjectPrefab("ValidPrefab"); + m_ClientSideValidPrefab = CreateNetworkObjectPrefab("ClientSideValidPrefab"); + m_ClientSideExceptionPrefab = CreateNetworkObjectPrefab("ClientSideExceptionPrefab"); + base.OnServerAndClientsCreated(); + } + + [UnityTest] + public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() + { + var nonAuthority = GetNonAuthorityNetworkManager(); + + var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); + nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); + nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var authority = GetAuthorityNetworkManager(); + + // Spawn the invalid object first. + var exceptionObject = SpawnObject(m_ClientSideExceptionPrefab, authority).GetComponent(); + + // Check the invalid object spawns on the authority, expect an error from non-authority. + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + LogAssert.Expect(LogType.Error, $"[Netcode] [GlobalObjectIdHash={exceptionObject.GlobalObjectIdHash}] Failed to spawn NetworkObject!"); + yield return WaitForConditionOrTimeOut(() => exceptionObject.IsSpawned); + AssertOnTimeout("Failed to spawn object on authority!"); + + // Now spawn a valid object + var validObject = SpawnObject(m_ValidPrefab, authority).GetComponent(); + + // The valid object should spawn as expected + yield return WaitForSpawnedOnAllOrTimeOut(validObject); + AssertOnTimeout("Failed to spawn valid prefab on all clients!"); + + // Create a new client and register the same PrefabHandlers on the client + var newClient = CreateNewClient(); + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); + newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + // Expect assertions fromt the new client + LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); + LogAssert.Expect(LogType.Error, $"[Netcode] [GlobalObjectIdHash={exceptionObject.GlobalObjectIdHash}] Failed to spawn NetworkObject!"); + + // Start and synchronize the new client + yield return StartClient(newClient); + + // Validate the valid prefab spawned on all clients without issue + var expectedAuthorityHash = m_ValidPrefab.GetComponent().GlobalObjectIdHash; + var expectedNonAuthorityHash = m_ClientSideValidPrefab.GetComponent().GlobalObjectIdHash; + foreach (var networkManager in m_NetworkManagers) + { + Assert.True(networkManager.SpawnManager.SpawnedObjects.TryGetValue(validObject.NetworkObjectId, out NetworkObject spawnedObject), $"Client-{networkManager.LocalClientId} failed to spawn version of valid object!"); + + if (spawnedObject.HasAuthority) + { + Assert.That(spawnedObject.GlobalObjectIdHash, Is.EqualTo(expectedAuthorityHash), "NetworkObject spawned with unexpected GlobalObjectIdHash!"); + Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.True, "Authority missing spawned NetworkObject!"); + } + else + { + Assert.That(spawnedObject.GlobalObjectIdHash, Is.EqualTo(expectedNonAuthorityHash), "NetworkObject spawned with unexpected GlobalObjectIdHash!"); + Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.False, "Non authority should not have spawned exception object!"); + } + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta new file mode 100644 index 0000000000..a0c417d419 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63f7e166459749bfbbf88e5b82ab917d +timeCreated: 1783026731 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index f680b24338..11c092a8a9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1987,7 +1987,7 @@ protected IEnumerator WaitForSpawnedOnAllOrTimeOut(GameObject gameObject, Timeou /// The list of s to wait for. /// An optional to control the timeout period. If null, the default timeout is used. /// An for use in Unity coroutines. - protected IEnumerator WaitForSpawnedOnAllOrTimeOut(List networkObjects, TimeoutHelper timeOutHelper = null) + protected IEnumerator WaitForSpawnedOnAllOrTimeOut(ICollection networkObjects, TimeoutHelper timeOutHelper = null) { bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) { From f4dfd4e117cb24328316b20ccf31ca9c89afeea0 Mon Sep 17 00:00:00 2001 From: Nikos Date: Wed, 8 Jul 2026 23:49:34 +0100 Subject: [PATCH 24/64] ci: Fix code coverage command in YAML configuration (#4069) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix code coverage command in YAML configuration Coverage reporting was not setup due to this * Moving CodeCov check to daily trigger --------- Co-authored-by: Michał Chrobot --- .yamato/_triggers.yml | 4 ++-- .yamato/code-coverage.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index 8cdf8445ce..cf304709d7 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -157,6 +157,8 @@ develop_nightly: - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_6000.0 # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} # Run all tests on weekly bases @@ -187,5 +189,3 @@ develop_weekly_trunk: - .yamato/_run-all.yml#run_all_webgl_builds # Run Runtime tests against CMB service - .yamato/_run-all.yml#run_all_project_tests_cmb_service - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index caeeff72ec..64a2692225 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -39,7 +39,7 @@ code_coverage_{{ platform.name }}_{{ editor }}: commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --unity .Editor - - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results + - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: From 3e579edf3bfd0b970afbe5e6b6d96370a507e20a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Sat, 11 Jul 2026 12:29:52 -0500 Subject: [PATCH 25/64] fix: soft synchronization errors if loading a new scene and setting it as active (#4065) * fix - NetworkObject null exception on warning log If the NetworkObject was not found then use "null" as the name of the NetworkObject. * fix - synchronizing clients should always load active scene first This fixes the issue where the active scene could end up not being the 1st scene loaded which upon loading the active scene and previously SceneEventData relative loaded scenes would get unloaded when loading the active scene since this will can end up being loaded in SingleMode. * fix - prevent marking dynamically spawned objects as in-scene This fix just ignores any spawned objects when setting any just loaded InScenePlaced objects while running in the editor. * style finishing an incomplete comment * update Removing the changes as this will be fixed in a separate PR. * update Change log entry. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../SceneManagement/NetworkSceneManager.cs | 90 ++++++++++--------- 2 files changed, 51 insertions(+), 40 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 5374068ba0..f8ca65e0ab 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -23,6 +23,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed - Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) +- Issue where the active scene was not being serialized as the 1st scene which could result in various errors including a soft synchronization error if, on the client-side, other synchronized scenes had already been loaded prior to the active scene, which is always loaded as `LoadSceneMode.SingleMode` when client synchronization is set to `LoadSceneMode.SingleMode`, resulting in the previously loaded scene(s) to be unloaded. (#4065) - Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) - Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index a28baf1f94..c0d4b44aee 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1951,6 +1951,21 @@ private void OnClientLoadedScene(uint sceneEventId, Scene scene) /// internal List ClientConnectionQueue = new List(); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void AddSceneToClientSynchronization(ref SceneEventData sceneEventData, ref Scene scene) + { + // If we are just a normal client and in distributed authority mode, then always use the known server scene handle + if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + } + else + { + sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + } + } + /// /// Server Side: /// This is used for players that have just had their connection approved and will assure they are synchronized @@ -2001,61 +2016,53 @@ internal void SynchronizeNetworkObjects(ulong clientId, bool synchronizingServic // Organize how (and when) we serialize our NetworkObjects var hasSynchronizedActive = false; - for (int i = 0; i < SceneManager.sceneCount; i++) - { - var scene = SceneManager.GetSceneAt(i); - // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager - // unless the scene in question is the currently active scene. - if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) + // It is possible a user might not want to synchronize the active scene, so we will check to see if it is valid before adding it to the synchronization list. + // !! Important !! + // The active scene MUST always be the first scene in the synchronization list. + if (ValidateSceneBeforeLoading(activeScene.buildIndex, activeScene.name, sceneEventData.LoadSceneMode)) + { + sceneEventData.SceneHash = SceneHashFromNameOrPath(activeScene.path); + if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) { - continue; + hasSynchronizedActive = true; } - if (scene == DontDestroyOnLoadScene) + // If we are just a normal client, then always use the server scene handle + if (NetworkManager.DistributedAuthorityMode) { - continue; + sceneEventData.SenderClientId = NetworkManager.LocalClientId; + sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[activeScene.handle]; } - - // This would depend upon whether we are additive or not - // If we are the base scene, then we set the root scene index; - if (activeScene == scene) + else { - if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, sceneEventData.LoadSceneMode)) - { - continue; - } - sceneEventData.SceneHash = SceneHashFromNameOrPath(scene.path); - if (sceneEventData.SceneHash == sceneEventData.ActiveSceneHash) - { - hasSynchronizedActive = true; - } - - // If we are just a normal client, then always use the server scene handle - if (NetworkManager.DistributedAuthorityMode) - { - sceneEventData.SenderClientId = NetworkManager.LocalClientId; - sceneEventData.SceneHandle = ClientSceneHandleToServerSceneHandle[scene.handle]; - } - else - { - sceneEventData.SceneHandle = scene.handle; - } + sceneEventData.SceneHandle = activeScene.handle; } - else if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) + AddSceneToClientSynchronization(ref sceneEventData, ref activeScene); + } + + for (int i = 0; i < SceneManager.sceneCount; i++) + { + var scene = SceneManager.GetSceneAt(i); + // Skip adding the active scene at this point as we are just adding all other additively loaded scenes to the synchronization list. + // Skip adding the dont destroy on load scene as that is never synchronized. + if ((scene.handle == activeScene.handle) || (scene == DontDestroyOnLoadScene)) { continue; } - // If we are just a normal client and in distributed authority mode, then always use the known server scene handle - if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection) + // NetworkSceneManager does not synchronize scenes that are not loaded by NetworkSceneManager + // unless the scene in question is the currently active scene. + if (ExcludeSceneFromSychronization != null && !ExcludeSceneFromSychronization(scene)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), ClientSceneHandleToServerSceneHandle[scene.handle]); + continue; } - else + + if (!ValidateSceneBeforeLoading(scene.buildIndex, scene.name, LoadSceneMode.Additive)) { - sceneEventData.AddSceneToSynchronize(SceneHashFromNameOrPath(scene.path), scene.handle); + continue; } + AddSceneToClientSynchronization(ref sceneEventData, ref scene); } if (!hasSynchronizedActive && NetworkManager.CMBServiceConnection && synchronizingService) @@ -2108,9 +2115,12 @@ private void OnClientBeginSync(uint sceneEventId) var sceneHash = sceneEventData.GetNextSceneSynchronizationHash(); var sceneHandle = sceneEventData.GetNextSceneSynchronizationHandle(); var sceneName = SceneNameFromHash(sceneHash); + var activeSceneName = SceneNameFromHash(sceneEventData.ActiveSceneHash); var activeScene = SceneManager.GetActiveScene(); - var loadSceneMode = sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; + var activeSceneLoaded = activeSceneName == activeScene.name; + + var loadSceneMode = sceneHash == sceneEventData.SceneHash && !activeSceneLoaded ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive; // Store the sceneHandle and hash sceneEventData.NetworkSceneHandle = sceneHandle; From 94be949a99f76fc0e32631f0420d81f96e4b68b7 Mon Sep 17 00:00:00 2001 From: Kitty Draper <284434+ShadauxCat@users.noreply.github.com> Date: Mon, 13 Jul 2026 11:39:48 -0500 Subject: [PATCH 26/64] fix: Support InLocalSpace in AnticipatedNetworkTransform (#3995) --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Components/AnticipatedNetworkTransform.cs | 171 +++++++++++-- .../NetworkTransformAnticipationTests.cs | 232 +++++++++++++++++- .../TestHelpers/NetcodeIntegrationTest.cs | 87 +++++++ 4 files changed, 466 insertions(+), 25 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index f8ca65e0ab..ba73356e80 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag (#3995) - Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) - Issue where the active scene was not being serialized as the 1st scene which could result in various errors including a soft synchronization error if, on the client-side, other synchronized scenes had already been loaded prior to the active scene, which is always loaded as `LoadSceneMode.SingleMode` when client synchronization is set to `LoadSceneMode.SingleMode`, resulting in the previously loaded scene(s) to be unloaded. (#4065) - Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs index ce277533c7..00bd431cbb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/AnticipatedNetworkTransform.cs @@ -161,8 +161,18 @@ public void AnticipateMove(Vector3 newPosition) { return; } - transform.position = newPosition; + + if (InLocalSpace) + { + transform.localPosition = newPosition; + } + else + { + transform.position = newPosition; + } + m_AnticipatedTransform.Position = newPosition; + if (CanCommitToTransform) { m_AuthoritativeTransform.Position = newPosition; @@ -187,7 +197,15 @@ public void AnticipateRotate(Quaternion newRotation) { return; } - transform.rotation = newRotation; + + if (InLocalSpace) + { + transform.localRotation = newRotation; + } + else + { + transform.rotation = newRotation; + } m_AnticipatedTransform.Rotation = newRotation; if (CanCommitToTransform) { @@ -240,7 +258,14 @@ public void AnticipateState(TransformState newState) return; } var transform_ = transform; - transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(newState.Position, newState.Rotation); + } + else + { + transform_.SetPositionAndRotation(newState.Position, newState.Rotation); + } transform_.localScale = newState.Scale; m_AnticipatedTransform = newState; if (CanCommitToTransform) @@ -277,7 +302,17 @@ private void ProcessSmoothing() m_PreviousAnticipatedTransform = m_AnticipatedTransform; if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, m_AnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(m_AnticipatedTransform.Position, + m_AnticipatedTransform.Rotation); + } + transform_.localScale = m_AnticipatedTransform.Scale; } } @@ -320,12 +355,25 @@ public void SetupForRender() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - Transform.m_AuthoritativeTransform = new TransformState + if (Transform.InLocalSpace) { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else + { + Transform.m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + if (Transform.m_CurrentSmoothTime >= Transform.m_SmoothDuration) { // If we've had a call to Smooth() we'll continue interpolating. @@ -334,7 +382,17 @@ public void SetupForRender() Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform; } - transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, Transform.m_AnticipatedTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AnticipatedTransform.Position, + Transform.m_AnticipatedTransform.Rotation); + } + transform_.localScale = Transform.m_AnticipatedTransform.Scale; } } @@ -344,7 +402,17 @@ public void SetupForUpdate() if (Transform.CanCommitToTransform) { var transform_ = Transform.transform; - transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, Transform.m_AuthoritativeTransform.Rotation); + if (Transform.InLocalSpace) + { + transform_.SetLocalPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(Transform.m_AuthoritativeTransform.Position, + Transform.m_AuthoritativeTransform.Rotation); + } + transform_.localScale = Transform.m_AuthoritativeTransform.Scale; } } @@ -367,12 +435,25 @@ public void ResetAnticipation() private void ResetAnticipatedState() { var transform_ = transform; - m_AuthoritativeTransform = new TransformState + if (InLocalSpace) + { + m_AuthoritativeTransform = new TransformState + { + Position = transform_.localPosition, + Rotation = transform_.localRotation, + Scale = transform_.localScale + }; + } + else { - Position = transform_.position, - Rotation = transform_.rotation, - Scale = transform_.localScale - }; + m_AuthoritativeTransform = new TransformState + { + Position = transform_.position, + Rotation = transform_.rotation, + Scale = transform_.localScale + }; + } + m_AnticipatedTransform = m_AuthoritativeTransform; m_PreviousAnticipatedTransform = m_AnticipatedTransform; @@ -439,7 +520,7 @@ public override void OnNetworkSpawn() return; } m_OutstandingAuthorityChange = true; - ApplyAuthoritativeState(); + //ApplyAuthoritativeState(); ResetAnticipatedState(); m_AnticipatedObject = new AnticipatedObject { Transform = this }; @@ -491,7 +572,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds { m_AnticipatedTransform = to; m_PreviousAnticipatedTransform = m_AnticipatedTransform; - transform_.SetPositionAndRotation(to.Position, to.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(to.Position, to.Rotation); + } + else + { + transform_.SetPositionAndRotation(to.Position, to.Rotation); + } + transform_.localScale = to.Scale; m_SmoothDuration = 0; m_CurrentSmoothTime = 0; @@ -502,7 +591,15 @@ public void Smooth(TransformState from, TransformState to, float durationSeconds if (!CanCommitToTransform) { - transform_.SetPositionAndRotation(from.Position, from.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(from.Position, from.Rotation); + } + else + { + transform_.SetPositionAndRotation(from.Position, from.Rotation); + } + transform_.localScale = from.Scale; } @@ -543,14 +640,30 @@ protected override void OnTransformUpdated() var previousAnticipatedTransform = m_AnticipatedTransform; // Update authority state to catch any possible interpolation data - m_AuthoritativeTransform.Position = transform_.position; - m_AuthoritativeTransform.Rotation = transform_.rotation; - m_AuthoritativeTransform.Scale = transform_.localScale; + if (InLocalSpace) + { + m_AuthoritativeTransform.Position = transform_.localPosition; + m_AuthoritativeTransform.Rotation = transform_.localRotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } + else + { + m_AuthoritativeTransform.Position = transform_.position; + m_AuthoritativeTransform.Rotation = transform_.rotation; + m_AuthoritativeTransform.Scale = transform_.localScale; + } if (!m_OutstandingAuthorityChange) { // Keep the anticipated value unchanged, we have no updates from the server at all. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + } transform_.localScale = previousAnticipatedTransform.Scale; return; } @@ -558,7 +671,17 @@ protected override void OnTransformUpdated() if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter) { // Keep the anticipated value unchanged because it is more recent than the authoritative one. - transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, previousAnticipatedTransform.Rotation); + if (InLocalSpace) + { + transform_.SetLocalPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + else + { + transform_.SetPositionAndRotation(previousAnticipatedTransform.Position, + previousAnticipatedTransform.Rotation); + } + transform_.localScale = previousAnticipatedTransform.Scale; return; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs index 61024d46da..f1ead09032 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransformAnticipationTests.cs @@ -6,6 +6,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools.Utils; +using Quaternion = UnityEngine.Quaternion; +using Vector3 = UnityEngine.Vector3; namespace Unity.Netcode.RuntimeTests { @@ -17,6 +19,12 @@ public void MoveRpc(Vector3 newPosition) transform.position = newPosition; } + [Rpc(SendTo.Server)] + public void LocalMoveRpc(Vector3 newPosition) + { + transform.localPosition = newPosition; + } + [Rpc(SendTo.Server)] public void ScaleRpc(Vector3 newScale) { @@ -29,6 +37,12 @@ public void RotateRpc(Quaternion newRotation) transform.rotation = newRotation; } + [Rpc(SendTo.Server)] + public void LocalRotateRpc(Quaternion newRotation) + { + transform.localRotation = newRotation; + } + public bool ShouldSmooth = false; public bool ShouldMove = false; @@ -59,12 +73,22 @@ internal class NetworkTransformAnticipationTests : NetcodeIntegrationTest protected override bool m_SetupIsACoroutine => false; protected override bool m_TearDownIsACoroutine => false; + private GameObject m_TestPrefab; + protected override void OnPlayerPrefabGameObjectCreated() { m_PlayerPrefab.AddComponent(); m_PlayerPrefab.AddComponent(); } + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("child object"); + var transform = m_TestPrefab.AddComponent(); + transform.InLocalSpace = true; + m_TestPrefab.AddComponent(); + } + protected override void OnTimeTravelServerAndClientsConnected() { var serverComponent = GetServerComponent(); @@ -80,6 +104,13 @@ protected override void OnTimeTravelServerAndClientsConnected() otherClientComponent.transform.position = Vector3.zero; otherClientComponent.transform.localScale = Vector3.one; otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward); + + var childObject = SpawnObject(m_TestPrefab, m_ServerNetworkManager); + childObject.transform.parent = serverComponent.transform; + childObject.transform.localPosition = Vector3.zero; + childObject.transform.localScale = Vector3.one; + childObject.transform.localRotation = Quaternion.LookRotation(Vector3.forward); + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(childObject.GetComponent()); } public AnticipatedNetworkTransform GetTestComponent() @@ -115,16 +146,36 @@ public AnticipatedNetworkTransform GetOtherClientComponent() return null; } + public AnticipatedNetworkTransform GetChildComponent(AnticipatedNetworkTransform parent) + { + foreach (var tr in parent.GetComponentsInChildren()) + { + if (tr == parent) + { + continue; + } + + return tr; + } + + return null; + } + [Test] public void WhenAnticipating_ValueChangesImmediately() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var quaternionComparer = new QuaternionEqualityComparer(0.000001f); testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale); Assert.That(testComponent.transform.rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. @@ -132,40 +183,71 @@ public void WhenAnticipating_ValueChangesImmediately() Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position); Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale); Assert.That(testComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.transform.localPosition); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.transform.localScale); + Assert.That(childComponent.transform.localRotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. + + Assert.AreEqual(new Vector3(0, 1, 2), childComponent.AnticipatedState.Position); + Assert.AreEqual(new Vector3(1, 2, 3), childComponent.AnticipatedState.Scale); + Assert.That(childComponent.AnticipatedState.Rotation, Is.EqualTo(Quaternion.LookRotation(new Vector3(2, 3, 4))).Using(quaternionComparer)); // Quaternion comparer added due to FP precision problems on Android devices. } [Test] public void WhenAnticipating_AuthoritativeValueDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale); Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation); + + Assert.AreEqual(childStartPosition, childComponent.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, childComponent.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, childComponent.AuthoritativeState.Rotation); } [Test] public void WhenAnticipating_ServerDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + childComponent.AnticipateMove(new Vector3(0, 1, 2)); + childComponent.AnticipateScale(new Vector3(1, 2, 3)); + childComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale); @@ -174,6 +256,13 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position); @@ -182,22 +271,35 @@ public void WhenAnticipating_ServerDoesNotChange() Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position); Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, serverChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, serverChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, serverChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, serverChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(childStartRotation, serverChild.AnticipatedState.Rotation); } [Test] public void WhenAnticipating_OtherClientDoesNotChange() { var testComponent = GetTestComponent(); + var childComponent = GetChildComponent(testComponent); var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; var startRotation = testComponent.transform.rotation; + var childStartPosition = childComponent.transform.localPosition; + var childStartScale = childComponent.transform.localScale; + var childStartRotation = childComponent.transform.localRotation; + testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale); @@ -206,6 +308,13 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); + TimeTravel(2, 120); Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position); @@ -214,24 +323,45 @@ public void WhenAnticipating_OtherClientDoesNotChange() Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale); Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation); + + Assert.AreEqual(childStartPosition, otherClientChild.AuthoritativeState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AuthoritativeState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AuthoritativeState.Rotation); + Assert.AreEqual(childStartPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(childStartScale, otherClientChild.AnticipatedState.Scale); + Assert.AreEqual(childStartRotation, otherClientChild.AnticipatedState.Rotation); } [Test] public void WhenServerChangesSnapValue_ValuesAreUpdated() { var testComponent = GetTestComponent(); + var testChild = GetChildComponent(testComponent); var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.AnticipateMove(new Vector3(0, 1, 2)); testComponent.AnticipateScale(new Vector3(1, 2, 3)); testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + testChild.AnticipateMove(new Vector3(0, 1, 2)); + testChild.AnticipateScale(new Vector3(1, 2, 3)); + testChild.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4))); + var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(new Vector3(2, 3, 4)); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(new Vector3(2, 3, 4)); - WaitForMessageReceivedWithTimeTravel(new List { m_ServerNetworkManager }); + WaitForMessagesReceivedWithTimeTravel(new List + { + typeof(RpcMessage), + typeof(RpcMessage), + }, new List { m_ServerNetworkManager }); var otherClientComponent = GetOtherClientComponent(); + var otherClientChild = GetChildComponent(otherClientComponent); WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position); @@ -242,6 +372,16 @@ public void WhenServerChangesSnapValue_ValuesAreUpdated() Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position); Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, testChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, testChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, testChild.AuthoritativeState.Position); + + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.transform.localPosition); + Assert.AreNotEqual(serverChild.transform.localPosition, otherClientChild.transform.position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AnticipatedState.Position); + Assert.AreEqual(serverChild.transform.localPosition, otherClientChild.AuthoritativeState.Position); } public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b) @@ -264,15 +404,23 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() { var testComponent = GetTestComponent(); var otherClientComponent = GetOtherClientComponent(); + var testChild = GetChildComponent(testComponent); + var otherChild = GetChildComponent(otherClientComponent); testComponent.StaleDataHandling = StaleDataHandling.Ignore; otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore; + testChild.StaleDataHandling = StaleDataHandling.Ignore; + otherChild.StaleDataHandling = StaleDataHandling.Ignore; var serverComponent = GetServerComponent(); + var serverChild = GetChildComponent(serverComponent); serverComponent.Interpolate = false; + serverChild.Interpolate = false; testComponent.GetComponent().ShouldSmooth = true; otherClientComponent.GetComponent().ShouldSmooth = true; + testChild.GetComponent().ShouldSmooth = true; + otherChild.GetComponent().ShouldSmooth = true; var startPosition = testComponent.transform.position; var startScale = testComponent.transform.localScale; @@ -287,17 +435,27 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() testComponent.AnticipateMove(anticipePosition); testComponent.AnticipateScale(anticipeScale); testComponent.AnticipateRotate(anticipeRotation); + testChild.AnticipateMove(anticipePosition); + testChild.AnticipateScale(anticipeScale); + testChild.AnticipateRotate(anticipeRotation); var rpcComponent = testComponent.GetComponent(); rpcComponent.MoveRpc(serverSetPosition); rpcComponent.RotateRpc(serverSetRotation); rpcComponent.ScaleRpc(serverSetScale); + var childRpcComponent = testChild.GetComponent(); + childRpcComponent.LocalMoveRpc(serverSetPosition); + childRpcComponent.LocalRotateRpc(serverSetRotation); + childRpcComponent.ScaleRpc(serverSetScale); WaitForMessagesReceivedWithTimeTravel(new List { typeof(RpcMessage), typeof(RpcMessage), typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), + typeof(RpcMessage), }, new List { m_ServerNetworkManager }); WaitForMessageReceivedWithTimeTravel(m_ClientNetworkManagers.ToList()); @@ -327,6 +485,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); + for (var i = 1; i < 60; ++i) { TimeTravel(1f / 60f, 1); @@ -355,6 +537,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.transform.localPosition); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } TimeTravel(1f / 60f, 1); @@ -381,6 +587,30 @@ public void WhenServerChangesSmoothValue_ValuesAreLerped() AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position); AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale); AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, testChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, testChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, testChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, testChild.AuthoritativeState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.transform.localPosition); + AssertVectorsAreEquivalent(serverSetScale, otherChild.transform.localScale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.transform.localRotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AnticipatedState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AnticipatedState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AnticipatedState.Rotation); + + AssertVectorsAreEquivalent(serverSetPosition, otherChild.AuthoritativeState.Position); + AssertVectorsAreEquivalent(serverSetScale, otherChild.AuthoritativeState.Scale); + AssertQuaternionsAreEquivalent(serverSetRotation, otherChild.AuthoritativeState.Rotation); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 11c092a8a9..2a0c74258f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -1933,6 +1933,22 @@ protected IEnumerator WaitForConditionOrTimeOut(Func checkF }, timeOutHelper); } + /// + /// Waits until the specified condition returns true or a timeout occurs. + /// This overload allows the condition to provide additional error details via a . + /// + /// A delegate that takes a for error details and returns true when the desired condition is met. + /// the maximum times to check for the condition (default is 60). + protected void WaitForConditionOrTimeOutWithTimeTravel(Func checkForCondition, int maxTries = 60) + { + WaitForConditionOrTimeOutWithTimeTravel(() => + { + // Clear errorBuilder before each check to ensure the errorBuilder only contains information from the lastest run + m_InternalErrorLog.Clear(); + return checkForCondition(m_InternalErrorLog); + }, maxTries); + } + /// /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. /// @@ -2008,6 +2024,77 @@ bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) yield return WaitForConditionOrTimeOut(ValidateObjectsSpawnedOnAllClients, timeOutHelper); } + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The id of the to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(ulong networkObjectId, int maxTries = 60) + { + bool ValidateObjectSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObjectId}!"); + return false; + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectSpawnedOnAllClients, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(NetworkObject networkObject, int maxTries = 60) + { + var networkObjectId = networkObject.NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until the given NetworkObject is spawned on all clients or a timeout occurs. + /// + /// The containing a to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(GameObject gameObject, int maxTries = 60) + { + var networkObjectId = gameObject.GetComponent().NetworkObjectId; + WaitForSpawnedOnAllOrTimeOutWithTimeTravel(networkObjectId, maxTries); + } + + /// + /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. + /// + /// The list of s to wait for. + /// the maximum times to check for the condition (default is 60). + protected void WaitForSpawnedOnAllOrTimeOutWithTimeTravel(List networkObjects, int maxTries = 60) + { + bool ValidateObjectsSpawnedOnAllClients(StringBuilder errorLog) + { + foreach (var client in m_NetworkManagers) + { + foreach (var networkObject in networkObjects) + { + if (!client.SpawnManager.SpawnedObjects.ContainsKey(networkObject.NetworkObjectId)) + { + errorLog.Append($"Client-{client.LocalClientId} has not spawned Object-{networkObject.NetworkObjectId}!"); + return false; + } + } + } + return true; + } + + WaitForConditionOrTimeOutWithTimeTravel(ValidateObjectsSpawnedOnAllClients, maxTries); + } + /// /// Waits until all given NetworkObjects are spawned on all clients or a timeout occurs. /// From c538a0fedf0a0af2efbefd27b8a33f95a99e5959 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 14 Jul 2026 13:15:47 +0200 Subject: [PATCH 27/64] ci: Update of pinned trunk and ci stability check (#4074) * Added CodeCoverage job to run on PRs to enable coverage check * Updated trunk version * Updated CI deps and regenerated recipes * Removed develop branch from Renovate updates * Added renovate grouping * Disabled StopAndStartMotion test * fixes from tests 1) Updated Nintendo SDK 2) Corrected standards check for minimalproject 3) Increased the timeout of WaitForNetworkEvent for lower end devices * Disabled Switch due to build failures * fixes * corrected standards * typo * reverted change --- .github/renovate.json5 | 10 +- .../workflows/pr-description-validation.yml | 2 +- .github/workflows/pr-supervisor.yaml | 2 +- .yamato/_triggers.yml | 2 + .yamato/project.metafile | 41 +-- .yamato/wrench/api-validation-jobs.yml | 4 +- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 240 ++-------------- .yamato/wrench/promotion-jobs.yml | 68 +---- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 270 ++---------------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 10 +- .../Editor/CodeGen/NetworkBehaviourILPP.cs | 19 +- .../Components/NetworkRigidBodyBase.cs | 4 +- .../InterpolationStopAndStartMotionTest.cs | 2 +- .../Runtime/Physics/NetworkRigidbodyTest.cs | 4 +- .../UnityTransportConnectionTests.cs | 5 +- 18 files changed, 127 insertions(+), 562 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 1143236874..679cec5d47 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,5 +1,5 @@ { - "baseBranches": ["develop", "develop-2.0.0"], + "baseBranches": ["develop-2.0.0"], "dependencyDashboard": true, "$schema": "https://docs.renovatebot.com/renovate-schema.json", @@ -27,10 +27,12 @@ "unity-upm-package" ], "enabled": "true", - "schedule": [ - "every weekend" - ], + "schedule": ["every weekend"], "rollbackPrs": false, + }, + { + "groupName": "CI deps updates", + "matchPackagePatterns": ["^RecipeEngine", "actions/checkout", "package-ci/ubuntu-22.04"] } ], } diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index 9d2b4ccc75..740fadb922 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -21,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Check PR description uses: actions/github-script@v9 diff --git a/.github/workflows/pr-supervisor.yaml b/.github/workflows/pr-supervisor.yaml index ba8da21e93..2326b8da4e 100644 --- a/.github/workflows/pr-supervisor.yaml +++ b/.github/workflows/pr-supervisor.yaml @@ -25,7 +25,7 @@ jobs: timeout-minutes: 720 steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Wait and Verify Yamato Job Status env: diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index cf304709d7..b88547780c 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -89,6 +89,8 @@ pr_code_changes_checks: # Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} triggers: expression: |- (pull_request.comment eq "ngo" OR diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 6db694d80c..13443d6451 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -24,7 +24,7 @@ small_agent_platform: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.86.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.small @@ -39,13 +39,13 @@ test_platforms: default: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.86.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.large standalone: StandaloneLinux64 desktop: - name: ubuntu type: Unity::VM - image: package-ci/ubuntu-22.04:v4.86.0 + image: package-ci/ubuntu-22.04:v4.87.0 flavor: b1.large smaller_flavor: b1.large larger_flavor: b1.xlarge @@ -117,12 +117,17 @@ test_platforms: # flavor: b1.large # larger_flavor: b1.xlarge # standalone: PS5 - - name: switch - type: Unity::VM - image: package-ci/win10-switch:v4 - flavor: b1.large - larger_flavor: b1.xlarge - standalone: Switch + # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 + # The NintendoSDK on the win10-switch:v4 Bokken image is incomplete for the SDK + # version the newer editor Playback Engines require: 18_3_0 is missing the + # nintendo-switch-support lib the new toolchain links against, and 21_4_3 is missing the + # Tools\Graphics\NvnTools (GraphicsConverter) libraries needed to package textures. + # Neither is fixable from .yamato; re-enable once Package CI ships a complete SDK on the image. + # type: Unity::VM + # image: package-ci/win10-switch:v4 + # flavor: b1.large + # larger_flavor: b1.xlarge + # standalone: Switch - name: GameCoreXboxOne type: Unity::VM image: package-ci/win10-xbox:v4 @@ -148,13 +153,13 @@ test_platforms: # flavor: b1.large # larger_flavor: b1.xlarge # standalone: PS5 - - name: switch - type: Unity::console::switch - image: package-ci/win10-switch:v4 - flavor: b1.large - larger_flavor: b1.xlarge - standalone: Switch - base: win + # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 (incomplete NintendoSDK on win10-switch:v4 image) + # type: Unity::console::switch + # image: package-ci/win10-switch:v4 + # flavor: b1.large + # larger_flavor: b1.xlarge + # standalone: Switch + # base: win - name: GameCoreXboxOne type: Unity::console::xbox image: package-ci/win10-xbox:v4 @@ -181,10 +186,10 @@ validation_editors: - 6000.5 - 6000.6 - trunk - - f1298548e194f35ff7dfa6fee699d4464ab3919c + - 5fe7931aab8c4fff9274e15ef0800125c68b8d6a minimal: - 6000.0 -pinnedTrunk: f1298548e194f35ff7dfa6fee699d4464ab3919c +pinnedTrunk: 5fe7931aab8c4fff9274e15ef0800125c68b8d6a # Scripting backends used by Standalone RunTimeTests--------------------------------------------------- diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 1b9106d777..611902cc9a 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -60,8 +60,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index fec8ec8847..a40678465b 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -29,5 +29,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index 1029bfeb24..bafc711428 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,9 +16,6 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_macos13 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_ubuntu2204 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_4_-_win10 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_win10 @@ -30,7 +27,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -85,10 +82,10 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -143,10 +140,10 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -202,10 +199,10 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -260,10 +257,10 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -318,10 +315,10 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -377,185 +374,10 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - -# Functional tests for dependents found in the latest 6000.4 manifest (MacOS). -preview_apv_-_6000_4_-_macos13: - name: Preview APV - 6000.4 - macos13 - agent: - image: package-ci/macos-13:v4 - type: Unity::VM::osx - flavor: b1.xlarge - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -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.4 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - -# Functional tests for dependents found in the latest 6000.4 manifest (Ubuntu). -preview_apv_-_6000_4_-_ubuntu2204: - name: Preview APV - 6000.4 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -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.4 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - -# Functional tests for dependents found in the latest 6000.4 manifest (Windows). -preview_apv_-_6000_4_-_win10: - name: Preview APV - 6000.4 - win10 - agent: - image: package-ci/win10:v4 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -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.4 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - - command: python PythonScripts/editor_manifest_validator.py --version=6000.4 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (MacOS). preview_apv_-_6000_5_-_macos13: @@ -610,10 +432,10 @@ preview_apv_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). preview_apv_-_6000_5_-_ubuntu2204: @@ -668,10 +490,10 @@ preview_apv_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (Windows). preview_apv_-_6000_5_-_win10: @@ -727,10 +549,10 @@ preview_apv_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (MacOS). preview_apv_-_6000_6_-_macos13arm: @@ -786,10 +608,10 @@ preview_apv_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). preview_apv_-_6000_6_-_ubuntu2204: @@ -844,10 +666,10 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Windows). preview_apv_-_6000_6_-_win10: @@ -903,10 +725,10 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (MacOS). preview_apv_-_6000_7_-_macos13arm: @@ -962,10 +784,10 @@ preview_apv_-_6000_7_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (Ubuntu). preview_apv_-_6000_7_-_ubuntu2204: @@ -1020,10 +842,10 @@ preview_apv_-_6000_7_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (Windows). preview_apv_-_6000_7_-_win10: @@ -1079,8 +901,8 @@ preview_apv_-_6000_7_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 929cce45a2..06dda4cdcf 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -91,36 +91,6 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_macos13 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-macos13 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-macos13 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-win10 - unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 specific_options: packages: @@ -213,10 +183,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -303,36 +273,6 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_macos13 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-macos13 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-macos13 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_4_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.4-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.4-win10 - unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 specific_options: packages: @@ -425,9 +365,9 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 34d5f02642..343a1a9b85 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 9e05a163af..88d03e589b 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -69,10 +69,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -139,10 +139,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -209,10 +209,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -279,10 +279,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -349,10 +349,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -419,220 +419,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - macos13 (6000.4 - MacOS). -validate_-_netcode_gameobjects_-_6000_4_-_macos13: - name: Validate - netcode.gameobjects - 6000.4 - macos13 - agent: - image: package-ci/macos-13:v4 - type: Unity::VM::osx - flavor: b1.xlarge - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - ubuntu2204 (6000.4 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_4_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.4 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.4 - win10 (6000.4 - Windows). -validate_-_netcode_gameobjects_-_6000_4_-_win10: - name: Validate - netcode.gameobjects - 6000.4 - win10 - agent: - image: package-ci/win10:v4 - type: Unity::VM - flavor: b1.large - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.4/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -699,10 +489,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -769,10 +559,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -839,10 +629,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -910,10 +700,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -980,10 +770,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -1050,10 +840,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -1121,10 +911,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -1191,10 +981,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects @@ -1261,10 +1051,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.0.0 + UPMPVP_CONTEXT_WRENCH: 3.2.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.0.0 + Wrench: 3.2.1.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 585d0dfb22..96b58ebf4f 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "3.2.0.0", + "wrench_version": "3.2.1.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index ef0ba3d3bc..e47057e13d 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs index 797197ec86..3794b976b0 100644 --- a/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -2150,16 +2150,17 @@ private bool GetReadMethodForParameter(TypeReference paramType, out MethodRefere { typeMethod = GetFastBufferReaderReadMethod(k_ReadValueInPlaceMethodName, paramType); } - else + else if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") +#else + if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") #endif - if (paramType.Resolve().FullName == "Unity.Collections.NativeArray`1") - { - typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); - } - else - { - typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); - } + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueTempMethodName, paramType); + } + else + { + typeMethod = GetFastBufferReaderReadMethod(k_ReadValueMethodName, paramType); + } if (typeMethod != null) { methodRef = m_MainModule.ImportReference(typeMethod); diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs index 4094e084a8..7df6af4e59 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs @@ -861,10 +861,10 @@ private bool HasInterpolationTypeSet(InterpolationTypes interpolationType) } #endif #if COM_UNITY_MODULES_PHYSICS && !COM_UNITY_MODULES_PHYSICS2D - return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; #endif #if !COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D - return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate : m_InternalRigidbody.interpolation == RigidbodyInterpolation.Interpolate; + return interpolationType == InterpolationTypes.Extrapolate ? m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Extrapolate : m_InternalRigidbody2D.interpolation == RigidbodyInterpolation2D.Interpolate; #endif } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs index d064d7b3b2..3690d6fb6e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/InterpolationStopAndStartMotionTest.cs @@ -101,7 +101,7 @@ private bool WaitForInstancesToFinishInterpolation() return true; } - [UnityTest] + [UnityTest, Ignore("Disabled due to instability. Tracked by MTT-15432.")] public IEnumerator StopAndStartMotion() { m_IsSecondPass = false; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index 1a7d768831..bf5b77b42f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -1,4 +1,4 @@ -#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D +#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D using System.Collections; using System.Collections.Generic; using NUnit.Framework; @@ -236,4 +236,4 @@ protected override IEnumerator OnTearDown() } } } -#endif // COM_UNITY_MODULES_PHYSICS +#endif // COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs index abbfcad948..2bdce8f205 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/UnityTransportConnectionTests.cs @@ -429,7 +429,10 @@ public IEnumerator ServerDisconnectWithDataInQueue() } // Check client disconnection with data in send queue. - [UnityTest] + // Excluded on iOS: sending Unreliable data then immediately disconnecting is a race on slow + // iOS CI devices (the unreliable packet can be dropped), so it flakes with a "Timed out while + // waiting for network event" even after raising the mobile timeout. Tracked by MTT-15433. + [UnityTest, UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer })] public IEnumerator ClientDisconnectWithDataInQueue() { InitializeTransport(out m_Server, out m_ServerEvents); From 347c00f5b5d8074b896c8125114bcafa025d131e Mon Sep 17 00:00:00 2001 From: Emma Date: Wed, 15 Jul 2026 20:17:12 -0400 Subject: [PATCH 28/64] fix: InScenePlaced edge cases (#4073) * fix: InScenePlaced edge cases * Fix tests * Dont use scene origin if it isn't valid * Fix tests * Fix build error * Ensure spawn count is reset on NetworkManager shutdown * Fix broken test --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 +- .../Editor/InScenePlacedProcessor.cs | 15 + .../Editor/NetworkManagerHelper.cs | 10 +- .../BufferedLinearInterpolator.cs | 48 +- .../Runtime/Components/NetworkAnimator.cs | 58 +- .../Runtime/Components/NetworkTransform.cs | 42 +- .../Runtime/Configuration/NetworkPrefab.cs | 16 + .../Connection/NetworkConnectionManager.cs | 21 +- .../Runtime/Core/NetworkObject.cs | 150 ++- .../Runtime/Logging/NetworkLog.cs | 40 +- .../DefaultSceneManagerHandler.cs | 12 +- .../SceneManagement/NetworkSceneManager.cs | 34 +- .../Runtime/SceneManagement/SceneEventData.cs | 38 +- .../Runtime/Spawning/NetworkPrefabHandler.cs | 10 +- .../Runtime/Spawning/NetworkSpawnManager.cs | 58 +- .../NetworkClientAndPlayerObjectTests.cs | 11 +- .../NetworkObjectDestroyTests.cs | 3 +- .../Tests/Runtime/NetworkShowHideTests.cs | 9 +- .../NetworkTransformNonAuthorityTests.cs | 13 +- ...etworkPrefabHandlerSynchronizationTests.cs | 14 +- .../Runtime/TestHelpers/NetcodeLogAssert.cs | 24 + .../AddressableAssetSettings.asset | 31 +- .../AssetGroups/Default Local Group.asset | 7 +- .../Prefabs/AddressableTestObject.prefab | 36 +- .../Scenes/AddressableInSceneObject.unity | 199 +++ .../AddressableInSceneObject.unity.meta | 7 + .../Assets/Scenes/MultiprocessTestScene.unity | 1062 ----------------- .../Assets/Scripts/MoveDynamicSpawnInAwake.cs | 38 + .../Scripts/MoveDynamicSpawnInAwake.cs.meta | 2 + .../EmptyScene3.unity.meta | 8 +- .../InSceneNetworkObjectMovesToDDOL.unity | 198 +++ ...nSceneNetworkObjectMovesToDDOL.unity.meta} | 2 +- .../MoveADynamicObjectInAwake.unity | 170 +++ .../MoveADynamicObjectInAwake.unity.meta | 7 + .../Manual/Scripts/MoveInScenePlacedToDDOL.cs | 34 + .../Scripts/MoveInScenePlacedToDDOL.cs.meta | 2 + .../Tests/Manual/Scripts/NetworkPrefabPool.cs | 12 +- .../Scripts/NetworkPrefabPoolAdditive.cs | 12 +- .../Assets/Tests/Runtime/AddressablesTests.cs | 76 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 9 +- .../InScenePlacedProcessorTests.cs | 75 ++ .../InScenePlacedProcessorTests.cs.meta | 3 + .../NetworkObjectSceneMigrationTests.cs | 465 ++++---- .../NetworkSceneManagerStartupTests.cs | 205 ++++ .../NetworkSceneManagerStartupTests.cs.meta | 2 + .../Tests/Runtime/PrefabExtendedTests.cs | 5 - .../ProjectSettings/EditorBuildSettings.asset | 10 +- 47 files changed, 1733 insertions(+), 1574 deletions(-) create mode 100644 testproject/Assets/Scenes/AddressableInSceneObject.unity create mode 100644 testproject/Assets/Scenes/AddressableInSceneObject.unity.meta delete mode 100644 testproject/Assets/Scenes/MultiprocessTestScene.unity create mode 100644 testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs create mode 100644 testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity rename testproject/Assets/{Scenes/MultiprocessTestScene.unity.meta => Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta} (74%) create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity create mode 100644 testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta create mode 100644 testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs create mode 100644 testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs create mode 100644 testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ba73356e80..ff9f37f6a8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,12 +22,14 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag (#3995) +- Issue where scene migration in a distributed authority session would throw an exception on clients migrating their owned `NetworkObject` instances to a different scene.(#4086) +- Issue where migrating a dynamically spawned or in-scene placed `NetworkObject` into a different scene during awake could result in that spawned instance to not be synchronized. (#4086) - Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) - Issue where the active scene was not being serialized as the 1st scene which could result in various errors including a soft synchronization error if, on the client-side, other synchronized scenes had already been loaded prior to the active scene, which is always loaded as `LoadSceneMode.SingleMode` when client synchronization is set to `LoadSceneMode.SingleMode`, resulting in the previously loaded scene(s) to be unloaded. (#4065) - Issue when FastBufferReader is attempting to read a string and all or a portion of the character count has already been read by user script, it could read a character length that results in a negative byte length which could result in an editor crash. (#4052) - Issue where NetworkRigidbodyBase was not applying rotation correctly when using Rigidbody2D. (#4012) - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) +- Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag. (#3995) ### Security diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs index 423ff4a65c..f7529de12c 100644 --- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -1,3 +1,4 @@ +using Unity.Netcode.Logging; using UnityEditor; using UnityEditor.Build; using UnityEditor.Build.Reporting; @@ -19,8 +20,22 @@ internal class SetInScenePlaced : IProcessSceneWithReport public int callbackOrder => 0; public void OnProcessScene(Scene scene, BuildReport report) { + var log = new ContextualLogger(); + log.AddInfo(scene.name, scene.handle); foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) { + if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle) + { + log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject)); + continue; + } + + if (networkObject.HasBeenSpawned) + { + log.Error(new Context(LogLevel.Normal, $"Processing {nameof(NetworkObject)} that has already been spawned! This should not be possible. Skipping processing.").AddNetworkObject(networkObject)); + continue; + } + networkObject.InScenePlaced = true; } } diff --git a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs index a3224e6471..66ddb2b6f0 100644 --- a/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs +++ b/com.unity.netcode.gameobjects/Editor/NetworkManagerHelper.cs @@ -195,11 +195,11 @@ public bool NotifyUserOfNestedNetworkManager(NetworkManager networkManager, bool { return isParented; } - else // If we are no longer a child, then we can remove ourself from this list - if (transform.root == gameObject.transform) - { - s_LastKnownNetworkManagerParents.Remove(networkManager); - } + else if (transform.root == gameObject.transform) + { + // If we are no longer a child, then we can remove ourself from this list + s_LastKnownNetworkManagerParents.Remove(networkManager); + } } if (!EditorApplication.isUpdating && isParented) { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 294cd9989f..8ee288b02f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -467,20 +467,20 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime InterpolateState.CurrentValue = InterpolateState.NextValue; } } - else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (m_BufferQueue.Count == 0) + // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + else if (m_BufferQueue.Count == 0) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) { - // When the delta between the time sent and the current tick latency time-window is greater than the max delta time - // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), - // we will want to reset the interpolator with the current known value. This prevents the next received state update's - // time to be calculated against the last calculated time which if there is an extended period of time between the two - // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then - // starts moving again). - if ((tickLatencyAsTime - InterpolateState.Target.Value.TimeSent) > InterpolateState.MaxDeltaTime + minDeltaTime) - { - InterpolateState.Reset(InterpolateState.CurrentValue); - } + InterpolateState.Reset(InterpolateState.CurrentValue); } + } } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; @@ -593,20 +593,20 @@ public T Update(float deltaTime, double renderTime, double serverTime) // Determine if we have reached our target InterpolateState.TargetReached = IsApproximately(InterpolateState.CurrentValue, InterpolateState.Target.Value.Item, GetPrecision()); } - else // If the target is reached and we have no more state updates, we want to check to see if we need to reset. - if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) + // If the target is reached and we have no more state updates, we want to check to see if we need to reset. + else if (InterpolateState.TargetReached && m_BufferQueue.Count == 0) + { + // When the delta between the time sent and the current tick latency time-window is greater than the max delta time + // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), + // we will want to reset the interpolator with the current known value. This prevents the next received state update's + // time to be calculated against the last calculated time which if there is an extended period of time between the two + // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then + // starts moving again). + if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. { - // When the delta between the time sent and the current tick latency time-window is greater than the max delta time - // plus the minimum delta time (a rough estimate of time to wait before we consider rate of change equal to zero), - // we will want to reset the interpolator with the current known value. This prevents the next received state update's - // time to be calculated against the last calculated time which if there is an extended period of time between the two - // it would cause a large delta time period between the two states (i.e. it stops moving for a second or two and then - // starts moving again). - if ((renderTime - InterpolateState.Target.Value.TimeSent) > 0.3f) // If we haven't recevied anything within 300ms, assume we stopped motion. - { - InterpolateState.Reset(InterpolateState.CurrentValue); - } + InterpolateState.Reset(InterpolateState.CurrentValue); } + } m_NbItemsReceivedThisFrame = 0; return InterpolateState.CurrentValue; } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index f87eee73ab..cdb25aa598 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -1180,26 +1180,25 @@ internal void CheckForAnimatorChanges() { SendAnimStateRpc(m_AnimationMessage); } + else if (!IsServer && IsOwner) + { + SendServerAnimStateRpc(m_AnimationMessage); + } else - if (!IsServer && IsOwner) - { - SendServerAnimStateRpc(m_AnimationMessage); - } - else + { + // Just notify all remote clients and not the local server + m_TargetGroup.Clear(); + foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds) { - // Just notify all remote clients and not the local server - m_TargetGroup.Clear(); - foreach (var clientId in LocalNetworkManager.ConnectionManager.ConnectedClientIds) + if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) { - if (clientId == LocalNetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId)) - { - continue; - } - m_TargetGroup.Add(clientId); + continue; } - m_RpcParams.Send.Target = m_TargetGroup.Target; - SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams); + m_TargetGroup.Add(clientId); } + m_RpcParams.Send.Target = m_TargetGroup.Target; + SendClientAnimStateRpc(m_AnimationMessage, m_RpcParams); + } } } @@ -1346,25 +1345,24 @@ private unsafe void WriteParameters(ref FastBufferWriter writer) BytePacker.WriteValuePacked(writer, (uint)valueInt); } } - else - if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterBool) + { + var valueBool = m_Animator.GetBool(hash); + fixed (void* value = cacheValue.Value) { - var valueBool = m_Animator.GetBool(hash); - fixed (void* value = cacheValue.Value) - { - UnsafeUtility.WriteArrayElement(value, 0, valueBool); - BytePacker.WriteValuePacked(writer, valueBool); - } + UnsafeUtility.WriteArrayElement(value, 0, valueBool); + BytePacker.WriteValuePacked(writer, valueBool); } - else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) + } + else if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterFloat) + { + var valueFloat = m_Animator.GetFloat(hash); + fixed (void* value = cacheValue.Value) { - var valueFloat = m_Animator.GetFloat(hash); - fixed (void* value = cacheValue.Value) - { - UnsafeUtility.WriteArrayElement(value, 0, valueFloat); - BytePacker.WriteValuePacked(writer, valueFloat); - } + UnsafeUtility.WriteArrayElement(value, 0, valueFloat); + BytePacker.WriteValuePacked(writer, valueFloat); } + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 63b43544bd..152cea793b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -2515,24 +2515,24 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is } } } - else // Just apply the full local scale when synchronizing - if (SynchronizeScale) + // Just apply the full local scale when synchronizing + else if (SynchronizeScale) + { + var localScale = CachedTransform.localScale; + if (!UseHalfFloatPrecision) { - var localScale = CachedTransform.localScale; - if (!UseHalfFloatPrecision) - { - networkState.ScaleX = localScale.x; - networkState.ScaleY = localScale.y; - networkState.ScaleZ = localScale.z; - } - else - { - networkState.Scale = localScale; - } - flagStates.MarkChanged(AxialType.Scale, true); - isScaleDirty = true; + networkState.ScaleX = localScale.x; + networkState.ScaleY = localScale.y; + networkState.ScaleZ = localScale.z; } + else + { + networkState.Scale = localScale; + } + flagStates.MarkChanged(AxialType.Scale, true); + isScaleDirty = true; + } isDirty |= isPositionDirty || isRotationDirty || isScaleDirty; if (isDirty) @@ -3488,12 +3488,12 @@ private void NonAuthorityFinalizeSynchronization() child.InternalInitialization(); } } - else // Otherwise, just run through standard synchronization of this instance - if (!CanCommitToTransform) - { - ApplySynchronization(); - InternalInitialization(); - } + // Otherwise, just run through standard synchronization of this instance + else if (!CanCommitToTransform) + { + ApplySynchronization(); + InternalInitialization(); + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs index c6f30d2835..b1c071e705 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs @@ -166,6 +166,22 @@ public bool Validate(int index = -1) return false; } + if (networkObject.InScenePlaced) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} InScenePlaced {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!"); + } + } + + if (networkObject.IsSpawned) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{NetworkPrefabHandler.PrefabDebugHelper(this)} Currently spawned {nameof(NetworkObject)} is being registered as a Prefab. This can cause issues!"); + } + } + return true; } diff --git a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs index dd22801412..3ff131131f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Connection/NetworkConnectionManager.cs @@ -967,19 +967,18 @@ internal void ProcessClientsToDisconnect() { return (true, playerPrefabHash.Value); } - else - if (NetworkManager.NetworkConfig.PlayerPrefab != null) + else if (NetworkManager.NetworkConfig.PlayerPrefab != null) + { + var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); + if (networkObject != null) { - var networkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent(); - if (networkObject != null) - { - return (true, networkObject.GlobalObjectIdHash); - } - else - { - NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!")); - } + return (true, networkObject.GlobalObjectIdHash); } + else + { + NetworkManager.Log.Error(new Logging.Context(LogLevel.Error, $"Player prefab {NetworkManager.NetworkConfig.PlayerPrefab.name} has no {nameof(NetworkObject)}!")); + } + } return (false, 0); } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6a9397e48e..52e4dad4f1 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -5,14 +5,11 @@ using System.Runtime.CompilerServices; using System.Text; using Unity.Netcode.Components; +using Unity.Netcode.Logging; using Unity.Netcode.Runtime; #if UNITY_EDITOR using UnityEditor; -#if UNITY_2021_2_OR_NEWER using UnityEditor.SceneManagement; -#else -using UnityEditor.Experimental.SceneManagement; -#endif #endif using UnityEngine; using UnityEngine.SceneManagement; @@ -106,6 +103,9 @@ public uint PrefabIdHash /// public NetworkObject CurrentParent { get; private set; } + private int m_SpawnCount; + internal bool HasBeenSpawned => m_SpawnCount > 0; + #if UNITY_EDITOR private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}"; @@ -129,9 +129,6 @@ public uint PrefabIdHash // The InContext or InIsolation edit mode network prefab scene instance of the prefab asset (s_PrefabAsset). private static NetworkObject s_PrefabInstance; - private static bool s_DebugPrefabIdGeneration; - - [ContextMenu("Refresh In-Scene Prefab Instances")] internal void RefreshAllPrefabInstances() { @@ -328,7 +325,7 @@ internal void OnValidate() /// private void CheckForInScenePlaced() { - if (gameObject.scene.IsValid() && gameObject.scene.isLoaded && gameObject.scene.buildIndex >= 0) + if (gameObject.scene.IsValid() && gameObject.scene.buildIndex >= 0) { if (PrefabUtility.IsPartOfAnyPrefab(this)) { @@ -348,6 +345,9 @@ private void CheckForInScenePlaced() SetSceneObjectStatus(true); #pragma warning restore CS0618 // Type or member is obsolete + // We go ahead and set this for "typical in-scene placed" usage patterns so this is serialized + InScenePlaced = true; + // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; } @@ -1228,15 +1228,35 @@ public bool HasOwnershipStatus(OwnershipStatus status) /// /// Gets if the object is a SceneObject. /// + /// + /// This method is marked for deprecation.
+ /// Use instead. + ///
[Obsolete("Use InScenePlaced instead")] public bool? IsSceneObject { get; internal set; } + /// - /// True if this object is placed in a scene; false otherwise. + /// The serialized value. /// [field: HideInInspector] [field: SerializeField] - public bool InScenePlaced { get; internal set; } + private bool m_InScenePlaced; + + /// + /// True if this object is placed in a scene; false otherwise. + /// + public bool InScenePlaced + { + get + { + return m_InScenePlaced; + } + internal set + { + m_InScenePlaced = value; + } + } /// /// Sets whether this NetworkObject was instantiated as part of a scene @@ -1448,12 +1468,16 @@ internal Scene SceneOrigin set { + if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded) + { + SceneOriginHandle = value.handle; + } + // The scene origin should only be set once. // Once set, it should never change. - if (SceneOriginHandle.IsEmpty() && value.IsValid() && value.isLoaded) + if (!m_SceneOrigin.IsValid()) { m_SceneOrigin = value; - SceneOriginHandle = value.handle; } } } @@ -1782,7 +1806,7 @@ private void OnDestroy() } [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject) + private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool playerObject) { if (NetworkManagerOwner == null) { @@ -1853,7 +1877,28 @@ internal void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool pla } } - if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), InScenePlaced, playerObject, ownerClientId, destroyWithScene)) + + // Calculate the legacy IsSceneObject value as the public field is obsolete with warning + // We can't break the public behavior of the field. +#pragma warning disable CS0618 // Type or member is obsolete + var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value; +#pragma warning restore CS0618 // Type or member is obsolete + + // If SpawnInternal is being called on an object that is marked as InScenePlaced, + // The scene object was never automatically spawned when the scene was loaded. + // Count this object as a dynamically spawned object. + // TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects + if (InScenePlaced && !HasBeenSpawned) + { + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer) + { + Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this); + } + + InScenePlaced = false; + } + + if (!NetworkManagerOwner.SpawnManager.AuthorityLocalSpawn(this, NetworkManagerOwner.SpawnManager.GetNetworkObjectId(), legacyIsSceneObject, playerObject, ownerClientId, destroyWithScene)) { if (NetworkManagerOwner.LogLevel <= LogLevel.Normal) { @@ -2053,6 +2098,7 @@ internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClie // When spawned, previous owner is always the first assigned owner PreviousOwnerId = ownerClientId; m_HasAuthority = NetworkManagerOwner.DistributedAuthorityMode ? OwnerClientId == NetworkManagerOwner.LocalClientId : NetworkManagerOwner.IsServer; + m_SpawnCount++; IsSpawned = true; // If this is the player, and the client is the owner, then lock ownership by default @@ -2094,6 +2140,17 @@ internal void SetupOnSpawn(ulong networkId, bool isPlayerObject, ulong ownerClie } } + /// + /// Resets this NetworkObject at the end of a session. + /// Ensures scene objects are ready to be reused + /// + internal void ResetOnShutdown() + { + NetworkLog.InternalAssert(NetworkManager.ShutdownInProgress, "This method should only be called while the NetworkManager is shutting down"); + m_SpawnCount = 0; + ResetOnDespawn(); + } + internal void ResetOnDespawn() { // Always clear out the observers list when despawned @@ -2654,26 +2711,26 @@ internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpa m_CachedWorldPositionStays = false; 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 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); + // If the parent still isn't spawned add this to the orphaned children and return false + else 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 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 - SetCachedParent(parentNetworkObject.transform); + // Set the cached parent + SetCachedParent(parentNetworkObject.transform); - return true; - } + return true; + } } // If we are removing the parent or our latest parent is not set, then remove the parent. @@ -3466,11 +3523,7 @@ internal static NetworkObject DeserializeAndSpawnObject(in SerializedObject seri if (serializedObject.NetworkObjectId == default) { - if (networkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{nameof(GlobalObjectIdHash)}={serializedObject.Hash}] Received spawn request with invalid {nameof(NetworkObjectId)} {serializedObject.NetworkObjectId}. This should not happen!"); - } - + NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"Received spawn request with invalid {nameof(NetworkObjectId)}. This should not happen!").AddInfo(nameof(NetworkObjectId), serializedObject.NetworkObjectId).AddInfo(nameof(GlobalObjectIdHash), serializedObject.Hash)); reader.Seek(endOfSynchronizationData); return null; } @@ -3602,6 +3655,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) return; } + // Don't create notification if there is a scene event in progress. if (NetworkManagerOwner.SceneManager.IsSceneEventInProgress()) { return; @@ -3627,17 +3681,17 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) NetworkSceneHandle = SceneOriginHandle; } } - else // Otherwise, the client did not find the client to server scene handle - if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) - { - // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject - // into, but that scenario seemed very edge case and under most instances a user should be notified that this - // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to - // the server-side too. - NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " + - $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + - $"has no associated server side (network) scene handle!"); - } + // Otherwise, the client did not find the client to server scene handle + else if (NetworkManagerOwner.LogLevel <= LogLevel.Developer) + { + // There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject + // into, but that scenario seemed very edge case and under most instances a user should be notified that this + // server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to + // the server-side too. + NetworkLog.LogWarningServer($"[Client-{NetworkManagerOwner.LocalClientId}][{name}] Server - " + + $"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" + + $"has no associated server side (network) scene handle!"); + } OnMigratedToNewScene?.Invoke(); // Only the authority side will notify clients of non-parented NetworkObject scene changes diff --git a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs index df08bee61f..66a7df1f96 100644 --- a/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs +++ b/com.unity.netcode.gameobjects/Runtime/Logging/NetworkLog.cs @@ -103,6 +103,8 @@ internal static void ConfigureIntegrationTestLogging(NetworkManager networkManag /// The message to log [HideInCallstack] public static void LogErrorServer(string message) => s_Log.ErrorServer(new Context(LogLevel.Error, message, true)); + [HideInCallstack] + internal static void LogErrorServer(Context context) => s_Log.ErrorServer(context); internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) { @@ -115,14 +117,14 @@ internal static LogType GetMessageLogType(UnityEngine.LogType engineLogType) }; } - private const string k_SenderId = "SenderId"; internal static Context BuildContextForServerMessage([NotNull] NetworkManager networkManager, LogLevel level, ulong senderId, string message) { - var ctx = new Context(level, message, true).AddInfo(k_SenderId, senderId); - if (TryGetNetworkObjectName(networkManager, message, out var name)) + var ctx = new Context(level, message, true).AddTag("Received log from client").AddInfo(k_SenderId, senderId); + var networkObject = TryGetNetworkObject(networkManager, message); + if (networkObject != null) { - ctx.AddTag(name); + ctx.AddNetworkObject(networkObject); } return ctx; } @@ -135,29 +137,41 @@ internal enum LogType : byte None } - private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}=(\d+)\]", RegexOptions.Compiled); + private static readonly Regex k_NetworkObjectId = new($@"\[{nameof(NetworkObject.NetworkObjectId)}:(\d+)\]", RegexOptions.Compiled); + private static readonly Regex k_GlobalObjectIdHash = new($@"\[{nameof(NetworkObject.GlobalObjectIdHash)}:(\d+)\]", RegexOptions.Compiled); [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static bool TryGetNetworkObjectName([NotNull] NetworkManager networkManager, string message, out string name) + private static NetworkObject TryGetNetworkObject([NotNull] NetworkManager networkManager, string message) { - name = null; + if (k_NetworkObjectId.IsMatch(message)) + { + var stringId = k_NetworkObjectId.Match(message).Groups[1].Value; + if (ulong.TryParse(stringId, out var networkObjectId) && networkObjectId > 0 && networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var networkObject)) + { + return networkObject; + } + } + if (!k_GlobalObjectIdHash.IsMatch(message)) { - return false; + return null; } var stringHash = k_GlobalObjectIdHash.Match(message).Groups[1].Value; if (!ulong.TryParse(stringHash, out var globalObjectIdHash)) { - return false; + return null; } - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(globalObjectIdHash, out var networkObject)) + NetworkObject matchingObject = null; + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) { - return false; + if (networkObject.GlobalObjectIdHash == globalObjectIdHash) + { + matchingObject = networkObject; + } } - name = networkObject.name; - return true; + return matchingObject; } [HideInCallstack] diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs index dba918a679..f7ce87d521 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/DefaultSceneManagerHandler.cs @@ -359,14 +359,14 @@ public void SetClientSynchronizationMode(ref NetworkManager networkManager, Load } return; } - else // Warn users if they are changing this after there are clients already connected and synchronized - if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) + // Warn users if they are changing this after there are clients already connected and synchronized + else if (!networkManager.DistributedAuthorityMode && networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode) + { + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); - } + NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!"); } + } // For additive client synchronization, we take into consideration scenes // already loaded. diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index c0d4b44aee..e3a5d2cfe4 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -1034,7 +1034,7 @@ internal void SetTheSceneBeingSynchronized(NetworkSceneHandle serverSceneHandle) // Most common scenario for DontDestroyOnLoad is when NetworkManager is set to not be destroyed if (serverSceneHandle == DontDestroyOnLoadScene.handle) { - SceneBeingSynchronized = NetworkManager.gameObject.scene; + SceneBeingSynchronized = DontDestroyOnLoadScene; return; } else @@ -2260,8 +2260,6 @@ private void SynchronizeNetworkObjectScene() { networkObject.SceneOriginHandle = ServerSceneHandleToClientSceneHandle[networkObject.NetworkSceneHandle]; - - // If the NetworkObject does not have a parent and is not in the same scene as it is on the server side, then find the right scene // and move it to that scene. if (networkObject.gameObject.scene.handle != networkObject.SceneOriginHandle && networkObject.transform.parent == null) @@ -2269,11 +2267,6 @@ private void SynchronizeNetworkObjectScene() if (ScenesLoaded.ContainsKey(networkObject.SceneOriginHandle)) { var scene = ScenesLoaded[networkObject.SceneOriginHandle]; - if (scene == DontDestroyOnLoadScene) - { - Debug.Log($"{networkObject.gameObject.name} migrating into DDOL!"); - } - SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene); } else if (NetworkManager.LogLevel <= LogLevel.Normal) @@ -2897,6 +2890,12 @@ internal bool IsSceneUnloading(NetworkObject networkObject) /// internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) { + if (networkObject.NetworkManagerOwner != NetworkManager) + { + Debug.Log($"!!!!!!!!!!!!! Integration test is registering for scene migration for instances outside of the bounds of this NetworkManager context !!!!!!!!!!!!!"); + return; + } + // Really, this should never happen but in case it does if (!networkObject.HasAuthority) { @@ -2920,7 +2919,7 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) // Ignore if the scene is the currently active scene and the NetworkObject is auto synchronizing/migrating // to the currently active scene. - if (networkObject.gameObject.scene == SceneManager.GetActiveScene() && networkObject.ActiveSceneSynchronization) + if (networkObject.gameObject.scene.name == SceneManager.GetActiveScene().name && networkObject.ActiveSceneSynchronization) { return; } @@ -2929,6 +2928,13 @@ internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject) // Note: This does not apply to SceneEventType.Synchronize since synchronization isn't a global connected client event. if (IsSceneEventInProgress()) { + Debug.Log($"{networkObject.name} scene event in progress -- ignoring!"); + return; + } + + if (IsSceneUnloading(networkObject)) + { + Debug.Log($"{networkObject.name} scene unloading in progress -- ignoring!"); return; } @@ -3053,7 +3059,15 @@ internal void CheckForAndSendNetworkObjectSceneChanged() // Some NetworkObjects still exist, send the message var sceneEvent = BeginSceneEvent(); sceneEvent.SceneEventType = SceneEventType.ObjectSceneChanged; - SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + // SendSceneEventData can throw an exception. We need to wrap this and recover from the exception gracefully. + try + { + SendSceneEventData(sceneEvent.SceneEventId, NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.LocalClientId).ToArray()); + } + catch (Exception ex) + { + Debug.LogException(ex); + } ObjectsMigratedIntoNewScene.Clear(); EndSceneEvent(sceneEvent.SceneEventId); } diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs index 2575bd6bfd..6195fa2335 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs @@ -1204,31 +1204,51 @@ private void ReadSceneEventProgressDone(FastBufferReader reader) private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer) { var sceneManager = m_NetworkManager.SceneManager; - var ownerId = m_NetworkManager.LocalClientId; + var networkManagerClientId = m_NetworkManager.LocalClientId; if (IsForwarding) { - ownerId = m_OwnerId; + networkManagerClientId = m_OwnerId; } // Write the owner identifier - writer.WriteValueSafe(ownerId); + writer.WriteValueSafe(networkManagerClientId); - // Write the number of scene handles - writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count); + // Create a place holder for the number of entries written. + // Distributed authority this could end up being just a single entry for + // one of several scenes loaded. As such, we need to count how many entries + // are actually written. + var countPosition = writer.Position; + writer.WriteValueSafe(0); + var entriesWritten = 0; foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene) { - if (!sceneHandleObjects.Value.ContainsKey(ownerId)) + // Since these are separated by scene then owner, there could be scenes that have + // no changes. + if (!sceneHandleObjects.Value.ContainsKey(networkManagerClientId)) { - throw new Exception($"Trying to send object scene migration for Client-{ownerId} but the client has no entries to send!"); + continue; } // Write the scene handle writer.WriteValueSafe(sceneHandleObjects.Key); // Write the number of NetworkObjectIds to expect - writer.WriteValueSafe(sceneHandleObjects.Value[ownerId].Count); - foreach (var networkObject in sceneHandleObjects.Value[ownerId]) + writer.WriteValueSafe(sceneHandleObjects.Value[networkManagerClientId].Count); + foreach (var networkObject in sceneHandleObjects.Value[networkManagerClientId]) { writer.WriteValueSafe(networkObject.NetworkObjectId); } + entriesWritten++; + } + if (entriesWritten == 0) + { + throw new Exception($"Trying to send object scene migration for Client-{networkManagerClientId} but the client has no entries to send!"); + } + else + { + // Write the number of entries written + var endPosition = writer.Position; + writer.Seek(countPosition); + writer.WriteValueSafe(entriesWritten); + writer.Seek(endPosition); } } diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs index ece7561161..19444a4ddb 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs @@ -341,11 +341,11 @@ internal void HandleNetworkPrefabDestroy(NetworkObject networkObjectInstance) prefabInstanceHandler.Destroy(networkObjectInstance); } } - else // Otherwise the NetworkObject is the source NetworkPrefab - if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) - { - prefabInstanceHandler.Destroy(networkObjectInstance); - } + // Otherwise the NetworkObject is the source NetworkPrefab + else if (m_PrefabAssetToPrefabHandler.TryGetValue(networkObjectInstanceHash, out var prefabInstanceHandler)) + { + prefabInstanceHandler.Destroy(networkObjectInstance); + } } /// diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 2f8ae30d03..74d0ee121e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -907,10 +907,7 @@ internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ow // If not, then there is an issue (user possibly didn't register the prefab properly?) if (networkPrefabReference == null) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{nameof(globalObjectIdHash)}={globalObjectIdHash}] Failed to create object locally. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with {NetworkManager.name}?"); - } + NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Failed to create object locally. {nameof(NetworkPrefab)} could not be found. Is the prefab registered with this {nameof(NetworkManager)}").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), globalObjectIdHash).AddTag(NetworkManager.name)); return null; } @@ -957,6 +954,7 @@ internal NetworkObject InstantiateNetworkPrefab([NotNull] GameObject networkPref /// /// For most cases this is client-side only, except when the server is spawning a player. /// + [return: MaybeNull] internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject serializedObject, byte[] instantiationData = null) { NetworkObject networkObject = null; @@ -977,11 +975,7 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject s networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle); if (networkObject == null) { - if (NetworkLog.CurrentLogLevel <= LogLevel.Error) - { - NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!"); - } - + NetworkLog.LogErrorServer(new Context(LogLevel.Error, $"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure!").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), globalObjectIdHash)); return null; } @@ -1122,7 +1116,14 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n return false; } - if (!sceneObject && NetworkManager.LogLevel <= LogLevel.Error) + if (playerObject && networkObject.InScenePlaced) + { + NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name)); + networkObject.InScenePlaced = false; + } + NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); + + if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error) { var networkObjectChildren = networkObject.GetComponentsInChildren(); if (networkObjectChildren.Length > 1) @@ -1137,7 +1138,7 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n networkObject.NetworkManagerOwner = NetworkManager; networkObject.InvokeBehaviourNetworkPreSpawn(); - if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && sceneObject) + if (NetworkManager.DistributedAuthorityMode && NetworkManager.NetworkConfig.EnableSceneManagement && networkObject.InScenePlaced) { networkObject.SceneOriginHandle = networkObject.gameObject.scene.handle; networkObject.NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[networkObject.gameObject.scene.handle]; @@ -1175,10 +1176,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize { if (SpawnedObjects.ContainsKey(serializedObject.NetworkObjectId)) { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogWarning($"Trying to spawn a {nameof(NetworkObject)} with a {nameof(NetworkObject.NetworkObjectId)} of {serializedObject.NetworkObjectId} but an object with that id is already in the spawned list. This should not happen!"); - } + NetworkManager.Log.Warning(new Context(LogLevel.Error, $"Trying to spawn a {nameof(NetworkObject)} but an object with that {nameof(NetworkObject.NetworkObjectId)} is already in the spawned list. This should not happen!")); networkObject = null; return false; } @@ -1195,14 +1193,11 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize // Log the error that the NetworkObject failed to construct if (networkObject == null) { - if (NetworkManager.LogLevel <= LogLevel.Normal) - { - NetworkLog.LogError($"[{nameof(NetworkObject.GlobalObjectIdHash)}={serializedObject.Hash}] Failed to spawn {nameof(NetworkObject)}!"); - } - + NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"Failed to spawn {nameof(NetworkObject)}!").AddInfo(nameof(NetworkObject.GlobalObjectIdHash), serializedObject.Hash)); return false; } + networkObject.InScenePlaced = serializedObject.IsSceneObject; networkObject.NetworkManagerOwner = NetworkManager; // This will get set again when the NetworkObject is spawned locally, but we set it here ahead of spawning @@ -1227,11 +1222,7 @@ internal bool NonAuthorityLocalSpawn(in NetworkObject.SerializedObject serialize if (networkObject.IsSpawned) { - if (NetworkManager.LogLevel <= LogLevel.Error) - { - NetworkLog.LogErrorServer($"[{networkObject.name}] Object-{networkObject.NetworkObjectId} is already spawned!"); - } - + NetworkManager.Log.ErrorServer(new Context(LogLevel.Normal, $"{nameof(NetworkObject)} is already spawned!").AddNetworkObject(networkObject)); // Mark the spawn as a success if the object is already spawned return true; } @@ -1455,13 +1446,13 @@ internal void DespawnObject(NetworkObject networkObject, bool destroyObject = fa internal void ServerResetShutdownStateForSceneObjects() { var networkObjects = FindObjects.ByType(orderByIdentifier: true, includeInactive: true); - foreach (var sobj in networkObjects) + foreach (var obj in networkObjects) { - if (!sobj.InScenePlaced) + // Only reset things that have been spawned are in-scene placed, and is assigned to the relative NetworkManager (for integration testing purposes) + if (obj.HasBeenSpawned && obj.InScenePlaced && obj.NetworkManagerOwner == NetworkManager) { - continue; + obj.ResetOnShutdown(); } - sobj.ResetOnDespawn(); } } @@ -1725,11 +1716,10 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro NetworkLog.LogError($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} could not be moved to the root when its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} was being destroyed"); } } - else - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); - } + else if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) + { + NetworkLog.LogWarning($"{nameof(NetworkObject)} #{spawnedNetObj.NetworkObjectId} moved to the root because its parent {nameof(NetworkObject)} #{networkObject.NetworkObjectId} is destroyed"); + } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 461d85a757..929592a9b0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -246,12 +246,11 @@ private bool ValidateAllPlayerObjects() m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkClient.PlayerObject)} is null!"); success = false; } - else - if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) - { - m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); - success = false; - } + else if (!client.SpawnManager.PlayerObjects.Contains(client.LocalClient.PlayerObject)) + { + m_ErrorLogLevel1.AppendLine($"[Client-{client.LocalClientId}] Local {nameof(NetworkSpawnManager.PlayerObjects)} does not contain {client.LocalClient.PlayerObject.name}!"); + success = false; + } } return success; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 0e226ec115..f27f28d53f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -193,7 +194,7 @@ private bool HaveLogsBeenReceived() return false; } - if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, $"[Netcode] [SenderId:{m_ClientNetworkManagers[0].LocalClientId}] [Invalid Destroy][{m_ClientPlayerName}][NetworkObjectId:{m_ClientNetworkObjectId}] Destroy a spawned {nameof(NetworkObject)} on a non-host client is not valid. Call Destroy or Despawn on the server/host instead.")) + if (!NetcodeLogAssert.HasLogBeenReceived(LogType.Error, new Regex($"SenderId:{m_ClientNetworkManagers[0].LocalClientId}]"))) { return false; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs index 43f6eac7de..1ef0a13eb4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkShowHideTests.cs @@ -488,11 +488,10 @@ private bool Object1IsNotVisibileToClient() m_ErrorLog.AppendLine($"{m_NetSpawnedObject1.name} is still visible to Client-{m_ClientWithoutVisibility}!"); } } - else - if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) - { - m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); - } + else if (client.SpawnManager.SpawnedObjects[m_NetSpawnedObject1.NetworkObjectId].IsNetworkVisibleTo(m_ClientWithoutVisibility)) + { + m_ErrorLog.AppendLine($"Local instance of {m_NetSpawnedObject1.name} on Client-{client.LocalClientId} thinks Client-{m_ClientWithoutVisibility} still has visibility!"); + } } return m_ErrorLog.Length == 0; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs index 5705d21942..350f312973 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformNonAuthorityTests.cs @@ -418,13 +418,12 @@ private bool ShouldSyncAxis(bool first, bool second, bool lastValue) // make the last one disabled. return false; } - else - if (!first && !second) - { - // If both are disabled, then make the - // last one enabled. - return true; - } + else if (!first && !second) + { + // If both are disabled, then make the + // last one enabled. + return true; + } } return Random.Range(start, 100) >= 50 ? false : true; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index d135f29b91..3124dec5f7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -1,4 +1,5 @@ using System.Collections; +using System.Text.RegularExpressions; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -42,7 +43,10 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Check the invalid object spawns on the authority, expect an error from non-authority. LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); - LogAssert.Expect(LogType.Error, $"[Netcode] [GlobalObjectIdHash={exceptionObject.GlobalObjectIdHash}] Failed to spawn NetworkObject!"); + LogAssert.Expect(LogType.Error, new Regex("Failed to spawn NetworkObject!")); + // Authority should receive an error from non-authority and should use the globalObjectIdHash to find the failing object + LogAssert.Expect(LogType.Error, new Regex($@"SenderId:{nonAuthority.LocalClientId}\]\[{Regex.Escape(exceptionObject.name)}")); + yield return WaitForConditionOrTimeOut(() => exceptionObject.IsSpawned); AssertOnTimeout("Failed to spawn object on authority!"); @@ -58,9 +62,13 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); - // Expect assertions fromt the new client + // Expect assertions from the new client LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); - LogAssert.Expect(LogType.Error, $"[Netcode] [GlobalObjectIdHash={exceptionObject.GlobalObjectIdHash}] Failed to spawn NetworkObject!"); + LogAssert.Expect(LogType.Error, new Regex("Failed to spawn NetworkObject!")); + + // Authority will receive an error from new client and should use the globalObjectIdHash to find the failing object + var expectedNewClientId = nonAuthority.LocalClientId + 1; + LogAssert.Expect(LogType.Error, new Regex($@"SenderId:{expectedNewClientId}\]\[{Regex.Escape(exceptionObject.name)}")); // Start and synchronize the new client yield return StartClient(newClient); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs index 72f9753ee1..e2c95d7450 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeLogAssert.cs @@ -222,6 +222,30 @@ public bool HasLogBeenReceived(LogType type, string message) return found; } + /// + /// Determines if a log message was logged or not. + /// + /// to check for. + /// containing the message to search for. + /// or + public bool HasLogBeenReceived(LogType type, Regex message) + { + var found = false; + lock (m_Lock) + { + foreach (var logEvent in AllLogs) + { + if (logEvent.LogType == type && message.IsMatch(logEvent.Message)) + { + found = true; + break; + } + } + } + return found; + } + + /// /// Clears out the log history that is searched. /// diff --git a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset index 6a1c39a006..69bec9b722 100644 --- a/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset +++ b/testproject/Assets/AddressableAssetsData/AddressableAssetSettings.asset @@ -15,7 +15,8 @@ MonoBehaviour: m_DefaultGroup: 93aa504d1b753cb41a8a779ae63f5795 m_currentHash: serializedVersion: 2 - Hash: c105cac3e36a87a6dabc4f3755bc1fd9 + Hash: 00000000000000000000000000000000 + m_ExtractTypeTreeData: 0 m_OptimizeCatalogSize: 0 m_BuildRemoteCatalog: 0 m_CatalogRequestsTimeout: 0 @@ -33,6 +34,7 @@ MonoBehaviour: m_UniqueBundleIds: 0 m_EnableJsonCatalog: 0 m_NonRecursiveBuilding: 1 + m_AllowNestedBundleFolders: 0 m_CCDEnabled: 0 m_maxConcurrentWebRequests: 500 m_UseUWRForLocalBundles: 0 @@ -41,6 +43,7 @@ MonoBehaviour: m_BundleRedirectLimit: -1 m_SharedBundleSettings: 0 m_SharedBundleSettingsCustomGroupIndex: 0 + m_simulatedLoadDelay: 0.1 m_ContiguousBundles: 0 m_StripUnityVersionFromBundleBuild: 0 m_DisableVisibleSubAssetRepresentations: 0 @@ -70,32 +73,32 @@ MonoBehaviour: m_Id: b7ba5fc73af2a49449023b732cdf652d m_ProfileName: Default m_Values: - - m_Id: a2553e7788fb43741926b3128a0aa641 - m_Value: '[UnityEditor.EditorUserBuildSettings.activeBuildTarget]' - - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 - m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]' + - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 + m_Value: 'http://localhost/[BuildTarget]' - m_Id: 80085c797452bb94ca0bf0a4b2ec258c m_Value: '{UnityEngine.AddressableAssets.Addressables.RuntimePath}/[BuildTarget]' + - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 + m_Value: '[UnityEngine.AddressableAssets.Addressables.BuildPath]/[BuildTarget]' + - m_Id: a2553e7788fb43741926b3128a0aa641 + m_Value: '[UnityEditor.EditorUserBuildSettings.activeBuildTarget]' - m_Id: eaae5cc67c56cfe4299363a742a284b3 m_Value: 'ServerData/[BuildTarget]' - - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Value: 'http://localhost/[BuildTarget]' m_ProfileEntryNames: - - m_Id: a2553e7788fb43741926b3128a0aa641 - m_Name: BuildTarget + - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 + m_Name: Remote.LoadPath + m_InlineUsage: 0 + - m_Id: 80085c797452bb94ca0bf0a4b2ec258c + m_Name: Local.LoadPath m_InlineUsage: 0 - m_Id: 8bc200e556c0a2f4daf1a4696958eaa6 m_Name: Local.BuildPath m_InlineUsage: 0 - - m_Id: 80085c797452bb94ca0bf0a4b2ec258c - m_Name: Local.LoadPath + - m_Id: a2553e7788fb43741926b3128a0aa641 + m_Name: BuildTarget m_InlineUsage: 0 - m_Id: eaae5cc67c56cfe4299363a742a284b3 m_Name: Remote.BuildPath m_InlineUsage: 0 - - m_Id: 2a3d80e942fdbfe49a979d4a22bbe893 - m_Name: Remote.LoadPath - m_InlineUsage: 0 m_ProfileVersion: 1 m_LabelTable: m_LabelNames: diff --git a/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset b/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset index 5d957b4c4e..36d70b4b1c 100644 --- a/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset +++ b/testproject/Assets/AddressableAssetsData/AssetGroups/Default Local Group.asset @@ -13,19 +13,16 @@ MonoBehaviour: m_Name: Default Local Group m_EditorClassIdentifier: m_GroupName: Default Local Group - m_Data: - m_SerializedData: [] m_GUID: 93aa504d1b753cb41a8a779ae63f5795 m_SerializeEntries: - m_GUID: ffa1ab8ed58b72343ad93116ded1700a m_Address: AddressableTestObject.prefab m_ReadOnly: 0 m_SerializedLabels: [] - m_MainAsset: {fileID: 0} - m_TargetAsset: {fileID: 0} + FlaggedDuringContentUpdateRestriction: 0 m_ReadOnly: 0 m_Settings: {fileID: 11400000, guid: 75e5cd8b6bfca5d49a5818e70d71b64d, type: 2} m_SchemaSet: m_Schemas: - - {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2} - {fileID: 11400000, guid: 07e6ff907f5779a42b6e632340d10c58, type: 2} + - {fileID: 11400000, guid: 6b8a226e7c996ad4b841b997672f866c, type: 2} diff --git a/testproject/Assets/Prefabs/AddressableTestObject.prefab b/testproject/Assets/Prefabs/AddressableTestObject.prefab index 473b199037..2930d6b0f3 100644 --- a/testproject/Assets/Prefabs/AddressableTestObject.prefab +++ b/testproject/Assets/Prefabs/AddressableTestObject.prefab @@ -9,8 +9,8 @@ GameObject: serializedVersion: 6 m_Component: - component: {fileID: 6175900562428407388} - - component: {fileID: 6175900562428407389} - component: {fileID: 6175900562428407386} + - component: {fileID: 6175900562428407389} m_Layer: 0 m_Name: AddressableTestObject m_TagString: Untagged @@ -25,14 +25,15 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 6175900562428407387} + serializedVersion: 2 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: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &6175900562428407389 +--- !u!114 &6175900562428407386 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -41,12 +42,24 @@ MonoBehaviour: m_GameObject: {fileID: 6175900562428407387} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3} + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: - AnIntVal: 1234567 - AStringVal: 1234567 ---- !u!114 &6175900562428407386 + GlobalObjectIdHash: 4054942115 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + k__BackingField: 0 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 1 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!114 &6175900562428407389 MonoBehaviour: m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {fileID: 0} @@ -55,10 +68,9 @@ MonoBehaviour: m_GameObject: {fileID: 6175900562428407387} m_Enabled: 1 m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Script: {fileID: 11500000, guid: 6c8e5a8a448245769b69af7de9c47b21, type: 3} m_Name: m_EditorClassIdentifier: - GlobalObjectIdHash: 951099334 - AlwaysReplicateAsRoot: 0 - DontDestroyWithOwner: 0 - AutoObjectParentSync: 1 + ShowTopMostFoldoutHeaderGroup: 1 + AnIntVal: 1234567 + AStringVal: 1234567 diff --git a/testproject/Assets/Scenes/AddressableInSceneObject.unity b/testproject/Assets/Scenes/AddressableInSceneObject.unity new file mode 100644 index 0000000000..e9fea46774 --- /dev/null +++ b/testproject/Assets/Scenes/AddressableInSceneObject.unity @@ -0,0 +1,199 @@ +%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: 10 + 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_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + 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_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: 3 + 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 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1001 &1879396995 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + serializedVersion: 3 + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 6175900562428407386, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: GlobalObjectIdHash + value: 1346653293 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407387, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_Name + value: AddressableTestObject + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.x + value: 1.09747 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalPosition.z + value: 1.59747 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 6175900562428407388, guid: ffa1ab8ed58b72343ad93116ded1700a, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_RemovedGameObjects: [] + m_AddedGameObjects: [] + m_AddedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: ffa1ab8ed58b72343ad93116ded1700a, type: 3} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1879396995} diff --git a/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta b/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta new file mode 100644 index 0000000000..dbdfd8aca5 --- /dev/null +++ b/testproject/Assets/Scenes/AddressableInSceneObject.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 68d21678646384e6291bb2b568b5d95c +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: addressablebundle + assetBundleVariant: diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity b/testproject/Assets/Scenes/MultiprocessTestScene.unity deleted file mode 100644 index f67263407e..0000000000 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity +++ /dev/null @@ -1,1062 +0,0 @@ -%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: 10 - 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_UseRadianceAmbientProbe: 0 ---- !u!157 &3 -LightmapSettings: - m_ObjectHideFlags: 0 - serializedVersion: 13 - m_BakeOnSceneLoad: 0 - 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_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: 130932425} ---- !u!196 &4 -NavMeshSettings: - serializedVersion: 2 - m_ObjectHideFlags: 0 - m_BuildSettings: - serializedVersion: 3 - 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 - buildHeightMesh: 0 - maxJobWorkers: 0 - preserveTilesOutsideBounds: 0 - debug: - m_Flags: 0 - m_NavMeshData: {fileID: 0} ---- !u!1 &127222500 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 127222502} - - component: {fileID: 127222501} - 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 &127222501 -Light: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 127222500} - m_Enabled: 1 - serializedVersion: 11 - m_Type: 1 - 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_ForceVisible: 0 - m_ShadowRadius: 0 - m_ShadowAngle: 0 - m_LightUnit: 1 - m_LuxAtDistance: 1 - m_EnableSpotReflector: 1 ---- !u!4 &127222502 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 127222500} - serializedVersion: 2 - 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_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} ---- !u!850595691 &130932425 -LightingSettings: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_Name: - serializedVersion: 9 - m_EnableBakedLightmaps: 0 - m_EnableRealtimeLightmaps: 0 - m_RealtimeEnvironmentLighting: 1 - m_BounceScale: 1 - m_AlbedoBoost: 1 - m_IndirectOutputScale: 1 - m_UsingShadowmask: 1 - m_BakeBackend: 1 - m_LightmapMaxSize: 1024 - m_LightmapSizeFixed: 0 - m_UseMipmapLimits: 1 - m_BakeResolution: 40 - m_Padding: 2 - m_LightmapCompression: 3 - m_AO: 0 - m_AOMaxDistance: 1 - m_CompAOExponent: 1 - m_CompAOExponentDirect: 0 - m_ExtractAO: 0 - m_MixedBakeMode: 2 - m_LightmapsBakeMode: 1 - m_FilterMode: 1 - m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} - m_ExportTrainingData: 0 - m_EnableWorkerProcessBaking: 1 - m_TrainingDataDestination: TrainingData - m_RealtimeResolution: 2 - m_ForceWhiteAlbedo: 0 - m_ForceUpdates: 0 - m_PVRCulling: 1 - m_PVRSampling: 1 - m_PVRDirectSampleCount: 32 - m_PVRSampleCount: 512 - m_PVREnvironmentSampleCount: 256 - m_PVREnvironmentReferencePointCount: 2048 - m_LightProbeSampleCountMultiplier: 4 - m_PVRBounces: 2 - m_PVRMinBounces: 2 - m_PVREnvironmentImportanceSampling: 1 - m_PVRFilteringMode: 1 - m_PVRDenoiserTypeDirect: 1 - m_PVRDenoiserTypeIndirect: 1 - m_PVRDenoiserTypeAO: 1 - m_PVRFilterTypeDirect: 0 - m_PVRFilterTypeIndirect: 0 - m_PVRFilterTypeAO: 0 - m_PVRFilteringGaussRadiusDirect: 1 - m_PVRFilteringGaussRadiusIndirect: 5 - m_PVRFilteringGaussRadiusAO: 2 - m_PVRFilteringAtrousPositionSigmaDirect: 0.5 - m_PVRFilteringAtrousPositionSigmaIndirect: 2 - m_PVRFilteringAtrousPositionSigmaAO: 1 - m_RespectSceneVisibilityWhenBakingGI: 0 ---- !u!1 &160940364 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 160940368} - - component: {fileID: 160940367} - - component: {fileID: 160940366} - - component: {fileID: 160940365} - m_Layer: 0 - m_Name: Boundary bottom left - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &160940365 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &160940366 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - 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_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, 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!33 &160940367 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &160940368 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 160940364} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: -10, y: -10, z: -10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &430011403 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 430011407} - - component: {fileID: 430011406} - - component: {fileID: 430011405} - - component: {fileID: 430011404} - m_Layer: 0 - m_Name: ThreeDText - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &430011404 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 04cf3cc1396054b009a1ed283aa50021, type: 3} - m_Name: - m_EditorClassIdentifier: - IsTestCoordinatorActiveAndEnabled: 0 - CommandLineArguments: ---- !u!102 &430011405 -TextMesh: - serializedVersion: 3 - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - m_Text: Hello World - m_OffsetZ: 0 - m_CharacterSize: 1 - m_LineSpacing: 1 - m_Anchor: 0 - m_Alignment: 0 - m_TabSize: 4 - m_FontSize: 0 - m_FontStyle: 0 - m_RichText: 1 - m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} - m_Color: - serializedVersion: 2 - rgba: 4294967295 ---- !u!23 &430011406 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - 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_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - 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!4 &430011407 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 430011403} - serializedVersion: 2 - m_LocalRotation: {x: 0.2164396, y: 0, z: 0, w: 0.97629607} - m_LocalPosition: {x: -45, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 25, y: 0, z: 0} ---- !u!1 &941021721 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 941021724} - - component: {fileID: 941021723} - - component: {fileID: 941021722} - 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 &941021722 -AudioListener: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - m_Enabled: 1 ---- !u!20 &941021723 -Camera: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - 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_Iso: 200 - m_ShutterSpeed: 0.005 - m_Aperture: 16 - m_FocusDistance: 10 - m_FocalLength: 50 - m_BladeCount: 5 - m_Curvature: {x: 2, y: 11} - m_BarrelClipping: 0.25 - m_Anamorphism: 0 - m_SensorSize: {x: 36, y: 24} - m_LensShift: {x: 0, y: 0} - 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 &941021724 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 941021721} - serializedVersion: 2 - m_LocalRotation: {x: 0.21736304, y: -0, z: -0, w: 0.97609085} - m_LocalPosition: {x: 0, y: 9.15, z: -27.5} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 25.108, y: 0, z: 0} ---- !u!1 &996484657 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 996484661} - - component: {fileID: 996484660} - - component: {fileID: 996484659} - - component: {fileID: 996484658} - m_Layer: 0 - m_Name: Boundary center - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &996484658 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &996484659 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - 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_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, 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!33 &996484660 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &996484661 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 996484657} - serializedVersion: 2 - 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_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1206022453 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1206022457} - - component: {fileID: 1206022456} - - component: {fileID: 1206022455} - - component: {fileID: 1206022454} - m_Layer: 0 - m_Name: Boundary top right - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!135 &1206022454 -SphereCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - m_Material: {fileID: 0} - m_IncludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_ExcludeLayers: - serializedVersion: 2 - m_Bits: 0 - m_LayerOverridePriority: 0 - m_IsTrigger: 0 - m_ProvidesContacts: 0 - m_Enabled: 1 - serializedVersion: 3 - m_Radius: 0.5 - m_Center: {x: 0, y: 0, z: 0} ---- !u!23 &1206022455 -MeshRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - 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_RayTracingAccelStructBuildFlagsOverride: 0 - m_RayTracingAccelStructBuildFlags: 1 - m_SmallMeshCulling: 1 - m_RenderingLayerMask: 1 - m_RendererPriority: 0 - m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, 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!33 &1206022456 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &1206022457 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1206022453} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 10, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!1 &1211923374 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1211923376} - - component: {fileID: 1211923375} - - component: {fileID: 1211923378} - - component: {fileID: 1211923377} - m_Layer: 0 - m_Name: '[NetworkManager] (Multiprocess)' - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1211923375 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} - m_Name: - m_EditorClassIdentifier: - NetworkManagerExpanded: 0 - NetworkConfig: - ProtocolVersion: 0 - NetworkTransport: {fileID: 2027640073} - PlayerPrefab: {fileID: 4700706668509470175, guid: 7eeaaf9e50c0afc4dab93584a54fb0d6, - type: 3} - Prefabs: - NetworkPrefabsLists: [] - 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: 10 - EnableNetworkLogs: 1 - NetworkTopology: 0 - UseCMBService: 0 - AutoSpawnPlayerPrefabClientSide: 1 - NetworkProfilingMetrics: 1 - OldPrefabList: - - Override: 0 - Prefab: {fileID: 9115731988109684252, guid: c2851feb7276442cc86a6f2d1d69ea11, - type: 3} - SourcePrefabToOverride: {fileID: 0} - SourceHashToOverride: 0 - OverridingTargetPrefab: {fileID: 0} - RunInBackground: 1 - LogLevel: 1 ---- !u!4 &1211923376 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - serializedVersion: 2 - 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: 2027640072} - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1211923377 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 068bf11ceb1344667af4cc40950f44f4, type: 3} - m_Name: - m_EditorClassIdentifier: - ReferencedPrefab: {fileID: 9115731988109684252, guid: c2851feb7276442cc86a6f2d1d69ea11, - type: 3} ---- !u!114 &1211923378 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1211923374} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 55d1c75ce242745ac98f7e7aca6d2d19, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!1 &1274245423 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 1274245425} - - component: {fileID: 1274245424} - - component: {fileID: 1274245426} - m_Layer: 0 - m_Name: TestCoordinator - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!114 &1274245424 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} - m_Name: - m_EditorClassIdentifier: - GlobalObjectIdHash: 2217825759 - InScenePlacedSourceGlobalObjectIdHash: 0 - DeferredDespawnTick: 0 - Ownership: 1 - AlwaysReplicateAsRoot: 0 - SynchronizeTransform: 1 - ActiveSceneSynchronization: 0 - SceneMigrationSynchronization: 1 - SpawnWithObservers: 1 - DontDestroyWithOwner: 0 - AutoObjectParentSync: 1 - SyncOwnerTransformWhenParented: 1 - AllowOwnerToParent: 0 ---- !u!4 &1274245425 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - serializedVersion: 2 - m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 10, y: 10, z: 10} - m_LocalScale: {x: 1, y: 1, z: 1} - m_ConstrainProportionsScale: 0 - m_Children: [] - m_Father: {fileID: 0} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &1274245426 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1274245423} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: ef1240e0784f84eadb77fe822e2e03c7, type: 3} - m_Name: - m_EditorClassIdentifier: ---- !u!1 &2027640071 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 2027640072} - - component: {fileID: 2027640073} - m_Layer: 0 - m_Name: UTP - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 1 ---- !u!4 &2027640072 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2027640071} - serializedVersion: 2 - 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: 1211923376} - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!114 &2027640073 -MonoBehaviour: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 2027640071} - m_Enabled: 1 - m_EditorHideFlags: 0 - m_Script: {fileID: 11500000, guid: 6960e84d07fb87f47956e7a81d71c4e6, type: 3} - m_Name: - m_EditorClassIdentifier: - m_ProtocolType: 0 - m_UseWebSockets: 0 - m_UseEncryption: 0 - m_MaxPacketQueueSize: 128 - m_MaxPayloadSize: 4096 - m_HeartbeatTimeoutMS: 500 - m_ConnectTimeoutMS: 1000 - m_MaxConnectAttempts: 60 - m_DisconnectTimeoutMS: 30000 - ConnectionData: - Address: 127.0.0.1 - Port: 7777 - ServerListenAddress: 127.0.0.1 - DebugSimulator: - PacketDelayMS: 0 - PacketJitterMS: 0 - PacketDropRate: 0 ---- !u!1660057539 &9223372036854775807 -SceneRoots: - m_ObjectHideFlags: 0 - m_Roots: - - {fileID: 941021724} - - {fileID: 127222502} - - {fileID: 1211923376} - - {fileID: 1206022457} - - {fileID: 996484661} - - {fileID: 160940368} - - {fileID: 1274245425} - - {fileID: 430011407} diff --git a/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs new file mode 100644 index 0000000000..23b1b909a8 --- /dev/null +++ b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using Unity.Netcode; +using UnityEngine; +using UnityEngine.SceneManagement; + +#if UNITY_6000_6_OR_NEWER && UNITY_EDITOR + [Unity.Scripting.LifecycleManagement.AutoStaticsCleanup] +#endif +public partial class MoveDynamicSpawnInAwake : MonoBehaviour +{ + public NetworkObject MovedObject { get; private set; } + private static readonly HashSet k_AlreadyMovedObjects = new(); + private void Awake() + { + var networkManager = NetworkManager.Singleton; + if (networkManager == null || !networkManager.IsListening) + { + return; + } + + foreach (var spawnedObject in networkManager.SpawnManager.SpawnedObjects.Values) + { + if (spawnedObject == null || !spawnedObject.IsSpawned || !spawnedObject.HasAuthority || spawnedObject.IsPlayerObject || spawnedObject.InScenePlaced) + { + continue; + } + + if (!k_AlreadyMovedObjects.Add(spawnedObject)) + { + continue; + } + + MovedObject = spawnedObject; + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, gameObject.scene); + return; + } + } +} diff --git a/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta new file mode 100644 index 0000000000..053fb00eb0 --- /dev/null +++ b/testproject/Assets/Scripts/MoveDynamicSpawnInAwake.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 566f22ce0b192499d8728df60af83423 \ No newline at end of file diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta index 46669dd13d..88bb22b242 100644 --- a/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity.meta @@ -1,7 +1,7 @@ fileFormatVersion: 2 -guid: abd4c8b51c445d54faa16c67ac973f1b +guid: bfb4addb64cf6455a88668eba2f759ba DefaultImporter: externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity new file mode 100644 index 0000000000..1cfda7fc2d --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity @@ -0,0 +1,198 @@ +%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: 10 + 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_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + 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_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: 3 + 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 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1633685737 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1633685739} + - component: {fileID: 1633685738} + - component: {fileID: 1633685740} + m_Layer: 0 + m_Name: InSceneObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1633685738 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 974939462 + InScenePlacedSourceGlobalObjectIdHash: 0 + DeferredDespawnTick: 0 + Ownership: 1 + AlwaysReplicateAsRoot: 0 + SynchronizeTransform: 1 + m_InScenePlaced: 1 + ActiveSceneSynchronization: 0 + SceneMigrationSynchronization: 0 + SpawnWithObservers: 1 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 + SyncOwnerTransformWhenParented: 1 + AllowOwnerToParent: 0 +--- !u!4 &1633685739 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + serializedVersion: 2 + 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_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1633685740 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1633685737} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 112072ebb4fab6341bfee4bd9d7e58da, type: 3} + m_Name: + m_EditorClassIdentifier: TestProject.ManualTests::TestProject.ManualTests.MoveInScenePlacedToDDOL + ShowTopMostFoldoutHeaderGroup: 1 +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1633685739} diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta similarity index 74% rename from testproject/Assets/Scenes/MultiprocessTestScene.unity.meta rename to testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta index 5a9d45d780..dfa51863fd 100644 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity.meta +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 76743cb7b342c49279327834918a9c6e +guid: 4c20c17b4f92e634f8796b0460851d49 DefaultImporter: externalObjects: {} userData: diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity new file mode 100644 index 0000000000..f5b385433f --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity @@ -0,0 +1,170 @@ +%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: 10 + 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_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 13 + m_BakeOnSceneLoad: 0 + 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_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: 3 + 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 + buildHeightMesh: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &1786247463 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1786247465} + - component: {fileID: 1786247464} + m_Layer: 0 + m_Name: MoveDynamicSpawnInAwake + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1786247464 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786247463} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 566f22ce0b192499d8728df60af83423, type: 3} + m_Name: + m_EditorClassIdentifier: TestProject::MoveDynamicSpawnInAwake +--- !u!4 &1786247465 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1786247463} + serializedVersion: 2 + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 1.09747, y: 0, z: 1.59747} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1660057539 &9223372036854775807 +SceneRoots: + m_ObjectHideFlags: 0 + m_Roots: + - {fileID: 1786247465} diff --git a/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta new file mode 100644 index 0000000000..46669dd13d --- /dev/null +++ b/testproject/Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: abd4c8b51c445d54faa16c67ac973f1b +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs new file mode 100644 index 0000000000..0e9fa457a9 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs @@ -0,0 +1,34 @@ +using Unity.Netcode; +using UnityEngine; + +namespace TestProject.ManualTests +{ + public class MoveInScenePlacedToDDOL : NetworkBehaviour + { + public bool ProcessedRpc { get; private set; } + + private void Awake() + { + ProcessedRpc = false; + var networkObject = GetComponent(); + Debug.Log($"[{name}][Moving to DDOL] InScenePlaced: {networkObject.InScenePlaced}"); + DontDestroyOnLoad(gameObject); + } + + protected override void OnNetworkPostSpawn() + { + if (HasAuthority) + { + SendOnSpawnRpc(); + } + + base.OnNetworkPostSpawn(); + } + + [Rpc(SendTo.Everyone)] + private void SendOnSpawnRpc(RpcParams rpcParams = default) + { + ProcessedRpc = true; + } + } +} diff --git a/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta new file mode 100644 index 0000000000..739e4ca2d4 --- /dev/null +++ b/testproject/Assets/Tests/Manual/Scripts/MoveInScenePlacedToDDOL.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 112072ebb4fab6341bfee4bd9d7e58da \ No newline at end of file diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs index 5b045b1855..9f441b8f58 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPool.cs @@ -496,12 +496,12 @@ public void UpdateSpawnsPerSecond() { StartSpawningBoxes(); } - else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) - { - m_IsSpawningObjects = false; - StopCoroutine(SpawnObjects()); - } + //Handle case where spawning coroutine is running but we set our spawn rate to zero + else if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + { + m_IsSpawningObjects = false; + StopCoroutine(SpawnObjects()); + } } } diff --git a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs index 0c4992cbd9..44decc3dce 100644 --- a/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs +++ b/testproject/Assets/Tests/Manual/Scripts/NetworkPrefabPoolAdditive.cs @@ -342,12 +342,12 @@ public void UpdateSpawnsPerSecond() { StartSpawningBoxes(); } - else //Handle case where spawning coroutine is running but we set our spawn rate to zero - if (SpawnsPerSecond == 0 && m_IsSpawningObjects) - { - m_IsSpawningObjects = false; - InternalStopCoroutine(); - } + //Handle case where spawning coroutine is running but we set our spawn rate to zero + else if (SpawnsPerSecond == 0 && m_IsSpawningObjects) + { + m_IsSpawningObjects = false; + InternalStopCoroutine(); + } } diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs index 4c9151c0f3..7dd8aff688 100644 --- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs +++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs @@ -7,6 +7,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.AddressableAssets; +using UnityEngine.ResourceManagement.AsyncOperations; +using UnityEngine.SceneManagement; using UnityEngine.TestTools; using Assert = UnityEngine.Assertions.Assert; @@ -24,6 +26,7 @@ public class AddressablesTests : NetcodeIntegrationTest protected override bool m_TearDownIsACoroutine => false; private const string k_ValidObject = "AddressableTestObject.prefab"; + private const string k_ValidScene = "Assets/Scenes/AddressableInSceneObject.unity"; public AddressablesTests(HostOrServer hostOrServer) { @@ -56,6 +59,24 @@ private IEnumerator LoadAsset(AssetReferenceGameObject asset, NetcodeIntegration prefab.Result = handle.Result; } + private IEnumerator LoadSceneWithInSceneObject(AssetReference asset, NetcodeIntegrationTestHelpers.ResultWrapper prefab) + { + var handle = Addressables.LoadSceneAsync(asset, LoadSceneMode.Additive); + while (!handle.IsDone) + { + var nextFrameNumber = Time.frameCount + 1; + yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + } + + Assert.AreEqual(AsyncOperationStatus.Succeeded, handle.Status, "Addressables.LoadSceneAsync failed!"); + + foreach (var networkObject in FindObjects.FromSceneByType(handle.Result.Scene, false)) + { + prefab.Result = networkObject.gameObject; + break; + } + } + protected void StartWithAddressableAssetAdded() { StartServerAndClientsWithTimeTravel(); @@ -70,7 +91,7 @@ private void AddPrefab(GameObject prefab) } } - private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false) + private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false, bool wasLoadedFromScene = false) { // Have to spawn it ourselves. var serverObj = Object.Instantiate(prefab); @@ -80,7 +101,14 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false // 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); + var numExpected = 1; + if (wasLoadedFromScene) + { + // If prefab was loaded from the scene, there'll be an additional object found + numExpected++; + } + + Assert.AreEqual(numExpected, objs.Length); var startTime = MockTimeProvider.StaticRealTimeSinceStartup; @@ -91,7 +119,7 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false // Since it's not added, after the CreateObjectMessage is received, it's not spawned yet // Verify that to be the case as a precondition. objs = FindObjects.ByType(); - Assert.AreEqual(1, objs.Length); + Assert.AreEqual(numExpected, objs.Length); WaitForConditionOrTimeOutWithTimeTravel(() => MockTimeProvider.StaticRealTimeSinceStartup - startTime >= m_ClientNetworkManagers[0].NetworkConfig.SpawnTimeout - 0.25); foreach (var client in m_ClientNetworkManagers) { @@ -100,12 +128,22 @@ private void SpawnAndValidate(GameObject prefab, bool waitAndAddOnClient = false } objs = FindObjects.ByType(); - Assert.AreEqual(NumberOfClients + 1, objs.Length); + Assert.AreEqual(NumberOfClients + numExpected, objs.Length); foreach (var obj in objs) { Assert.AreEqual(1234567, obj.AnIntVal); Assert.AreEqual("1234567", obj.AStringVal); Assert.AreEqual("12345671234567", obj.GetValue()); + + // TODO-[MTT-15388]: Object spawned from a scene should be InScenePlaced after this ticket + if (obj.IsSpawned) + { + Assert.IsFalse(obj.NetworkObject.InScenePlaced, "Object was dynamically spawned and should be marked as such!"); + } + else + { + Assert.IsTrue(obj.NetworkObject.InScenePlaced, "Object that was loaded from scene should have been marked as in-scene placed during loading!"); + } } } @@ -144,7 +182,7 @@ public IEnumerator WhenLoadingAValidObjectAfterStarting_SpawningItSucceedsOnServ } [UnityTest] - public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningItSucceedsOnServerAndClientAfterConfiguredDelay([Values(1, 2, 3)] int timeout) + public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningItSucceedsOnServerAndClientAfterDelay() { var asset = new AssetReferenceGameObject(k_ValidObject); @@ -152,7 +190,7 @@ public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningI m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; foreach (var client in m_ClientNetworkManagers) { - client.NetworkConfig.SpawnTimeout = timeout; + client.NetworkConfig.SpawnTimeout = 3; client.NetworkConfig.ForceSamePrefabs = false; } @@ -163,6 +201,32 @@ public IEnumerator WhenSpawningServerPrefabBeforeClientPrefabHasLoaded_SpawningI SpawnAndValidate(prefabResult.Result, true); } + + // TODO-[MTT-15388]: Reconsider whether this test should be valid + // Reported on Github issue https://github.com/Unity-Technologies/com.unity.netcode.gameobjects/issues/4049 + //[UnityTest] + //public IEnumerator RegisteringPrefabFromLoadedAddressablesSceneWorks() + //{ + // var asset = new AssetReference(k_ValidScene); + + // CreateServerAndClients(); + // foreach (var manager in m_NetworkManagers) + // { + // manager.NetworkConfig.ForceSamePrefabs = false; + // } + + // StartServerAndClientsWithTimeTravel(); + + // var prefabResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + // yield return LoadSceneWithInSceneObject(asset, prefabResult); + + // foreach (var manager in m_NetworkManagers) + // { + // manager.AddNetworkPrefab(prefabResult.Result); + // } + + // SpawnAndValidate(prefabResult.Result, wasLoadedFromScene: true); + //} } } #endif diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index dc2493ec28..715e7f6156 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -527,11 +527,10 @@ private bool AllObserversSameLayerWeight(OwnerShipMode ownerShipMode, int layer, return false; } } - else - if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) - { - return false; - } + else if (animatorTestHelper.Value.GetLayerWeight(layer) != targetWeight) + { + return false; + } } return true; } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs new file mode 100644 index 0000000000..807ff09eeb --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs @@ -0,0 +1,75 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + internal class InScenePlacedProcessorTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private static readonly string k_DynamicObjectMoverScene = "MoveADynamicObjectInAwake"; + + private GameObject m_DynamicSpawnPrefab; + + protected override void OnServerAndClientsCreated() + { + m_DynamicSpawnPrefab = CreateNetworkObjectPrefab("DynamicSpawnObject"); + base.OnServerAndClientsCreated(); + } + + [UnityTest] + public IEnumerator InScenePlacedProcessorSkipsMovedObject() + { + var authority = GetAuthorityNetworkManager(); + var spawnedObj = SpawnObject(m_DynamicSpawnPrefab, authority).GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(spawnedObj); + AssertOnTimeout("Timed out waiting for object to spawn!"); + + Assert.IsFalse(spawnedObj.InScenePlaced, "Dynamically spawned object should not be InScenePlaced!"); + + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + var status = authority.SceneManager.LoadScene(k_DynamicObjectMoverScene, LoadSceneMode.Additive); + Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start lading scene {k_DynamicObjectMoverScene} with status {status}!"); + yield return WaitForConditionOrTimeOut(() => m_SceneLoaded.IsValid()); + AssertOnTimeout("Timed out waiting for scene to load!"); + + var movers = FindObjects.ByType(); + foreach (var mover in movers) + { + var movedObject = mover.MovedObject; + Assert.IsFalse(movedObject == null); + Assert.IsFalse(movedObject.InScenePlaced, "Dynamically spawned object should not be re-processed as InScenePlaced!"); + Assert.AreEqual(mover.gameObject.scene, movedObject.gameObject.scene, "Object should have moved scenes!"); + } + + yield return CreateAndStartNewClient(); + AssertOnTimeout("Timed out waiting for late joining client!"); + + yield return WaitForSpawnedOnAllOrTimeOut(spawnedObj); + AssertOnTimeout("Timed out waiting for object to be spawned on late joining client!"); + } + + private Scene m_SceneLoaded; + private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) + { + var authority = GetAuthorityNetworkManager(); + switch (sceneEvent.SceneEventType) + { + case SceneEventType.LoadComplete: + { + if (sceneEvent.ClientId == authority.LocalClientId) + { + m_SceneLoaded = sceneEvent.Scene; + } + return; + } + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta new file mode 100644 index 0000000000..3d0a9439ec --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/InScenePlacedNetworkObject/InScenePlacedProcessorTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9abce9656f174f768488bb052afa8d92 +timeCreated: 1783723861 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs index d5eb5f427c..736de24acf 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkObjectSceneMigrationTests.cs @@ -1,6 +1,9 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; using NUnit.Framework; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; @@ -13,27 +16,30 @@ namespace TestProject.RuntimeTests { /// /// NetworkObject Scene Migration Integration Tests - /// - /// /// - public class NetworkObjectSceneMigrationTests : NetcodeIntegrationTest + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class NetworkObjectSceneMigrationTests : NetcodeIntegrationTest { - private List m_TestScenes = new List() { "EmptyScene1", "EmptyScene2", "EmptyScene3" }; + private readonly List m_TestScenes = new() { "EmptyScene1", "EmptyScene2", "EmptyScene3" }; protected override int NumberOfClients => 2; + private GameObject m_TestPrefab; private GameObject m_TestPrefabAutoSynchActiveScene; private GameObject m_TestPrefabDestroyWithScene; private Scene m_OriginalActiveScene; - private bool m_ClientsLoadedScene; - private bool m_ClientsUnloadedScene; - private Scene m_SceneLoaded; private List m_ServerSpawnedPrefabInstances = new List(); private List m_ServerSpawnedDestroyWithSceneInstances = new List(); - private List m_ScenesLoaded = new List(); - private string m_CurrentSceneLoading; - private string m_CurrentSceneUnloading; + private readonly List m_ScenesLoaded = new List(); + public NetworkObjectSceneMigrationTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + // TODO: [MTT-15430] Fix automatic scene object migration between clients + protected override bool UseCMBService() + { + return false; + } protected override IEnumerator OnSetup() { @@ -41,106 +47,70 @@ protected override IEnumerator OnSetup() return base.OnSetup(); } + protected override void OnCreatePlayerPrefab() + { + Object.DontDestroyOnLoad(m_PlayerPrefab); + m_PlayerPrefab.GetComponent().ActiveSceneSynchronization = true; + base.OnCreatePlayerPrefab(); + } + protected override void OnServerAndClientsCreated() { // Synchronize Scene Changes (default) Test Network Prefab m_TestPrefab = CreateNetworkObjectPrefab("TestObject"); + m_TestPrefab.AddComponent(); // Auto Synchronize Active Scene Changes Test Network Prefab - m_TestPrefabAutoSynchActiveScene = CreateNetworkObjectPrefab("ASASObject"); + m_TestPrefabAutoSynchActiveScene = CreateNetworkObjectPrefab("ActiveSceneSynchronizationObject"); m_TestPrefabAutoSynchActiveScene.GetComponent().ActiveSceneSynchronization = true; + m_TestPrefabAutoSynchActiveScene.AddComponent(); // Destroy With Scene Test Network Prefab - m_TestPrefabDestroyWithScene = CreateNetworkObjectPrefab("DWSObject"); + m_TestPrefabDestroyWithScene = CreateNetworkObjectPrefab("DestroyWithSceneObject"); m_TestPrefabDestroyWithScene.AddComponent(); + m_TestPrefabDestroyWithScene.AddComponent(); - DestroyWithSceneInstancesTestHelper.ShouldNeverSpawn = m_TestPrefabDestroyWithScene; + var neverSpawnObj = CreateNetworkObjectPrefab("ShouldNeverSpawn"); + var shouldNeverSpawn = neverSpawnObj.AddComponent(); + DestroyWithSceneInstancesTestHelper.ShouldNeverSpawn = shouldNeverSpawn; + + var authority = GetAuthorityNetworkManager(); + authority.OnServerStarted += OnServerStarted; base.OnServerAndClientsCreated(); } - private bool DidClientsSpawnInstance(NetworkObject serverObject, bool checkDestroyWithScene = false) - { - foreach (var networkManager in m_ClientNetworkManagers) - { - if (!s_GlobalNetworkObjects.ContainsKey(networkManager.LocalClientId)) - { - return false; - } - var clientNetworkObjects = s_GlobalNetworkObjects[networkManager.LocalClientId]; - if (!clientNetworkObjects.ContainsKey(serverObject.NetworkObjectId)) - { - return false; - } - - if (checkDestroyWithScene) - { - if (serverObject.DestroyWithScene != clientNetworkObjects[serverObject.NetworkObjectId]) - { - return false; - } - } - } - return true; - } - private bool VerifyAllClientsSpawnedInstances() + private void OnServerStarted() { - foreach (var serverObject in m_ServerSpawnedPrefabInstances) - { - if (!DidClientsSpawnInstance(serverObject)) - { - return false; - } - } - - foreach (var serverObject in m_ServerSpawnedDestroyWithSceneInstances) - { - if (!DidClientsSpawnInstance(serverObject, true)) - { - return false; - } - } - - return true; + var authority = GetAuthorityNetworkManager(); + authority.OnServerStarted -= OnServerStarted; + authority.SceneManager.ActiveSceneSynchronizationEnabled = true; } - private bool AreClientInstancesInTheRightScene(NetworkObject serverObject) + private bool VerifyAllScenesMatch(StringBuilder errorLog, List authorityInstances) { - foreach (var networkManager in m_ClientNetworkManagers) + foreach (var authorityInstance in authorityInstances) { - var clientNetworkObjects = s_GlobalNetworkObjects[networkManager.LocalClientId]; - if (clientNetworkObjects == null) - { - continue; - } - // If a networkObject is null then it was destroyed - if (clientNetworkObjects[serverObject.NetworkObjectId].gameObject.scene.name != serverObject.gameObject.scene.name) + foreach (var networkManager in m_NetworkManagers) { - return false; - } - } - return true; - } + if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityInstance.NetworkObjectId, out var instance)) + { + errorLog.AppendLine($"[{authorityInstance.name}] Client-{networkManager.LocalClientId} doesn't have a local version of network object {authorityInstance.name} with id {authorityInstance.NetworkObjectId}"); + return false; + } - private bool VerifySpawnedObjectsMigrated() - { - foreach (var serverObject in m_ServerSpawnedPrefabInstances) - { - if (!AreClientInstancesInTheRightScene(serverObject)) - { - return false; - } - } + if (instance.gameObject.scene.name != authorityInstance.gameObject.scene.name) + { + errorLog.AppendLine($"[{instance.name}] NetworkObject-{authorityInstance.NetworkObjectId} is in the wrong scene! Expected: {authorityInstance.gameObject.scene.name}, Actual: {instance.gameObject.scene.name}"); + return false; + } - foreach (var serverObject in m_ServerSpawnedDestroyWithSceneInstances) - { - if (!AreClientInstancesInTheRightScene(serverObject)) - { - return false; + // The SceneOrigin should never change + var originalSceneTracker = instance.GetComponent(); + Assert.AreEqual(originalSceneTracker.SceneWhereAwakeHappened, (NetworkSceneHandle)instance.SceneOrigin.handle, "The SceneOrigin of an object should never change!"); } } - return true; } @@ -154,28 +124,31 @@ private bool VerifySpawnedObjectsMigrated() [UnityTest] public IEnumerator MigrateIntoNewSceneTest() { + var authority = GetAuthorityNetworkManager(); + + var authoritySpawnedInstances = new List(); // Spawn 9 NetworkObject instances for (int i = 0; i < k_MaxObjectsToSpawn; i++) { - var serverInstance = Object.Instantiate(m_TestPrefab); - var serverNetworkObject = serverInstance.GetComponent(); - serverNetworkObject.Spawn(); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var instance = SpawnObject(m_TestPrefab, authority); + var spawnedObject = instance.GetComponent(); + authoritySpawnedInstances.Add(spawnedObject); } - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); + + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); // Now load three scenes to migrate the newly spawned NetworkObjects into - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - for (int i = 0; i < 3; i++) + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + foreach (var sceneToLoad in m_TestScenes) { - m_ClientsLoadedScene = false; - m_CurrentSceneLoading = m_TestScenes[i]; - var status = m_ServerNetworkManager.SceneManager.LoadScene(m_TestScenes[i], LoadSceneMode.Additive); - Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start loading scene {m_CurrentSceneLoading}! Return status: {status}"); - yield return WaitForConditionOrTimeOut(() => m_ClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for all clients to load scene {m_CurrentSceneLoading}!"); + yield return LoadScene(authority, sceneToLoad); } + authority.SceneManager.OnSceneEvent -= SceneManager_OnSceneEvent; + Assert.AreEqual(m_TestScenes.Count, m_ScenesLoaded.Count, "Not all the test scenes were loaded!"); + + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); + AssertOnTimeout("[After spawn] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); var objectCount = 0; // Migrate each networkObject into one of the three scenes. @@ -183,31 +156,32 @@ public IEnumerator MigrateIntoNewSceneTest() foreach (var scene in m_ScenesLoaded) { // Now migrate the NetworkObject - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount].gameObject, scene); - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount + 1].gameObject, scene); - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[objectCount + 2].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount + 1].gameObject, scene); + SceneManager.MoveGameObjectToScene(authoritySpawnedInstances[objectCount + 2].gameObject, scene); objectCount += 3; } - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Register for the server-side client synchronization so we can send an object scene migration event at the same time // the new client begins to synchronize - m_ServerNetworkManager.SceneManager.OnSynchronize += MigrateObjects_OnSynchronize; + m_ServerSpawnedPrefabInstances = authoritySpawnedInstances; + authority.SceneManager.OnSynchronize += MigrateObjects_OnSynchronize; // Verify that a late joining client synchronizes properly even while new scene migrations occur // during its synchronization yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that a late joining client synchronizes properly even if we migrate // during its synchronization and despawn some of the NetworkObjects migrated. - m_ServerNetworkManager.SceneManager.OnSynchronize += MigrateAndDespawnObjects_OnSynchronize; + authority.SceneManager.OnSynchronize += MigrateAndDespawnObjects_OnSynchronize; yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); } @@ -219,19 +193,27 @@ public IEnumerator MigrateIntoNewSceneTest() /// private void MigrateAndDespawnObjects_OnSynchronize(ulong clientId) { - var objectCount = 0; + var authority = GetAuthorityNetworkManager(); // Migrate the NetworkObjects into different scenes than they originally were migrated into for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) { var scene = m_ScenesLoaded[i % m_ScenesLoaded.Count]; - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[i].gameObject, scene); + var obj = m_ServerSpawnedPrefabInstances[i]; + if (m_DistributedAuthority) + { + // When the new client joins, authority will be distributed. + // Ensure we have the authority instance. + obj = GetAuthorityInstance(obj); + } + SceneManager.MoveGameObjectToScene(obj.gameObject, scene); // De-spawn every-other object if (i % 2 == 0) { - m_ServerSpawnedPrefabInstances[objectCount + i].Despawn(); + obj.Despawn(); m_ServerSpawnedPrefabInstances.RemoveAt(i); } } + authority.SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; } /// @@ -252,7 +234,24 @@ private void MigrateObjects_OnSynchronize(ulong clientId) } // Unsubscribe to this event for this part of the test - m_ServerNetworkManager.SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; + GetAuthorityNetworkManager().SceneManager.OnSynchronize -= MigrateObjects_OnSynchronize; + } + + protected override void OnNewClientCreated(NetworkManager networkManager) + { + var authority = GetAuthorityNetworkManager(); + foreach (var prefab in authority.NetworkConfig.Prefabs.Prefabs) + { + networkManager.NetworkConfig.Prefabs.Add(prefab); + } + networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + base.OnNewClientCreated(networkManager); + } + + private void SetActiveScene(Scene scene) + { + Debug.Log($"[Previous = {SceneManager.GetActiveScene().name}][New = {scene.name}] Changing the active scene!"); + SceneManager.SetActiveScene(scene); } /// @@ -263,36 +262,47 @@ private void MigrateObjects_OnSynchronize(ulong clientId) [UnityTest] public IEnumerator ActiveSceneSynchronizationTest() { + var authority = GetAuthorityNetworkManager(); // Disable resynchronization for this test to avoid issues with trying // to synchronize them. NetworkSceneManager.DisableReSynchronization = true; + // Load three scenes first + authority.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; + foreach (var sceneName in m_TestScenes) + { + yield return LoadScene(authority, sceneName); + } + authority.SceneManager.OnSceneEvent -= SceneManager_OnSceneEvent; + + // Set the active scene to be the 1st scene loaded so we don't instantiate within the test runner scene. + SetActiveScene(m_ScenesLoaded[0]); + + var autoSyncActive = new List(); // Spawn 3 NetworkObject instances that auto synchronize to active scene changes for (int i = 0; i < 3; i++) { - var serverInstance = Object.Instantiate(m_TestPrefabAutoSynchActiveScene); - var serverNetworkObject = serverInstance.GetComponent(); // We are also testing that objects marked to synchronize with changes to // the active scene and marked to destroy with scene =are destroyed= if // the scene being unloaded is currently the active scene and the scene that // the NetworkObjects reside within. - serverNetworkObject.Spawn(true); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var serverInstance = SpawnObject(m_TestPrefabAutoSynchActiveScene, authority, true); + var serverNetworkObject = serverInstance.GetComponent(); + autoSyncActive.Add(serverNetworkObject); } - // Spawn 3 NetworkObject instances that do not auto synchronize to active scene changes // and ==should not be== destroyed with the scene (these should be the only remaining // instances) + var autoSyncInactive = new List(); for (int i = 0; i < 3; i++) { - var serverInstance = Object.Instantiate(m_TestPrefab); - var serverNetworkObject = serverInstance.GetComponent(); // This set of NetworkObjects will be used to verify that NetworkObjets // spawned with DestroyWithScene set to false will migrate into the current // active scene if the scene they currently reside within is destroyed and // is not the currently active scene. - serverNetworkObject.Spawn(); - m_ServerSpawnedPrefabInstances.Add(serverNetworkObject); + var serverInstance = SpawnObject(m_TestPrefab, authority); + var serverNetworkObject = serverInstance.GetComponent(); + autoSyncInactive.Add(serverNetworkObject); } // Spawn 3 NetworkObject instances that do not auto synchronize to active scene changes @@ -307,104 +317,90 @@ public IEnumerator ActiveSceneSynchronizationTest() serverNetworkObject.Spawn(true); m_ServerSpawnedDestroyWithSceneInstances.Add(serverNetworkObject); } + var authoritySpawnedInstances = new List(); + authoritySpawnedInstances.AddRange(autoSyncActive); + authoritySpawnedInstances.AddRange(autoSyncInactive); + authoritySpawnedInstances.AddRange(m_ServerSpawnedDestroyWithSceneInstances); - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); - // Now load three scenes - m_ServerNetworkManager.SceneManager.OnSceneEvent += SceneManager_OnSceneEvent; - for (int i = 0; i < 3; i++) - { - m_ClientsLoadedScene = false; - m_CurrentSceneLoading = m_TestScenes[i]; - var loadStatus = m_ServerNetworkManager.SceneManager.LoadScene(m_TestScenes[i], LoadSceneMode.Additive); - Assert.True(loadStatus == SceneEventProgressStatus.Started, $"Failed to start loading scene {m_CurrentSceneLoading}! Return status: {loadStatus}"); - yield return WaitForConditionOrTimeOut(() => m_ClientsLoadedScene); - AssertOnTimeout($"Timed out waiting for all clients to load scene {m_CurrentSceneLoading}!"); - } - + var sceneToMigrateTo = m_ScenesLoaded[2]; // Migrate the instances that don't synchronize with active scene changes into the 3rd loaded scene // (We are making sure these stay in the same scene they are migrated into) - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var spawnedObject in autoSyncInactive) { - SceneManager.MoveGameObjectToScene(m_ServerSpawnedPrefabInstances[i].gameObject, m_ScenesLoaded[2]); + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, sceneToMigrateTo); } // Migrate the instances that don't synchronize with active scene changes and are destroyed with the // scene unloading into the 3rd loaded scene // (We are making sure these get destroyed when the scene is unloaded) - for (int i = 0; i < m_ServerSpawnedDestroyWithSceneInstances.Count; i++) + foreach (var spawnedObject in m_ServerSpawnedDestroyWithSceneInstances) { - SceneManager.MoveGameObjectToScene(m_ServerSpawnedDestroyWithSceneInstances[i].gameObject, m_ScenesLoaded[2]); + SceneManager.MoveGameObjectToScene(spawnedObject.gameObject, sceneToMigrateTo); } // Make sure they migrated to the proper scene - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Now change the active scene - SceneManager.SetActiveScene(m_ScenesLoaded[1]); - // We have to do this - Object.DontDestroyOnLoad(m_TestPrefabAutoSynchActiveScene); + var newActiveScene = m_ScenesLoaded[1]; + SetActiveScene(newActiveScene); // First, make sure server-side scenes and client side scenes match - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that the auto-active-scene synchronization NetworkObjects migrated to the newly // assigned active scene - for (int i = 0; i < 3; i++) + foreach (var obj in autoSyncActive) { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} did not migrate into scene {m_ScenesLoaded[1].name}!"); + Assert.AreEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} did not migrate into scene {newActiveScene.name}!"); } // Verify that the other NetworkObjects that don't synchronize with active scene changes did // not migrate into the active scene. - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var obj in autoSyncInactive) { - Assert.False(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} migrated into scene {m_ScenesLoaded[1].name}!"); + Assert.AreNotEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} migrated into scene {newActiveScene.name}!"); } - for (int i = 0; i < 3; i++) + foreach (var obj in m_ServerSpawnedDestroyWithSceneInstances) { - Assert.False(m_ServerSpawnedDestroyWithSceneInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedDestroyWithSceneInstances[i].gameObject.name} migrated into scene {m_ScenesLoaded[1].name}!"); + Assert.AreNotEqual(newActiveScene, obj.gameObject.scene, $"{obj.gameObject.name} migrated into scene {newActiveScene.name}!"); } // Verify that a late joining client synchronizes properly and destroys the appropriate NetworkObjects yield return CreateAndStartNewClient(); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + AssertOnTimeout("Failed to start or create a new client!"); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client #1] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Now, unload the scene containing the NetworkObjects that don't synchronize with active scene changes DestroyWithSceneInstancesTestHelper.NetworkObjectDestroyed += OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed; - m_ClientsUnloadedScene = false; - m_CurrentSceneUnloading = m_ScenesLoaded[2].name; - var status = m_ServerNetworkManager.SceneManager.UnloadScene(m_ScenesLoaded[2]); - Assert.True(status == SceneEventProgressStatus.Started, $"Failed to start unloading scene {m_ScenesLoaded[2].name} with status {status}!"); - yield return WaitForConditionOrTimeOut(() => m_ClientsUnloadedScene); + + yield return UnloadScene(authority, sceneToMigrateTo); // Clean up any destroyed NetworkObjects - for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) + for (int i = authoritySpawnedInstances.Count - 1; i >= 0; i--) { - if (m_ServerSpawnedPrefabInstances[i] == null) + if (authoritySpawnedInstances[i] == null) { - m_ServerSpawnedPrefabInstances.RemoveAt(i); + authoritySpawnedInstances.RemoveAt(i); } } - AssertOnTimeout($"Timed out waiting for all clients to unload scene {m_CurrentSceneUnloading}!"); - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + AssertOnTimeout($"Timed out waiting for all clients to unload scene {sceneToMigrateTo.name}!"); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // Verify that the NetworkObjects that don't synchronize with active scene changes but marked to not // destroy with the scene are migrated into the current active scene - for (int i = 3; i < m_ServerSpawnedPrefabInstances.Count; i++) + foreach (var obj in autoSyncInactive) { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.scene == m_ScenesLoaded[1], - $"{m_ServerSpawnedPrefabInstances[i].gameObject.name} did not migrate into scene {m_ScenesLoaded[1].name} but are in scene {m_ServerSpawnedPrefabInstances[i].gameObject.scene.name}!"); + Assert.True(obj.gameObject.scene == newActiveScene, $"{obj.gameObject.name} did not migrate into scene {newActiveScene.name} but are in scene {obj.gameObject.scene.name}!"); } // Verify all NetworkObjects that should have been destroyed with the scene unloaded were destroyed @@ -414,18 +410,14 @@ public IEnumerator ActiveSceneSynchronizationTest() // Now unload the active scene to verify all remaining NetworkObjects are migrated into the SceneManager // assigned active scene - m_ClientsUnloadedScene = false; - m_CurrentSceneUnloading = m_ScenesLoaded[1].name; - m_ServerNetworkManager.SceneManager.UnloadScene(m_ScenesLoaded[1]); - yield return WaitForConditionOrTimeOut(() => m_ClientsUnloadedScene); - AssertOnTimeout($"Timed out waiting for all clients to unload scene {m_CurrentSceneUnloading}!"); + yield return UnloadScene(authority, newActiveScene); // Clean up any destroyed NetworkObjects - for (int i = m_ServerSpawnedPrefabInstances.Count - 1; i >= 0; i--) + for (int i = authoritySpawnedInstances.Count - 1; i >= 0; i--) { - if (m_ServerSpawnedPrefabInstances[i] == null) + if (authoritySpawnedInstances[i] == null) { - m_ServerSpawnedPrefabInstances.RemoveAt(i); + authoritySpawnedInstances.RemoveAt(i); } } @@ -433,22 +425,63 @@ public IEnumerator ActiveSceneSynchronizationTest() yield return CreateAndStartNewClient(); // Verify the late joining client spawns all instances - yield return WaitForConditionOrTimeOut(VerifyAllClientsSpawnedInstances); - AssertOnTimeout($"Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); + yield return WaitForSpawnedOnAllOrTimeOut(authoritySpawnedInstances); + AssertOnTimeout($"[Late Joined Client #2] Timed out waiting for all clients to spawn {nameof(NetworkObject)}s!"); // Verify the instances are in the correct scenes - yield return WaitForConditionOrTimeOut(VerifySpawnedObjectsMigrated); + yield return WaitForConditionOrTimeOut(errorLog => VerifyAllScenesMatch(errorLog, authoritySpawnedInstances)); AssertOnTimeout($"[Late Joined Client #2] Timed out waiting for all clients to migrate all NetworkObjects into the appropriate scenes!"); // All but 3 instances should be destroyed - Assert.True(m_ServerSpawnedPrefabInstances.Count == 3, $"{nameof(m_ServerSpawnedPrefabInstances)} still has a count of {m_ServerSpawnedPrefabInstances.Count} " + - $"NetworkObject instances!"); - Assert.True(m_ServerSpawnedDestroyWithSceneInstances.Count == 0, $"{nameof(m_ServerSpawnedDestroyWithSceneInstances)} still has a count of " + - $"{m_ServerSpawnedDestroyWithSceneInstances.Count} NetworkObject instances!"); - for (int i = 0; i < 3; i++) - { - Assert.True(m_ServerSpawnedPrefabInstances[i].gameObject.name.Contains(m_TestPrefab.gameObject.name), $"Expected {m_ServerSpawnedPrefabInstances[i].gameObject.name} to contain {m_TestPrefab.gameObject.name}!"); - } + Assert.IsEmpty(autoSyncActive.Where(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.ActiveSceneSynchronization)}=true should have been destroyed when the active scene was unloaded!"); + Assert.IsEmpty(m_ServerSpawnedDestroyWithSceneInstances.Where(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.DestroyWithScene)} should have been destroyed when the active scene was unloaded!"); + Assert.AreEqual(3, autoSyncInactive.Count(obj => obj != null), $"All the NetworkObjects with {nameof(NetworkObject.ActiveSceneSynchronization)}=false should have survived the active scene change!"); + } + + /// + /// Helper method to load a scene and wait for the OnLoadEventCompleted event to trigger. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerator LoadScene(NetworkManager authority, string sceneToLoad) + { + m_LoadEventCompleted = false; + authority.SceneManager.OnLoadEventCompleted += OnLoadEventCompleted; + var loadStatus = authority.SceneManager.LoadScene(sceneToLoad, LoadSceneMode.Additive); + Assert.True(loadStatus == SceneEventProgressStatus.Started, $"Failed to start loading scene {sceneToLoad}! Return status: {loadStatus}"); + yield return WaitForConditionOrTimeOut(() => m_LoadEventCompleted); + // Remove subscription prior to potentially asserting. + authority.SceneManager.OnLoadEventCompleted -= OnLoadEventCompleted; + AssertOnTimeout($"Timed out waiting for all clients to load scene {sceneToLoad}!"); + } + + /// + /// Helper method to load a scene and wait for the OnUnloadEventCompleted event to trigger. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private IEnumerator UnloadScene(NetworkManager authority, Scene sceneToUnload) + { + m_UnloadEventCompleted = false; + authority.SceneManager.OnUnloadEventCompleted += OnUnloadEventCompleted; + authority.SceneManager.UnloadScene(sceneToUnload); + + // Always make sure the scene event has completed. Trying to check if the scenes are loaded as a metric can + // create edge case scenarios where the scene might have been just loaded but not processed during synchronization. + yield return WaitForConditionOrTimeOut(() => m_UnloadEventCompleted); + // Remove subscription prior to potentially asserting. + authority.SceneManager.OnUnloadEventCompleted -= OnUnloadEventCompleted; + AssertOnTimeout($"Timed out waiting for all clients to unload scene {sceneToUnload.name}!"); + } + + private bool m_LoadEventCompleted; + private void OnLoadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + m_LoadEventCompleted = true; + } + + private bool m_UnloadEventCompleted; + private void OnUnloadEventCompleted(string sceneName, LoadSceneMode loadSceneMode, List clientsCompleted, List clientsTimedOut) + { + m_UnloadEventCompleted = true; } /// @@ -462,30 +495,16 @@ private void OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed(NetworkObjec private void SceneManager_OnSceneEvent(SceneEvent sceneEvent) { + var authority = GetAuthorityNetworkManager(); switch (sceneEvent.SceneEventType) { case SceneEventType.LoadComplete: { - if (sceneEvent.ClientId == m_ServerNetworkManager.LocalClientId) + if (sceneEvent.ClientId == authority.LocalClientId) { - m_SceneLoaded = sceneEvent.Scene; m_ScenesLoaded.Add(sceneEvent.Scene); } - break; - } - case SceneEventType.LoadEventCompleted: - { - Assert.IsTrue(sceneEvent.ClientsThatTimedOut.Count == 0, $"{sceneEvent.ClientsThatTimedOut.Count} clients timed out while trying to load scene {m_CurrentSceneLoading}!"); - m_ClientsLoadedScene = true; - break; - } - case SceneEventType.UnloadEventCompleted: - { - if (sceneEvent.SceneName == m_CurrentSceneUnloading) - { - m_ClientsUnloadedScene = true; - } - break; + return; } } } @@ -495,12 +514,51 @@ protected override IEnumerator OnTearDown() m_TestPrefab = null; m_TestPrefabAutoSynchActiveScene = null; m_TestPrefabDestroyWithScene = null; + // Any static event that could be subscribed to but not unsubscribed to due to an assert needs to be cleaned up here. + DestroyWithSceneInstancesTestHelper.NetworkObjectDestroyed -= OnNonActiveSynchDestroyWithSceneNetworkObjectDestroyed; SceneManager.SetActiveScene(m_OriginalActiveScene); m_ServerSpawnedDestroyWithSceneInstances.Clear(); m_ServerSpawnedPrefabInstances.Clear(); m_ScenesLoaded.Clear(); yield return base.OnTearDown(); } + + private NetworkObject GetAuthorityInstance(NetworkObject instance) + { + if (instance.IsOwner) + { + return instance; + } + + var owner = instance.OwnerClientId; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.LocalClientId == owner) + { + networkManager.SpawnManager.SpawnedObjects.TryGetValue(instance.NetworkObjectId, out var networkObject); + return networkObject; + } + } + + return null; + } + } + + internal class SceneOriginTracker : NetworkBehaviour + { + public NetworkSceneHandle SceneWhereAwakeHappened; + private void Awake() + { + SceneWhereAwakeHappened = gameObject.scene.handle; + } + } + + internal class ShouldNeverSpawn : NetworkBehaviour + { + public override void OnNetworkSpawn() + { + Assert.Fail("Should never spawn!"); + } } /// @@ -509,7 +567,7 @@ protected override IEnumerator OnTearDown() /// internal class DestroyWithSceneInstancesTestHelper : NetworkBehaviour { - public static GameObject ShouldNeverSpawn; + public static ShouldNeverSpawn ShouldNeverSpawn; public static Dictionary> ObjectRelativeInstances = new Dictionary>(); @@ -544,12 +602,9 @@ public override void OnNetworkDespawn() public override void OnDestroy() { - if (NetworkManager != null) + if (IsSpawned && HasAuthority) { - if (NetworkManager.LocalClientId == NetworkManager.ServerClientId) - { - NetworkObjectDestroyed?.Invoke(NetworkObject); - } + NetworkObjectDestroyed?.Invoke(NetworkObject); } base.OnDestroy(); } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs new file mode 100644 index 0000000000..28cd0b49d2 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs @@ -0,0 +1,205 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace TestProject.RuntimeTests +{ + /// + /// Validates client synchronization. + /// + /// + /// This includes both client synchronization mode passes along with verifying + /// that s migrated into the DDOL will still be spawned + /// and preserve their value. + /// + internal class NetworkSceneManagerStartupTests : NetcodeIntegrationTest + { + private const string k_ActiveScene = "SessionSynchronize"; + private const string k_AdditionalScene = "InSceneNetworkObjectMovesToDDOL"; + + private readonly List m_ObjectsInScenes = new List(); + private Scene m_OriginalActiveScene; + private Scene m_SceneLoaded; + private bool m_CanStart = false; + + // Used with scene pre-loading + private string m_SceneToLoad; + private bool m_SceneWasLoaded; + + #region NetcodeIntegrationTest overrides + protected override int NumberOfClients => 0; + protected override bool CanStartServerAndClients() => m_CanStart; + + protected override void OnOneTimeSetup() + { + // Get the active scene prior to any interation running through the OnSetup. + m_OriginalActiveScene = SceneManager.GetActiveScene(); + base.OnOneTimeSetup(); + } + + protected override IEnumerator OnSetup() + { + // Always reset + m_CanStart = false; + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + // Avoid trying to spawn this + Object.DontDestroyOnLoad(m_PlayerPrefab); + base.OnCreatePlayerPrefab(); + } + + protected override IEnumerator OnTearDown() + { + LogAssert.ignoreFailingMessages = false; + m_ObjectsInScenes.Clear(); + // Restore the integration test scene as the active scene. + SceneManager.SetActiveScene(m_OriginalActiveScene); + + // Unload everything else. + for (int i = 0; i < SceneManager.sceneCount - 1; i++) + { + var scene = SceneManager.GetSceneAt(i); + if (scene == m_OriginalActiveScene) + { + continue; + } + SceneManager.UnloadSceneAsync(scene); + yield return WaitForConditionOrTimeOut(() => !scene.isLoaded); + } + yield return base.OnTearDown(); + } + #endregion + + /// + /// Validates things migrated into the DDOL will be included when synchronizing clients. + /// + /// The client synchronization mode to use for the current pass. + [UnityTest] + public IEnumerator AllExistingObjectsAreSpawnedAtStartup([Values] LoadSceneMode clientSynchronizationMode) + { + LogAssert.ignoreFailingMessages = true; + yield return PreLoadScene(k_ActiveScene); + yield return PreLoadScene(k_AdditionalScene); + SceneManager.SetActiveScene(m_SceneLoaded); + + var existingObjects = new List(); + var dontDestroyOnLoadCount = 0; + + // Now get everything migrated into the DDOL and the DDOL scene itself. + var ddolScene = GetNetworkObjectsInDDOL(); + // Validate NetworkObjects in DDOL + foreach (var obj in m_ObjectsInScenes) + { + Assert.IsFalse(obj.IsSpawned, $"NetworkObject {obj.name} should not have been spawned!"); + + existingObjects.Add(obj); + if (obj.gameObject.scene.name == ddolScene.name) + { + dontDestroyOnLoadCount++; + } + } + + Assert.IsNotEmpty(existingObjects, $"Found no existing {nameof(NetworkObject)}s!"); + Assert.That(dontDestroyOnLoadCount, Is.GreaterThan(0), "Found no {nameof(NetworkObject)}s in the DDOL scene!"); + + // Now enable starting server and clients and start the server + m_CanStart = true; + yield return StartServerAndClients(); + + // Apply the test's client synchronization mode + GetAuthorityNetworkManager().SceneManager.SetClientSynchronizationMode(clientSynchronizationMode); + + // Validate the existing objects + foreach (var existingObject in existingObjects) + { + Assert.IsFalse(existingObject == null, "Expected existing object to still exist!"); + Assert.IsTrue(existingObject.IsSpawned, $"NetworkObject {existingObject.name} in scene {existingObject.gameObject.scene.name} was not spawned!"); + Assert.IsTrue(existingObject.InScenePlaced, $"NetworkObject {existingObject.name} in scene {existingObject.gameObject.scene.name} was not inScenePlaced!"); + } + + // If additive client synchronization mode, load the scenes that are already loaded + // on the scene authority instance so they will be used during client synchronization. + if (clientSynchronizationMode == LoadSceneMode.Additive) + { + yield return PreLoadScene(k_ActiveScene); + yield return PreLoadScene(k_AdditionalScene); + } + + // Late join a client + yield return CreateAndStartNewClient(); + + // Wait for all existing objects to spawn on the client + yield return WaitForSpawnedOnAllOrTimeOut(existingObjects); + AssertOnTimeout("Timed out waiting for objects to spawn on all clients!"); + } + + #region Scene loading and related methods + + /// + /// Uses the 's current scene which should be + /// the DDOL scene. + /// + /// The DDOL scene + private Scene GetNetworkObjectsInDDOL() + { + // This does catch any newly instantiated in-scene placed NetworkObjects moved into DDOL + // during awake. + var sceneToUse = NetworkManager.Singleton.gameObject.scene; + Assert.IsTrue(sceneToUse.IsValid() && sceneToUse.name == "DontDestroyOnLoad", $"[{NetworkManager.Singleton.name}] Is not in the DDOL! Is this being invoked too early?"); + + foreach (var rootObject in sceneToUse.GetRootGameObjects()) + { + foreach (var networkObject in rootObject.GetComponentsInChildren()) + { + if (!m_ObjectsInScenes.Contains(networkObject) && networkObject.InScenePlaced) + { + m_ObjectsInScenes.Add(networkObject); + } + } + } + return sceneToUse; + } + + private IEnumerator PreLoadScene(string sceneName) + { + m_SceneToLoad = sceneName; + m_SceneWasLoaded = false; + SceneManager.sceneLoaded += OnSceneLoad; + SceneManager.LoadScene(sceneName, LoadSceneMode.Additive); + yield return WaitForConditionOrTimeOut(() => m_SceneWasLoaded); + AssertOnTimeout("Timed out waiting for scene to load!"); + SceneManager.sceneLoaded -= OnSceneLoad; + } + + private void TrackObjectsInScene(Scene scene) + { + // This does not catch things moved into the DDOL during awake. + foreach (var rootObject in scene.GetRootGameObjects()) + { + foreach (var networkObject in rootObject.GetComponentsInChildren()) + { + m_ObjectsInScenes.Add(networkObject); + } + } + } + + private void OnSceneLoad(Scene scene, LoadSceneMode loadSceneMode) + { + if (m_SceneToLoad == scene.name) + { + m_SceneWasLoaded = true; + m_SceneLoaded = scene; + TrackObjectsInScene(scene); + } + } + #endregion + } +} diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta new file mode 100644 index 0000000000..ddf1a7550e --- /dev/null +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: ca8e58d04b6fc6c48a6661c90d4c12dd \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs index 909bb7585d..e22d924bac 100644 --- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs +++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs @@ -1,6 +1,5 @@ using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Text; using NUnit.Framework; using TestProject.ManualTests; @@ -213,9 +212,6 @@ public enum InstantiateAndSpawnMethods [UnityTest] public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods instantiateAndSpawnType) { - var gloabalObjectId = m_SceneManagementEnabled ? 0 : InScenePlacedHelper.ServerInSceneDefined.First().GlobalObjectIdHash; - var firstError = $"[Netcode] Failed to create object locally. [globalObjectIdHash={gloabalObjectId}]. NetworkPrefab could not be found. Is the prefab registered with NetworkManager?"; - var secondError = $"[Netcode] Failed to spawn NetworkObject for Hash {gloabalObjectId}."; m_InstantiateAndSpawnType = instantiateAndSpawnType; // We have to spawn the first client manually in order to account for the errors when scene management is disabled. @@ -224,7 +220,6 @@ public IEnumerator TestPrefabsSpawning([Values] InstantiateAndSpawnMethods insta // spawn the original prefab and when spawning dynamically the override is used. yield return CreateAndStartNewClient(); - var spawnManager = m_ServerNetworkManager.SpawnManager; // If scene management is enabled, then we want to verify against the editor // assigned in-scene placed NetworkObjects if (m_SceneManagementEnabled) diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index b2632cd9f5..63b5a1481e 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -44,9 +44,6 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkSceneManagerCallbacks/SceneWeAreSwitchingFrom.unity guid: 073bd2111475c0643be45b7abe6a97ad - - enabled: 1 - path: Assets/Scenes/MultiprocessTestScene.unity - guid: 76743cb7b342c49279327834918a9c6e - enabled: 1 path: Assets/Scenes/EmptyScene.unity guid: a2545a872c007404fbb6b0393ab74974 @@ -133,6 +130,9 @@ EditorBuildSettings: guid: 17b92153f7381d34fa48c4d5c0393d13 - enabled: 1 path: Assets/Tests/Manual/IntegrationTestScenes/EmptyScene3.unity + guid: bfb4addb64cf6455a88668eba2f759ba + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/MoveADynamicObjectInAwake.unity guid: abd4c8b51c445d54faa16c67ac973f1b - enabled: 1 path: Assets/Samples/SpawnObject/SimpleSpawn.unity @@ -158,7 +158,9 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkAnimatorTests/AnimationBidirectionalTriggers/NetworkAnimatorDualTriggerCheer.unity guid: e12df855278120245a8a936a6a52b5bd + - enabled: 1 + path: Assets/Tests/Manual/IntegrationTestScenes/InSceneNetworkObjectMovesToDDOL.unity + guid: 4c20c17b4f92e634f8796b0460851d49 m_configObjects: com.unity.addressableassets: {fileID: 11400000, guid: 5a3d5c53c25349c48912726ae850f3b0, type: 2} - m_UseUCBPForAssetBundles: 0 From 501ca0209aa6086e324c88610045e6f077288b9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Thu, 16 Jul 2026 18:47:58 +0200 Subject: [PATCH 29/64] chore: added up-port section to a PR template (#4088) Added up-port section to a PR Co-authored-by: Noel Stephens --- .github/pull_request_template.md | 6 ++++++ .github/workflows/pr-description-validation.yml | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1af44ed7da..5872f6cbbd 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -56,6 +56,12 @@ _Does the change require QA team to:_ If any boxes above are checked the QA team will be automatically added as a PR reviewer. +## Up-port +[//]: # ( +This section is REQUIRED and should link to the PR that targets develop-3.0.0 branch. Assuming that the PR lands agains default develop-2.0.0 branch +If this is not needed, for example feature specific to NGOv2.X, then just mention this fact. +) + ## Backports [//]: # ( This section is REQUIRED and should link to the PR that targets other NGO version which is either develop or develop-2.0.0 branch diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index 740fadb922..748e887db5 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -51,6 +51,10 @@ jobs: header: '## Backports', description: 'PR description must include a "## Backports" section. Please add this section and provide information about this PR backport to develop or develop-2.0.0 branch respectively or explain why backport is not needed.' }, + { + header: '## Up-port', + description: 'PR description must include a "## Up-port" section. Please add this section and provide information about this PR up-port to develop-3.0.0 branch or explain why up-port is not needed.' + }, { header: '## Testing & QA', description: 'PR description must include a "## Testing & QA" section. Please add this section and provide information about the testing performed for this PR. It can range from adding unit tests to full samples and is needed from QA side to analyze PRs while Playtesting for the release.' From 8f8b25c08b6393cad2aa4d321efb4e3e5188388a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 16 Jul 2026 16:41:20 -0500 Subject: [PATCH 30/64] fix: networkanimator does no bounds check when reading parameters (#4090) * fix Adding bounds check for reading NetworkAnimator parameters. * test Adding a unit test to validate the bounds check. * update adding change log entry * style adding additional comments for clarity on what the test is doing. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkAnimator.cs | 7 ++++ .../Runtime/Animation/NetworkAnimatorTests.cs | 35 +++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index ff9f37f6a8..045b685fc8 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue where `NetworkAnimator` did no bounds check on the parameter index read prior to obtaining a pointer to the location within the array. (#4090) - Issue where scene migration in a distributed authority session would throw an exception on clients migrating their owned `NetworkObject` instances to a different scene.(#4086) - Issue where migrating a dynamically spawned or in-scene placed `NetworkObject` into a different scene during awake could result in that spawned instance to not be synchronized. (#4086) - Issue where a NullReferenceException was thrown when a non-authority failed to spawn a NetworkObject. (#4067) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index cdb25aa598..c999c26e9f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -1377,6 +1377,13 @@ private unsafe void ReadParameters(FastBufferReader reader) while (totalParametersRead < totalParametersToRead) { ByteUnpacker.ReadValuePacked(reader, out uint parameterIndex); + + // Do bounds check prior to getting the element as a reference at that index. + if (parameterIndex >= m_CachedAnimatorParameters.Length) + { + NetworkManager.Log.ErrorServer(new Logging.Context(LogLevel.Error, $"[{nameof(NetworkAnimator)}][{name}] Invalid index of {parameterIndex} was received when there are only {m_CachedAnimatorParameters.Length} parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); + return; + } ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), (int)parameterIndex); var hash = cacheValue.Hash; if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt) diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index 715e7f6156..d685fb68ca 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -3,9 +3,11 @@ using System.Linq; using NUnit.Framework; using Unity.Netcode; +using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using static Unity.Netcode.Components.NetworkAnimator; namespace TestProject.RuntimeTests @@ -336,7 +338,40 @@ public void ParameterExcludedTests() VerboseDebug($" ------------------ Parameter Test [{m_OwnerShipMode}] Stopping ------------------ "); } + private unsafe void MockWritingParameters(ref FastBufferWriter writer) + { + writer.Seek(0); + writer.Truncate(); + + // Write out how many parameter entries to read + BytePacker.WriteValuePacked(writer, (uint)1); + // Write an invalid index level (anything would be invalid with no parameters) + BytePacker.WriteValuePacked(writer, (uint)1000); + // Write some value for the invalid parameter + BytePacker.WriteValuePacked(writer, (uint)10); + } + + [Test] + public void ParameterBoundsCheck() + { + var gameObject = new GameObject(); + gameObject.AddComponent(); + var networkAnimator = gameObject.AddComponent(); + var writer = new FastBufferWriter(40, Unity.Collections.Allocator.TempJob); + + // Mock a parameter update with an invalid index + MockWritingParameters(ref writer); + + var invalidParameters = new ParametersUpdateMessage() + { + Parameters = writer.ToArray() + }; + // Expect an error message + LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex($"parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); + // Pass in the invalid ParametersUpdateMessage + networkAnimator.UpdateParameters(ref invalidParameters); + } private bool AllTriggersDetected(OwnerShipMode ownerShipMode) { From 9bc24c8f3a473254edc4825d94756d76302d284e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 17 Jul 2026 03:23:32 -0500 Subject: [PATCH 31/64] chore: adding a single player session section (#4062) * update adding single player session section under the configuration area after transports. * Refactor ExtendedNetworkManager for single player Rearranged variable declarations and removed StartSinglePlayer method. * update Using the newer approach to adding examples where the example script lives in the same integration test file that tests the example to assure it is always a working example. * update Adding change log entry. * Change class visibility to internal for example class --------- Co-authored-by: Unity Netcode CI <74025435+netcode-ci-service@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Documentation~/TableOfContents.md | 1 + .../advanced-topics/singleplayer.md | 18 ++ .../Configuration.meta | 8 + .../Configuration/SinglePlayerSessions.cs | 220 ++++++++++++++++++ .../SinglePlayerSessions.cs.meta | 2 + 6 files changed, 250 insertions(+) create mode 100644 com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 045b685fc8..2f88b9e7a2 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added +- Single player session section to provide users with information about `SinglePlayerTransport` and an example script of how to switch between single and multi player sessions. (#4062) ### Changed diff --git a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md index 47bd50c6bc..24219a83e5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md +++ b/com.unity.netcode.gameobjects/Documentation~/TableOfContents.md @@ -18,6 +18,7 @@ * [Connection approval](basics/connection-approval.md) * [Max players](basics/maxnumberplayers.md) * [Transports](advanced-topics/transports.md) + * [Single player sessions](advanced-topics/singleplayer.md) * [Unity Relay](relay/relay.md) * [Command-line arguments](command-line-arguments.md) * [Network components](network-components.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md new file mode 100644 index 0000000000..e4f45a5ead --- /dev/null +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md @@ -0,0 +1,18 @@ +# Single player sessions + +Netcode for GameObjects provides a [SinglePlayerTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport.html) which derives from [NetworkTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.NetworkTransport.html). + +This provides the ability to run a hosted session using the single player transport without having to modify your primary netcode script. + +## Adding the single player transport + +- Add the `SinglePlayerTransport` to your NetworkManager. +- You can create a custom `MonoBehaviour` component to handle your connection flow or you can derive from `NetworkManager` and add additional methods/logic to handle starting a single player session or multiplayer session. + - When starting a single player session, prior to starting the NetworkManager as a host (_required_), you will want to assign the `SinglePlayerTransport` to the `NetworkManager.NetworkConfig.NetworkTransport`. + - When starting a multiplayer session, prior to starting the NetworkManager, you will want to assign the `UnityTransport` (_or any other `NetworkTransport` derived class that you might use for multiplayer sessions_) to the `NetworkManager.NetworkConfig.NetworkTransport`. + +## Example script + +Below is an example component script that provides a single method to start a single or multi player session: + +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs#SinglePlayerTransportExample)] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta new file mode 100644 index 0000000000..baf6e6f199 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ac415df2337f69349996d9da97f07242 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs new file mode 100644 index 0000000000..945807a06c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs @@ -0,0 +1,220 @@ +using System.Collections; +using System.Text; +using NUnit.Framework; +using Unity.Netcode; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.Transports.SinglePlayer; +using Unity.Netcode.Transports.UTP; +using UnityEngine; +using UnityEngine.TestTools; + +namespace DocumentationCodeSamples +{ + #region SinglePlayerTransportExample + /// + /// Example of how to start a single player or multiplayer session. + /// Place this on your NetworkManager's GameObject. + /// + internal class SwitchingTransportTypesExample : MonoBehaviour + { + public enum StartType + { + SinglePlayer, + Client, + Host, + Server + } + + private UnityTransport m_UnityTransport; + private SinglePlayerTransport m_SinglePlayerTransport; + private NetworkManager m_NetworkManager; + + private void Awake() + { + m_UnityTransport = GetComponent(); + m_SinglePlayerTransport = GetComponent(); + m_NetworkManager = GetComponent(); + } + + public bool StartSession(StartType startType) + { + var startStatus = false; + // Set the transport to use before starting + m_NetworkManager.NetworkConfig.NetworkTransport = startType == StartType.SinglePlayer ? m_SinglePlayerTransport : m_UnityTransport; + switch (startType) + { + case StartType.Host: + case StartType.SinglePlayer: + { + // Starting a host or single player is the same + startStatus = m_NetworkManager.StartHost(); + break; + } + case StartType.Server: + { + startStatus = m_NetworkManager.StartServer(); + break; + } + case StartType.Client: + { + startStatus = m_NetworkManager.StartClient(); + break; + } + } + return startStatus; + } + } + #endregion + + internal class VerifyNetcodeSessionActive : NetworkBehaviour + { + public bool RpcReceived { get; private set; } + + public bool TestNetworkVariableEvent { get; private set; } + public NetworkVariable TestNetworkVariable = new NetworkVariable(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner); + + [Rpc(SendTo.Everyone, InvokePermission = RpcInvokePermission.Owner)] + private void VerifyRpc(RpcParams rpcParams = default) + { + RpcReceived = true; + } + + public void TestConnectivity() + { + if (IsOwner) + { + VerifyRpc(); + TestNetworkVariable.Value = true; + } + } + + protected override void OnNetworkPostSpawn() + { + TestNetworkVariable.OnValueChanged += TestNetworkVariableChanged; + base.OnNetworkPostSpawn(); + } + + private void TestNetworkVariableChanged(bool previous, bool current) + { + TestNetworkVariableEvent = true; + } + } + + [TestFixture(SessionType.SinglePlayer)] + [TestFixture(SessionType.MultiPlayer)] + internal class SwitchingTransportTypesTests : NetcodeIntegrationTest + { + public enum SessionType + { + SinglePlayer, + MultiPlayer + } + + protected override int NumberOfClients => 0; + + // We do not need to test this against CMB. + protected override bool UseCMBService() + { + return false; + } + + private SessionType m_SessionType; + public SwitchingTransportTypesTests(SessionType sessionType) + { + m_SessionType = sessionType; + } + + protected override IEnumerator OnSetup() + { + m_CanStart = false; + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + + private bool m_CanStart = false; + protected override bool CanStartServerAndClients() + { + return m_CanStart; + } + + private NetworkManager m_Client; + protected override void OnNewClientCreated(NetworkManager networkManager) + { + m_Client = networkManager; + networkManager.NetworkConfig.EnableSceneManagement = false; + base.OnNewClientCreated(networkManager); + } + + [UnityTest] + public IEnumerator SwitchTransportTest() + { + var authority = GetAuthorityNetworkManager(); + authority.NetworkConfig.EnableSceneManagement = false; + var startType = SwitchingTransportTypesExample.StartType.Host; + if (m_SessionType == SessionType.SinglePlayer) + { + startType = SwitchingTransportTypesExample.StartType.SinglePlayer; + authority.gameObject.AddComponent(); + } + m_CanStart = true; + var example = authority.gameObject.AddComponent(); + Assert.IsTrue(example.StartSession(startType), "Failed to start single player session!"); + + if (m_SessionType != SessionType.SinglePlayer) + { + yield return CreateAndStartNewClient(); + AssertOnTimeout("Timed out waiting for client to start and connect!"); + } + + var verifyNetcode = authority.LocalClient.PlayerObject.GetComponent(); + verifyNetcode.TestConnectivity(); + if (m_SessionType != SessionType.SinglePlayer) + { + m_Client.LocalClient.PlayerObject.GetComponent().TestConnectivity(); + } + + yield return WaitForConditionOrTimeOut(VerifyNetcodeSession); + AssertOnTimeout($"Single player session had netcode related errors:"); + } + + /// + /// Verifies that all spawned players received the RPC and NetworkVariable + /// changed event. + /// + private bool VerifyNetcodeSession(StringBuilder errorLog) + { + foreach (var networkManager in m_NetworkManagers) + { + foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList) + { + if (!networkObject.IsPlayerObject) + { + continue; + } + var verify = networkObject.gameObject.GetComponent(); + + if (!verify.RpcReceived) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] Rpc was not recieved!"); + } + + if (!verify.TestNetworkVariableEvent) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] NetworkVariable.OnValueChanged did not get invoked!"); + } + + if (!verify.TestNetworkVariable.Value) + { + errorLog.AppendLine($"[Client-{networkManager.LocalClientId}][{verify.name}] {nameof(VerifyNetcodeSessionActive.TestNetworkVariable)} did not get set!"); + } + } + } + return errorLog.Length == 0; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta new file mode 100644 index 0000000000..bd9ea061b2 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: cd8ec5118fc47e449ae915c51a3806b4 \ No newline at end of file From 2f1a459e63010b2a95347ba174ef55cd7087bf1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= Date: Fri, 17 Jul 2026 13:08:06 +0200 Subject: [PATCH 32/64] Disabled NetworkPrefabHandlerSpawnAndSynchronizeTests for iOS and macOS platforms --- .../Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index 3124dec5f7..0c5895b612 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -28,6 +28,7 @@ protected override void OnServerAndClientsCreated() } [UnityTest] + [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer, RuntimePlatform.OSXPlayer, RuntimePlatform.OSXEditor })] // Ignored test tracked in MTT-15473 public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() { var nonAuthority = GetNonAuthorityNetworkManager(); From 19fdff89c9b1dcec44c86d721d39af9a37bd5172 Mon Sep 17 00:00:00 2001 From: Netcode team bot Date: Mon, 20 Jul 2026 11:18:18 +0200 Subject: [PATCH 33/64] chore: Updated aspects of Netcode package in anticipation of v2.13.1 release (#4092) --- com.unity.netcode.gameobjects/CHANGELOG.md | 22 +++++++++++++++------- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2f88b9e7a2..fa0e39c223 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -10,7 +10,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Added -- Single player session section to provide users with information about `SinglePlayerTransport` and an example script of how to switch between single and multi player sessions. (#4062) ### Changed @@ -21,6 +20,21 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Removed +### Fixed + + +### Security + + +### Obsolete + + +## [2.13.1] - 2026-07-19 + +### Added + +- Single player session section to provide users with information about `SinglePlayerTransport` and an example script of how to switch between single and multi player sessions. (#4062) + ### Fixed - Issue where `NetworkAnimator` did no bounds check on the parameter index read prior to obtaining a pointer to the location within the array. (#4090) @@ -33,12 +47,6 @@ Additional documentation and release notes are available at [Multiplayer Documen - Issue where NetworkRigidbodyBase was always checking the 3D rigid body's interpolation mode when determining if it is kinematic and needs to put the rigid body to sleep and then switch to interpolation. (#4012) - Fixed AnticipatedNetworkTransform not respecting the InLocalSpace flag. (#3995) -### Security - - -### Obsolete - - ## [2.13.0] - 2026-06-21 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 074afe6e47..8c1951af95 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": "2.13.1", + "version": "2.13.2", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 80c55ded598314c16120ab61b68df09f360b710e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Thu, 23 Jul 2026 22:48:24 +0200 Subject: [PATCH 34/64] chore: Adding a note about DontDestroyOnLoad usage for Networked scene objects (#4070) Docs correction --- .../basics/scenemanagement/inscene-placed-networkobjects.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md index 6bc209722b..0cf6db3bff 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md @@ -78,6 +78,9 @@ private void Start() > [!NOTE] > Once migrated into the DDoL, migrating the in-scene placed NetworkObject back into a different scene after it has already been spawned will cause soft synchronization errors with late-joining clients. Once in the DDoL it should stay in the DDoL. This is only for scene switching. If you aren't using scene switching, then it's recommended to use an additively loaded scene and keep that scene loaded for as long as you wish to persist the in-scene placed NetworkObject(s) being used for state management purposes. +> [!WARNING] +> Manually calling `DontDestroyOnLoad(gameObject)` is only supported for the scene-switching manager scenario described above (that is, a single in-scene placed NetworkObject used for state management that's migrated into the DDoL and stays there). Marking ordinary in-scene placed NetworkObjects as `DontDestroyOnLoad` isn't supported and can cause the object's `GlobalObjectIdHash` to no longer resolve after a scene change, resulting in errors such as "NetworkPrefab hash was not found!". To persist in-scene placed NetworkObjects across scene changes, prefer keeping them in an additively loaded scene that stays loaded for the duration of the session rather than using `DontDestroyOnLoad`. + ## Complex in-scene NetworkObjects The most common mistake when using an in-scene placed NetworkObject is to try and use it like a dynamically spawned NetworkObject. When trying to decide if you should use an in-scene placed or dynamically spawned NetworkObject, you should ask yourself the following questions: From b998fc0a3b1c8470b4a41db46e736e70cdcad6d3 Mon Sep 17 00:00:00 2001 From: Nikos Date: Sat, 25 Jul 2026 01:06:03 +0100 Subject: [PATCH 35/64] ci: Split code coverage into Package Test and Project Test jobs (#4101) * ci: Split code coverage into Package Test and Project Test jobs Convert the existing coverage job to [Code Coverage] Package Test - NGO [ubuntu, trunk] and add [Code Coverage] Project Test - NGO testproject [ubuntu, trunk]. Wire Package Test into PR/nightly triggers and Project Test into the weekly run. * ci: Run both coverage jobs on PR and nightly * ci: Use pinnedTrunk for PR coverage and drop package-test job --- .yamato/_triggers.yml | 8 ++++---- .yamato/code-coverage.yml | 29 +++++++++++++++++------------ 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index b88547780c..d70ba68fe9 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -89,8 +89,8 @@ pr_code_changes_checks: # Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} + # Run code coverage test (PRs use the pinned "safe" trunk) + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ pinnedTrunk }} triggers: expression: |- (pull_request.comment eq "ngo" OR @@ -159,8 +159,8 @@ develop_nightly: - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_6000.0 # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test - # Run code coverage test - - .yamato/code-coverage.yml#code_coverage_ubuntu_{{ validation_editors.default }} + # Run code coverage test (nightly uses actual trunk) + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_trunk # Run all tests on weekly bases diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index 64a2692225..18adbef416 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -2,7 +2,8 @@ --- # DESCRIPTION-------------------------------------------------------------------------- - # This job is responsible for executing package tests with code coverage analysis enabled. + # This job runs the in-repo testproject tests with code coverage analysis enabled. + # The project tests also exercise the NGO package tests, so a separate package-test coverage job is not needed. # Coverage analysis provides insights into: # Test coverage metrics for NGO assemblies # Line and branch coverage statistics @@ -10,25 +11,28 @@ # Additional metrics for coverage analysis # CONFIGURATION STRUCTURE-------------------------------------------------------------- - # Jobs are generated using nested loops through: - # 1. For default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) - # 2. For default editor version (trunk) since coverage would not vary between editors (no need for checks on more editors) + # Jobs are generated for: + # 1. Default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) + # 2. Two editors: actual "trunk" (used by nightly) and the "pinnedTrunk" safe version (used by PRs). + # Occasionally trunk breaks our tests, so PRs run against a pinned "safe" trunk while nightly runs actual trunk. #TECHNICAL CONSIDERATIONS--------------------------------------------------------------- - # In theory this job also runs package tests, but we don't want to use it as default since is heavier (because of added coverage analysis) and coverage is not changing that often # Requires Unity Editor installation # Burst compilation is disabled to ensure accurate coverage measurement # In order to properly use -coverage-results-path parameter we need to start it with $PWD (which means the absolute path). Otherwise coverage results will not be visible # QUALITY CONSIDERATIONS-------------------------------------------------------------------- - # To see where this job is included (in trigger job definitions) look into _triggers.yml file - + # To see where this job is included (in trigger job definitions) look into _triggers.yml file. Currently: + # [Code Coverage] Project Test runs on PR changes (pr_code_changes_checks, pinnedTrunk) and nightly (develop_nightly, trunk) +{% assign coverage_editors = "trunk," | append: pinnedTrunk | split: "," -%} {% for platform in test_platforms.default -%} -{% for editor in validation_editors.default -%} -code_coverage_{{ platform.name }}_{{ editor }}: - name: Code Coverage - NGO [{{ platform.name }}, {{ editor }}] +{% for project in projects.default -%} +{% for editor in coverage_editors -%} +{% if editor == "trunk" -%}{% assign editor_label = "trunk" -%}{% else -%}{% assign editor_label = "pinnedTrunk" -%}{% endif -%} +code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: + name: '[Code Coverage] Project Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}]' agent: type: {{ platform.type }} image: {{ platform.image }} @@ -38,13 +42,14 @@ code_coverage_{{ platform.name }}_{{ editor }}: {% endif %} commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models - - upm-pvp create-test-project test-project --packages "upm-ci~/packages/*.tgz" --unity .Editor - - UnifiedTestRunner --suite=editor --suite=playmode --editor-location=.Editor --testproject=test-project --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_{{ platform.name }}_{{ editor }};flags:NGOv2_{{ platform.name }}_{{ editor }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results + - UnifiedTestRunner --testproject={{ project.path }} --suite=editor --suite=playmode --editor-location=.Editor --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};flags:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: - "test-results/**/*" dependencies: + - .yamato/_run-all.yml#run_quick_checks # initial checks to perform fast validation of common errors - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} {% endfor -%} +{% endfor -%} From b8a8ddb71e66febe2289e1542df1c38a92a0bfa2 Mon Sep 17 00:00:00 2001 From: Emma Date: Mon, 27 Jul 2026 16:11:03 -0400 Subject: [PATCH 36/64] chore: Point CMB resources at new monorepo (#4106) * chore: Point CMB resources at new monorepo --- Tools/CI/service.cmb/README.md | 6 +++++- Tools/CI/service.cmb/run_cmb_service.sh | 11 +++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Tools/CI/service.cmb/README.md b/Tools/CI/service.cmb/README.md index e50f68b86b..97079d3393 100644 --- a/Tools/CI/service.cmb/README.md +++ b/Tools/CI/service.cmb/README.md @@ -1,6 +1,6 @@ # Testing against the CMB Service -The CMB Service is a tool that is external to our repository. The tool is inside the `runtime` folder in the [mps-common-multiplayer-backend](https://github.com/Unity-Technologies/mps-common-multiplayer-backend) repository. +The CMB Service is a tool that is external to our repository. The tool is inside the `runtime` folder of the [CMB service](https://github.com/Unity-Technologies/unity-player-services/tree/main/services/common-multiplayer-backend) in the Unity Player Services monorepo. Due to this, there is some more setup needed when running tests against the CMB Service. @@ -56,3 +56,7 @@ The following environment variables allow for further configuration of the setup `CMB_SERVICE_PORT` defines the port where the tests will try to connect to the service (defaults to `7789`). `NGO_HOST` defines the http address where the tests will try to connect to the service (defaults to `127.0.0.1`). + +## Running on CI (Yamato) + +The CMB tests can also be run from Yamato. The jobs are defined in [`.yamato/cmb-service-standalone-tests.yml`](../../../.yamato/cmb-service-standalone-tests.yml) and appear in Yamato as `CMB Service Test - NGO - [, , ]`. The job can be triggered manually from any branch meaning it can be easier to run the CMB tests from Yamato rather than set them up locally. The test uses [`run_cmb_service.sh`](./run_cmb_service.sh) to setup and run the CMB service. diff --git a/Tools/CI/service.cmb/run_cmb_service.sh b/Tools/CI/service.cmb/run_cmb_service.sh index 4bcf8c7468..8dd26df88a 100755 --- a/Tools/CI/service.cmb/run_cmb_service.sh +++ b/Tools/CI/service.cmb/run_cmb_service.sh @@ -17,7 +17,7 @@ # Example usage: # .//run_cmb_service.sh -e 7788 -s 7799 - # This script is currently used in the desktop-standalone-tests yamato job. + # This script is currently used in the cmb-service-standalone-tests yamato job (found at ../../../.yamato/cmb-service-standalone-tests.yml). # TECHNICAL CONSIDERATIONS--------------------------------------------------------------- # This is a bash script and so needs to be run on a Unix based system. @@ -136,11 +136,14 @@ else logMessage "Protocol Buffer Compiler Installed & ENV variables verified!\n PROTOC path is: $PROTOC" fi -# clone the cmb service repo -git clone https://github.com/Unity-Technologies/mps-common-multiplayer-backend.git +# Sparse-checkout only the CMB service directory from the unity-player-services monorepo. +# --filter=blob:none + --depth 1 avoids downloading file contents and history for the rest of the monorepo. +git clone --depth 1 --filter=blob:none --sparse https://github.com/Unity-Technologies/unity-player-services.git +cd ./unity-player-services +git sparse-checkout set services/common-multiplayer-backend/runtime # navigate to the cmb service directory -cd ./mps-common-multiplayer-backend/runtime +cd ./services/common-multiplayer-backend/runtime # Install rust curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y From 3a09971547bfd91a02876bca1ff9a86f042c2a0a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 28 Jul 2026 09:33:44 -0500 Subject: [PATCH 37/64] fix: NetworkVariable change prior to ownership change using distributed authority topology fails to sync NetworkVariable (#4107) * fix This resolves the issue where in a distributed authority session changing a NetworkVariable prior to changing ownership in the same call-stack would result in the NetworkVariable not being synchronized. * fix - update Removing the additional authority check as it is not needed. Fixing a very edge case scenario that shouldn't happen but in the event it does when finding objects by type within a specific scene, if the scene is invalid then use an empty array for the found objects. --- .../Runtime/Core/FindObjects.cs | 4 +-- .../Runtime/Core/NetworkObject.cs | 28 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs index 59329f8a81..109d40f4d3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/FindObjects.cs @@ -69,8 +69,8 @@ private struct ObjectsInSceneEnumerator : IEnumerable, IEnumerator wher internal ObjectsInSceneEnumerator(Scene scene, bool includeInactive) { m_IncludeInactive = includeInactive; - - m_RootObjects = scene.GetRootGameObjects(); + // If the scene is invalid, use an empty array. + m_RootObjects = scene.IsValid() ? scene.GetRootGameObjects() : new UnityEngine.GameObject[0]; m_RootIndex = 0; m_CurrentChildObjects = null; m_CurrentChildIndex = 0; diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 52e4dad4f1..6905caf3b2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -2971,17 +2971,31 @@ internal void SynchronizeOwnerNetworkVariables(ulong originalOwnerId, ulong orig childBehaviour.MarkOwnerReadDirtyAndCheckOwnerWriteIsDirty(); } + // If the spawn authority of a distributed authority network topology has invoked a change in ownership, + // then we want to invoke the NetworkBehaviourUpdate prior to changing the owner back. + if (NetworkManager.DistributedAuthorityMode) + { + // Force send a state update for all owner read NetworkVariables and any currently dirty + // owner write NetworkVariables. + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + } + // Now set the new owner and previous owner identifiers back to their original new values - // before we run the NetworkBehaviourUpdate. For owner read only permissions this order of - // operations is **particularly important** as we need to first (above) mark things as dirty - // from the context of the original owner and then second (below) we need to send the messages - // which requires the new owner to be set for owner read permission NetworkVariables. + // after we run the NetworkBehaviourUpdate. OwnerClientId = currentOwnerId; PreviousOwnerId = originalOwnerId; - // Force send a state update for all owner read NetworkVariables and any currently dirty - // owner write NetworkVariables. - NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + // For owner read only permissions this order of operations is **particularly important** as + // we need to first (above) mark things as dirty from the context of the original owner and + // then second (below) we need to send the messages which requires the new owner to be set. + // Note: Owner read only NetworkVariables do not make sense in a distributed authority topology + // since the only instance that can write is the owner. + if (!NetworkManager.DistributedAuthorityMode) + { + // Force send a state update for all owner read NetworkVariables and any currently dirty + // owner write NetworkVariables. + NetworkManagerOwner.BehaviourUpdater.NetworkBehaviourUpdate(true); + } } // NGO currently guarantees that the client will receive spawn data for all objects in one network tick. From 9b620fbd7f3ced20be9d384443ec581ab4d3bda2 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Tue, 28 Jul 2026 18:13:00 -0500 Subject: [PATCH 38/64] fix: spawn disabled in scene placed and network prefab registration (#4093) * fix - Issue where disabled in-scene placed NetworkObjects could not be spawned after starting a session. - Issue where users are allowed to spawn a NetworkObject with a GlobalObjectIdHash value of 0 (zero). * update NetworkAnimator not pointing to an animator within awake now just logs a warning as opposed to an error. * test Updating tests based on the fixes applied to this branch. A large portion is related to some integration tests trying to spawn NetworkObjects with a GlobalObjectIdHash value of zero(0). * update Style and providing internal write access to the prefabs list. * test Potentially last iteration on fixing/updating integration tests. * test - fix Using the fixed version of NetworkPrefabHandlerSpawnAndSynchronizeTests. Fixing some issues with prefab and handler creation. * style Fixing formatting issues. * style Fixing some standards formatting related issues. * style removing trailing spaces. * style Super weird one... but using ? as opposed to an if null check. * style Removing commented out code that is no longer needed/used. * update Committing suggested changes. Co-authored-by: Emma * update Applying changes based on review discussion. * update Making adjustments based on review discussion. * style Removing field that is no longer valid. Moving Awake higher in NetworkObject as it has become wedged in the middle of the code. * update Adding change log entries. * update Inverting logic (oops) * doc Updating NetworkObject documentation and in-scene placed documentation. * Update documentation for the two types of NetworkObjects Clarified the categories of NetworkObjects and their requirements. Added details for dynamically instantiated and in-scene placed NetworkObjects. * style White spaces! * Apply suggestions from code review Co-authored-by: Amy Reeve --------- Co-authored-by: Emma Co-authored-by: Amy Reeve --- com.unity.netcode.gameobjects/CHANGELOG.md | 6 + .../inscene-placed-networkobjects.md | 6 +- .../components/core/networkobject.md | 26 + .../Editor/InScenePlacedProcessor.cs | 14 + .../Runtime/Components/NetworkAnimator.cs | 2 +- .../Runtime/Configuration/NetworkPrefabs.cs | 124 ++++- .../Runtime/Core/NetworkObject.cs | 55 ++- .../Runtime/Spawning/NetworkSpawnManager.cs | 47 +- .../BufferDataValidationComponent.cs | 21 +- .../NetworkVariableTestComponent.cs | 99 ++-- .../Runtime/Connection/ConnectionApproval.cs | 5 + .../Runtime/NetworkBehaviourGenericTests.cs | 47 +- .../Runtime/NetworkManagerPlayerPrefab.cs | 17 +- .../NetworkVariable/NetworkVariableTests.cs | 46 +- .../Runtime/Prefabs/AddNetworkPrefabTests.cs | 49 +- ...etworkPrefabHandlerSynchronizationTests.cs | 65 ++- .../Prefabs/NetworkPrefabHandlerTests.cs | 195 ++++---- .../NetworkPrefabHandlerWithDataTests.cs | 23 +- .../Profiling/NetworkVariableNameTests.cs | 48 +- .../Tests/Runtime/Rpc/RpcQueueTests.cs | 57 +-- .../Serialization/BaseReferenceTests.cs | 373 +++++++++++++++ .../Serialization/BaseReferenceTests.cs.meta | 2 + .../NetworkBehaviourReferenceTests.cs | 223 ++++----- .../NetworkObjectReferenceTests.cs | 443 ++++-------------- .../TestHelpers/NetcodeIntegrationTest.cs | 11 +- .../Runtime/Animation/NetworkAnimatorTests.cs | 14 +- 26 files changed, 1192 insertions(+), 826 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index fa0e39c223..db85aa2f2a 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 ### Changed + + ### Deprecated @@ -22,6 +24,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) +- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) +- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) + ### Security diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md index 0cf6db3bff..6387ea33da 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md @@ -184,9 +184,13 @@ public class MyInSceneNetworkObjectBehaviour : NetworkBehaviour > [!NOTE] > You only need to enable the NetworkObject on the server-side to be able to respawn it. Netcode for GameObjects only enables a disabled in-scene placed NetworkObject on the client-side if the server-side spawns it. This **does not** apply to dynamically spawned `NetworkObjects`. Refer to [the object pooling page](../../advanced-topics/object-pooling.md) for an example of recycling dynamically spawned NetworkObjects. +### Pre-disabled in-scene placed NetworkObjects + +To initialize an in-scene placed NetworkObject in a disabled state and spawn it later, set its GameObject to inactive in the Editor while the respective scene is open. Once a networked session begins, you can re-enable and spawn it at any time. + ### Setting an in-scene placed NetworkObject to a despawned state when instantiating -Since in-scene placed NetworkObjects are automatically spawned when their respective scene has finished loading during a network session, you might run into the scenario where you want it to start in a despawned state until a certain condition has been met. To do this, you need to add some additional code in the `OnNetworkSpawn` part of your NetworkBehaviour component: +To programmatically disable an in-scene placed NetworkObject, add some additional code in the `OnNetworkSpawn` part of a NetworkBehaviour component: ```csharp using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md index 56730e4c71..e190aa201d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md @@ -19,6 +19,32 @@ When spawning a NetworkObject, the `NetworkObject.GlobalObjectIdHash` value init You can use [NetworkBehaviours](networkbehaviour.md) to add your own custom Netcode logic to the associated NetworkObject. +### What is a valid NetworkObject? + +There are two categories of NetworkObjects: +* Dynamically instantiated +* [In-scene placed](../../basics/scenemanagement/inscene-placed-networkobjects.md) + +The following provides the validity requirements for both types. + +#### Dynamically instantiated network prefabs + +Dynamically instantiated network prefabs must: + +* Be a valid [network prefab](./networkobject.md#network-prefabs) created within the Editor. +* Be registered in a network prefab list that's assigned to your NetworkManager. + +#### In-scene placed network prefabs + +In-scene placed network prefabs must: + +* Be a valid network prefab instance within a scene. +* Be a GameObject with a NetworkObject component created within the scene while in the Editor. + +### What is an invalid NetworkObject? + +GameObjects that have NetworkObject components added to them during runtime are **not supported** and will result in the NetworkObject's `GlobalObjectIdHash` being zero, which causes synchronization issues. In the event you make this mistake, a warning message will be logged and the NetworkObject won't be spawned. + ### Component order The order of components on a networked GameObject matters. When adding netcode components to a GameObject, ensure that the NetworkObject component is ordered before any NetworkBehaviour components. diff --git a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs index f7529de12c..8f66c3aa4f 100644 --- a/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs +++ b/com.unity.netcode.gameobjects/Editor/InScenePlacedProcessor.cs @@ -24,6 +24,12 @@ public void OnProcessScene(Scene scene, BuildReport report) log.AddInfo(scene.name, scene.handle); foreach (var networkObject in FindObjects.FromSceneByType(scene, true)) { + // Trap for users just creating things during runtime where this will be zero. + if (networkObject.GlobalObjectIdHash == 0) + { + log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s GlobalObjectIdHash value is zero! Runtime creating of {nameof(NetworkObject)}s is not supported. Skipping processing.").AddNetworkObject(networkObject)); + continue; + } if (networkObject.SceneOrigin.IsValid() && networkObject.SceneOrigin.handle != scene.handle) { log.Warning(new Context(LogLevel.Developer, $"{nameof(NetworkObject)}'s SceneOrigin doesn't match current scene being processed! Skipping processing.").AddInfo("SceneOrigin", networkObject.SceneOriginHandle).AddNetworkObject(networkObject)); @@ -36,7 +42,15 @@ public void OnProcessScene(Scene scene, BuildReport report) continue; } + // If already marked, the do nothing. + if (networkObject.InScenePlaced) + { + continue; + } + networkObject.InScenePlaced = true; + // Will not be true when making a build and the values are serialized. + networkObject.InScenePlacedPostProcessorMarkedDuringRuntime = Application.isPlaying; } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs index c999c26e9f..bf275ab3c3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs @@ -768,7 +768,7 @@ protected virtual void Awake() if (!m_Animator) { #if !UNITY_EDITOR - Debug.LogError($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); + Debug.LogWarning($"{nameof(NetworkAnimator)} {name} does not have an {nameof(UnityEngine.Animator)} assigned to it. The {nameof(NetworkAnimator)} will not initialize properly."); #endif return; } diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs index 45d93ad9d7..303055174f 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs @@ -47,22 +47,110 @@ public class NetworkPrefabs [NonSerialized] private List m_Prefabs = new List(); + /// + /// Returns the last registered prefab. + /// + internal NetworkPrefab GetLastRegisteredPrefab() + { + if (m_Prefabs.Count == 0) + { + return null; + } + return m_Prefabs[m_Prefabs.Count - 1]; + } + + /// + /// Applies a network prefab at a specific index + /// + /// index to apply + /// network prefab to be applied + /// + internal bool AssignPrefabAtIndex(int index, NetworkPrefab networkPrefab) + { + if (index >= m_Prefabs.Count) + { + NetworkManager.Singleton.Log.Error(new Logging.Context(LogLevel.Normal, $"[{nameof(NetworkPrefabs)}][{nameof(AssignPrefabAtIndex)}] Cannot apply prefab to index {index} when the {nameof(m_Prefabs)} count is only {m_Prefabs.Count}!")); + return false; + } + m_Prefabs[index] = networkPrefab; + return true; + } + + [NonSerialized] + private Dictionary m_PrefabHashIds = new Dictionary(); + [NonSerialized] private List m_RuntimeAddedPrefabs = new List(); - private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private bool InternalAddPrefab(NetworkPrefab networkPrefab) { if (AddPrefabRegistration(networkPrefab)) { // Don't add this to m_RuntimeAddedPrefabs // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. m_Prefabs.Add(networkPrefab); + + // We are not getting all potential overrides but just determining if the prefab has been registered. + if (!m_PrefabHashIds.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab); + } + if (!m_PrefabHashIds.ContainsKey(networkPrefab.TargetPrefabGlobalObjectIdHash)) + { + m_PrefabHashIds.Add(networkPrefab.TargetPrefabGlobalObjectIdHash, networkPrefab); + } + return true; } + return false; } - private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + private void InternalRemovePrefab(NetworkPrefab networkPrefab) { m_Prefabs.Remove(networkPrefab); + m_PrefabHashIds.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash); + } + + internal bool IsBasedOnRegisteredPrefab(NetworkObject networkObject) + { + + + return m_PrefabHashIds.ContainsKey(networkObject.GlobalObjectIdHash); + } + + internal bool IsActualPrefabAsset(NetworkObject networkObject) + { + var isActualPrefabAsset = false; + if (m_PrefabHashIds.TryGetValue(networkObject.GlobalObjectIdHash, out NetworkPrefab networkPrefab)) + { + switch (networkPrefab.Override) + { + case NetworkPrefabOverride.Prefab: + case NetworkPrefabOverride.None: + { + isActualPrefabAsset = networkPrefab.Prefab != null && networkObject.gameObject == networkPrefab.Prefab; + break; + } + case NetworkPrefabOverride.Hash: + { + isActualPrefabAsset = networkPrefab.SourceHashToOverride == networkObject.GlobalObjectIdHash; + break; + } + } + } + return isActualPrefabAsset; + } + + private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + // Don't add this to m_RuntimeAddedPrefabs + // This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there. + InternalAddPrefab(networkPrefab); + // Log warning if this returns false? + } + + private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab) + { + InternalRemovePrefab(networkPrefab); } /// @@ -93,6 +181,7 @@ internal void Shutdown() /// When true, logs warnings about invalid prefabs that are removed during initialization public void Initialize(bool warnInvalid = true) { + m_PrefabHashIds.Clear(); m_Prefabs.Clear(); NetworkPrefabsLists.RemoveAll(x => x == null); foreach (var list in NetworkPrefabsLists) @@ -113,7 +202,7 @@ public void Initialize(bool warnInvalid = true) prefabs.AddRange(list.PrefabList); } } - + m_PrefabHashIds = new Dictionary(); m_Prefabs = new List(); List removeList = null; @@ -124,11 +213,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in prefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -136,11 +221,7 @@ public void Initialize(bool warnInvalid = true) foreach (var networkPrefab in m_RuntimeAddedPrefabs) { - if (AddPrefabRegistration(networkPrefab)) - { - m_Prefabs.Add(networkPrefab); - } - else + if (!InternalAddPrefab(networkPrefab)) { removeList?.Add(networkPrefab); } @@ -171,14 +252,12 @@ public void Initialize(bool warnInvalid = true) /// public bool Add(NetworkPrefab networkPrefab) { - if (AddPrefabRegistration(networkPrefab)) + var added = InternalAddPrefab(networkPrefab); + if (added) { - m_Prefabs.Add(networkPrefab); m_RuntimeAddedPrefabs.Add(networkPrefab); - return true; } - - return false; + return added; } /// @@ -197,8 +276,7 @@ public void Remove(NetworkPrefab prefab) { throw new ArgumentNullException(nameof(prefab)); } - - m_Prefabs.Remove(prefab); + InternalRemovePrefab(prefab); m_RuntimeAddedPrefabs.Remove(prefab); OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash); NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash); @@ -294,14 +372,12 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab) uint source = networkPrefab.SourcePrefabGlobalObjectIdHash; uint target = networkPrefab.TargetPrefabGlobalObjectIdHash; - // Make sure the prefab isn't already registered. if (NetworkPrefabOverrideLinks.ContainsKey(source)) { - var networkObject = networkPrefab.Prefab.GetComponent(); - + var nameOrHashOverride = networkPrefab.Override == NetworkPrefabOverride.Hash ? $"Hash: {networkPrefab.SourcePrefabGlobalObjectIdHash}" : networkPrefab.Prefab?.name; // This should never happen, but in the case it somehow does log an error and remove the duplicate entry - Debug.LogError($"{nameof(NetworkPrefab)} ({networkObject.name}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); + Debug.LogError($"{nameof(NetworkPrefab)} ({nameOrHashOverride}) has a duplicate {nameof(NetworkObject.GlobalObjectIdHash)} source entry value of: {source}!"); return false; } diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs index 6905caf3b2..4c42f83a30 100644 --- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs +++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs @@ -350,10 +350,31 @@ private void CheckForInScenePlaced() // Default scene migration synchronization to false for in-scene placed NetworkObjects SceneMigrationSynchronization = false; + + // Set our disabled in-scene placed flag for spawning initially disabled in-scene placed objects. + m_InScenePlacedDisabledByDefault = !gameObject.activeInHierarchy; } } #endif // UNITY_EDITOR + /// + /// This is intentionally private since this is a sealed class. + /// + private void Awake() + { + SetCachedParent(transform.parent); + SceneOrigin = gameObject.scene; + } + + /// + /// Used to provide support for initially disabled in-scene placed objects. + /// This is only ever set on in-scene placed objects that are already disabled + /// in the scene asset itself. + /// + [HideInInspector] + [SerializeField] + private bool m_InScenePlacedDisabledByDefault; + /// /// Gets the NetworkManager that owns this NetworkObject instance /// @@ -1258,6 +1279,13 @@ internal set } } + /// + /// This provides a means to determine if the post processing had applied + /// the in-scene placed status or if it was already serialized. This is used + /// when determining if the thing being spawned is a valid thing to spawn. + /// + internal bool InScenePlacedPostProcessorMarkedDuringRuntime; + /// /// Sets whether this NetworkObject was instantiated as part of a scene /// @@ -1877,6 +1905,13 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play } } + // Trap for runtime generated instances as this is not valid + if (GlobalObjectIdHash == 0) + { + NetworkManager.Log.ErrorServer(new Context(LogLevel.Error, $"Detected {nameof(NetworkObject)} {nameof(GlobalObjectIdHash)} value of 0!" + + $"This is typically a sign of runtime generated network prefab assets which are not supported.").AddNetworkObject(this)); + return; + } // Calculate the legacy IsSceneObject value as the public field is obsolete with warning // We can't break the public behavior of the field. @@ -1884,13 +1919,15 @@ private void SpawnInternal(bool destroyWithScene, ulong ownerClientId, bool play var legacyIsSceneObject = IsSceneObject.HasValue && IsSceneObject.Value; #pragma warning restore CS0618 // Type or member is obsolete - // If SpawnInternal is being called on an object that is marked as InScenePlaced, - // The scene object was never automatically spawned when the scene was loaded. - // Count this object as a dynamically spawned object. - // TODO-[MTT-15388]: Actually support disabled/not spawned InScenePlaced NetworkObjects - if (InScenePlaced && !HasBeenSpawned) + // If the initial state of the GameObject was disabled and InScenePlaced is marked, + // then spawn it as in-scene placed. + // Otherwise: + // If we are marked as in-scene place, have never been spawned, and the root GameObject + // was not disabled upon being instantiated, then treat this as a dynamically spawned + // instance. + if (InScenePlaced && !m_InScenePlacedDisabledByDefault && !HasBeenSpawned) { - if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Developer) + if (NetworkManagerOwner.NetworkConfig.EnableSceneManagement && NetworkManagerOwner.LogLevel <= LogLevel.Normal) { Debug.LogWarning($"[{name}][SceneOrigin={SceneOriginHandle}] Dynamically spawning InScenePlaced network object. This can cause issues!", this); } @@ -3715,11 +3752,7 @@ internal void SceneChangedUpdate(Scene scene, bool notify = false) } } - private void Awake() - { - SetCachedParent(transform.parent); - SceneOrigin = gameObject.scene; - } + /// /// Update diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs index 74d0ee121e..573edb7d13 100644 --- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs @@ -1121,7 +1121,6 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n NetworkLog.LogError(new Context(LogLevel.Developer, "Player prefab is marked as belonging to a scene. This may cause issues.").AddNetworkObject(networkObject).AddInfo("SceneName", networkObject.SceneOrigin.name)); networkObject.InScenePlaced = false; } - NetworkLog.InternalAssert(sceneObject == networkObject.InScenePlaced, "Legacy sceneObject value should match calculated InScenePlaced value."); if (!networkObject.InScenePlaced && NetworkManager.LogLevel <= LogLevel.Error) { @@ -1580,10 +1579,48 @@ internal void ServerSpawnSceneObjectsOnStartSweep() continue; } - // This used to be two loops. - // The first added all NetworkObjects to a list and the second spawned all NetworkObjects in the list. - // Now, a parent will set its children's IsSceneObject value when spawned, so we check for null or for true. - if (networkObject.InScenePlaced) + // Do not attempt to spawn if it is the actual prefab asset itself: + // - This is not supported by NGO. + // - This will lead to other issues if it gets destroyed, when de-spawned, but the prefab is still registered. + // - This also prevents from spawning integration test prefabs. + if (NetworkManager.NetworkConfig.Prefabs.IsActualPrefabAsset(networkObject)) + { + NetworkManager.Log.Warning(new Context(LogLevel.Developer, $"Skipping {networkObject.name} as it is the actual prefab asset itself!")); + continue; + } + + // Determine if this is even a valid thing to spawn: + // - If it is not based on a registered prefab, it is invalid. + // - If the GlobalObjectIdHash is zero, it is invalid. + var isValidInstanceToSpawn = NetworkManager.NetworkConfig.Prefabs.IsBasedOnRegisteredPrefab(networkObject) && networkObject.GlobalObjectIdHash != 0; + + // If we are a valid prefab asset, marked as in-scene placed, but this was marked during runtime by the post processor. + if (isValidInstanceToSpawn && networkObject.InScenePlaced && networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // Then it is not in-scene placed and was pre-instantiated. Spawn dynamically. + networkObject.InScenePlaced = false; + } + else if (networkObject.InScenePlaced && !networkObject.InScenePlacedPostProcessorMarkedDuringRuntime) + { + // If this was marked as in-scene placed within the editor, then it is valid. + isValidInstanceToSpawn = true; + } + + var wasPreInstantiated = !networkObject.IsSpawned && !networkObject.InScenePlaced; + + // Dynamically created NetworkObjects instances are not supported and will not be spawned during the sweep. + if (wasPreInstantiated && !isValidInstanceToSpawn) + { + NetworkManager.Log.Error(new Context(LogLevel.Error, $"Detected a pre-instantiated {nameof(GameObject)} " + + $"with a {nameof(NetworkObject)} component instance that is not a registered prefab nor an in-scene placed {nameof(NetworkObject)}." + + $" Dynamically creating unregistered {nameof(NetworkObject)}s is not supported! This {nameof(NetworkObject)} will not be spawned.").AddNetworkObject(networkObject)); + continue; + } + + // The only valid things to spawn during the sweep are: + // - In-scene placed NetworkObjects. + // - Pre-instantiated NetworkObjects that are registered with the NetworkManager's network prefab list(s). + if (networkObject.InScenePlaced || wasPreInstantiated) { var ownerId = networkObject.OwnerClientId; if (NetworkManager.DistributedAuthorityMode) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs index 3e46ec4b9e..d427251afe 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -38,8 +38,7 @@ internal class BufferDataValidationComponent : NetworkBehaviour private List m_SendBuffer; private List m_PreCalculatedBufferValues; - // Start is called before the first frame update - private void Start() + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) { m_WaitForValidation = false; m_CurrentBufferSize = BufferSizeStart; @@ -49,6 +48,7 @@ private void Start() { m_PreCalculatedBufferValues.Add((byte)Random.Range(0, 255)); } + base.OnNetworkPreSpawn(ref networkManager); } /// @@ -67,7 +67,12 @@ public bool IsTestComplete() // Update is called once per frame private void Update() { - if (NetworkManager.Singleton.IsListening && EnableTesting && !IsTestComplete() && !m_WaitForValidation) + if (!EnableTesting || !IsSpawned) + { + return; + } + + if (!m_WaitForValidation && !IsTestComplete()) { m_SendBuffer.Clear(); //Keep the current contents of the bufffer and fill the buffer with the delta difference of the buffer's current size and new size from the m_PreCalculatedBufferValues @@ -77,16 +82,16 @@ private void Update() m_WaitForValidation = true; //Send the buffer - SendBufferServerRpc(m_SendBuffer.ToArray()); + SendBufferRpc(m_SendBuffer.ToArray()); } + } /// - /// Server side RPC for testing + /// Sends to self for buffer queue testing /// - /// server rpc parameters - [ServerRpc] - private void SendBufferServerRpc(byte[] buffer) + [Rpc(SendTo.Me)] + private void SendBufferRpc(byte[] buffer) { TestFailed = !NetworkManagerHelper.BuffersMatch(0, buffer.Length, buffer, m_SendBuffer.ToArray()); if (!TestFailed) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 3e77d9762b..616c9f03d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -242,7 +242,7 @@ internal class NetworkVariableTestComponent : NetworkBehaviour private float m_WaitForChangesTimeout; // Start is called before the first frame update - private void InitializeTest() + public void InitializeTest() { // Generic Constructor Test Coverage m_NetworkVariableBool = new NetworkVariable(); @@ -356,11 +356,6 @@ public bool IsTestComplete() return m_FinishedTests; } - public void Awake() - { - InitializeTest(); - } - public void AssertAllValuesAreCorrect() { Assert.AreEqual(false, m_NetworkVariableBool.Value); @@ -423,56 +418,52 @@ public void AssertAllValuesAreCorrect() // Update is called once per frame private void Update() { - if (EnableTesting) + if (!EnableTesting || !IsSpawned) { - //Added timeout functionality for near future changes to NetworkVariables - if (!m_FinishedTests && m_ChangesAppliedToNetworkVariables) - { - //We finish testing if all NetworkVariables changed their value or we timed out waiting for - //all NetworkVariables to change their value - m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); - } - else + return; + } + + if (!m_ChangesAppliedToNetworkVariables) + { + //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_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), 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; + 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 { - if (NetworkManager != null && NetworkManager.IsListening) - { - //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_NetworkVariablePose.Value = new Pose(new Vector3(100, 100, 100), 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; - 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 }, - Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } - }; - - //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; - } - } + Str = "ManagedNetworkSerializableType", + Ints = new[] { 1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000, 10000 }, + Embedded = new EmbeddedManagedNetworkSerializableType { Int = 20000 } + }; + + //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; + } + else if (!m_FinishedTests) + { + m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs index 2e0c81b690..56231899d4 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Connection/ConnectionApproval.cs @@ -40,6 +40,11 @@ public ConnectionApprovalTests(PlayerCreation playerCreation) private string m_ValidationToken; + internal override bool ShouldCreatePlayerPrefab() + { + return m_PlayerCreation != PlayerCreation.NoPlayer && m_PlayerCreation != PlayerCreation.FailValidation; + } + protected override bool ShouldCheckForSpawnedPlayers() { return m_PlayerCreation != PlayerCreation.NoPlayer; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs index 5569278aa0..aa5b2c819c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -39,11 +39,9 @@ public override void OnNetworkDespawn() protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("TestPrefab"); + m_PrefabToSpawn.AddComponent(); - var childObject = new GameObject - { - name = "ChildObject" - }; + var childObject = new GameObject("ChildObject"); childObject.transform.parent = m_PrefabToSpawn.transform; childObject.AddComponent(); base.OnServerAndClientsCreated(); @@ -137,8 +135,6 @@ public IEnumerator ValidatedDisableddNetworkBehaviourWarning([Values] bool disab public IEnumerator ValidateNoSpam() { m_AllowServerToStart = true; - var objectToTest = new GameObject(); - var simpleNetworkBehaviour = objectToTest.AddComponent(); // Now just start the Host yield return StartServerAndClients(); @@ -146,23 +142,36 @@ 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. + // We make an instance of the m_PrefabToSpawn + var validateInstance = Object.Instantiate(m_PrefabToSpawn); + + // Then destroy the NetworkObject componwent of that instance. + Object.DestroyImmediate(validateInstance.GetComponent()); + + // Now get the Networkbehaviour and verify when you attempt to get a reference + // to the NetworkObject a warning is logged. + var simpleNetworkBehaviour = validateInstance.GetComponent(); 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?"); - var networkObjectToTest = objectToTest.AddComponent(); - networkObjectToTest.NetworkManagerOwner = m_ServerNetworkManager; - networkObjectToTest.Spawn(); + simpleNetworkBehaviour.IsSpawned = false; + simpleNetworkBehaviour = null; + + // Destroy this test instance + Object.DestroyImmediate(validateInstance); + + // Now create a spawned instance (NetworkObject will exist) + var instance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()).GetComponent(); // Assure no log messages are logged when they should not be logged - isNull = simpleNetworkBehaviour.NetworkObject != null; + isNull = instance.GetComponent().NetworkObject != null; LogAssert.NoUnexpectedReceived(); - networkObjectToTest.Despawn(); - Object.Destroy(networkObjectToTest); + instance.Despawn(); + Object.Destroy(instance.gameObject); } /// @@ -182,13 +191,11 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // 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(); + var serverInstance = SpawnObject(m_PrefabToSpawn, GetAuthorityNetworkManager()); + var parentNetworkObject = serverInstance.GetComponent(); + var childObject = parentNetworkObject.transform.GetChild(0).gameObject; + yield return s_DefaultWaitForTick; // Destroy the child object with child NetworkBehaviour @@ -201,7 +208,7 @@ public IEnumerator ValidateDeleteChildNetworkBehaviour() // Destroy the parent object which should not cause any exceptions // (validating the fix) - Object.Destroy(parentObject); + Object.Destroy(serverInstance); } protected override void OnPlayerPrefabGameObjectCreated() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs index 2eeb1dac6f..0e3fffe6ab 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkManagerPlayerPrefab.cs @@ -20,22 +20,9 @@ public NetworkManagerPlayerPrefab(HostOrServer hostOrServer) : base(hostOrServer { } - /// - /// Assure no player prefab is assigned. - /// - protected override void OnServerAndClientsCreated() - { - foreach (var networkManager in m_NetworkManagers) - { - networkManager.NetworkConfig.PlayerPrefab = null; - } - base.OnServerAndClientsCreated(); - } - - protected override void OnNewClientCreated(NetworkManager networkManager) + internal override bool ShouldCreatePlayerPrefab() { - networkManager.NetworkConfig.PlayerPrefab = null; - base.OnNewClientCreated(networkManager); + return false; } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 44367d5120..85a72d9f19 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -251,9 +251,17 @@ public NetworkVariableTests(Serialization serialization) m_EnsureLengthSafety = serialization == Serialization.EnsureLengthSafety; } + private bool m_CanStart = false; + protected override bool CanStartServerAndClients() { - return false; + return m_CanStart; + } + + protected override void OnInlineSetup() + { + m_CanStart = false; + base.OnInlineSetup(); } protected override void OnOneTimeSetup() @@ -343,21 +351,30 @@ private void InitializeServerAndClients(HostOrServer useHost) [Test] public void AllNetworkVariableTypes([Values] HostOrServer useHost) { - // Create, instantiate, and host - // This would normally go in Setup, but since every other test but this one - // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, - // for now we put this within this one test until we migrate it to MIH - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out NetworkManager server, useHost == HostOrServer.Host ? NetworkManagerHelper.NetworkManagerOperatingMode.Host : NetworkManagerHelper.NetworkManagerOperatingMode.Server)); + var prefabToSpawn = CreateNetworkObjectPrefab("NetVarTest"); + prefabToSpawn.AddComponent(); - Assert.IsTrue(server.IsHost == (useHost == HostOrServer.Host), $"{nameof(useHost)} does not match the server.IsHost value!"); + m_CanStart = true; + StartServerAndClientsWithTimeTravel(); + var authority = GetAuthorityNetworkManager(); - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); + // Shutdown the other clients + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager == authority) + { + continue; + } + StopOneClientWithTimeTravel(networkManager); + } - var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + var instance = SpawnObject(prefabToSpawn, authority); + var networkVariableTestComponent = instance.GetComponent(); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + Assert.IsTrue(networkVariableTestComponent.IsSpawned, $"Failed to spawn {instance.name}!"); // Start Testing + networkVariableTestComponent.InitializeTest(); networkVariableTestComponent.EnableTesting = true; var success = WaitForConditionOrTimeOutWithTimeTravel(() => true == networkVariableTestComponent.IsTestComplete()); @@ -369,13 +386,8 @@ public void AllNetworkVariableTypes([Values] HostOrServer useHost) Assert.IsTrue(networkVariableTestComponent.DidAllValuesChange()); networkVariableTestComponent.AssertAllValuesAreCorrect(); - // Disable this once we are done. - networkVariableTestComponent.gameObject.SetActive(false); - - // This would normally go in Teardown, but since every other test but this one - // uses NetworkManagerHelper, and it does its own NetworkManager setup / teardown, - // for now we put this within this one test until we migrate it to MIH - NetworkManagerHelper.ShutdownNetworkManager(); + // Stop the authority NetworkManager instance + StopOneClientWithTimeTravel(authority); } [Test] diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs index baab984b82..ac14f5bfc0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/AddNetworkPrefabTests.cs @@ -25,14 +25,12 @@ protected override IEnumerator OnSetup() yield return null; } - protected override void OnServerAndClientsCreated() + private GameObject GenerateAndRegisterPrefab() { - m_Prefab = new GameObject("Object"); - var networkObject = m_Prefab.AddComponent(); - m_Prefab.AddComponent(); - + var originalPrefabInstance = NetcodeIntegrationTestHelpers.CreateNetworkObject("PrefabTest"); // Make it a prefab - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(originalPrefabInstance.GetComponent()); + m_ServerNetworkManager.NetworkConfig.SpawnTimeout = 0; m_ServerNetworkManager.NetworkConfig.ForceSamePrefabs = false; @@ -41,6 +39,12 @@ protected override void OnServerAndClientsCreated() client.NetworkConfig.SpawnTimeout = 0; client.NetworkConfig.ForceSamePrefabs = false; } + return originalPrefabInstance; + } + + protected override void OnServerAndClientsCreated() + { + RegisterPrefab(); } private EmptyComponent GetObjectForClient(ulong clientId) @@ -50,15 +54,25 @@ private EmptyComponent GetObjectForClient(ulong clientId) { if (component.IsSpawned && component.NetworkManager.LocalClientId == clientId) { - return component; + var prefabGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + var componentGlobalObjectIdHash = m_Prefab.GetComponent().GlobalObjectIdHash; + if (prefabGlobalObjectIdHash == componentGlobalObjectIdHash) + { + return component; + } } } return null; } - private void RegisterPrefab() + private void RegisterPrefab(bool includeClients = true) { + m_Prefab = GenerateAndRegisterPrefab(); m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + if (!includeClients) + { + return; + } foreach (var client in m_ClientNetworkManagers) { client.AddNetworkPrefab(m_Prefab); @@ -89,7 +103,7 @@ public IEnumerator WhenSpawningBeforeAddingPrefab_SpawnFails() [UnityTest] public IEnumerator WhenSpawningAfterAddingServerPrefabButBeforeAddingClientPrefab_SpawnFails() { - m_ServerNetworkManager.AddNetworkPrefab(m_Prefab); + RegisterPrefab(false); var serverObject = Object.Instantiate(m_Prefab); serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; @@ -104,10 +118,12 @@ public IEnumerator WhenSpawningAfterAddingPrefabOnServerAndClient_SpawnSucceeds( RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); - serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; - serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeHandled(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + var serverNetworkObject = serverObject.GetComponent(); + serverNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + serverNetworkObject.Spawn(); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); } [UnityTest] @@ -116,10 +132,13 @@ public IEnumerator WhenSpawningAfterRemovingPrefabOnClient_SpawnFails() RegisterPrefab(); var serverObject = Object.Instantiate(m_Prefab); + var serverNetworkObject = serverObject.GetComponent(); + serverObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; serverObject.GetComponent().Spawn(); - yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); - Assert.IsNotNull(GetObjectForClient(m_ClientNetworkManagers[0].LocalClientId)); + yield return WaitForSpawnedOnAllOrTimeOut(serverObject); + AssertOnTimeout($"{serverObject.name} did not spawn on all clients!"); + Assert.IsTrue(m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(serverNetworkObject.NetworkObjectId), $"Client did not spawn object!"); serverObject.GetComponent().Despawn(); yield return NetcodeIntegrationTestHelpers.WaitForMessageOfTypeReceived(m_ClientNetworkManagers[0]); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs index 0c5895b612..d15522665d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerSynchronizationTests.cs @@ -19,6 +19,28 @@ public NetworkPrefabHandlerSynchronizationTests(HostOrServer hostOrServer) : bas private GameObject m_ClientSideValidPrefab; private GameObject m_ClientSideExceptionPrefab; + public class VerifyLastClientSentRpcToServer : NetworkBehaviour + { + public bool RpcReceived { get; private set; } + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + RpcReceived = false; + base.OnNetworkPreSpawn(ref networkManager); + } + + public void DelayUntilOneMessageReceivedRpc(RpcParams rpcParams = default) + { + RpcReceived = true; + } + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } + protected override void OnServerAndClientsCreated() { m_ValidPrefab = CreateNetworkObjectPrefab("ValidPrefab"); @@ -28,14 +50,22 @@ protected override void OnServerAndClientsCreated() } [UnityTest] - [UnityPlatform(exclude = new[] { RuntimePlatform.IPhonePlayer, RuntimePlatform.OSXPlayer, RuntimePlatform.OSXEditor })] // Ignored test tracked in MTT-15473 public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() { var nonAuthority = GetNonAuthorityNetworkManager(); var networkObjectToSpawnOnClient = m_ClientSideValidPrefab.GetComponent(); - nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var clientSideHandler = new GameObject(); + var clientPrefabHandler = clientSideHandler.AddComponent(); + clientPrefabHandler.Initialize(nonAuthority, m_ClientSideValidPrefab.GetComponent()); + + nonAuthority.PrefabHandler.AddHandler(m_ValidPrefab, clientPrefabHandler); + + var clientSideExceptionHandler = new GameObject(); + var clientSideExceptionPrefabHandler = clientSideExceptionHandler.AddComponent(); + + nonAuthority.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, clientSideExceptionPrefabHandler); var authority = GetAuthorityNetworkManager(); @@ -60,8 +90,16 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Create a new client and register the same PrefabHandlers on the client var newClient = CreateNewClient(); - newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, new NetworkPrefabExceptionThrower()); - newClient.PrefabHandler.AddHandler(m_ValidPrefab, new NetworkPrefabInstanceHandler(networkObjectToSpawnOnClient)); + + var lateJoinClientSideHandler = new GameObject(); + var lateJoinClientPrefabHandler = clientSideHandler.AddComponent(); + var lateJoinExceptionHandler = new GameObject(); + var lateJoinClientExceptionHandler = lateJoinExceptionHandler.AddComponent(); + + lateJoinClientPrefabHandler.Initialize(newClient, m_ClientSideValidPrefab.GetComponent()); + + newClient.PrefabHandler.AddHandler(m_ClientSideExceptionPrefab, lateJoinClientExceptionHandler); + newClient.PrefabHandler.AddHandler(m_ValidPrefab, lateJoinClientPrefabHandler); // Expect assertions from the new client LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); @@ -73,6 +111,8 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() // Start and synchronize the new client yield return StartClient(newClient); + AssertOnTimeout($"Timed out waiting for the late joining client, {newClient.name}, to connect!"); + // Validate the valid prefab spawned on all clients without issue var expectedAuthorityHash = m_ValidPrefab.GetComponent().GlobalObjectIdHash; @@ -92,6 +132,21 @@ public IEnumerator NetworkPrefabHandlerSpawnAndSynchronizeTests() Assert.That(networkManager.SpawnManager.SpawnedObjects.ContainsKey(exceptionObject.NetworkObjectId), Is.False, "Non authority should not have spawned exception object!"); } } + + // Assure this test continues to run until we verify the late joining client has sent 1 message to the server + // This should be the fix for MTT-15473 where the test finishes/exits before the message from the client has been received and processed by the server. + Assert.IsTrue(authority.SpawnManager.SpawnedObjects.ContainsKey(newClient.LocalClient.PlayerObject.NetworkObjectId), $"Server does not have a player for Client-{newClient.LocalClientId}!"); + + // Get server and late joining client's VerifyLastClientSentRpcToServer NetworkBehaviour + var serverLateClientInstance = authority.SpawnManager.SpawnedObjects[newClient.LocalClient.PlayerObject.NetworkObjectId].GetComponent(); + var sendRpc = newClient.LocalClient.PlayerObject.GetComponent(); + + // Send a message from the late joining client to the server + sendRpc.DelayUntilOneMessageReceivedRpc(); + + // Wait for the server to have received this message before exiting the test. + // If the log message has not been received by the server at this point, then there is some other type of bug specific to iOS and Mac. + yield return WaitForConditionOrTimeOut(() => serverLateClientInstance.RpcReceived); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs index f964271a28..56b6eb710d 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerTests.cs @@ -1,6 +1,6 @@ using System; +using System.Collections; using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -15,111 +15,120 @@ namespace Unity.Netcode.RuntimeTests /// Destroying a newly spawned NetworkObject instance works /// Removing a INetworkPrefabInstanceHandler is removed and can be verified (very last check) /// - internal class NetworkPrefabHandlerTests + internal class NetworkPrefabHandlerTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } private const string k_TestPrefabObjectName = "NetworkPrefabTestObject"; - private uint m_ObjectId = 1; + private uint m_ObjectId = 0; + + private bool m_CanStart; + + protected override bool CanStartServerAndClients() + { + return m_CanStart; + } + private GameObject MakeValidNetworkPrefab() { - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); - NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; - NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); m_ObjectId++; - return validPrefab.gameObject; + return CreateNetworkObjectPrefab(k_TestPrefabObjectName + m_ObjectId.ToString()); } - - /// /// Tests the NetwokConfig NetworkPrefabsList initialization during NetworkManager's Init method to make sure that /// it will still initialize but remove the invalid prefabs /// - [Test] - public void NetworkConfigInvalidNetworkPrefabTest() + [UnityTest] + public IEnumerator NetworkConfigInvalidNetworkPrefabTest() { + var authority = GetAuthorityNetworkManager(); // Add null entry - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(null); + authority.NetworkConfig.Prefabs.Add(null); // Add a NetworkPrefab with no prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab()); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab()); // Add a NetworkPrefab override with an invalid hash - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 }); // Add a NetworkPrefab override with a valid hash but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with a valid hash to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null }); // Add a NetworkPrefab override with an invalid source prefab to override - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null }); - // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = null }); + // Create a valid network prefab "asset". + var validPrefabAsset = MakeValidNetworkPrefab().GetComponent(); - // Add a valid prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = MakeValidNetworkPrefab() }); + // Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab. + authority.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = validPrefabAsset.gameObject, OverridingTargetPrefab = null }); - // Add a NetworkPrefab override with a valid hash and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 11111111, OverridingTargetPrefab = MakeValidNetworkPrefab() }); + var validPrefabForSourceHash = MakeValidNetworkPrefab().GetComponent(); + // This would be the scenario that a hash would be used (typically when scene management is disabled) + validPrefabForSourceHash.InScenePlaced = true; - // Add a NetworkPrefab override with a valid prefab and valid target prefab - NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() }); + var networkPrefab = authority.NetworkConfig.Prefabs.GetLastRegisteredPrefab(); + networkPrefab.SourceHashToOverride = validPrefabForSourceHash.GlobalObjectIdHash; + networkPrefab.OverridingTargetPrefab = validPrefabAsset.gameObject; + networkPrefab.Override = NetworkPrefabOverride.Hash; + Assert.True(authority.NetworkConfig.Prefabs.AssignPrefabAtIndex(authority.NetworkConfig.Prefabs.Prefabs.Count - 1, networkPrefab), $"Failed to assign network prefab!"); - var exceptionOccurred = false; - try - { - NetworkManagerHelper.NetworkManagerObject.StartHost(); - } - catch - { - exceptionOccurred = true; - } + var sourcePrefab = MakeValidNetworkPrefab(); + networkPrefab = authority.NetworkConfig.Prefabs.GetLastRegisteredPrefab(); + var index = authority.NetworkConfig.Prefabs.Prefabs.Count - 1; + var targetPrefab = MakeValidNetworkPrefab(); + networkPrefab.Prefab = sourcePrefab; + networkPrefab.SourcePrefabToOverride = sourcePrefab; + networkPrefab.OverridingTargetPrefab = targetPrefab; + Assert.True(authority.NetworkConfig.Prefabs.AssignPrefabAtIndex(index, networkPrefab), $"Failed to assign network prefab!"); - Assert.False(exceptionOccurred); + m_CanStart = true; + yield return StartServerAndClients(); // In the end we should only have 3 valid registered network prefabs - Assert.True(NetworkManagerHelper.NetworkManagerObject.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count == 3); + Assert.AreEqual(5, authority.NetworkConfig.Prefabs.NetworkPrefabOverrideLinks.Count); } - private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject"; - [Test] - public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) + [UnityTest] + public IEnumerator NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType) { - var networkConfig = new NetworkConfig() - { - NetworkTopology = topologyType, - }; + var authority = GetAuthorityNetworkManager(); + authority.NetworkConfig.NetworkTopology = topologyType; + var baseObject = MakeValidNetworkPrefab().GetComponent(); - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, networkConfig: networkConfig)); - var testPrefabObjectName = k_PrefabObjectName; + m_CanStart = true; + yield return StartServerAndClients(); - Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName); - NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; + var testPrefabObjectName = k_TestPrefabObjectName; - var networkPrefabHandler = new NetworkPrefabHandler(); - var networkPrefabInstanceHandler = new NetworkPrefabInstanceHandler(baseObject); + var networkPrefabHandler = authority.PrefabHandler; + var prefabHandlerObject = new GameObject(); + var networkPrefabInstanceHandler = prefabHandlerObject.AddComponent(); + networkPrefabInstanceHandler.Initialize(authority, baseObject); var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f); var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f); //Register via GameObject - var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefabInstanceHandler); + var gameObjectRegistered = authority.PrefabHandler.ContainsHandler(baseObject); //Test result of registering via GameObject reference Assert.True(gameObjectRegistered); - var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + var spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -131,11 +140,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via NetworkObject - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabInstanceHandler); //Test result of registering via NetworkObject reference Assert.True(gameObjectRegistered); @@ -144,7 +153,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(2.0f, 1.0f, 5.0f); prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -156,11 +165,11 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler //Register via GlobalObjectIdHash - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefabInstanceHandler); //Test result of registering via GlobalObjectIdHash reference Assert.True(gameObjectRegistered); @@ -169,7 +178,7 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType prefabPosition = new Vector3(6.0f, 4.0f, 1.0f); prefabRotation = new Quaternion(3f, 2f, 4f, 1f); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); //Test that something was instantiated Assert.NotNull(spawnedObject); @@ -181,59 +190,49 @@ public void NetworkPrefabHandlerClass([Values] NetworkTopologyTypes topologyType Assert.True(prefabPosition == spawnedObject.transform.position); Assert.True(prefabRotation == spawnedObject.transform.rotation); - networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler // Register a handler that throws an exception var networkPrefabExceptionThrower = new NetworkPrefabExceptionThrower(); - gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); + gameObjectRegistered = authority.PrefabHandler.AddHandler(baseObject, networkPrefabExceptionThrower); //Test result of registering exception handler Assert.True(gameObjectRegistered); LogAssert.Expect(LogType.Exception, "Exception: exception while instantiating"); - spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); + spawnedObject = authority.PrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation); // No object should have been spawned, but test should have continued Assert.Null(spawnedObject); - networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler + authority.PrefabHandler.RemoveHandler(baseObject); //Remove our handler Assert.False(networkPrefabInstanceHandler.StillHasInstances()); - } - [SetUp] - public void Setup() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None); + UnityEngine.Object.Destroy(prefabHandlerObject); } - [TearDown] - public void TearDown() + protected override IEnumerator OnTearDown() { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - var networkObjects = FindObjects.ByType(); - var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName)); - foreach (var networkObject in networkObjectsList) - { - UnityEngine.Object.DestroyImmediate(networkObject); - } + m_CanStart = false; + return base.OnTearDown(); } } /// /// The Prefab instance handler to use for this test /// - internal class NetworkPrefabInstanceHandler : INetworkPrefabInstanceHandler + internal class NetworkPrefabInstanceHandler : MonoBehaviour, INetworkPrefabInstanceHandler { private NetworkObject m_NetworkObject; private List m_Instances; + private NetworkManager m_NetworkManager; + public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { - var networkObjectInstance = UnityEngine.Object.Instantiate(m_NetworkObject.gameObject).GetComponent(); + var networkObjectInstance = Instantiate(m_NetworkObject.gameObject).GetComponent(); networkObjectInstance.transform.SetPositionAndRotation(position, rotation); m_Instances.Add(networkObjectInstance); return networkObjectInstance; @@ -241,28 +240,40 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - var instancesContainsNetworkObject = m_Instances.Contains(networkObject); - Assert.True(instancesContainsNetworkObject); - m_Instances.Remove(networkObject); - UnityEngine.Object.Destroy(networkObject.gameObject); + if (m_Instances == null || m_Instances.Count > 0) + { + var instancesContainsNetworkObject = m_Instances.Contains(networkObject); + Assert.True(instancesContainsNetworkObject); + m_Instances.Remove(networkObject); + Destroy(networkObject.gameObject); + } } public bool StillHasInstances() { - return (m_Instances.Count > 0); + return m_Instances.Count > 0; + } + + private void OnDestroy() + { + m_NetworkManager?.PrefabHandler.RemoveHandler(m_NetworkObject); + m_Instances.Clear(); + m_Instances = null; } - public NetworkPrefabInstanceHandler(NetworkObject networkObject) + public void Initialize(NetworkManager networkManager, NetworkObject networkObject) { + m_NetworkManager = networkManager; m_NetworkObject = networkObject; m_Instances = new List(); + networkManager.PrefabHandler.AddHandler(networkObject, this); } } /// /// Causes an exception during client connection /// - internal class NetworkPrefabExceptionThrower : INetworkPrefabInstanceHandler + internal class NetworkPrefabExceptionThrower : MonoBehaviour, INetworkPrefabInstanceHandler { public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation) { @@ -271,7 +282,7 @@ public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaterni public void Destroy(NetworkObject networkObject) { - UnityEngine.Object.Destroy(networkObject.gameObject); + Destroy(networkObject.gameObject); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs index 7b06e49576..c4bcd3f124 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Prefabs/NetworkPrefabHandlerWithDataTests.cs @@ -5,6 +5,7 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { @@ -24,7 +25,7 @@ public NetworkPrefabHandlerWithDataTests(NetworkTopologyTypes topology) : base(t protected override void OnServerAndClientsCreated() { // Creates a network object prefab and registers it to all clients. - m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName).gameObject; + m_Prefab = CreateNetworkObjectPrefab(k_TestPrefabObjectName); var authority = GetAuthorityNetworkManager(); m_ClientHandlers = new PrefabInstanceHandlerWithData[NumberOfClients]; @@ -67,8 +68,11 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() var data = new NetworkSerializableTest { Value = 42, Value2 = 2.71f }; var spawned = SpawnPrefabWithData(data); + Debug.Log("Spawn"); yield return WaitForConditionOrTimeOut(() => AllHandlersSynchronized(data)); AssertOnTimeout("Not all handlers synchronized"); + yield return WaitForSpawnedOnAllOrTimeOut(spawned); + AssertOnTimeout($"Not all clients spawned {spawned.name}!"); // When running with Distributed Authority, test a late-joiner after an ownership change // The object owner will synchronize the late joining client, showing that the instantiationData will survive host migration. @@ -88,7 +92,7 @@ public IEnumerator InstantiationPayload_LateJoinersReceiveData() }); AssertOnTimeout($"Timed out while waiting for Client-{newOwner.LocalClientId} to own object"); } - + Debug.Log("Late client..."); // Late join a client yield return CreateAndStartNewClient(); @@ -105,9 +109,12 @@ private void RegisterPrefabHandler(NetworkManager manager, out PrefabInstanceHan private NetworkObject SpawnPrefabWithData(NetworkSerializableTest data) { - var instance = UnityEngine.Object.Instantiate(m_Prefab).GetComponent(); + var authority = GetAuthorityNetworkManager(); + var instance = Object.Instantiate(m_Prefab).GetComponent(); + GetAuthorityNetworkManager().PrefabHandler.SetInstantiationData(instance, data); - instance.Spawn(); + + SpawnObjectInstance(instance, authority); return instance; } @@ -129,12 +136,12 @@ public PrefabInstanceHandlerWithData(GameObject prefab) public override NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation, NetworkSerializableTest data) { InstantiationData = data; - return UnityEngine.Object.Instantiate(m_Prefab, position, rotation).GetComponent(); + return Object.Instantiate(m_Prefab, position, rotation).GetComponent(); } public override void Destroy(NetworkObject networkObject) { - UnityEngine.Object.DestroyImmediate(networkObject.gameObject); + Object.DestroyImmediate(networkObject.gameObject); } } @@ -150,7 +157,9 @@ public void NetworkSerialize(BufferSerializer serializer) where T : IReade } public bool IsSynchronizedWith(NetworkSerializableTest other) - => Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + { + return Value == other.Value && Math.Abs(Value2 - other.Value2) < 0.0001f; + } } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs index c0b870193a..56de84deb9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Profiling/NetworkVariableNameTests.cs @@ -1,41 +1,41 @@ -using System; +using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { - internal sealed class NetworkVariableNameTests + internal class NetworkVariableNameTests : NetcodeIntegrationTest { + protected override int NumberOfClients => 1; private NetworkVariableNameComponent m_NetworkVariableNameComponent; - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } + private GameObject m_PrefabToTest; - [SetUp] - public void SetUp() + protected override void OnServerAndClientsCreated() { - NetworkManagerHelper.StartNetworkManager(out _); - - var gameObjectId = NetworkManagerHelper.AddGameNetworkObject(Guid.NewGuid().ToString()); - m_NetworkVariableNameComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + m_PrefabToTest = CreateNetworkObjectPrefab("NetVarNameTest"); + m_PrefabToTest.AddComponent(); + base.OnServerAndClientsCreated(); } - [TearDown] - public void TearDown() + [UnityTest] + public IEnumerator VerifyNetworkVariableNameInitialization() { - NetworkManagerHelper.ShutdownNetworkManager(); - } + var authority = GetAuthorityNetworkManager(); + var authorityInstance = SpawnObject(m_PrefabToTest, authority); + var authorityNetworkObject = authorityInstance.GetComponent(); - [Test] - public void VerifyNetworkVariableNameInitialization() - { - // Fields have regular naming - Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), m_NetworkVariableNameComponent.NetworkVarList.Name); + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance); + AssertOnTimeout($"Not all clients spawned {authorityInstance.name}!"); + + foreach (var networkManager in m_NetworkManagers) + { + var componentInstance = networkManager.SpawnManager.SpawnedObjects[authorityNetworkObject.NetworkObjectId].GetComponent(); + // Verify fields have regular naming + Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), componentInstance.NetworkVarList.Name); + } } private class NetworkVariableNameComponent : NetworkBehaviour diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs index 1489fb61b9..94741ad3e5 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcQueueTests.cs @@ -1,6 +1,4 @@ -using System; using System.Collections; -using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -13,62 +11,47 @@ namespace Unity.Netcode.RuntimeTests /// - That all RPCs invoke at the appropriate `NetworkUpdateStage` (Client and Server) /// - A lower level `MessageQueueContainer` test that validates `MessageQueueFrameItems` after they have been put into the queue /// - internal class RpcQueueTests + internal class RpcQueueTests : NetcodeIntegrationTest { - [OneTimeSetUp] - public void OneTimeSetup() + protected override int NumberOfClients => 0; + + private GameObject m_TestPrefab; + + protected override void OnOneTimeSetup() { // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } - [SetUp] - public void Setup() + protected override void OnServerAndClientsCreated() { - // Create, instantiate, and host - Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _)); + m_TestPrefab = CreateNetworkObjectPrefab("RpcQueueTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); } /// /// This tests the RPC Queue outbound and inbound buffer capabilities. /// /// IEnumerator - [UnityTest, Order(2)] + [UnityTest] public IEnumerator BufferDataValidation() { - Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("GrowingBufferObject"); + var authority = GetAuthorityNetworkManager(); + var instance = SpawnObject(m_TestPrefab, authority); - var growingRpcBufferSizeComponent = NetworkManagerHelper.AddComponentToObject(gameObjectId); + yield return WaitForSpawnedOnAllOrTimeOut(instance); + AssertOnTimeout($"Not all clients spawned {instance.name}!"); - NetworkManagerHelper.SpawnNetworkObject(gameObjectId); + var bufferDataValidationComponent = instance.GetComponent(); // Start Testing - growingRpcBufferSizeComponent.EnableTesting = true; - - var testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - - // Wait for the RPC pipeline test to complete or if we exceeded the maximum iterations bail - while (!testsAreComplete) - { - yield return new WaitForSeconds(0.003f); - - testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete(); - } + bufferDataValidationComponent.EnableTesting = true; - // Stop Testing - growingRpcBufferSizeComponent.EnableTesting = false; + yield return WaitForConditionOrTimeOut(() => bufferDataValidationComponent.IsTestComplete()); + AssertOnTimeout($"Timed out waiting for the {nameof(BufferDataValidationComponent)} tests to complete!"); - // Just disable this once we are done. - growingRpcBufferSizeComponent.gameObject.SetActive(false); - - Assert.IsTrue(testsAreComplete); - } - - [TearDown] - public void TearDown() - { - // Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs new file mode 100644 index 0000000000..dabc67996b --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs @@ -0,0 +1,373 @@ +using System.Collections; +using System.Runtime.CompilerServices; +using System.Text; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; + + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.DAHost)] + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + internal class BaseReferenceTests : NetcodeIntegrationTest + { + protected struct GroupedComponents + { + public GameObject GameObject; + public NetworkObject NetworkObject; + public TestNetworkBehaviour TestNetworkBehaviour; + } + + protected override int NumberOfClients => 1; + + protected GameObject m_TestPrefab; + + protected GroupedComponents m_ValidatingInstance + { + get; + private set; + } + protected GroupedComponents m_ReferenceToUse + { + get; + private set; + } + + public BaseReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("ReferenceTest"); + m_TestPrefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected GroupedComponents GetGroup(GameObject gameObject) + { + return new GroupedComponents() + { + GameObject = gameObject, + NetworkObject = gameObject.GetComponent(), + TestNetworkBehaviour = gameObject.GetComponent() + }; + } + + protected IEnumerator SpawnTestPrefabInstance(bool spawnSingle = false) + { + var authority = GetAuthorityNetworkManager(); + m_ValidatingInstance = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ValidatingInstance.GameObject); + AssertOnTimeout($"[{GetType().Name}][Validating Instance] Faild to spawn {m_ValidatingInstance.GameObject.name} on all clients!"); + + if (!spawnSingle) + { + m_ReferenceToUse = GetGroup(SpawnObject(m_TestPrefab, authority)); + + yield return WaitForSpawnedOnAllOrTimeOut(m_ReferenceToUse.GameObject); + AssertOnTimeout($"[{GetType().Name}][Reference to use] Faild to spawn {m_ReferenceToUse.GameObject.name} on all clients!"); + } + } + + #region NetworkBehaviour specific conditional methods + protected bool RpcWasReceivedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire the reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.RpcReceivedBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.RpcReceivedBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableChangedAndBehaviourValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject) + && networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the NetworkVariable update!"); + continue; + } + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} failed to acquire a reference!"); + continue; + } + if (referenceBehaviour != validatingBehaviour.TestVariableBehaviour) + { + var currentReferenceBehaviour = validatingBehaviour.TestVariableBehaviour != null ? validatingBehaviour.RpcReceivedBehaviour.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference {referenceBehaviour.name} but was {currentReferenceBehaviour}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool RpcSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.RpcReceivedBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + protected bool NetworkVariableSerializingNullValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.TestVariableChanged) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not detected a change in the NetworkVariable!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.TestVariableBehaviour != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected behaviour reference NULL but was {validatingBehaviour.TestVariableBehaviour.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + #endregion + + protected bool NetworkObjectSerializedValidation(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var authorityReferenceId = m_ReferenceToUse.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + var referenceNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityReferenceId, out referenceNetworkObject) && + networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + var referenceBehaviour = referenceNetworkObject.GetComponent(); + + if (!validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingNetworkObject.name} failed to acquire the reference!"); + continue; + } + if (referenceNetworkObject != validatingBehaviour.RpcReceivedNetworkObject) + { + var currentReferencedObject = validatingBehaviour.RpcReceivedNetworkObject != null ? validatingBehaviour.RpcReceivedNetworkObject.name : "null"; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObject)} reference " + + $"{referenceNetworkObject.name} but was {currentReferencedObject}!"); + } + } + else + { + var wasNotSpawned = validatingNetworkObject == null ? m_ValidatingInstance.GameObject.name : m_ReferenceToUse.GameObject.name; + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {wasNotSpawned}!"); + } + validatingNetworkObject = null; + referenceNetworkObject = null; + } + + return stringBuilder.Length == 0; + } + + protected bool SerializingNullNetworkObjectValidated(StringBuilder stringBuilder) + { + var authorityValidatingId = m_ValidatingInstance.NetworkObject.NetworkObjectId; + var validatingNetworkObject = (NetworkObject)null; + foreach (var networkManager in m_NetworkManagers) + { + if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(authorityValidatingId, out validatingNetworkObject)) + { + var validatingBehaviour = validatingNetworkObject.GetComponent(); + + if (!validatingBehaviour.ReceivedRPC) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} has not received the RPC!"); + continue; + } + if (validatingBehaviour.AcquiredReference) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} Acquired a reference when it should not have!"); + continue; + } + if (validatingBehaviour.RpcReceivedNetworkObject != null) + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} {validatingBehaviour.name} expected {nameof(NetworkObjectReference)} to be NULL but was {validatingBehaviour.RpcReceivedNetworkObject.name}!"); + } + } + else + { + stringBuilder.AppendLine($"Client-{networkManager.LocalClientId} has not yet spawned {m_ValidatingInstance.GameObject.name}!"); + } + validatingNetworkObject = null; + } + return stringBuilder.Length == 0; + } + + + protected class TestNetworkBehaviour : NetworkBehaviour + { + public bool ReceivedRPC; + public bool TestVariableChanged; + public bool AcquiredReference; + + public NetworkVariable NetworkBehaviourVariable = new NetworkVariable(); + + public TestNetworkBehaviour TestVariableBehaviour; + + public TestNetworkBehaviour RpcReceivedBehaviour; + public TestNetworkBehaviour RpcReceived; + + public NetworkVariable NetworkObjectVariable = new NetworkVariable(); + + public NetworkObject TestVariableNetworkObject; + public GameObject TestVariableGameObject; + + public NetworkObject RpcReceivedNetworkObject; + public GameObject RpcReceivedGameObject; + + protected override void OnNetworkPreSpawn(ref NetworkManager networkManager) + { + // Set it to ourself so we can validate serializing null too. + RpcReceivedBehaviour = this; + TestVariableBehaviour = null; + base.OnNetworkPreSpawn(ref networkManager); + } + + public override void OnNetworkSpawn() + { + NetworkBehaviourVariable.OnValueChanged += OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged += OnNetworkObjectVariableChanged; + base.OnNetworkSpawn(); + } + + public override void OnNetworkPreDespawn() + { + NetworkBehaviourVariable.OnValueChanged -= OnTestVariableChanged; + NetworkObjectVariable.OnValueChanged -= OnNetworkObjectVariableChanged; + base.OnNetworkPreDespawn(); + } + private void OnTestVariableChanged(NetworkBehaviourReference previous, NetworkBehaviourReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableBehaviour, NetworkManager); + } + + private void OnNetworkObjectVariableChanged(NetworkObjectReference previous, NetworkObjectReference current) + { + TestVariableChanged = true; + AcquiredReference = current.TryGet(out TestVariableNetworkObject, NetworkManager); + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkBehaviourReferenceRpc(NetworkBehaviourReference value) + { + AcquiredReference = value.TryGet(out RpcReceivedBehaviour, NetworkManager); + ReceivedRPC = true; + } + + [Rpc(SendTo.Everyone)] + public void SendNetworkObjectReferenceRpc(NetworkObjectReference value) + { + ReceivedRPC = true; + AcquiredReference = value.TryGet(out RpcReceivedNetworkObject, NetworkManager); + RpcReceivedGameObject = value; + } + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta new file mode 100644 index 0000000000..5d79890713 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/BaseReferenceTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 87bdaa820aaed704f8309de710e9153f \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 51d2ae8e71..20e27d6492 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -2,191 +2,160 @@ using System.Collections; using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; -using UnityEngine; using UnityEngine.TestTools; +using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { /// /// Unit tests to test: - /// - Serializing NetworkObject to NetworkObjectReference - /// - Deserializing NetworkObjectReference to NetworkObject - /// - Implicit operators of NetworkObjectReference + /// - Serializing NetworkBehaviour to NetworkBehaviourReference + /// - Deserializing NetworkBehaviourReference to NetworkBehaviour + /// - Implicit operators of NetworkBehaviourReference /// - internal class NetworkBehaviourReferenceTests : IDisposable + internal class NetworkBehaviourReferenceTests : BaseReferenceTests { - [OneTimeSetUp] - public void OneTimeSetup() + public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); } - private class TestNetworkBehaviour : NetworkBehaviour - { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public TestNetworkBehaviour RpcReceivedBehaviour; - - [ServerRpc] - public void SendReferenceServerRpc(NetworkBehaviourReference value) - { - RpcReceivedBehaviour = (TestNetworkBehaviour)value; - ReceivedRPC = true; - } - } + #region Tests using non-null NetworkBehaviours and RPCs [UnityTest] public IEnumerator TestRpc() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(new NetworkBehaviourReference(m_ReferenceToUse.TestNetworkBehaviour)); - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(testNetworkBehaviour)); + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); + } - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } - yield return null; - } + [UnityTest] + public IEnumerator TestRpcImplicitNetworkBehaviour() + { + yield return SpawnTestPrefabInstance(); - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); + // Implicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(m_ReferenceToUse.TestNetworkBehaviour); + + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcWasReceivedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); } + #endregion + #region Tests using non-null NetworkBehaviours and NetworkVariable [UnityTest] - public IEnumerator TestSerializeNull([Values] bool initializeWithNull) + public IEnumerator TestNetworkVariable() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); - - // If not initializing with null, then use the default constructor with no assigned NetworkBehaviour - if (!initializeWithNull) - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference()); - } - else // Otherwise, initialize and pass in null as the reference - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null)); - } + yield return SpawnTestPrefabInstance(); - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Assure the authority instance's value is the default (null) value + Assert.IsNull((NetworkBehaviour)m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value); - yield return null; - } + // Implicitly assign the NetworkBehaviourReference by assigning the NetworkBehaviour to the NetworkVariable. + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = m_ReferenceToUse.TestNetworkBehaviour; - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour); + // Validated the NetworkVariable reference propogates to clients + yield return WaitForConditionOrTimeOut(NetworkVariableChangedAndBehaviourValidated); + AssertOnTimeout($"[{nameof(TestNetworkVariable)}] Failed to validate reference!"); } + #endregion + #region Validating using NULL as a NetworkBehaviourReference [UnityTest] - public IEnumerator TestRpcImplicitNetworkBehaviour() + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(true); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL parameter + var initializeWithNull = new NetworkBehaviourReference(null); + // Initialize with no parameter + var initializeWithNothing = new NetworkBehaviourReference(); - testNetworkBehaviour.SendReferenceServerRpc(testNetworkBehaviour); + // Initialized with NULL parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNull); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedBehaviour == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); - yield return null; + // Reset the RPC NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedBehaviour = testBehaviour; } - // validate - Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour); - } + // Initialized with no parameter + // Explicitly send the NetworkBehaviour as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkBehaviourReferenceRpc(initializeWithNothing); - [Test] - public void TestNetworkVariable() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + // Validated the reference + yield return WaitForConditionOrTimeOut(RpcSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestRpc)}] Failed to validate reference!"); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); - // check default value is null - Assert.IsNull((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value); + // Initialize NetworkBehaviourVariable with NULL parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNull; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with null parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); - testNetworkBehaviour.TestVariable.Value = testNetworkBehaviour; + // Reset the NetworkVaraible NetworkBehaviourReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.TestVariableBehaviour = testBehaviour; + testBehaviour.TestVariableChanged = false; + } - Assert.AreEqual((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value, testNetworkBehaviour); + // Initialize NetworkBehaviourVariable with no parameter + m_ValidatingInstance.TestNetworkBehaviour.NetworkBehaviourVariable.Value = initializeWithNothing; + yield return WaitForConditionOrTimeOut(NetworkVariableSerializingNullValidated); + AssertOnTimeout($"[{nameof(TestSerializeNull)}][Initialize with no parameter] Failed to validate null {nameof(NetworkBehaviour)} reference!"); } + #endregion + + #region Serialization Failure validation tests - [Test] - public void FailSerializeNonSpawnedNetworkObject() + /// + /// This test is ok to create but not spawn. + /// + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var component = networkObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); + + Object.Destroy(instance); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); - var component = gameObjectContext.Object.gameObject.AddComponent(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Object.Destroy(instance.GetComponent()); Assert.Throws(() => { - NetworkBehaviourReference outReference = component; + NetworkBehaviourReference outReference = instance.GetComponent(); }); - } - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - - public NetworkBehaviourReferenceTests() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); + Object.Destroy(instance); } + #endregion } /// diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 1fbce803fc..699f4a21f9 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections; +using System.Collections.Generic; using NUnit.Framework; -using Unity.Collections; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -15,415 +15,154 @@ namespace Unity.Netcode.RuntimeTests /// - Deserializing NetworkObjectReference to NetworkObject /// - Implicit operators of NetworkObjectReference /// - internal class NetworkObjectReferenceTests : IDisposable + internal class NetworkObjectReferenceTests : BaseReferenceTests { - [OneTimeSetUp] - public void OneTimeSetup() - { - // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. - NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); - } - - private class TestNetworkBehaviour : NetworkBehaviour - { - public static bool ReceivedRPC; - - public NetworkVariable TestVariable = new NetworkVariable(); - - public NetworkObject RpcReceivedNetworkObject; - - public GameObject RpcReceivedGameObject; - - [ServerRpc] - public void SendReferenceServerRpc(NetworkObjectReference value) - { - ReceivedRPC = true; - RpcReceivedGameObject = value; - RpcReceivedNetworkObject = value; - } - } - - [Test] - public void TestSerializeNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - - // validate - Assert.NotNull((NetworkObject)inReference); - Assert.AreEqual(inReference.NetworkObjectId, networkObjectContext.Object.NetworkObjectId); - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)inReference); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestSerializeGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var outWriter = new FastBufferWriter(1300, Allocator.Temp); - try - { - // serialize - var outSerializer = new BufferSerializer(new BufferSerializerWriter(outWriter)); - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - outReference.NetworkSerialize(outSerializer); - - // deserialize - NetworkObjectReference inReference = default; - var inReader = new FastBufferReader(outWriter, Allocator.Temp); - try - { - var inSerializer = - new BufferSerializer(new BufferSerializerReader(inReader)); - inReference.NetworkSerialize(inSerializer); - } - finally - { - inReader.Dispose(); - } - GameObject gameObject = inReference; - - // validate - Assert.AreEqual(outReference, inReference); - Assert.AreEqual(networkObjectContext.Object.gameObject, gameObject); - } - finally - { - outWriter.Dispose(); - } - } - - [Test] - public void TestImplicitConversionToGameObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); + protected override int NumberOfClients => 1; - NetworkObjectReference outReference = networkObjectContext.Object.gameObject; - GameObject go = outReference; - Assert.AreEqual(networkObjectContext.Object.gameObject, go); - } - - [Test] - public void TestImplicitToGameObjectIsNullWhenNotFound() + public NetworkObjectReferenceTests(HostOrServer hostOrServer) : base(hostOrServer) { - 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() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - - Assert.True(networkObjectReference.TryGet(out NetworkObject networkObject)); - Assert.NotNull(networkObject); - networkObjectReference.TryGet(out NetworkObject result); - Assert.AreEqual(networkObject, result); } - public enum NetworkObjectConstructorTypes + protected override void OnOneTimeSetup() { - None, - NullNetworkObject, - NullGameObject + // TODO: [CmbServiceTests] if this test is deemed needed to test against the CMB server then update this test. + NetcodeIntegrationTestHelpers.IgnoreIfServiceEnviromentVariableSet(); + base.OnOneTimeSetup(); } [UnityTest] - public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes) + public IEnumerator TestSerializeNetworkObject() { - TestNetworkBehaviour.ReceivedRPC = false; - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - switch (networkObjectConstructorTypes) - { - case NetworkObjectConstructorTypes.None: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference()); - break; - } - case NetworkObjectConstructorTypes.NullNetworkObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null)); - break; - } - case NetworkObjectConstructorTypes.NullGameObject: - { - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null)); - break; - } - } + // Explicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.NetworkObject)); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Explicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (!TestNetworkBehaviour.ReceivedRPC) - { + // Implicitly send the NetworkObject as a reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(m_ReferenceToUse.NetworkObject); - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][Implicit reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; - } + // Use the GameObject to set the NetworkObjet reference + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(new NetworkObjectReference(m_ReferenceToUse.GameObject)); - // validate - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject); + // Validated the reference + yield return WaitForConditionOrTimeOut(NetworkObjectSerializedValidation); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject reference] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpc() + public IEnumerator TestSerializeNull() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(true); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL parameter + var initializeWithNullGameObject = new NetworkObjectReference((GameObject)null); + var initializeWithNullNetworkObject = new NetworkObjectReference((NetworkObject)null); + // Initialize with no parameter + var initializeWithNothing = new NetworkObjectReference(); - testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference(otherObjectContext.Object)); + // Initialize with NULL GameObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullGameObject); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][GameObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - yield return null; + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) + { + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [UnityTest] - public IEnumerator TestRpcImplicitNetworkObject() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); - - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Initialize with NULL NetworkObject parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNullNetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object); + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][NetworkObject as NULL] Failed to validate {nameof(NetworkObjectReference)} serialization!"); - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) + // Reset the RPC NetworkObjectReference to the local instance for all spawned instances. + foreach (var networkManager in m_NetworkManagers) { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } - - yield return null; + var testBehaviour = networkManager.SpawnManager.SpawnedObjects[m_ValidatingInstance.NetworkObject.NetworkObjectId].GetComponent(); + testBehaviour.RpcReceivedNetworkObject = testBehaviour.NetworkObject; } - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); + // Initialize with no parameter + m_ValidatingInstance.TestNetworkBehaviour.SendNetworkObjectReferenceRpc(initializeWithNothing); + + // Validated the reference + yield return WaitForConditionOrTimeOut(SerializingNullNetworkObjectValidated); + AssertOnTimeout($"[{nameof(TestSerializeNetworkObject)}][No Parameter] Failed to validate {nameof(NetworkObjectReference)} serialization!"); } [UnityTest] - public IEnumerator TestRpcImplicitGameObject() + public IEnumerator TestGetReferenceAndConversion() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + yield return SpawnTestPrefabInstance(); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + var referenceToUse = new NetworkObjectReference(m_ReferenceToUse.NetworkObject); - testNetworkBehaviour.SendReferenceServerRpc(otherObjectContext.Object.gameObject); - - // wait for rpc completion - float t = 0; - while (testNetworkBehaviour.RpcReceivedGameObject == null) - { - t += Time.deltaTime; - if (t > 5f) - { - new AssertionException("RPC with NetworkBehaviour reference hasn't been received"); - } + Assert.True(referenceToUse.TryGet(out NetworkObject networkObject)); + Assert.NotNull(networkObject, $"TryGet succeeded but value returned is null!"); - yield return null; - } - - // validate - Assert.AreEqual(otherObjectContext.Object, testNetworkBehaviour.RpcReceivedNetworkObject); - Assert.AreEqual(otherObjectContext.Object.gameObject, testNetworkBehaviour.RpcReceivedGameObject); - } - - [Test] - public void TestNetworkVariable() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent(); - networkObjectContext.Object.Spawn(); + // TODO: Revisit this to determine if this portion of the test is actually needed + referenceToUse.TryGet(out NetworkObject result); + Assert.AreEqual(networkObject, result); - using var otherObjectContext = UnityObjectContext.CreateNetworkObject(); - otherObjectContext.Object.Spawn(); + // Now implicitly convert from a NetworkObjectReference to a GameObject + GameObject fromReference = referenceToUse; + Assert.IsTrue(fromReference == m_ReferenceToUse.GameObject, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed!"); - // check default value is null - Assert.IsNull((NetworkObject)testNetworkBehaviour.TestVariable.Value); + // Despawn + m_ReferenceToUse.NetworkObject.Despawn(); + var referenceName = m_ReferenceToUse.GameObject.name; + yield return WaitForDespawnedOnAllOrTimeOut(new List() { m_ReferenceToUse.NetworkObject }); + AssertOnTimeout($"Timed out waiting for {referenceName} to de-spawn!"); - testNetworkBehaviour.TestVariable.Value = networkObjectContext.Object; + // Destroy + Object.DestroyImmediate(m_ReferenceToUse.GameObject); - Assert.AreEqual((GameObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object.gameObject); - Assert.AreEqual((NetworkObject)testNetworkBehaviour.TestVariable.Value, networkObjectContext.Object); + // Validate trying to implicitly convert returns a null value when the GameObject + // no longer exists + fromReference = referenceToUse; + Assert.IsTrue(fromReference == null, $"Implicitly converting {nameof(NetworkObjectReference)} to {nameof(GameObject)} failed when destroyed!"); } - [Test] - public void TestDespawn() - { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); - networkObjectContext.Object.Spawn(); - var originalId = networkObjectContext.Object.NetworkObjectId; - - NetworkObjectReference networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - - networkObjectContext.Object.Despawn(); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - networkObjectContext.Object.Spawn(); - - // After spawning again the reference will still no longer work as it still points to the old object - Assert.AreNotEqual(originalId, networkObjectContext.Object.NetworkObjectId); - Assert.IsFalse(networkObjectReference.TryGet(out NetworkObject _)); - - // creating a new reference will make it work again - networkObjectReference = networkObjectContext.Object; - Assert.AreEqual(networkObjectContext.Object, (NetworkObject)networkObjectReference); - } - - [Test] - public void FailSerializeNonSpawnedNetworkObject() + [UnityTest] + public IEnumerator FailSerializeNonSpawnedNetworkObject() { - using var networkObjectContext = UnityObjectContext.CreateNetworkObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = networkObjectContext.Object; + NetworkObjectReference outReference = instance.GetComponent(); }); } - [Test] - public void FailSerializeGameObjectWithoutNetworkObject() + [UnityTest] + public IEnumerator FailSerializeGameObjectWithoutNetworkObject() { - using var gameObjectContext = UnityObjectContext.CreateGameObject(); + yield return s_DefaultWaitForTick; + var instance = Object.Instantiate(m_TestPrefab); + Assert.Throws(() => { - NetworkObjectReference outReference = gameObjectContext.Object; + NetworkObjectReference outReference = instance; }); } - - public void Dispose() - { - //Stop, shutdown, and destroy - NetworkManagerHelper.ShutdownNetworkManager(); - } - - public NetworkObjectReferenceTests() - { - //Create, instantiate, and host - NetworkManagerHelper.StartNetworkManager(out _); - } - } - - /// - /// Helper method for tests to create and destroy Unity Objects. - /// - /// The type of Object this context incorporates. - internal class UnityObjectContext : UnityObjectContext where T : Object - { - private T m_Object; - - internal UnityObjectContext(T unityObject, Object root) - : base(root) - { - m_Object = unityObject; - } - - public T Object => m_Object; - } - - internal class UnityObjectContext : IDisposable - { - private Object m_Root; - - protected UnityObjectContext(Object root) - { - m_Root = root; - } - - public static UnityObjectContext CreateGameObject(string name = "") - { - var gameObject = new GameObject(name); - return new UnityObjectContext(gameObject, gameObject); - } - - public static UnityObjectContext CreateNetworkObject(string name = "") - { - var gameObject = new GameObject(name); - var networkObject = gameObject.AddComponent(); - return new UnityObjectContext(networkObject, gameObject); - } - - public void Dispose() - { - Object.DestroyImmediate(m_Root); - } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 2a0c74258f..5b2f5a986b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -777,6 +777,11 @@ protected void CreateServerAndClients() CreateServerAndClients(NumberOfClients); } + internal virtual bool ShouldCreatePlayerPrefab() + { + return true; + } + /// /// Creates the server and clients /// @@ -785,7 +790,11 @@ protected void CreateServerAndClients(int numberOfClients) { VerboseDebug($"Entering {nameof(CreateServerAndClients)}"); - CreatePlayerPrefab(); + if (ShouldCreatePlayerPrefab()) + { + CreatePlayerPrefab(); + } + if (m_EnableTimeTravel) { diff --git a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs index d685fb68ca..46fd493b5c 100644 --- a/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs +++ b/testproject/Assets/Tests/Runtime/Animation/NetworkAnimatorTests.cs @@ -356,6 +356,7 @@ public void ParameterBoundsCheck() { var gameObject = new GameObject(); gameObject.AddComponent(); + gameObject.AddComponent(); var networkAnimator = gameObject.AddComponent(); var writer = new FastBufferWriter(40, Unity.Collections.Allocator.TempJob); @@ -371,6 +372,8 @@ public void ParameterBoundsCheck() LogAssert.Expect(LogType.Error, new System.Text.RegularExpressions.Regex($"parameters. Ignoring the remainger of this {nameof(ParametersUpdateMessage)}!")); // Pass in the invalid ParametersUpdateMessage networkAnimator.UpdateParameters(ref invalidParameters); + + Object.DestroyImmediate(gameObject); } private bool AllTriggersDetected(OwnerShipMode ownerShipMode) @@ -1078,27 +1081,18 @@ public void ShutdownWhileSpawnedAndStartBackUpTest() TimeTravelToNextTick(); - WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress); + WaitForConditionOrTimeOutWithTimeTravel(() => !m_ServerNetworkManager.ShutdownInProgress && m_ServerNetworkManager.IsConnectedClient); 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.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; // Now, restart the server and the client m_ServerNetworkManager.StartHost(); foreach (var clientNetworkManager in m_ClientNetworkManagers) { - clientNetworkManager.NetworkConfig.Prefabs.Prefabs[playerPrefabIndex].Prefab = m_PlayerPrefab; - clientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; clientNetworkManager.StartClient(); } From 46743e43c295fc5605cc4e24cef982aaedc21f41 Mon Sep 17 00:00:00 2001 From: Amy Reeve Date: Wed, 29 Jul 2026 13:30:46 +0100 Subject: [PATCH 39/64] docs: Docs-wide link fixes and xref update (#4102) * Docs-wide link fixes and xref update * Typo in API docs --- .../advanced-topics/custom-serialization.md | 2 +- .../fastbufferwriter-fastbufferreader.md | 2 +- .../message-system/rpc-params.md | 4 +-- .../advanced-topics/message-system/rpc.md | 2 +- .../advanced-topics/network-prefab-handler.md | 12 +++---- .../network-update-loop-reference.md | 4 +-- .../networkobject-parenting.md | 6 ++-- .../advanced-topics/networktime-ticks.md | 2 +- .../Documentation~/advanced-topics/physics.md | 2 +- .../serialization/inetworkserializable.md | 4 +-- .../inetworkserializebymemcpy.md | 2 +- .../networkobject-serialization.md | 2 +- .../serialization/serialization-arrays.md | 2 +- .../serialization/serialization-overview.md | 2 +- .../advanced-topics/session-management.md | 2 +- .../advanced-topics/singleplayer.md | 2 +- .../advanced-topics/transports.md | 2 +- .../basics/custom-networkvariables.md | 2 +- .../Documentation~/basics/object-spawning.md | 2 +- .../scenemanagement/custom-management.md | 2 +- .../inscene-placed-networkobjects.md | 2 +- .../basics/scenemanagement/scene-events.md | 2 +- .../using-networkscenemanager.md | 6 ++-- .../components/core/corecomponents.md | 2 +- .../core/networkbehaviour-ownership.md | 18 +++++------ .../core/networkbehaviour-synchronize.md | 4 +-- .../components/core/networkbehaviour.md | 2 +- .../components/core/networkmanager.md | 16 +++++----- .../core/networkobject-ownership.md | 32 +++++++++---------- .../components/core/networkobject.md | 4 +-- .../components/core/playerobjects.md | 4 +-- .../components/helper/componentcontroller.md | 2 +- .../components/helper/helpercomponents.md | 4 +-- .../components/helper/networkrigidbody.md | 4 +-- .../components/helper/networktransform.md | 18 +++++------ .../Documentation~/install.md | 2 +- .../learn/dealing-with-latency.md | 6 ++-- .../learn/listenserverhostarchitecture.md | 2 +- .../Documentation~/learn/rpcvnetvar.md | 2 +- .../learn/ticks-and-update-rates.md | 4 +-- .../Documentation~/migratingfromUNet.md | 4 +-- .../networkbehaviour-landing.md | 2 +- .../Documentation~/removed-tableofcontents.md | 2 +- .../samples/bitesize/bitesize-landing.md | 2 +- .../samples/bitesize/bitesize-usecases.md | 4 +-- .../bossroom/getting-started-boss-room.md | 4 +-- .../samples/bossroom/optimizing-bossroom.md | 8 ++--- .../Documentation~/serialization.md | 2 +- .../terms-concepts/distributed-authority.md | 2 +- .../terms-concepts/ownership.md | 4 +-- .../tutorials/get-started-with-ngo.md | 10 +++--- ..._tricks_for_debugging_multiplayer_games.md | 4 +-- .../testing_with_artificial_conditions.md | 8 ++--- .../Runtime/Messaging/RpcTargets/RpcTarget.cs | 4 +-- 54 files changed, 128 insertions(+), 128 deletions(-) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md index b7b23e4afc..f1210fbc95 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/custom-serialization.md @@ -68,4 +68,4 @@ Reading and writing a value provides the minimal amount of `NetworkVariable` fun Here is a full implementation of a custom type with the methods needed for `UserNetworkVariableSerialization` -[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/NetworkVariableSerialization.cs#HealthExample)] +[!code-cs[](../../Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs#HealthExample)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md index 3f8327fc17..7b9d5d65c3 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/fastbufferwriter-fastbufferreader.md @@ -82,7 +82,7 @@ This allows the four bytes of the embedded struct to be rapidly serialized as a `FastBufferWriter` and `FastBufferReader` are replacements for the old `NetworkWriter` and `NetworkReader`. For those familiar with the old classes, there are some key differences: -- `FastBufferWriter` uses `WriteValue()` as the name of the method for all types *except* [`INetworkSerializable`](serialization/inetworkserializable) types, which are serialized through `WriteNetworkSerializable()` +- `FastBufferWriter` uses `WriteValue()` as the name of the method for all types *except* [`INetworkSerializable`](serialization/inetworkserializable.md) types, which are serialized through `WriteNetworkSerializable()` - `FastBufferReader` similarly uses `ReadValue()` for all types except INetworkSerializable (which is read through `ReadNetworkSerializable`), with the output changed from a return value to an `out` parameter to allow for method overload resolution to pick the correct value. - `FastBufferWriter` and `FastBufferReader` outsource packed writes and reads to `BytePacker` and `ByteUnpacker`, respectively. - `FastBufferWriter` and `FastBufferReader` are **structs**, not **classes**. This means they can be constructed and destructed without GC allocations. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md index 28d0607ef3..7c37bd606d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc-params.md @@ -75,7 +75,7 @@ void AbcdServerRpc(int somenumber) { /* ... */ } void XyzwServerRpc(int somenumber, ServerRpcParams serverRpcParams = default) { /* ... */ } ``` -[ServerRpcParams Documentation](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ServerRpcParams.html) +[ServerRpcParams Documentation](xref:Unity.Netcode.ServerRpcParams) ## ClientRpc Params @@ -94,7 +94,7 @@ void AbcdClientRpc(int framekey) { /* ... */ } void XyzwClientRpc(int framekey, ClientRpcParams clientRpcParams = default) { /* ... */ } ``` -[ClientRpcParams Documentation](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ClientRpcParams.html) +[ClientRpcParams Documentation](xref:Unity.Netcode.ClientRpcParams) > [!NOTE] > `ClientRpcSendParams`'s `TargetClientIds` property is a `ulong[]` which means everytime you try to specify a subset of target clients or even a single client target, you will have to allocate a `new ulong[]`. This pattern can quickly lead into lots of heap allocations and pressure GC which would cause GC spikes at runtime. We suggest developers cache their `ulong[]` variables or use an array pool to cycle `ulong[]` instances so that it would cause less heap allocations. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md index b46a1b8b91..ac95d63bd7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/message-system/rpc.md @@ -328,4 +328,4 @@ void Update() ## Additional resources * [RPC parameters](rpc-params.md) -* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpc) +* [Customizing serialization](../custom-serialization.md#remote-procedure-call-rpcs) diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md index 6608534375..8eab667a68 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-prefab-handler.md @@ -2,7 +2,7 @@ The network prefab handler system provides advanced control over how network prefabs are instantiated and destroyed during runtime. You can use it to override the default Netcode for GameObjects [object spawning](../basics/object-spawning.md) behavior by implementing custom prefab handlers. -The network prefab handler system is accessible from the [NetworkManager](../components/networkmanager.md) as `NetworkManager.PrefabHandler`. +The network prefab handler system is accessible from the [NetworkManager](../components/core/networkmanager.md) as `NetworkManager.PrefabHandler`. ## When to use a prefab handler @@ -13,14 +13,14 @@ For an overview of the default object spawning behavior, refer to the [object sp - **Custom initialization**: Setting up objects with game client specific data or configurations. - **Conditional spawning**: Initializing different prefab variants based on runtime conditions. -The prefab handler system addresses these needs through an interface-based architecture. The system relies on two key methods: `Instantiate` and `Destroy`. `Instantiate` is called on non-authority clients when an [authority](../terms-concepts/authority.md) spawns a new [NetworkObject](../basics/networkobject.md) that has a registered network prefab handler. `Destroy` is called on all game clients whenever a registered [NetworkObject](../basics/networkobject.md) is destroyed. +The prefab handler system addresses these needs through an interface-based architecture. The system relies on two key methods: `Instantiate` and `Destroy`. `Instantiate` is called on non-authority clients when an [authority](../terms-concepts/authority.md) spawns a new [NetworkObject](../components/core/networkobject.md) that has a registered network prefab handler. `Destroy` is called on all game clients whenever a registered [NetworkObject](../components/core/networkobject.md) is destroyed. ## Create a prefab handler Prefab handlers are classes that implement one of the Netcode for GameObjects prefab handler descriptions. There are currently two such descriptions: -- [**INetworkPrefabInstanceHandler**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.INetworkPrefabInstanceHandler.html): This is the simplest interface for custom prefab handlers. -- [**NetworkPrefabInstanceHandlerWithData**](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabInstanceHandlerWithData.html): This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. +- [**INetworkPrefabInstanceHandler**](xref:Unity.Netcode.INetworkPrefabInstanceHandler): This is the simplest interface for custom prefab handlers. +- [**NetworkPrefabInstanceHandlerWithData**](xref:Unity.Netcode.NetworkPrefabInstanceHandlerWithData): This specialized handler receives custom data from the authority during spawning, enabling dynamic prefab customization. When using a prefab handler, Netcode for GameObjects uses the `Instantiate` and `Destroy` methods instead of default spawn handlers for the NetworkObject during spawning and despawning. The authority instance uses the traditional spawning approach where it will, via user script, instantiate and spawn a network prefab (even for those registered with a prefab handler). However, all non-authority clients will automatically use the instantiate method defined by the `INetworkPrefabInstanceHandler` implementation if the network prefab spawned has a registered `INetworkPrefabInstanceHandler` implementation with the `NetworkPrefabHandler` (`NetworkManager.PrefabHandler`). @@ -55,7 +55,7 @@ public abstract class NetworkPrefabInstanceHandlerWithData : INetworkPrefabIn ## Register a prefab handler -Once you've [created a prefab handler](#create-a-prefab-handler), whether by implementing or deriving, you need to register any new instance of that handler with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../basics/networkobject.md#using-networkobjects). +Once you've [created a prefab handler](#create-a-prefab-handler), whether by implementing or deriving, you need to register any new instance of that handler with the network prefab handler system using `NetworkManager.PrefabHandler.AddHandler`. Prefab handlers are registered against a NetworkObject's [GlobalObjectIdHash](../components/core/networkobject.md#using-networkobjects). ```csharp public class GameManager : NetworkBehaviour @@ -70,7 +70,7 @@ public class GameManager : NetworkBehaviour } ``` -To un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkPrefabHandler.html#Unity_Netcode_NetworkPrefabHandler_RemoveHandler_System_UInt32_). There are several override versions of this method. +To un-register a prefab handler, you can [invoke the `NetworkManager.PrefabHandler.RemoveHandler` method](xref:Unity.Netcode.NetworkPrefabHandler.RemoveHandler*). There are several override versions of this method. ## Object spawning with prefab handlers diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md index fd013937a4..72ec0db77c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/network-update-loop-system/network-update-loop-reference.md @@ -6,7 +6,7 @@ The following diagrams provide insight into the Network Update Loop process and
-![Injecting NetworkUpdateLoop Systems Into PlayerLoop](../images/injecting-networkupdatesloop.svg) +![Injecting NetworkUpdateLoop Systems Into PlayerLoop](../../images/injecting-networkupdatesloop.svg)
@@ -14,6 +14,6 @@ The following diagrams provide insight into the Network Update Loop process and
-![NetworkUpdateLoop Running INetworkUpdateSystem Updates](../images/runninginetworkupdatesystemupdates.svg) +![NetworkUpdateLoop Running INetworkUpdateSystem Updates](../../images/runninginetworkupdatesystemupdates.svg)
diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md index 435edf39a5..a2c6c755ea 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networkobject-parenting.md @@ -26,7 +26,7 @@ If you aren't familiar with transform parenting in Unity, then it's recommended ### OnNetworkObjectParentChanged -[`NetworkBehaviour.OnNetworkObjectParentChanged`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnNetworkObjectParentChanged_Unity_Netcode_NetworkObject_) is a virtual method you can override to be notified when a NetworkObject component's parent has changed. The [`MonoBehaviour.OnTransformParentChanged()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnTransformParentChanged.html) method is used by NetworkObject component to catch `transform.parent` changes and notify its associated NetworkBehaviour components. +[`NetworkBehaviour.OnNetworkObjectParentChanged`](xref:Unity.Netcode.NetworkBehaviour.OnNetworkObjectParentChanged*) is a virtual method you can override to be notified when a NetworkObject component's parent has changed. The [`MonoBehaviour.OnTransformParentChanged()`](https://docs.unity3d.com/ScriptReference/MonoBehaviour.OnTransformParentChanged.html) method is used by NetworkObject component to catch `transform.parent` changes and notify its associated NetworkBehaviour components. ```csharp /// @@ -49,7 +49,7 @@ The [owner](../terms-concepts/ownership.md) of a NetworkObject can always parent By default, only the [authority](../terms-concepts/authority.md) of a NetworkObject can parent a NetworkObject under a non-networked object. This means in a client-server game, only the server (or host) can control NetworkObject component parenting. In a distributed authority game the [owner](../terms-concepts/ownership.md) of the object can always parent the object. -To allow the [owner](../terms-concepts/ownership.md) to parent their owned NetworkObject in a client-server game, use the [`NetworkObject.AllowOwnerToParent`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_AllowOwnerToParent) property. +To allow the [owner](../terms-concepts/ownership.md) to parent their owned NetworkObject in a client-server game, use the [`NetworkObject.AllowOwnerToParent`](xref:Unity.Netcode.NetworkObject.AllowOwnerToParent) property. ![image](../images/networkobject/allowOwnerToParent.png) @@ -78,7 +78,7 @@ If you plan on parenting in-scene placed NetworkObject components with a player For more information, refer to: - [Real World In-scene NetworkObject Parenting of Players Solution](inscene_parenting_player.md) -- [Scene Event Notifications](../basics/scenemanagement/scene-events#scene-event-notifications) +- [Scene Event Notifications](../basics/scenemanagement/scene-events.md#scene-event-notifications) - [In-Scene NetworkObjects](../basics/scenemanagement/inscene-placed-networkobjects.md) ### WorldPositionStays usage diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md index 1742f87805..500b3d4b7c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/networktime-ticks.md @@ -183,6 +183,6 @@ For games with short play sessions casting the time to float is safe or `TimeAsF > [!NOTE] > The properties of the `NetworkTimeSystem` should be left untouched on the server/host. Changing the values on the client is sufficient to change the behavior of the time system. -The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkTimeSystem.html) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance, buffer values can be increased for a player with a bad connection. +The way network time gets calculated can be configured in the `NetworkTimeSystem` if needed. Refer to the [API docs](xref:Unity.Netcode.NetworkTimeSystem) for information about the properties which can be modified. All properties can be safely adjusted at runtime. For instance, buffer values can be increased for a player with a bad connection. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md index 8a309cca24..134a78cb43 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/physics.md @@ -34,7 +34,7 @@ Since PhysX has no concept of local space, it can be difficult to synchronize tw ## Using AttachableBehaviour or Joint -The implementation of physics in a networked project differs from a single player project. This is especially true when you're using NetworkTransform and NetworkRigidbody components with [`NetworkRigidbody.UseRigidBodyForMotion`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkRigidbodyBase.html#Unity_Netcode_Components_NetworkRigidbodyBase_UseRigidBodyForMotion) enabled. Deciding whether to use a Joint or an AttachableBehaviour component depends on your project's requirements. +The implementation of physics in a networked project differs from a single player project. This is especially true when you're using NetworkTransform and NetworkRigidbody components with [`NetworkRigidbody.UseRigidBodyForMotion`](xref:Unity.Netcode.Components.NetworkRigidbodyBase.UseRigidBodyForMotion) enabled. Deciding whether to use a Joint or an AttachableBehaviour component depends on your project's requirements. For example, if you want to create world items that players can pick up, you may have the following requirements: diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md index 2733a0e373..ab60e161c6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializable.md @@ -1,7 +1,7 @@ # Customize serializable types with INetworkSerializable > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before customizing serializable types with `INetworkSerializable`. You can use the `INetworkSerializable` interface to define custom serializable types. This interface has one function: `NetworkSerialize(BufferSerializer serializer)`, which ingests a bi-directional [`BufferSerializer`](../bufferserializer.md) that you can use to implement bi-directional custom serialization. @@ -23,7 +23,7 @@ struct SpawnPoint : INetworkSerializable } ``` -Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](./fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). +Types implementing `INetworkSerializable` are supported by [`FastBufferReader` and `FastBufferWriter`](../fastbufferwriter-fastbufferreader.md), [`RPC`s'](../message-system/rpc.md), and [`NetworkVariable`s](../../basics/networkvariable.md). ```csharp [Rpc(SendTo.Server)] diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md index 212bfbf339..b79df9ff27 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/inetworkserializebymemcpy.md @@ -1,7 +1,7 @@ # Serialize unmanaged structs with INetworkSerializeByMemcpy > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before using `INetworkSerializeByMemcpy` to serialize unmanaged structs. The `INetworkSerializeByMemcpy` interface is used to mark an unmanaged struct type as being trivially serializable over the network by directly copying the whole struct, byte-for-byte, as it appears in memory, into and out of the buffer. This can offer some benefits for performance compared to serializing one field at a time, especially if the struct has many fields in it, but it may be less efficient from a bandwidth-usage perspective, as fields will often be padded for memory alignment and you won't be able to "pack" any of the fields to optimize for space usage. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md index 466d545b4b..d3800a7b09 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/networkobject-serialization.md @@ -1,7 +1,7 @@ # NetworkObject and NetworkBehaviour serialization > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before learning how to serialize `NetworkObjects` and `NetworkBehaviours`. `GameObject`, [`NetworkObject`](../../components/core/networkobject.md) and [`NetworkBehaviour`](../../components/core/networkbehaviour.md) aren't serializable types so they can't be used in [`RPC`s](../message-system/rpc.md) or [`NetworkVariable`s](../../basics/networkvariable.md) by default. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md index 5681e5952c..3ecc26cb99 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-arrays.md @@ -1,7 +1,7 @@ # Arrays and native containers > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand the basics of serialization before learning how to serialize arrays and native containers. +> Read the [Serialization overview](./serialization-overview.md) page to understand the basics of serialization before learning how to serialize arrays and native containers. Netcode for GameObjects has built-in serialization code for arrays of [C# value-type primitives](cprimitives.md), like `int[]`, and [Unity primitive types](unity-primitives.md). Any arrays of types that aren't handled by the built-in serialization code, such as custom types, need to be handled using a container class or structure that implements the [`INetworkSerializable`](inetworkserializable.md) interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md index fb52e42773..ad7fcd4d21 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/serialization/serialization-overview.md @@ -10,7 +10,7 @@ When Netcode for GameObjects first receives a type, it checks for any custom typ By default, any type that satisfies the unmanaged generic constraint can be automatically serialized as RPC parameters. This includes all basic types (bool, byte, int, float, enum, for example), as well as any structs that contain only these basic types. -Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](fastbufferwriter-fastbufferreader.md). These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. +Serialization and deserialization is done via the structs [`FastBufferWriter` and `FastBufferReader`](../fastbufferwriter-fastbufferreader.md). These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. `FastBufferWriter` and `FastBufferReader` also contain the functions `FastBufferWriter.WriteNetworkSerializable()` and `FastBufferReader.ReadNetworkSerializable` for writing and reading values that use the `INetworkSerializable` interface. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md index 1f80a08d1a..614d2ce32b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/session-management.md @@ -19,7 +19,7 @@ You can also decide to clear all data when a session completes or add a timeout # Reconnection -The best way to reconnect players depends on your game. For example, if you use a [Player Object](../components/core/networkobject.md#player-objects), a new `Default Player Prefab` automatically spawns when a player connects to the game (including when they reconnect). You can use the player's earlier saved session data to update that object so that it returns to the same state before disconnecting. In those cases, you would need to keep all the important data that you want to restore and map it to the player using your identification system. You can save this data when a player disconnects or update it periodically. You can then use the `OnNetworkSpawn` event on the Player Object's `NetworkBehavior`(s) to get this data and apply it where needed. +The best way to reconnect players depends on your game. For example, if you use a [Player Object](../components/core/playerobjects.md), a new `Default Player Prefab` automatically spawns when a player connects to the game (including when they reconnect). You can use the player's earlier saved session data to update that object so that it returns to the same state before disconnecting. In those cases, you would need to keep all the important data that you want to restore and map it to the player using your identification system. You can save this data when a player disconnects or update it periodically. You can then use the `OnNetworkSpawn` event on the Player Object's `NetworkBehavior`(s) to get this data and apply it where needed. In cases where we don't use the Player Object approach and instead manually attribute client ownership to NetworkObject(s), we can keep the objects that a player owns when they disconnect, and set the reconnected player as their new owner. To accomplish this, the only data we would need to keep would be the mapping between those objects and their owning player's identifier, then when a player reconnects we can use this mapping to set them as the new owner. This mapping can be as simple as a dictionary mapping the player identifier with the `NetworkObjectId`(s) of the NetworkObject(s) they own. Then, in the `OnClientConnectedCallback` from the NetworkManager, the server can set the ownership of these objects. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md index e4f45a5ead..b0d54e88be 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/singleplayer.md @@ -1,6 +1,6 @@ # Single player sessions -Netcode for GameObjects provides a [SinglePlayerTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport.html) which derives from [NetworkTransport](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.13/api/Unity.Netcode.NetworkTransport.html). +Netcode for GameObjects provides a [SinglePlayerTransport](xref:Unity.Netcode.Transports.SinglePlayer.SinglePlayerTransport) which derives from [NetworkTransport](xref:Unity.Netcode.NetworkTransport). This provides the ability to run a hosted session using the single player transport without having to modify your primary netcode script. diff --git a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md index 1939d8a0ea..783f0f633c 100644 --- a/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md +++ b/com.unity.netcode.gameobjects/Documentation~/advanced-topics/transports.md @@ -16,7 +16,7 @@ A transport layer can provide: ## Unity Transport package -Netcode's default transport Unity Transport is an entire transport layer that you can use to add multiplayer and network features to your project with or without Netcode. See the Transport [documentation](../../../transport/current/about) for more information and how to [install](../../../transport/current/install). +Netcode's default transport Unity Transport is an entire transport layer that you can use to add multiplayer and network features to your project with or without Netcode. Refer to the Transport [documentation](https://docs.unity3d.com/Packages/com.unity.transport@latest) for more information and how to [install](https://docs.unity3d.com/Packages/com.unity.transport@latest?subfolder=/manual/install.html). ## Unity's UNet Transport Layer API diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md index de160d26d2..7c6dd428a7 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/custom-networkvariables.md @@ -3,7 +3,7 @@ In addition to the standard [`NetworkVariable`s](networkvariable.md) available in Netcode for GameObjects, you can also create custom `NetworkVariable`s for advanced implementations. The `NetworkVariable` and `NetworkList` classes were created as `NetworkVariableBase` class implementation examples. While the `NetworkVariable` class is considered production ready, you might run into scenarios where you have a more advanced implementation in mind. In this case, you can create your own custom implementation. > [!NOTE] -> Read the [Serialization overview](./serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. +> Read the [Serialization overview](../advanced-topics/serialization/serialization-overview.md) page to understand how Netcode for GameObjects handles serialization. To create your own `NetworkVariableBase`-derived container, you should: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md index 7fdd95ee4d..b72213f32e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/object-spawning.md @@ -30,7 +30,7 @@ When using a [server authoritative networking model](../terms-concepts/authority To spawn a network prefab, you must first create an instance of the network prefab and then invoke the spawn method on the NetworkObject component of the instance you created. In most cases, you will want to keep the NetworkObject component attached to the root GameObject of the network prefab. -Refer to [NetworkObject ownership](../advanced-topics/networkobject-ownership.md) for more information. +Refer to [NetworkObject ownership](../components/core/networkobject-ownership.md) for more information. The following is a basic example of how to spawn a network prefab instance: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md index dff54d2da4..ffb1cde762 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/custom-management.md @@ -39,7 +39,7 @@ Once you've registered your in-scene placed Network Prefabs with your `NetworkPr > When a client first connects, it deletes any in-scene placed `NetworkObjects` in any of the scenes it has currently loaded. When using a custom scene management solution, in-scene placed NetworkObjects are actually dynamically spawned. This means any changes you make to your in-scene placed Network Prefabs will *not* be synchronized with clients automatically. ### Synchronizing In-Scene Placed Network Prefab Instances -If you want to change an in-scene placed network prefab instance, you need to handle the serialization of these settings yourself. You can do this by overriding `NetworkBehaviour.OnSynchronize` and serializing any property updates you want to have synchronized with clients when they join. [Read More About OnSynchronize Here](../../components/core/networkbehaviour.md#prespawn-synchronization). +If you want to change an in-scene placed network prefab instance, you need to handle the serialization of these settings yourself. You can do this by overriding `NetworkBehaviour.OnSynchronize` and serializing any property updates you want to have synchronized with clients when they join. [Read More About OnSynchronize Here](../../components/core/networkbehaviour-synchronize.md#prespawn-synchronization-with-onsynchronize). ## Starting a Netcode Enabled Game Session The recommended way of starting session using your own scene management solution is to assure that when a client attempts to join a netcode game session it should already have (as best as possible) any scenes that the server might have loaded. While this does not assure that your newly connecting client will load any additional scenes that might have been loaded, using this approach initially will get you started so you can then come up with a strategy to handling: diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md index 6387ea33da..617a8f5f36 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/inscene-placed-networkobjects.md @@ -232,7 +232,7 @@ The above example keeps track of whether the in-scene placed NetworkObject has s ### Synchronizing late-joining clients when an in-scene placed NetworkObject has been despawned and destroyed -Referring back to the [section on complex in-scene NetworkObjects](#complex-in-scene-networkobjects), it's recommended to use dynamically spawned NetworkObjects if you intend to destroy the object when it's despawned. However, if either despawning but not destroying or using the [hybrid approach](#a-hybrid-approach-example) don't appear to be options for your project's needs, then there are two other possible (but not recommended) alternatives: +Referring back to the [section on complex in-scene NetworkObjects](#complex-in-scene-networkobjects), it's recommended to use dynamically spawned NetworkObjects if you intend to destroy the object when it's despawned. However, if either despawning but not destroying or using the [hybrid approach](#hybrid-approach) don't appear to be options for your project's needs, then there are two other possible (but not recommended) alternatives: - Have another in-scene placed NetworkObject track which in-scene placed NetworkObjects have been destroyed and upon a player late-joining (that is, `OnClientConnected`) you would need to send the newly-joined client the list of in-scene placed NetworkObjects that it should destroy. This adds an additional in-scene placed NetworkObject to your scene hierarchy and will consume memory keeping track of what was destroyed. - Disable the visual and physics-related components (in Editor as a default) of the in-scene placed NetworkObject(s) in question and only enable them in `OnNetworkSpawn`. This doesn't delete/remove the in-scene placed NetworkObject(s) for the late-joining client and can be tricky to implement without running into edge case scenario bugs. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md index 817affd33f..c01ffa1df5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/scene-events.md @@ -240,7 +240,7 @@ So, the big "take-away" from the above table is that you need to understand the ### SceneEventType Specific Notifications There might be a time where you aren't interested in all of the details for each scene event type that occurs. As it just so happens, `NetworkSceneManager` includes a single delegate handler for each `SceneEventType` that is only triggered for the associated `SceneEventType`. -You can explore the [NetworkSceneManager](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventType.html) for a full listing of the corresponding single `SceneEventType` events. +You can explore the [NetworkSceneManager](xref:Unity.Netcode.SceneEventType) for a full listing of the corresponding single `SceneEventType` events. Some examples: - NetworkSceneManager.OnLoad: Triggered when for `OnLoad` scene events. - NetworkSceneManager.OnUnload: Triggered when for `OnUnload` scene events. diff --git a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md index 5837249132..ac54460414 100644 --- a/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/basics/scenemanagement/using-networkscenemanager.md @@ -19,7 +19,7 @@ > [!NOTE] > In-Scene placed NetworkObjects can be used in many ways and are treated uniquely from that of dynamically spawned NetworkObjects. An in-scene placed NetworkObject is a GameObject with a NetworkObject and typically at least one NetworkBehaviour component attached to a child of or the same GameObject. it's recommended to read through all integrated scene management materials (this document, [Scene Events](scene-events.md), and [Timing Considerations](timing-considerations.md)) before learning about more advanced [In-Scene (placed) NetworkObjects](inscene-placed-networkobjects.md) topics. -All of these scene management features (and more) are handled by the [`NetworkSceneManager`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkSceneManager.html). +All of these scene management features (and more) are handled by the [`NetworkSceneManager`](xref:Unity.Netcode.NetworkSceneManager). ### Accessing `NetworkSceneManager` The `NetworkSceneManager` lives within the NetworkManager and is instantiated when the NetworkManager is started. @@ -122,7 +122,7 @@ The purpose behind the above outline is to show that a Scene Event can lead to o #### Scene Event Notifications You can be notified of scene events by registering in one of two ways: 1. Receive all scene event notification types: `NetworkSceneManager.OnSceneEvent` -2. Receive only a specific scene event notification type: [`NetworkSceneManager`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkSceneManager.html#events) has one for each [`SceneEventType`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventType.html)
+2. Receive only a specific scene event notification type: [`NetworkSceneManager`](xref:Unity.Netcode.NetworkSceneManager) has one for each [`SceneEventType`](xref:Unity.Netcode.SceneEventType)
> [!NOTE] > Receiving (via subscribing to the associated event callback) only specific scene event notification types does not change how a server or client receives and processes notifications. @@ -137,7 +137,7 @@ Typically, this is used with clients or components that might only need to be no You might want to register for the `SceneEventType.LoadEventCompleted` scene event type to know, from a client perspective, that the server and all other clients have finished loading a scene. This notification lets you know when you can start performing other netcode related actions on the newly loaded and spawned NetworkObjects. #### Scene Event Progress Status -As we discussed in the earlier code example, it's important to check the status returned by `NetworkSceneManager.Load` to make sure your scene loading event has started. The following is a list of all [SceneEventProgressStatus](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.SceneEventProgressStatus.html) `enum` values with some additional helpful information: +As we discussed in the earlier code example, it's important to check the status returned by `NetworkSceneManager.Load` to make sure your scene loading event has started. The following is a list of all [SceneEventProgressStatus](xref:Unity.Netcode.SceneEventProgressStatus) `enum` values with some additional helpful information: - Started - The scene event has started (success) - SceneNotLoaded diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md index 8c4c49d2d8..b07a4113cf 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/corecomponents.md @@ -20,7 +20,7 @@ Learn about the three core components of Netcode for GameObjects: NetworkObject, | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](networkbehaviour.md)** | [NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. | +| **[NetworkBehaviour](networkbehaviour.md)** | [NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. | | **[Synchronizing](networkbehaviour-synchronize.md)** | Understand a NetworkBehaviour component's order of operations when it comes to spawning, despawning, and adding custom synchronization data. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md index 3511fd44f8..fee658cd12 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-ownership.md @@ -1,25 +1,25 @@ # NetworkBehaviour ownership -Before reading these docs, ensure you understand the concepts of [ownership](../terms-concepts/ownership.md) and [NetworkObject ownership](./networkobject-ownership.md). It's also important to be familiar with the [NetworkBehaviour](./networkbehaviour.md) +Before reading these docs, ensure you understand the concepts of [ownership](../../terms-concepts/ownership.md) and [NetworkObject ownership](./networkobject-ownership.md). It's also important to be familiar with the [NetworkBehaviour](./networkbehaviour.md) -The owner of each NetworkBehaviour in your game is decided by the owner of that NetworkBehaviour's NetworkObject. The NetworkObject is found as a property on the NetworkBehaviour: [`NetworkBehaviour.NetworkObject`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_NetworkObject). +The owner of each NetworkBehaviour in your game is decided by the owner of that NetworkBehaviour's NetworkObject. The NetworkObject is found as a property on the NetworkBehaviour: [`NetworkBehaviour.NetworkObject`](xref:Unity.Netcode.NetworkBehaviour.NetworkObject). ## Helpful properties > [!NOTE] -> The following properties are only valid if the NetworkBehaviour has been spawned. Use [`NetworkBehaviour.IsSpawned`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsSpawned) to check the spawned status of the NetworkBehaviour +> The following properties are only valid if the NetworkBehaviour has been spawned. Use [`NetworkBehaviour.IsSpawned`](xref:Unity.Netcode.NetworkBehaviour.IsSpawned) to check the spawned status of the NetworkBehaviour -To identify whether the local client is the owner of a NetworkBehaviour, you can check the[`NetworkBehaviour.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsOwner) property. +To identify whether the local client is the owner of a NetworkBehaviour, you can check the[`NetworkBehaviour.IsOwner`](xref:Unity.Netcode.NetworkBehaviour.IsOwner) property. -To identify whether the server owns a NetworkBehaviour, you can check the [`NetworkBehaviour.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_IsOwnedByServer) property. +To identify whether the server owns a NetworkBehaviour, you can check the [`NetworkBehaviour.IsOwnedByServer`](xref:Unity.Netcode.NetworkBehaviour.IsOwnedByServer) property. -To identify whether the local client has authority of a NetworkBehaviour, you can check the[`NetworkBehaviour.HasAuthority`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_HasAuthority) property. +To identify whether the local client has authority of a NetworkBehaviour, you can check the[`NetworkBehaviour.HasAuthority`](xref:Unity.Netcode.NetworkBehaviour.HasAuthority) property. ## Detecting ownership changes There are three functions that can be implemented to detect ownership changes on a NetworkBehaviour. These functions are invoked in the order they are listed here. -### [OnLostOwnership](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnLostOwnership) +### [OnLostOwnership](xref:Unity.Netcode.NetworkBehaviour.OnLostOwnership) When using a [client-server network topology](../../terms-concepts/client-server.md) `OnLostOwnership` is invoked on both the server any time a connected client loses ownership of this NetworkBehaviour. It is also invoked on the game client who just lost ownership. @@ -34,7 +34,7 @@ void OnLostOwnership() } ``` -### [OnGainedOwnership](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnGainedOwnership) +### [OnGainedOwnership](xref:Unity.Netcode.NetworkBehaviour.OnGainedOwnership) When using a client-server network topology `OnGainedOwnership` is invoked on the server any time ownership is gained. It is also be invoked on the game client who just gained ownership. @@ -52,7 +52,7 @@ void OnGainedOwnership() } ``` -### [OnOwnershipChanged](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html#Unity_Netcode_NetworkBehaviour_OnOwnershipChanged_System_UInt64_System_UInt64_) +### [OnOwnershipChanged](xref:Unity.Netcode.NetworkBehaviour.OnOwnershipChanged*) Whenever you want notification on any and all ownership changes, implement the `OnOwnershipChanged` method. `OnOwnershipChanged` is invoked on all connected game clients whenever the ownership of the NetworkBehaviour it is implemented on changes. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md index 166b69c4bc..e273296ea5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour-synchronize.md @@ -1,6 +1,6 @@ # NetworkBehaviour synchronization -[NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. +[NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. @@ -238,4 +238,4 @@ However, there is an additional check to assure that the total expected bytes to ## Serializing NetworkBehaviours -NetworkBehaviours require the use of specialized [`NetworkBehaviourReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviourReference.html) structures to be serialized and used with RPCs and `NetworkVariable`s. +NetworkBehaviours require the use of specialized [`NetworkBehaviourReference`](xref:Unity.Netcode.NetworkBehaviourReference) structures to be serialized and used with RPCs and `NetworkVariable`s. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md index 0922a6d35a..4b1f7e0b02 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkbehaviour.md @@ -1,6 +1,6 @@ # NetworkBehaviour -[NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. +[NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](networkobject.md) component and at least one NetworkBehaviour component. A NetworkBehaviour requires a NetworkObject component on the same relative GameObject or on a parent of the GameObject with the NetworkBehaviour component assigned to it. If you add a NetworkBehaviour to a GameObject that doesn't have a NetworkObject (or any parent), then Netcode for GameObjects automatically adds a NetworkObject component to the GameObject in which the NetworkBehaviour was added. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md index aa59d87d89..2a487b20a6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md @@ -5,7 +5,7 @@ The NetworkManager is a required Netcode for GameObjects component that has all ## NetworkManager Inspector properties - **LogLevel**: Sets the network logging level -- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object. For more information about player prefabs, refer to [Player NetworkObjects](networkobject.md#player-networkobjects). +- **PlayerPrefab**: When a Prefab is assigned, the Prefab will be instantiated as the player object. For more information about player prefabs, refer to [Player NetworkObjects](playerobjects.md). - **NetworkPrefabs**: Where you register your network prefabs. You can also create a single network Prefab override per registered network Prefab here. - **Protocol Version**: Set this value to help distinguish between builds when the most current build has new assets that can cause issues with older builds connecting. - **Network Transport**: Where your network specific settings and transport type is set. When using a [client-server topology](../../terms-concepts/client-server.md), This field accepts any INetworkTransport implementation. However, unless you have unique transport specific needs UnityTransport is the recommended transport to use with Netcode for GameObjects. For a [distributed authority topology](../../terms-concepts/distributed-authority.md), refer to the [distributed authority quickstart](../../learn/distributed-authority-quick-start.md). @@ -68,7 +68,7 @@ NetworkManager.Singleton.StartClient(); When starting a Server or joining an already started session as client, the NetworkManager can spawn a "Player Object" belonging to the client. For more information about player prefabs, refer to: - [NetworkObject Player Prefab Documentation](networkobject.md) - - [Connection Approval](../../basics/connection-approval) + - [Connection Approval](../../basics/connection-approval.md) ## Connecting @@ -135,7 +135,7 @@ The server-host attempts to wait for all client connections to close before it f At times you might need to disconnect a client for various reasons without shutting down the server. To do this, you can call the `NetworkManager.DisconnectClient` method while passing the identifier of the client you wish to disconnect as the only parameter. The client identifier can be found within: -- The `NetworkManager.ConnectedClients` dictionary that uses the client identifier as a key and the value as the [`NetworkClient`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkClient.html). +- The `NetworkManager.ConnectedClients` dictionary that uses the client identifier as a key and the value as the [`NetworkClient`](xref:Unity.Netcode.NetworkClient). - As a read only list of `NetworkClients` via the `NetworkManager.ConnectedClientsList`. - A full list of all connected client identifiers can be accessed via `NetworkManager.ConnectedClientsIds`. - The client identifier is passed as a parameter to all subscribers of the `NetworkManager.OnClientConnected` event. @@ -261,16 +261,16 @@ public class ConnectionNotificationManager : MonoBehaviour There are two static NetworkManager events you can use to be notified when a NetworkManager is instantiated or is about to be destroyed: -- [`NetworkManager.OnInstantiated`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnInstantiated): This is invoked when a NetworkManager is instantiated. -- [`NetworkManager.OnDestroying`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnDestroying): This is invoked when a NetworkManager is about to be destroyed. +- [`NetworkManager.OnInstantiated`](xref:Unity.Netcode.NetworkManager.OnInstantiated): This is invoked when a NetworkManager is instantiated. +- [`NetworkManager.OnDestroying`](xref:Unity.Netcode.NetworkManager.OnDestroying): This is invoked when a NetworkManager is about to be destroyed. ### When a NetworkManager is stopped Knowing when a NetworkManager has stopped is useful for establishing when it's safe to transition back to a main menu scene, or other similar tasks. There are two events you can use to be notified that the NetworkManager has finished shutting down: -- [`NetworkManager.OnClientStopped`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnClientStopped): This is invoked on a host or client when the NetworkManager has completely shut down and is ready to be restarted. -- [`NetworkManager.OnServerStopped`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnServerStopped): This is invoked on a host or server when the NetworkManager has completely shut down and is ready to be restarted. +- [`NetworkManager.OnClientStopped`](xref:Unity.Netcode.NetworkManager.OnClientStopped): This is invoked on a host or client when the NetworkManager has completely shut down and is ready to be restarted. +- [`NetworkManager.OnServerStopped`](xref:Unity.Netcode.NetworkManager.OnServerStopped): This is invoked on a host or server when the NetworkManager has completely shut down and is ready to be restarted. Since a host is both a client and a server, the event invocation order is: @@ -283,4 +283,4 @@ Since a host is both a client and a server, the event invocation order is: If you need to save the state of spawned objects before they're destroyed when the NetworkManager shuts down, you can use the following event notification: -- [`NetworkManager.OnPreShutdown`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnPreShutdown): This is invoked prior to finalizing the NetworkManager shut down process. Any remaining spawned objects will still be instantiated and spawned when this event is invoked. +- [`NetworkManager.OnPreShutdown`](xref:Unity.Netcode.NetworkManager.OnPreShutdown): This is invoked prior to finalizing the NetworkManager shut down process. Any remaining spawned objects will still be instantiated and spawned when this event is invoked. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md index 81bad2dfff..3ad5d5235b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md @@ -9,11 +9,11 @@ Read more about how to detect when ownership has changed in [NetworkBehaviour ow ## Helpful properties > [!NOTE] -> All NetworkObject properties are only valid while the NetworkObject is spawned. Use [`NetworkObject.IsSpawned`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsSpawned) to check the spawned status of the NetworkObject. +> All NetworkObject properties are only valid while the NetworkObject is spawned. Use [`NetworkObject.IsSpawned`](xref:Unity.Netcode.NetworkObject.IsSpawned) to check the spawned status of the NetworkObject. -To identify whether the local client is the owner of a NetworkObject, you can check the[`NetworkObject.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.IsOwner.html) or the [`NetworkBehaviour.IsOwner`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwner.html) property. +To identify whether the local client is the owner of a NetworkObject, you can check the[`NetworkObject.IsOwner`](xref:Unity.Netcode.NetworkObject.IsOwner) or the [`NetworkBehaviour.IsOwner`](xref:Unity.Netcode.NetworkBehaviour.IsOwner) property. -To identify whether the server owns a NetworkObject, you can check the [`NetworkObject.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.IsOwnedByServer.html) or the [`NetworkBehaviour.IsOwnedByServer`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.IsOwnedByServer.html) property. +To identify whether the server owns a NetworkObject, you can check the [`NetworkObject.IsOwnedByServer`](xref:Unity.Netcode.NetworkObject.IsOwnedByServer) or the [`NetworkBehaviour.IsOwnedByServer`](xref:Unity.Netcode.NetworkBehaviour.IsOwnedByServer) property. > [!NOTE] > To assure a spawned NetworkObject persists after the owner leaves a session, set the `NetworkObject.DontDestroyWithOwner` property to true. This assures the client-owned NetworkObject doesn't get destroyed when the owning client leaves. @@ -39,7 +39,7 @@ NetworkObject.SpawnWithOwnership(clientId); > [!NOTE] > Using `SpawnWithOwnership` can result in unexpected behavior when the spawning game client makes any other changes on the object immediately after spawning. -Using `SpawnWithOwnership` and then editing the NetworkObject locally means that the client doing the spawning will behave as the spawn authority. The spawn authority has limited local [authority](../terms-concepts/authority.md) over the NetworkObject, but not [ownership](../terms-concepts/ownership.md) of the NetworkObject that's spawned. This means any owner-specific checks during the spawn sequence will not be invoked on the spawn authority side. +Using `SpawnWithOwnership` and then editing the NetworkObject locally means that the client doing the spawning will behave as the spawn authority. The spawn authority has limited local [authority](../../terms-concepts/authority.md) over the NetworkObject, but not [ownership](../../terms-concepts/ownership.md) of the NetworkObject that's spawned. This means any owner-specific checks during the spawn sequence will not be invoked on the spawn authority side. If you want to spawn a NetworkObject for another client and then immediately make adjustments to that NetworkObject, it's recommended to use the `Spawn` method. After adjusting, the spawn authority can immediately follow with a call to `ChangeOwnership`. @@ -111,15 +111,15 @@ The authority of any NetworkObject can always change ownership, as outlined in [ ### Ownership permission settings -The following ownership permission settings, defined by [`NetworkObject.OwnershipStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipStatus.html), control how ownership of NetworkObjects can be changed during a distributed authority session: +The following ownership permission settings, defined by [`NetworkObject.OwnershipStatus`](xref:Unity.Netcode.NetworkObject.OwnershipStatus), control how ownership of NetworkObjects can be changed during a distributed authority session: |**Ownership setting**|Description|Related Property|Multi-select| |-----|-----|-----|-----| |`None`|Ownership of this NetworkObject can't be redistributed, requested, or transferred (a Player might have this, for example).||No| -|`Distributable`|Ownership of this NetworkObject is automatically redistributed when a client joins or leaves, as long as ownership is not locked or a request is pending.|[`IsOwnershipDistributable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipDistributable)|**Yes**| -|`Transferable`|Any client can change ownership of this NetworkObject at any time, as long as ownership is not locked or a request is pending.|[`IsOwnershipTransferable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipTransferable)|**Yes**| -|`RequestRequired`|Ownership of this NetworkObject must be requested before ownership can be changed.|[`IsOwnershipRequestRequired`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipRequestRequired), [`IsRequestInProgress`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsRequestInProgress)|**Yes**| -|`SessionOwner`|This NetworkObject is always owned by the [session owner](distributed-authority.md#session-ownership) and can't be transferred or distributed. If the session owner changes, this NetworkObject is automatically transferred to the new session owner.|[`IsOwnershipSessionOwner`](Unity_Netcode_NetworkObject_IsOwnershipSessionOwner)|No| +|`Distributable`|Ownership of this NetworkObject is automatically redistributed when a client joins or leaves, as long as ownership is not locked or a request is pending.|[`IsOwnershipDistributable`](xref:Unity.Netcode.NetworkObject.IsOwnershipDistributable)|**Yes**| +|`Transferable`|Any client can change ownership of this NetworkObject at any time, as long as ownership is not locked or a request is pending.|[`IsOwnershipTransferable`](xref:Unity.Netcode.NetworkObject.IsOwnershipTransferable)|**Yes**| +|`RequestRequired`|Ownership of this NetworkObject must be requested before ownership can be changed.|[`IsOwnershipRequestRequired`](xref:Unity.Netcode.NetworkObject.IsOwnershipRequestRequired), [`IsRequestInProgress`](xref:Unity.Netcode.NetworkObject.IsRequestInProgress)|**Yes**| +|`SessionOwner`|This NetworkObject is always owned by the [session owner](../../terms-concepts/distributed-authority.md#session-ownership) and can't be transferred or distributed. If the session owner changes, this NetworkObject is automatically transferred to the new session owner.|[`IsOwnershipSessionOwner`](xref:Unity.Netcode.NetworkObject.IsOwnershipSessionOwner)|No| Ownership permissions can be set in the editor using the **Ownership** dropdown. @@ -127,7 +127,7 @@ Ownership permissions can be set in the editor using the **Ownership** dropdown. They can also be set in script using -You can also use `NetworkObject.SetOwnershipLock` to lock and unlock the permission settings of a NetworkObject for a period of time, preventing ownership changes on a temporary basis. The [`IsOwnershipLocked`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_IsOwnershipLocked) property can be used to detect if an object has locked ownership. +You can also use `NetworkObject.SetOwnershipLock` to lock and unlock the permission settings of a NetworkObject for a period of time, preventing ownership changes on a temporary basis. The [`IsOwnershipLocked`](xref:Unity.Netcode.NetworkObject.IsOwnershipLocked) property can be used to detect if an object has locked ownership. ```csharp // To lock an object from any ownership changes @@ -148,7 +148,7 @@ NetworkObject.ChangeOwnership(clientId); NetworkObject.ChangeOwnership(NetworkManager.LocalClientId); ``` -When a non-authoritative game client calls `ChangeOwnership`, the ownership change can fail. On a failed attempt to change ownership, the `OnOwnershipPermissionsFailure` callback will be invoked with a [`NetworkObject.OwnershipPermissionsFailureStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus.html) to give information on the failure. +When a non-authoritative game client calls `ChangeOwnership`, the ownership change can fail. On a failed attempt to change ownership, the `OnOwnershipPermissionsFailure` callback will be invoked with a [`NetworkObject.OwnershipPermissionsFailureStatus`](xref:Unity.Netcode.NetworkObject.OwnershipPermissionsFailureStatus) to give information on the failure. ```csharp /* @@ -200,9 +200,9 @@ When a NetworkObject is set with `OwnershipPermissions.RequestRequired` any clie var requestStatus = NetworkObject.RequestOwnership(); ``` -`RequestOwnership` returns an [`OwnershipRequestStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipRequestStatus.html) to indicate the initial status of the request. To view the result of the request, the `OnOwnershipRequestResponse` callback will be invoked with a [`NetworkObject.OwnershipRequestResponseStatus`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus.html). +`RequestOwnership` returns an [`OwnershipRequestStatus`](xref:Unity.Netcode.NetworkObject.OwnershipRequestStatus) to indicate the initial status of the request. To view the result of the request, the `OnOwnershipRequestResponse` callback will be invoked with a [`NetworkObject.OwnershipRequestResponseStatus`](xref:Unity.Netcode.NetworkObject.OwnershipRequestResponseStatus). -By default, any requests for ownership will automatically be approved. To control which client is approved for ownership, use the [`OnOwnershipRequested`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_OnOwnershipRequested) callback +By default, any requests for ownership will automatically be approved. To control which client is approved for ownership, use the [`OnOwnershipRequested`](xref:Unity.Netcode.NetworkObject.OnOwnershipRequested) callback ```csharp /* @@ -264,8 +264,8 @@ public class RequestableOwnershipBehaviour : NetworkBehaviour ## Additional resources -- [NetworkObject](../components/core/networkobject.md) +- [NetworkObject](./networkobject.md) - [NetworkBehaviour](./networkbehaviour.md) - [NetworkBehaviour ownership](./networkbehaviour-ownership.md) -- [Ownership](../terms-concepts/ownership.md) -- [Authority](../terms-concepts/authority.md) +- [Ownership](../../terms-concepts/ownership.md) +- [Authority](../../terms-concepts/authority.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md index e190aa201d..495bed2691 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject.md @@ -7,7 +7,7 @@ Netcode for GameObjects' high level components, [the RPC system](../../advanced- 1. NetworkObject 2. [NetworkBehaviour](networkbehaviour.md) -NetworkObjects require the use of specialized [`NetworkObjectReference`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObjectReference.html) structures before you can serialize and use them with RPCs and `NetworkVariable`s +NetworkObjects require the use of specialized [`NetworkObjectReference`](xref:Unity.Netcode.NetworkObjectReference) structures before you can serialize and use them with RPCs and `NetworkVariable`s Netcode for GameObjects also has [PlayerObjects](playerobjects.md), an optional feature that you can use to assign a NetworkObject to a specific client. @@ -91,7 +91,7 @@ Refer to the [NetworkSceneManager active scene synchronization](../../basics/sce ![image](../../images/SceneMigrationSynchronization.png) -Similar to [`NetworkObject.ActiveSceneSynchronization`](#active-scene-synchronization), [`NetworkObject.SceneMigrationSynchronization`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkObject.html#Unity_Netcode_NetworkObject_SceneMigrationSynchronization) automatically synchronizes client-side NetworkObject instances that are migrated to a scene via [`SceneManager.MoveGameObjectToScene`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html) on the host or server side. This can be useful if you have a specific scene you wish to migrate NetworkObject instances to that is not the currently active scene. +Similar to [`NetworkObject.ActiveSceneSynchronization`](#active-scene-synchronization), [`NetworkObject.SceneMigrationSynchronization`](xref:Unity.Netcode.NetworkObject.SceneMigrationSynchronization) automatically synchronizes client-side NetworkObject instances that are migrated to a scene via [`SceneManager.MoveGameObjectToScene`](https://docs.unity3d.com/ScriptReference/SceneManagement.SceneManager.MoveGameObjectToScene.html) on the host or server side. This can be useful if you have a specific scene you wish to migrate NetworkObject instances to that is not the currently active scene. `NetworkObject.ActiveSceneSynchronization` can be used with `NetworkObject.SceneMigrationSynchronization` as long as you take into consideration that if you migrate a NetworkObject into a non-active scene via `SceneManager.MoveGameObjectToScene` and later change the active scene, then the NetworkObject instance will be automatically migrated to the newly set active scene. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md index e7ae88353f..a679806c44 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/playerobjects.md @@ -53,14 +53,14 @@ In addition to the [session-mode agnostic spawning methods](#session-mode-agnost ### Distributed authority contexts only -In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_OnFetchLocalPlayerPrefabToSpawn) method to assign a unique player prefab on a per-client basis when in [distributed authority contexts](../../terms-concepts/distributed-authority.md). +In addition to the [session-mode agnostic spawning methods](#session-mode-agnostic-methods) above, you can use the [`OnFetchLocalPlayerPrefabToSpawn`](xref:Unity.Netcode.NetworkManager.OnFetchLocalPlayerPrefabToSpawn) method to assign a unique player prefab on a per-client basis when in [distributed authority contexts](../../terms-concepts/distributed-authority.md). To use `OnFetchLocalPlayerPrefabToSpawn` in your project, assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn` and whatever the client script returns is what will be spawned for that client. Ensure that the prefab being spawned is in a NetworkPrefabList [registered with the NetworkManager](../../basics/object-spawning.md#registering-a-network-prefab). If you don't assign a callback handler to `OnFetchLocalPlayerPrefabToSpawn`, then the default behavior is to return the `NetworkConfig.PlayerPrefab` (or null if neither are set). :::note `AutoSpawnPlayerPrefabClientSide` required -For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_AutoSpawnPlayerPrefabClientSide) must be enabled. +For `OnFetchLocalPlayerPrefabToSpawn` to work, [`AutoSpawnPlayerPrefabClientSide`](xref:Unity.Netcode.NetworkManager.AutoSpawnPlayerPrefabClientSide) must be enabled. ::: ## PlayerObject spawning timeline diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md index 26465b9d6e..09136b095e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/componentcontroller.md @@ -1,6 +1,6 @@ # ComponentController -Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. +Use a [ComponentController](xref:Unity.Netcode.ComponentController) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. For example, you can use a ComponentController to enable or disable a MeshRenderer component on the owner of the ComponentController instance, while disabling it for all other clients. This is useful for controlling visibility of objects that should only be visible to the owner, such as a player's avatar or a weapon they are holding. diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md index 64df3f347b..d9515ebb47 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/helpercomponents.md @@ -6,8 +6,8 @@ Understand the helper components available to use in your Netcode for GameObject | :------------------------------ | :------------------------------- | | **[AttachableBehaviour](attachablebehaviour.md)**| Use the AttachableBehaviour component to manage [ComponentController](componentcontroller.md) components and to attach a child GameObject to an [AttachableNode](attachablenode.md). The AttachableBehaviour component provides an alternative to NetworkObject parenting, allowing you to attach and detach child objects dynamically during runtime. | | **[AttachableNode](attachablenode.md)**| Use an AttachableNode component to provide an attachment point for an [AttachableBehaviour](attachablebehaviour.md) component. | -| **[ComponentController](componentcontroller.md)**| Use a [ComponentController](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.ComponentController.html) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. | +| **[ComponentController](componentcontroller.md)**| Use a [ComponentController](xref:Unity.Netcode.ComponentController) component to enable or disable one or more components depending on the authority state of the ComponentController and have those changes synchronized with non-authority instances. | | **[NetworkAnimator](networkanimator.md)**| The NetworkAnimator component provides you with a fundamental example of how to synchronize animations during a network session. Animation states are synchronized with players joining an existing network session and any client already connected before the animation state changing. | | **[NetworkRigidbody](networkrigidbody.md)**| NetworkRigidbody is a component that sets the Rigidbody of the GameObject into kinematic mode on all non-authoritative instances. | -| **[NetworkTransform](networktransform.md)**| [NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | +| **[NetworkTransform](networktransform.md)**| [NetworkTransform](xref:Unity.Netcode.Components.NetworkTransform) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. | | **[Physics](../../advanced-topics/physics.md)**| Netcode for GameObjects has a built in approach which allows for server-authoritative physics where the physics simulation only runs on the server. | diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md index 8f412f7898..b931d59afb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networkrigidbody.md @@ -12,7 +12,7 @@ When looking at a NetworkRigidbody in the Inspector view, there are three expose - When enabled and using a [NetworkTransform](networktransform.md), the NetworkTransform uses the PhysX position and rotation to synchronize changes during the `FixedUpdate` loop update stage. - __Auto Update Kinematic State__ - When enabled, NetworkRigidbody automatically determines whether the current instance should be kinematic or non-kinematic. - - For custom solutions, you can opt to disable this field or derive from [NetworkRigidbodyBase](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.5/api/Unity.Netcode.Components.NetworkRigidbodyBase.html) and design your own custom networked Rigidbody handler. + - For custom solutions, you can opt to disable this field or derive from [NetworkRigidbodyBase](xref:Unity.Netcode.Components.NetworkRigidbodyBase) and design your own custom networked Rigidbody handler. - __Auto Set Kinematic On Despawn__ - When enabled, this option makes the rigid body kinematic when despawned (which can be useful for [object pools](../../advanced-topics/object-pooling.md)). @@ -25,7 +25,7 @@ Some collision events aren't fired when using NetworkRigidBody: - You can adjust the __Contact Pairs Mode__ to use kinematic and non-kinematic by setting it to __Enable All Contact Pairs__.![image](../../images/networktransform/ProjectPhysicsSettings2.png) > [!NOTE] -> If there's a need for a gameplay event to happen on a collision, you can listen to the `OnCollisionEnter` function on the server and synchronize the event via `Rpc(SendTo.Everyone)` to all clients. If you plan on handling many collisions, then it's recommended to use the [RigidbodyContactEventManager component](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.RigidbodyContactEventManager.html) to handle collision checking during a job (`OnCollisionenter` can become expensive from a processing perspective if you have enough instances colliding). +> If there's a need for a gameplay event to happen on a collision, you can listen to the `OnCollisionEnter` function on the server and synchronize the event via `Rpc(SendTo.Everyone)` to all clients. If you plan on handling many collisions, then it's recommended to use the [RigidbodyContactEventManager component](xref:Unity.Netcode.Components.RigidbodyContactEventManager) to handle collision checking during a job (`OnCollisionenter` can become expensive from a processing perspective if you have enough instances colliding). ### NetworkRigidbody2D diff --git a/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md index b77c930647..8db6f66114 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/helper/networktransform.md @@ -1,6 +1,6 @@ # NetworkTransform -[NetworkTransform](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Components.NetworkTransform.html) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. +[NetworkTransform](xref:Unity.Netcode.Components.NetworkTransform) is a concrete class that inherits from [NetworkBehaviour](../core/networkbehaviour.md) and synchronizes [Transform](https://docs.unity3d.com/Manual/class-Transform.html) properties across the network, ensuring that the position, rotation, and scale of a [GameObject](https://docs.unity3d.com/Manual/working-with-gameobjects.html) are replicated to other clients. The synchronization of a GameObject's Transform is a key netcode task, and usually proceeds in the following order: @@ -39,7 +39,7 @@ Theoretically, you can have a NetworkTransform on every child object of a 100 le When nesting NetworkTransforms, you should first determine if there are any alternative approaches to handling child object motion (such as synchronizing through animations or using an algorithm that uses the synchronized network time) to ensure you're not consuming unnecessary processing time and bandwidth. -For example, if you use a [NetworkAnimator](networkanimator.md) component to synchronize animations (instead of nested NetworkTransforms), then you don't need to manage synchronizing each child node of the model because the animation system and NetworkAnimator handle it for you. Similarly, if you want a child object to orbit its parent object, you can use the server time ([`NetworkManager.ServerTime`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?sufolder=/api/Unity.Netcode.NetworkManager.html#Unity_Netcode_NetworkManager_ServerTime)) to feed into an algorithm that's used on all instances (local and remote). This removes the need to synchronize motion between child and parent because the algorithm is driven by a value that's already being synchronized on all clients. +For example, if you use a [NetworkAnimator](networkanimator.md) component to synchronize animations (instead of nested NetworkTransforms), then you don't need to manage synchronizing each child node of the model because the animation system and NetworkAnimator handle it for you. Similarly, if you want a child object to orbit its parent object, you can use the server time ([`NetworkManager.ServerTime`](xref:Unity.Netcode.NetworkManager.ServerTime)) to feed into an algorithm that's used on all instances (local and remote). This removes the need to synchronize motion between child and parent because the algorithm is driven by a value that's already being synchronized on all clients. > [!NOTE] > Generally, unless you have some unique motion that needs to be applied to a child object and it can't be synchronized through an animation or an algorithm based on a synchronized value, then you shouldn't nest NetworkTransforms. However, nesting NetworkTransforms is more optimal then creating multiple individual single-node network prefabs with NetworkTransforms and parenting those once they're spawned. @@ -58,13 +58,13 @@ Some NetworkTransform properties are automatically synchronized by the authorita The following are a list of NetworkTransform properties that will cause a full state update (effectively a teleport) when changed during runtime by the authority instance: -- [UseUnreliableDeltas](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseUnreliableDeltas) -- [InLocalSpace](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_InLocalSpace) -- [Interpolate](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_Interpolate) -- [SlerpPosition](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_SlerpPosition) -- [UseQuaternionSynchronization](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseQuaternionSynchronization) -- [UseQuaternionCompression](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseQuaternionCompression) -- [UseHalfFloatPrecision](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.3/api/Unity.Netcode.Components.NetworkTransform.html#Unity_Netcode_Components_NetworkTransform_UseHalfFloatPrecision) +- [UseUnreliableDeltas](xref:Unity.Netcode.Components.NetworkTransform.UseUnreliableDeltas) +- [InLocalSpace](xref:Unity.Netcode.Components.NetworkTransform.InLocalSpace) +- [Interpolate](xref:Unity.Netcode.Components.NetworkTransform.Interpolate) +- [SlerpPosition](xref:Unity.Netcode.Components.NetworkTransform.SlerpPosition) +- [UseQuaternionSynchronization](xref:Unity.Netcode.Components.NetworkTransform.UseQuaternionSynchronization) +- [UseQuaternionCompression](xref:Unity.Netcode.Components.NetworkTransform.UseQuaternionCompression) +- [UseHalfFloatPrecision](xref:Unity.Netcode.Components.NetworkTransform.UseHalfFloatPrecision) The following NetworkTransform properties can cause a full state update when changed during runtime by the authority instance: diff --git a/com.unity.netcode.gameobjects/Documentation~/install.md b/com.unity.netcode.gameobjects/Documentation~/install.md index 9fb1b54ef6..c3e5b597ec 100644 --- a/com.unity.netcode.gameobjects/Documentation~/install.md +++ b/com.unity.netcode.gameobjects/Documentation~/install.md @@ -7,7 +7,7 @@ Follow the instructions on this page to set up Netcode for GameObjects in your U Before you begin, you need the following: - An active Unity account with a valid license. -- A supported version of Unity. Check [Netcode for GameObjects' requirements](#netcode-installation-requirements) for the specific version details. +- A supported version of Unity. Check [Netcode for GameObjects' requirements](#compatibility) for the specific version details. - An existing Unity project. If you're new to Unity, you can refer to the [get started](./tutorials/get-started-with-ngo.md) section for guidance. ### Compatibility diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md index a14cc6d0da..e1c5b056ca 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/dealing-with-latency.md @@ -52,7 +52,7 @@ Note here an input from a client can be anything, from a user interacting with a #### Issue: Reactivity -An issue with server authority is you're waiting for your server to tell you to update your world. This means that if you send an input to the server and wait for the server to tell you your position change, you'll need to wait for a full **RTT** before you see the effect. There are [patterns](#patterns-to-solve-these-issues) you can use to solve this issue while still remaining server authoritative. +An issue with server authority is you're waiting for your server to tell you to update your world. This means that if you send an input to the server and wait for the server to tell you your position change, you'll need to wait for a full **RTT** before you see the effect. There are [patterns](#patterns-to-solve-latency-issues-in-a-server-authoritative-game) you can use to solve this issue while still remaining server authoritative. ### Client authority @@ -82,7 +82,7 @@ To avoid this, it's recommended to use client **owner** authority, which allows Client authority is a pretty dangerous door to leave open on your server because any malicious player can forge messages to say "kill player a, b, c, d, e, f, g" and win the game. It's pretty useful though for reactivity. Since the client is making all the important gameplay decisions, it can display the result of user inputs as soon as they happen instead of waiting a few hundred milliseconds. -When you don't think there's any reason for your players to cheat, client authority can be a great way to have reactivity without the complexity added with techniques like [input prediction](#prediction). +When you don't think there's any reason for your players to cheat, client authority can be a great way to have reactivity without the complexity added with techniques like [input prediction](#client-side-prediction). Another way of solving this issue in a client authoritative game is using soft validation server side. Instead of doing all simulation server side, the server only does basic validation. The server would, for example, do range checks to make sure a player isn't teleporting to places it shouldn't. Doing so is acceptable for most [PvE](https://en.wikipedia.org/wiki/Player_versus_environment) games. However, [PvP](https://en.wikipedia.org/wiki/Player_versus_player) games usually require server authority. @@ -157,7 +157,7 @@ To do continuous client driven actions, there's a few more considerations to tak - You then need to make sure you don't send RPCs to the server (containing your authoritative state) when no data has changed and do dirty checks. - You'd need to send it on tick or at worst on FixedUpdate. Sending on Update() would spam your connection. -A sample for a [ClientNetworkTransform](../components/helper/networktransform.md#clientnetworktransform) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). +A sample for a [ClientNetworkTransform](../components/helper/networktransform.md#client-vs-server-authority) has been created, so you don't have to reimplement this yourself for transform updates. A [sample](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/tree/main/Basic/ClientDriven) has been created on how to use it. See [movement script](https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize/blob/v1.2.1/Basic/ClientDriven/Assets/Scripts/ClientPlayerMove.cs). > [!NOTE] > A rule of thumb here is to ask yourself: "Can the server correct me on this?". If it can, use server authority. diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md b/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md index e32e3858c8..d85764142b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/listenserverhostarchitecture.md @@ -33,7 +33,7 @@ In contrast to dedicated servers, listen servers are cheaper without the need to ## Connecting to a listen server -Personal computers are hidden behind NATs (Network Address Translation devices) and routers to protect them from direct access. To connect to a listen server, you may choose an option such as [port forwarding](#port-forwarding), a [relay server](#relay-server), [NAT punch-through](#nat-punchthrough), or a [NAT punch with relay fallback](#nat-punch-and-relay-fallback). +Personal computers are hidden behind NATs (Network Address Translation devices) and routers to protect them from direct access. To connect to a listen server, you may choose an option such as [port forwarding](#port-forwarding), a [relay server](#relay-server), [NAT punch-through](#nat-punch-through), or a [NAT punch with relay fallback](#nat-punch-and-relay-fallback). ### Port Forwarding diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md b/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md index 0713cbab28..3d31914ddb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/rpcvnetvar.md @@ -1,7 +1,7 @@ # RPC vs NetworkVariable Choosing the wrong data syncing mechanism can create bugs, use too much bandwidth, and add too much complexity to your code. -Netcode for GameObjects (Netcode) has two main ways of syncing information between players: RPCs ([Remote Procedure Calls](../advanced-topics/messaging-system.md)) and replicated states [(`NetworkVariable`s)](../basics/networkvariable). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other. +Netcode for GameObjects (Netcode) has two main ways of syncing information between players: RPCs ([Remote Procedure Calls](../advanced-topics/messaging-system.md)) and replicated states [(`NetworkVariable`s)](../basics/networkvariable.md). They both send messages over the network. The logic and your design around how they send messages is what will make you choose one over the other. ## Choosing between `NetworkVariable`s or RPCs diff --git a/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md b/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md index 2ab45e73c2..dac2bc523a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md +++ b/com.unity.netcode.gameobjects/Documentation~/learn/ticks-and-update-rates.md @@ -16,7 +16,7 @@ When a server fails to process all incoming client data before the end of the cu Update rate is a measure of how frequently the client sends and receives data to and from the server. It's also measured in hertz and, like tick rate, a higher update rate results in a more responsive game at the cost of increased processing and network demands on the client and server. -![](../../images/update-rates-light.png) +![](../images/update-rates-light.png) In addition to adding to perceived latency, low update rates can cause their own issues, such as multiple updates being bundled together and arriving at the same time, causing undesirable behavior. @@ -24,7 +24,7 @@ In the example of 'super bullets': if an in-game gun is capable of shooting more The diagram below illustrates the difference between a 10 Hz update rate and a 60 Hz update rate. With a 10 Hz update rate, the game sends updates to the server ten times a second, so a gun that can fire 600 rounds per minute (RPM), or ten times a second, will only ever send a single bullet per update. A gun that fires at 750 RPM, more than ten times a second, will end up running into the super bullet problem. A higher update rate of 60 Hz resolves this issue, ensuring that there are enough updates for each bullet to arrive separately in its own update. -![](../../images/rpm_update_rates-light.png) +![](../images/rpm_update_rates-light.png) ### Discrepancy between tick rate and update rate diff --git a/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md b/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md index ee35801235..8d58a3366d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md +++ b/com.unity.netcode.gameobjects/Documentation~/migratingfromUNet.md @@ -115,7 +115,7 @@ public class MyNetcodeExample : NetworkBehaviour } ``` -Refer to [NetworkBehaviour](basics/networkbehaviour.md) for more information. +Refer to [NetworkBehaviour](components/core/networkbehaviour.md) for more information. ## Replace SyncVar @@ -343,7 +343,7 @@ Refer to [Object Spawning](basics/object-spawning.md) for more information. ## Custom Spawn Handlers -Netcode has `Custom Spawn Handlers` to replace UNet's `Custom Spawn Functions`. See [Object Pooling](../advanced-topics/object-pooling) for more information. +Netcode has `Custom Spawn Handlers` to replace UNet's `Custom Spawn Functions`. See [Object Pooling](advanced-topics/object-pooling.md) for more information. ## Replace `NetworkContextProperties` diff --git a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md index c690759906..8d1cbeac17 100644 --- a/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/networkbehaviour-landing.md @@ -5,5 +5,5 @@ Understand how to use NetworkBehaviour components in your project. | **Topic** | **Description** | | :------------------------------ | :------------------------------- | -| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [NetworkBehaviour](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.NetworkBehaviour.html) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/core/networkobject.md) component and at least one NetworkBehaviour component. | +| **[NetworkBehaviour](components/core/networkbehaviour.md)** | [NetworkBehaviour](xref:Unity.Netcode.NetworkBehaviour) is an abstract class that derives from [MonoBehaviour](https://docs.unity3d.com/ScriptReference/MonoBehaviour.html) and is primarily used to create unique netcode or game logic. To replicate any netcode-aware properties or send and receive RPCs, a [GameObject](https://docs.unity3d.com/Manual/GameObjects.html) must have a [NetworkObject](components/core/networkobject.md) component and at least one NetworkBehaviour component. | | **[Synchronize](components/core/networkbehaviour-synchronize.md)** | You can use NetworkBehaviours to synchronize settings before, during, and after spawning NetworkObjects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md b/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md index e9eadb4428..224e9f2f47 100644 --- a/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md +++ b/com.unity.netcode.gameobjects/Documentation~/removed-tableofcontents.md @@ -10,7 +10,7 @@ * [Bitesize introduction](samples/bitesize/bitesize-introduction.md) * [Bitesize space shooter](samples/bitesize/bitesize-spaceshooter.md) * [Bitesize client driven](samples/bitesize/bitesize-clientdriven.md) - * [Bitesize dynamic prefabs](samples/bitesize/bitesize-dynamicPrefabs.md) + * [Bitesize dynamic prefabs](samples/bitesize/bitesize-dynamicprefabs.md) * [Bitesize social hub](samples/bitesize/bitesize-socialhub.md) --> \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md index 880fbb821e..1009a7343e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-landing.md @@ -8,5 +8,5 @@ The Bite Size samples are small, focused examples that demonstrate specific feat | **[Bitesize introduction](bitesize-introduction.md)** | The Bitesize Samples repository provides a series of sample code as modules to use in your games and better understand Netcode for GameObjects. | | **[Bitesize space shooter](bitesize-spaceshooter.md)** | Learn more about physics movement and status effects using Netcode for GameObjects (Netcode) NetworkVariables and ObjectPooling. | | **[Bitesize client driven](bitesize-clientdriven.md)** | Learn more about Client driven movements, networked physics, spawning vs statically placed objects, object reparenting. | -| **[Bitesize dynamic prefabs](bitesize-dynamicPrefabs.md)** | Learn more about the dynamic Prefab system, which allows you to add new spawnable Prefabs at runtime. | +| **[Bitesize dynamic prefabs](bitesize-dynamicprefabs.md)** | Learn more about the dynamic Prefab system, which allows you to add new spawnable Prefabs at runtime. | | **[Bitesize social hub](bitesize-socialhub.md)** | The Distributed Authority Social Hub Sample is a project that demonstrates Distributed Authority's features and helps you integrate Distributed Authority into your own game projects. | \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md index ebdbfc5705..a41c3d1238 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bitesize/bitesize-usecases.md @@ -16,11 +16,11 @@ The tutorials that open with each scene use the [Tutorial Framework package](htt ## The Anticipation scene The Anticipation scene demonstrates the Client Anticipation feature of Netcode for GameObjects in the following use cases: -- [`AnticipatedNetworkVariable`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.2/api/Unity.Netcode.AnticipatedNetworkVariable-1.html): +- [`AnticipatedNetworkVariable`](xref:Unity.Netcode.AnticipatedNetworkVariable%601): - Anticipate server actions based on player interaction to change NetworkVariables responsively. - Compensate for latency that the server causes when it changes a value. - Handle incorrect anticipation. -- [`AnticipatedNetworkTransform`](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.2/api/Unity.Netcode.Components.AnticipatedNetworkTransform.html): +- [`AnticipatedNetworkTransform`](xref:Unity.Netcode.Components.AnticipatedNetworkTransform): - Responsive server-authoritative player movement. - Smooth player movement across clients. diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md index 096d7f72db..214a8fdae5 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/getting-started-boss-room.md @@ -25,8 +25,8 @@ Join the multiplayer community on the Unity [Discord](https://discord.gg/mNgM2XR ### Contents and quick links - [Boss Room Overview](#boss-room-overview) -- [Install the Boss Room project](#getting-the-project) - - [Install Git LFS to clone locally](#installing-git-lfs-to-clone-locally) +- [Install the Boss Room project](#install-the-boss-room-project) + - [Install Git LFS to clone locally](#install-git-lfs-to-clone-locally) - [Direct download](#direct-download) - [Registering the project with Unity Gaming Services (UGS)](#registering-the-project-with-unity-gaming-services-ugs) - [Opening the project for the first time](#opening-the-project-for-the-first-time) diff --git a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md index 234830e4c0..ddde94a432 100644 --- a/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md +++ b/com.unity.netcode.gameobjects/Documentation~/samples/bossroom/optimizing-bossroom.md @@ -73,19 +73,19 @@ The Boss Room sample shows how to adapt some Unity Transport properties to fit i ### Disconnect Timeout {#disconnect-timeout} -The [Disconnect Timeout property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_DisconnectTimeoutMS) controls how long the server (and the clients) wait before disconnecting. The Boss Room sample uses a Disconnect Timeout value of 10 seconds to prevent the server and the clients from hanging onto a connection for too long. +The [Disconnect Timeout property](xref:Unity.Netcode.Transports.UTP.UnityTransport.DisconnectTimeoutMS) controls how long the server (and the clients) wait before disconnecting. The Boss Room sample uses a Disconnect Timeout value of 10 seconds to prevent the server and the clients from hanging onto a connection for too long. ### Max Connect Attempts {#max-connect-attempts} -The [Max Connect Attempts property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_MaxConnectAttempts) controls the times a client tries to connect before declaring a connection failure. The Boss Room sample uses a Max Connect Attempts value of 10 to prevent clients from waiting too long before declaring a connection failure. +The [Max Connect Attempts property](xref:Unity.Netcode.Transports.UTP.UnityTransport.MaxConnectAttempts) controls the times a client tries to connect before declaring a connection failure. The Boss Room sample uses a Max Connect Attempts value of 10 to prevent clients from waiting too long before declaring a connection failure. ### Connect Timeout {#connect-timeout} -The [Connect Timeout property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_ConnectTimeoutMS) controls the number of times clients attempt to connect per second. The Boss Room sample uses a Connect Timeout value of 1 second, meaning that clients try to connect once per second. Having the Connect Timeout set to 1 second and the Max Connect Attempts set to 10 means that clients fail to connect after 10 seconds of waiting. +The [Connect Timeout property](xref:Unity.Netcode.Transports.UTP.UnityTransport.ConnectTimeoutMS) controls the number of times clients attempt to connect per second. The Boss Room sample uses a Connect Timeout value of 1 second, meaning that clients try to connect once per second. Having the Connect Timeout set to 1 second and the Max Connect Attempts set to 10 means that clients fail to connect after 10 seconds of waiting. ### Max Packet Queue Size {#max-packet-queue-size} -The [Max Packet Queue Size property](https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@latest?subfolder=/api/Unity.Netcode.Transports.UTP.UnityTransport.html#Unity_Netcode_Transports_UTP_UnityTransport_MaxPacketQueueSize) defines the maximum number of packets that can be sent and received during a single frame. +The [Max Packet Queue Size property](xref:Unity.Netcode.Transports.UTP.UnityTransport.MaxPacketQueueSize) defines the maximum number of packets that can be sent and received during a single frame. The impact of surpassing the Max Packet Queue Size threshold varies depending on the packet direction (sending or receiving). diff --git a/com.unity.netcode.gameobjects/Documentation~/serialization.md b/com.unity.netcode.gameobjects/Documentation~/serialization.md index 4dfd16f484..51aee596eb 100644 --- a/com.unity.netcode.gameobjects/Documentation~/serialization.md +++ b/com.unity.netcode.gameobjects/Documentation~/serialization.md @@ -14,4 +14,4 @@ Netcode for GameObjects has built-in serialization support for C# and Unity prim | **[Custom serialization](advanced-topics/custom-serialization.md)** | Create custom serialization types. | | **[NetworkObject serialization](advanced-topics/serialization/networkobject-serialization.md)** | `GameObjects`, `NetworkObjects` and NetworkBehaviour aren't serializable types so they can't be used in `RPCs` or `NetworkVariables` by default. There are two convenience wrappers which can be used to send a reference to a NetworkObject or a NetworkBehaviour over RPCs or `NetworkVariables`. | | **[FastBufferWriter and FastBufferReader](advanced-topics/fastbufferwriter-fastbufferreader.md)** | The serialization and deserialization is done via `FastBufferWriter` and `FastBufferReader`. These have methods for serializing individual types and methods for serializing packed numbers, but in particular provide a high-performance method called `WriteValue()/ReadValue()` (for Writers and Readers, respectively) that can extremely quickly write an entire unmanaged struct to a buffer. | -| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](serialization/inetworkserializable.md) types. | +| **[BufferSerializer](advanced-topics/bufferserializer.md)** | A bi-directional serializer primarily used for serializing within [`INetworkSerializable`](advanced-topics/serialization/inetworkserializable.md) types. | diff --git a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md index 74f49a39c8..17c1f8b77b 100644 --- a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md +++ b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/distributed-authority.md @@ -53,7 +53,7 @@ You can use this property to conditionally execute logic that should only run on ### Session owner NetworkObjects -For game systems that should always be [owned](./ownership.md) by the session owner, you can set the NetworkObject to have the `OwnershipStatus.SessionOwner` [ownership permission](../advanced-topics/networkobject-ownership.md). This ensures that the NetworkObject always belongs to the current session owner. If that session owner disconnects or leaves the game, the ownership of that NetworkObject will be automatically moved to the newly selected session owner. +For game systems that should always be [owned](./ownership.md) by the session owner, you can set the NetworkObject to have the `OwnershipStatus.SessionOwner` [ownership permission](../components/core/networkobject-ownership.md). This ensures that the NetworkObject always belongs to the current session owner. If that session owner disconnects or leaves the game, the ownership of that NetworkObject will be automatically moved to the newly selected session owner. ## NetworkObject distribution diff --git a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md index 3d47f69024..56ff42526e 100644 --- a/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/terms-concepts/ownership.md @@ -35,7 +35,7 @@ In [distributed authority](./distributed-authority.md) the owner of a NetworkObj ![diagram showing ownership precedes authority](../images/diagrams/distributedAuthorityOwnership.png) -When building your game you can use [ownership permissions](../advanced-topics/networkobject-ownership.md#ownership-permission-settings) to control how and when ownership of NetworkObjects can be transferred between clients. +When building your game you can use [ownership permissions](../components/core/networkobject-ownership.md#ownership-permission-settings) to control how and when ownership of NetworkObjects can be transferred between clients. NetworkObjects with the `OwnershipStatus.Distributable` permission will have their ownership automatically distributed between all connected game clients whenever a new client joins or an existing client leaves. This is the key mechanism in how the game simulation is distributed between clients in a distributed authority session. @@ -48,4 +48,4 @@ The `IsOwner` property, which is available on both NetworkObjects and NetworkBeh * [Authority](authority.md) * [Client-server](client-server.md) * [Distributed authority](distributed-authority.md) -* [NetworkObject ownership](../advanced-topics/networkobject-ownership.md) +* [NetworkObject ownership](../components/core/networkobject-ownership.md) diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md index 4a80b3fd1a..bbd635c497 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/get-started-with-ngo.md @@ -33,7 +33,7 @@ This section guides you through adding the essential components of a networked g - [A NetworkManager component](#create-the-networkmanager-component) - [A player object](#create-an-object-to-spawn-for-each-connected-player) -- [A scene](#add-your-scene-to-the-build) +- [A scene](#scene-management-and-the-scenes-in-build-list) ### Create the NetworkManager component @@ -68,7 +68,7 @@ First, create the NetworkManager component: ### Create an object to spawn for each connected player > [!NOTE] -> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/core/networkobject.md#finding-playerobjects). +> When you drop the prefab into the **PlayerPrefab** slot, you're telling the library that when a client connects to the game, it automatically spawns this prefab as the character for the connecting client. Netcode for GameObjects won't spawn a player object if you don't have any prefab set as the **PlayerPrefab**. Refer to [Player Objects](../components/core/playerobjects.md#finding-playerobjects). This section guides you through creating an object that spawns for each connected player. @@ -111,7 +111,7 @@ Netcode for GameObjects comes with an integrated scene management solution that Now that you have a **NetworkManager**, assigned a **PlayerPrefab**, and added your current scene to the scenes in build test, you can quickly verify everything is functioning/configured correctly via entering play mode in the Unity Editor. By starting a host, you are starting NetworkManager as both a server and a client at the same time. -You can test your Hello World project using the Unity Editor or a command-line helper. If you choose the latter, refer to [Create a command line helper](../tutorials/command-line-helper/). Otherwise, refer to the following instructions to test using the Unity Editor. Only the Plane appears on the server until the first client connects. Then, Netcode for GameObjects spawns a new Player prefab for each connected client; however, they overlap in the Game view. +You can test your Hello World project using the Unity Editor or a command-line helper. If you choose the latter, refer to [Create a command line helper](command-line-helper.md). Otherwise, refer to the following instructions to test using the Unity Editor. Only the Plane appears on the server until the first client connects. Then, Netcode for GameObjects spawns a new Player prefab for each connected client; however, they overlap in the Game view. 1. Select **Play** from the top of the Unity Editor to start the scene. @@ -119,7 +119,7 @@ You can test your Hello World project using the Unity Editor or a command-line h 2. Select **NetworkManager** from the **Hierarchy** list. -![](\img\get-started-ngo\ngo-2.png) +![](../images/get-started-ngo/ngo-2.png) 3. With **NetworkManager** selected (in the Hierarchy tab), select **Start Host** from the **Inspector** tab. Alternatively, you can use the in-game GUI buttons. @@ -239,7 +239,7 @@ The `HelloWorldManager.cs` script accomplishes this menu within the `StartButton As seen in the earlier code snippet, the `HelloWorldManager.cs` script also uses the NetworkManager's instance via its singleton to grab properties like the `IsClient`, `IsServer`, and `IsLocalClient`. The `IsClient` and `IsServer` properties dictate the established connection state. -The `HelloWorldManager.cs` script also introduces a new method called `SubmitNewPosition()` that the `HelloWorldPlayer` script uses to [create a simple RPC call](#add-simple-rpc-use). +The `HelloWorldManager.cs` script also introduces a new method called `SubmitNewPosition()` that the `HelloWorldPlayer` script uses to [create a simple RPC call](#adding-rpcs-remote-procedure-calls). ## Adding RPCs (Remote Procedure Calls) diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md index aadbec86b8..6f5bbcd88d 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/techniques_and_tricks_for_debugging_multiplayer_games.md @@ -10,7 +10,7 @@ Below is a list of practices and techniques that we use daily when working on th ### Use ParrelSync clone-based workflow during development. -[ParrelSync workflow](testing_locally.md#parrelsync) is faster than creating builds and it allows you to debug the separate editor instances via separate IDE debuggers. +[ParrelSync workflow](testing_with_artificial_conditions.md#clone-based-workflow-parrelsync) is faster than creating builds and it allows you to debug the separate editor instances via separate IDE debuggers. Use ParrelSync to run separate editor instances for your Host/Server and Client. @@ -22,7 +22,7 @@ Unity engine has two debug rendering APIs that are useful for the purposes of mu Both of these functions allow us to draw arbitrary debug lines that would be visible in the Scene view and in the Game view, provided Gizmo rendering is enabled (to enable Gizmo rendering in Game view you need to click on the `Gizmos` menu at the top of the Game view). -The key trick here is to use different colors for different kinds of information and to make the lines stay long enough for visual inspection by setting `duration` parameter. This technique shines when it's combined with [screen recordings](#7-recording-the-video-of-gameplay) of [multiple peers running side by side in separate editor instances via ParrelSync](#1-use-parrelsync-workflow-during-development). +The key trick here is to use different colors for different kinds of information and to make the lines stay long enough for visual inspection by setting `duration` parameter. This technique shines when it's combined with [screen recordings](#capturing-screen-recordings-of-the-game-instances) of [multiple peers running side by side in separate editor instances via ParrelSync](#use-parrelsync-clone-based-workflow-during-development). The code below would render a green debug line that's 2 units tall at the position of the transform, and this line would stay on screen for 4 seconds: `Debug.DrawLine(this.transform.position, this.transform.position + Vector3.UP * 2f, Color.green, duration: 4f);` diff --git a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md index f3c0646638..634f84288a 100644 --- a/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md +++ b/com.unity.netcode.gameobjects/Documentation~/tutorials/testing/testing_with_artificial_conditions.md @@ -11,11 +11,11 @@ This is challenging when we are [iterating on our game locally](testing_locally. Thankfully there are a number of tools that can simulate adverse network conditions. -For testing locally within the Editor, you can use the [Network Simulator tool](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) along with [clone-based workflow via ParrelSync](testing_locally.md#parrelsync). +For testing locally within the Editor, you can use the [Network Simulator tool](https://docs-multiplayer.unity3d.com/tools/current/tools-network-simulator/) along with [clone-based workflow via ParrelSync](#clone-based-workflow-parrelsync). For testing development builds with built-in artificial latency we suggest using [Network Simulator tools with some custom code to inject artificial conditions into the build](#debug-builds). -For testing release builds we suggest using [Clumsy](#clumsy-windows) if you're on Windows and Network Link Conditioner if you're on [macOS](#network-link-conditioner-mac-os) or [iOS](#network-link-conditioner-ios). A scriptable alternative to Network Link Conditioner on macOS is [dummynet](#dummynet-dnctl-and-pftcl-mac-os), which offers great control and comes packaged with the operating system. +For testing release builds we suggest using [Clumsy](#clumsy-windows) if you're on Windows and Network Link Conditioner if you're on [macOS](#network-link-conditioner-macos) or [iOS](#network-link-conditioner-ios). A scriptable alternative to Network Link Conditioner on macOS is [dummynet](#dummynet-dnctl-and-pftcl-macos), which offers great control and comes packaged with the operating system. > [!NOTE] > While artificial latency is great for simulating network conditions during development - it won't accurately emulate real world conditions. We recommend to test your game often on the targeted platforms and real live networking conditions. @@ -49,7 +49,7 @@ Adding packet loss, apart from introducing even more effective delay to our syst ### Different network conditions for different peers -[Clumsy](#clumsy-on-windows), [Network Link Conditioner](#network-link-conditioner-mac-os) and [dummynet](#dummynet-dnctl-and-pftcl-mac-os) are introducing changes on OS level, thus all the instances of the game that we open on our local machine would run under the same network conditions. +[Clumsy](#clumsy-windows), [Network Link Conditioner](#network-link-conditioner-macos) and [dummynet](#dummynet-dnctl-and-pftcl-macos) are introducing changes on OS level, thus all the instances of the game that we open on our local machine would run under the same network conditions. Don't forget to disable it once you're done debugging, else your network connection will feel slow! @@ -65,7 +65,7 @@ In this case we would want to have an ability to set artificial conditions on a > ParallelSync is **not** supported by Unity. More information on its usage is available [here](https://github.com/VeriorPies/ParrelSync). Troubleshooting information can be found [here](https://github.com/VeriorPies/ParrelSync/wiki/Troubleshooting-&-FAQs) -Simulator Tools effects only apply to editor instances and to [debug builds](#debug-builds), as such it matches well with [clone-based workflow via ParrelSync](testing_locally.md#parrelsync). +Simulator Tools effects only apply to editor instances and to [debug builds](#debug-builds), as such it matches well with [clone-based workflow via ParrelSync](#clone-based-workflow-parrelsync). Other tools should be used when testing release builds locally. diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs index 02fbe958e9..dd10eddc68 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/RpcTargets/RpcTarget.cs @@ -51,7 +51,7 @@ public enum SendTo ///
NotMe, /// - /// Send this RPC to everone, filtered to the current observer list. + /// Send this RPC to everyone, filtered to the current observer list. /// Will execute locally. /// Everyone, @@ -220,7 +220,7 @@ public void Dispose() public BaseRpcTarget NotMe; /// - /// Send this RPC to everone, filtered to the current observer list. + /// Send this RPC to everyone, filtered to the current observer list. /// Will execute locally. /// public BaseRpcTarget Everyone; From 31197062807a9df33f7467185407ae08762e2422 Mon Sep 17 00:00:00 2001 From: mnachury-unity <120488254+mnachury-unity@users.noreply.github.com> Date: Thu, 30 Jul 2026 19:04:11 -0400 Subject: [PATCH 40/64] chore: Add NGO_EXCLUDE_HEAVY_TESTS define (#4114) Add NGO_EXCLUDE_HEAVY_TESTS --- com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs index 7ee23412bf..1bb2238479 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Build/BuildTests.cs @@ -1,3 +1,4 @@ +#if !NGO_EXCLUDE_HEAVY_TESTS using System.IO; using System.Reflection; using NUnit.Framework; @@ -40,3 +41,4 @@ public void BasicBuildTest() } } } +#endif From c6475da5404aca878cab00c0e8f61c8d5ca18e5e Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Wed, 5 Aug 2026 12:35:33 -0500 Subject: [PATCH 41/64] chore: update permissions with note about Multiplayer Services (#4113) * update Adding note about permissions not displaying if the Multiplayer Services is not installed. * update Added a distributed authority sub-section under the NetworkManager Inspector properties with a description of the Multiplayer Services required Network Topology property. Added a similar note about network topology not being visible in NetworkManager if Multiplayer Services is not installed. * Update com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md Co-authored-by: Amy Reeve --------- Co-authored-by: Amy Reeve --- .../Documentation~/components/core/networkmanager.md | 9 +++++++++ .../components/core/networkobject-ownership.md | 3 +++ 2 files changed, 12 insertions(+) diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md index 2a487b20a6..7cd646a6ce 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkmanager.md @@ -19,6 +19,15 @@ The NetworkManager is a required Netcode for GameObjects component that has all - **Enable Scene Management**: When checked, Netcode for GameObjects will handle scene management and client synchronization for you. When not checked, you will have to create your own scene management scripts and handle client synchronization. - **Load Scene Time Out**: When Enable Scene Management is checked, this specifies the period of time the `NetworkSceneManager` will wait while a scene is being loaded asynchronously before `NetworkSceneManager` considers the load/unload scene event to have failed/timed out. +### Distributed authority network topology + +> [!NOTE] +> The distributed authority network topology requires Multiplayer Services. If the Multiplayer Services package is not installed, the **Network Topology** property will not be displayed. + +- **Network Topology**: Defines the network topology to use. + - **Client-Server**: Selects the client-server network topology. + - **Distributed authority**: Selects the distributed authority network topology. + ## NetworkManager sub-systems NetworkManager is also where you can find references to other Netcode related management systems:
diff --git a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md index 3ad5d5235b..f2975393e6 100644 --- a/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md +++ b/com.unity.netcode.gameobjects/Documentation~/components/core/networkobject-ownership.md @@ -111,6 +111,9 @@ The authority of any NetworkObject can always change ownership, as outlined in [ ### Ownership permission settings +> [!NOTE] +> Permissions are only valid when using the distributed authority network topology, which requires Multiplayer Services. If the Multiplayer Services package is not installed, the permissions setting will not be displayed. + The following ownership permission settings, defined by [`NetworkObject.OwnershipStatus`](xref:Unity.Netcode.NetworkObject.OwnershipStatus), control how ownership of NetworkObjects can be changed during a distributed authority session: |**Ownership setting**|Description|Related Property|Multi-select| From 64123a733c87de9394150eb933c49800ed02c734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Mon, 10 Aug 2026 09:54:59 +0200 Subject: [PATCH 42/64] ci: fixing issues around Standards check (#4097) * Update standars editor and messaging * Updated testproject to editor used by standards check * Unified package-lock approach with 3.x.x branch * Updated minimalproject to editor used by standards check * Updated Examples/PingTool to editor used by standards check * Updated Examples/POverridingScenesAndPrefabs to editor used by standards check * Updated Examples/CharacterControllerMovingBodies to editor used by standards check * Removed not needed mobile dependency resolver * missed removals * corrected jobs definition * missed one * Corrected test-framework package version to match Standards editor * corrected editor version used for standards --- .gitignore | 2 + .yamato/_run-all.yml | 4 +- .yamato/_triggers.yml | 4 +- .yamato/project-standards.yml | 5 +- .yamato/project.metafile | 3 + .../Packages/manifest.json | 18 +- .../Packages/packages-lock.json | 643 ----------------- .../ProjectSettings/ProjectVersion.txt | 4 +- .../Packages/manifest.json | 18 +- .../Packages/packages-lock.json | 643 ----------------- .../ProjectSettings/ProjectVersion.txt | 4 +- Examples/PingTool/Packages/manifest.json | 22 +- Examples/PingTool/Packages/packages-lock.json | 674 ------------------ .../ProjectSettings/ProjectVersion.txt | 4 +- .../VirtualProjectsConfig.json | 2 +- minimalproject/Packages/manifest.json | 2 +- minimalproject/Packages/packages-lock.json | 108 --- .../ProjectSettings/ProjectVersion.txt | 4 +- testproject/Packages/manifest.json | 2 +- testproject/Packages/packages-lock.json | 570 --------------- .../ProjectSettings/ProjectVersion.txt | 4 +- 21 files changed, 53 insertions(+), 2687 deletions(-) delete mode 100644 Examples/CharacterControllerMovingBodies/Packages/packages-lock.json delete mode 100644 Examples/OverridingScenesAndPrefabs/Packages/packages-lock.json delete mode 100644 Examples/PingTool/Packages/packages-lock.json delete mode 100644 minimalproject/Packages/packages-lock.json delete mode 100644 testproject/Packages/packages-lock.json diff --git a/.gitignore b/.gitignore index 1d3ee28bec..b38b538937 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,5 @@ utr.bat Tools/CI/bin Tools/CI/obj +# Do not include the packages-lock file +packages-lock.json diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index 24daf40665..5f9e320670 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -16,7 +16,7 @@ run_quick_checks: name: Run Quick Initial Checks dependencies: # Ensure the code is running to our current standards - - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.default }} + - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.standards }} # This is needed for most of the jobs to execute tests + it runs xray PVP checks (all fast checks) - .yamato/package-pack.yml#package_pack_-_ngo_win @@ -115,7 +115,7 @@ run_all_projects_standards: dependencies: {% for platform in test_platforms.default -%} {% for project in projects.all -%} -{% for editor in validation_editors.default -%} +{% for editor in validation_editors.standards -%} - .yamato/project-standards.yml#standards_{{ platform.name }}_{{ project.name }}_{{ editor }} {% endfor -%} {% endfor -%} diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index d70ba68fe9..3b292603b4 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -51,7 +51,7 @@ pr_minimal_required_checks: name: Minimal PR checks dependencies: - - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.default }} + - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.standards }} - .yamato/package-pack.yml#package_pack_-_ngo_win triggers: expression: |- @@ -130,7 +130,7 @@ develop_nightly: rerun: always dependencies: # Run project standards to verify package/default project - - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.default }} + - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.standards }} # Run APV jobs to make sure the change won't break any dependants - .yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs # Run package EditMode and Playmode tests on desktop platforms on trunk and 6000.0 diff --git a/.yamato/project-standards.yml b/.yamato/project-standards.yml index 24380a152e..2ebae81b5e 100644 --- a/.yamato/project-standards.yml +++ b/.yamato/project-standards.yml @@ -30,7 +30,7 @@ {% for project in projects.all -%} {% for platform in test_platforms.default -%} -{% for editor in validation_editors.default -%} +{% for editor in validation_editors.standards -%} standards_{{ platform.name }}_{{ project.name }}_{{ editor }}: name: Standards Check - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}] agent: @@ -48,8 +48,7 @@ standards_{{ platform.name }}_{{ project.name }}_{{ editor }}: - unity-downloader-cli --fast --wait -u {{ editor }} -c editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models. Downloads basic editor - .Editor/Unity -batchmode -nographics -logFile - -executeMethod Packages.Rider.Editor.RiderScriptEditor.SyncSolution -projectPath {{ project.path }} -quit # This command is used to invoke Unity in a "headless" mode. It's used to sync the project - dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --fix # Auto-fix formatting issues - - git checkout -- {{ project.path }}/Packages/manifest.json {{ project.path }}/Packages/packages-lock.json {{ project.path }}/ProjectSettings/ProjectVersion.txt 2>/dev/null || true # Restore files that Unity may have modified (we only want to check code formatting) - - 'git diff --exit-code || (echo "ERROR: Code formatting issues found. Run dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --fix locally and commit the changes." && exit 1)' # Fail if formatter made any changes + - 'git diff --exit-code || (echo "ERROR: Code formatting issues found. Run dotnet run --project=dotnet-tools/netcode.standards -- --project={{ project.path }} --fix locally and commit the changes. Note that dotnet version that was used in this check depends on the image that was used so local results may differ (see the first command of this job to know which dotnet version was used)" && exit 1)' # Fail if formatter made any changes {% endfor -%} {% endfor -%} {% endfor -%} \ No newline at end of file diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 13443d6451..9c52b691a8 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -176,6 +176,7 @@ test_platforms: # EDITOR CONFIGURATIONS------------------------------------------------------------------------------- # Editors to be used for testing. NGOv2.X official support started from 6000.0 editor # TODO: When a new editor will be released it should be added to this list +# standards editor is used for standards check so we won't get random manifest changes due to newer editor being used validation_editors: default: @@ -189,6 +190,8 @@ validation_editors: - 5fe7931aab8c4fff9274e15ef0800125c68b8d6a minimal: - 6000.0 + standards: + - 6000.0.72f1 pinnedTrunk: 5fe7931aab8c4fff9274e15ef0800125c68b8d6a diff --git a/Examples/CharacterControllerMovingBodies/Packages/manifest.json b/Examples/CharacterControllerMovingBodies/Packages/manifest.json index 0a8e3c4a4b..750ad11d6c 100644 --- a/Examples/CharacterControllerMovingBodies/Packages/manifest.json +++ b/Examples/CharacterControllerMovingBodies/Packages/manifest.json @@ -2,23 +2,23 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ads": "4.4.2", - "com.unity.ai.navigation": "2.0.9", + "com.unity.ads": "4.16.4", + "com.unity.ai.navigation": "2.0.12", "com.unity.analytics": "3.8.1", - "com.unity.collab-proxy": "2.10.0", + "com.unity.collab-proxy": "2.12.4", "com.unity.feature.development": "1.0.2", - "com.unity.ide.rider": "3.0.38", - "com.unity.ide.visualstudio": "2.0.25", + "com.unity.ide.rider": "3.0.39", + "com.unity.ide.visualstudio": "2.0.27", "com.unity.multiplayer.center": "1.0.0", "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "com.unity.purchasing": "4.12.2", + "com.unity.purchasing": "4.14.2", "com.unity.services.multiplayer": "1.2.0", "com.unity.test-framework": "1.6.0", - "com.unity.timeline": "1.8.9", + "com.unity.timeline": "1.8.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", - "com.unity.transport": "2.6.0", + "com.unity.transport": "2.7.2", "com.unity.ugui": "2.0.0", - "com.unity.visualscripting": "1.9.7", + "com.unity.visualscripting": "1.9.11", "com.unity.xr.legacyinputhelpers": "2.1.12", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", diff --git a/Examples/CharacterControllerMovingBodies/Packages/packages-lock.json b/Examples/CharacterControllerMovingBodies/Packages/packages-lock.json deleted file mode 100644 index c19504fc77..0000000000 --- a/Examples/CharacterControllerMovingBodies/Packages/packages-lock.json +++ /dev/null @@ -1,643 +0,0 @@ -{ - "dependencies": { - "com.unity.2d.sprite": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.2d.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.tilemap": "1.0.0", - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.ads": { - "version": "4.4.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ai.navigation": { - "version": "2.0.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.ai": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.analytics": { - "version": "3.8.1", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.analytics": "1.0.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.burst": { - "version": "1.8.25", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.collab-proxy": { - "version": "2.10.0", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.collections": { - "version": "2.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", - "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" - }, - "com.unity.editorcoroutines": { - "version": "1.0.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.ext.nunit": { - "version": "2.0.5", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.feature.development": { - "version": "1.0.2", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ide.visualstudio": "2.0.25", - "com.unity.ide.rider": "3.0.38", - "com.unity.editorcoroutines": "1.0.1", - "com.unity.performance.profile-analyzer": "1.2.4", - "com.unity.test-framework": "1.6.0", - "com.unity.testtools.codecoverage": "1.2.7" - } - }, - "com.unity.ide.rider": { - "version": "3.0.38", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ide.visualstudio": { - "version": "2.0.25", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.31" - }, - "url": "https://packages.unity.com" - }, - "com.unity.mathematics": { - "version": "1.3.2", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.multiplayer.center": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.netcode.gameobjects": { - "version": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "depth": 0, - "source": "git", - "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.6.0" - }, - "hash": "37bdf528127a9ae3e104d76b9a13343bffd653cb" - }, - "com.unity.nuget.mono-cecil": { - "version": "1.11.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.performance.profile-analyzer": { - "version": "1.2.4", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.purchasing": { - "version": "4.12.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.5", - "com.unity.modules.androidjni": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.analytics": { - "version": "6.1.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.4", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.authentication": { - "version": "3.5.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.15.1", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.core": { - "version": "1.15.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.modules.androidjni": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment": { - "version": "1.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.15.1", - "com.unity.services.deployment.api": "1.1.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment.api": { - "version": "1.1.2", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.services.multiplayer": { - "version": "1.2.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.transport": "2.5.0", - "com.unity.collections": "2.2.1", - "com.unity.services.qos": "1.3.0", - "com.unity.services.core": "1.15.1", - "com.unity.services.wire": "1.4.0", - "com.unity.services.deployment": "1.6.2", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "3.5.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.qos": { - "version": "1.3.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.services.core": "1.12.4", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "2.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.wire": { - "version": "1.4.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.12.5", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.authentication": "2.7.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.settings-manager": { - "version": "2.1.0", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot": { - "version": "2.0.10", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.9", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10" - }, - "url": "https://packages.unity.com" - }, - "com.unity.test-framework": { - "version": "1.6.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ext.nunit": "2.0.3", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.test-framework.performance": { - "version": "3.2.0", - "depth": 2, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.33", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.testtools.codecoverage": { - "version": "1.2.7", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.0.16", - "com.unity.settings-manager": "1.0.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.timeline": { - "version": "1.8.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.10", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10", - "com.unity.sysroot.linux-x86_64": "2.0.9" - }, - "url": "https://packages.unity.com" - }, - "com.unity.transport": { - "version": "2.6.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ugui": { - "version": "2.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0" - } - }, - "com.unity.visualscripting": { - "version": "1.9.7", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.xr.legacyinputhelpers": { - "version": "2.1.12", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.xr": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.modules.accessibility": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.ai": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.androidjni": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.animation": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.assetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.audio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.cloth": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.director": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.animation": "1.0.0" - } - }, - "com.unity.modules.hierarchycore": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imageconversion": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imgui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.jsonserialize": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.particlesystem": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics2d": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.screencapture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.subsystems": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.terrain": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.terrainphysics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.terrain": "1.0.0" - } - }, - "com.unity.modules.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics2d": "1.0.0" - } - }, - "com.unity.modules.ui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.uielements": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0" - } - }, - "com.unity.modules.umbra": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unityanalytics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.unitywebrequest": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unitywebrequestassetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestaudio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.audio": "1.0.0" - } - }, - "com.unity.modules.unitywebrequesttexture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestwww": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.vehicles": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.video": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.vr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.xr": "1.0.0" - } - }, - "com.unity.modules.wind": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.xr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.subsystems": "1.0.0" - } - } - } -} diff --git a/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectVersion.txt b/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectVersion.txt index 47df254537..ba577c5bf5 100644 --- a/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectVersion.txt +++ b/Examples/CharacterControllerMovingBodies/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.61f1 -m_EditorVersionWithRevision: 6000.0.61f1 (74a0adb02c31) +m_EditorVersion: 6000.0.72f1 +m_EditorVersionWithRevision: 6000.0.72f1 (b731fd3ae857) diff --git a/Examples/OverridingScenesAndPrefabs/Packages/manifest.json b/Examples/OverridingScenesAndPrefabs/Packages/manifest.json index 0a8e3c4a4b..750ad11d6c 100644 --- a/Examples/OverridingScenesAndPrefabs/Packages/manifest.json +++ b/Examples/OverridingScenesAndPrefabs/Packages/manifest.json @@ -2,23 +2,23 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ads": "4.4.2", - "com.unity.ai.navigation": "2.0.9", + "com.unity.ads": "4.16.4", + "com.unity.ai.navigation": "2.0.12", "com.unity.analytics": "3.8.1", - "com.unity.collab-proxy": "2.10.0", + "com.unity.collab-proxy": "2.12.4", "com.unity.feature.development": "1.0.2", - "com.unity.ide.rider": "3.0.38", - "com.unity.ide.visualstudio": "2.0.25", + "com.unity.ide.rider": "3.0.39", + "com.unity.ide.visualstudio": "2.0.27", "com.unity.multiplayer.center": "1.0.0", "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "com.unity.purchasing": "4.12.2", + "com.unity.purchasing": "4.14.2", "com.unity.services.multiplayer": "1.2.0", "com.unity.test-framework": "1.6.0", - "com.unity.timeline": "1.8.9", + "com.unity.timeline": "1.8.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", - "com.unity.transport": "2.6.0", + "com.unity.transport": "2.7.2", "com.unity.ugui": "2.0.0", - "com.unity.visualscripting": "1.9.7", + "com.unity.visualscripting": "1.9.11", "com.unity.xr.legacyinputhelpers": "2.1.12", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", diff --git a/Examples/OverridingScenesAndPrefabs/Packages/packages-lock.json b/Examples/OverridingScenesAndPrefabs/Packages/packages-lock.json deleted file mode 100644 index c19504fc77..0000000000 --- a/Examples/OverridingScenesAndPrefabs/Packages/packages-lock.json +++ /dev/null @@ -1,643 +0,0 @@ -{ - "dependencies": { - "com.unity.2d.sprite": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.2d.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.tilemap": "1.0.0", - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.ads": { - "version": "4.4.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ai.navigation": { - "version": "2.0.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.ai": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.analytics": { - "version": "3.8.1", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.analytics": "1.0.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.burst": { - "version": "1.8.25", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.collab-proxy": { - "version": "2.10.0", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.collections": { - "version": "2.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", - "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" - }, - "com.unity.editorcoroutines": { - "version": "1.0.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.ext.nunit": { - "version": "2.0.5", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.feature.development": { - "version": "1.0.2", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ide.visualstudio": "2.0.25", - "com.unity.ide.rider": "3.0.38", - "com.unity.editorcoroutines": "1.0.1", - "com.unity.performance.profile-analyzer": "1.2.4", - "com.unity.test-framework": "1.6.0", - "com.unity.testtools.codecoverage": "1.2.7" - } - }, - "com.unity.ide.rider": { - "version": "3.0.38", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ide.visualstudio": { - "version": "2.0.25", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.31" - }, - "url": "https://packages.unity.com" - }, - "com.unity.mathematics": { - "version": "1.3.2", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.multiplayer.center": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.netcode.gameobjects": { - "version": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "depth": 0, - "source": "git", - "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.6.0" - }, - "hash": "37bdf528127a9ae3e104d76b9a13343bffd653cb" - }, - "com.unity.nuget.mono-cecil": { - "version": "1.11.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.performance.profile-analyzer": { - "version": "1.2.4", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.purchasing": { - "version": "4.12.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.5", - "com.unity.modules.androidjni": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.analytics": { - "version": "6.1.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.4", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.authentication": { - "version": "3.5.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.15.1", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.core": { - "version": "1.15.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.modules.androidjni": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment": { - "version": "1.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.15.1", - "com.unity.services.deployment.api": "1.1.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment.api": { - "version": "1.1.2", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.services.multiplayer": { - "version": "1.2.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.transport": "2.5.0", - "com.unity.collections": "2.2.1", - "com.unity.services.qos": "1.3.0", - "com.unity.services.core": "1.15.1", - "com.unity.services.wire": "1.4.0", - "com.unity.services.deployment": "1.6.2", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "3.5.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.qos": { - "version": "1.3.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.services.core": "1.12.4", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "2.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.wire": { - "version": "1.4.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.12.5", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.authentication": "2.7.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.settings-manager": { - "version": "2.1.0", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot": { - "version": "2.0.10", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.9", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10" - }, - "url": "https://packages.unity.com" - }, - "com.unity.test-framework": { - "version": "1.6.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ext.nunit": "2.0.3", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.test-framework.performance": { - "version": "3.2.0", - "depth": 2, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.33", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.testtools.codecoverage": { - "version": "1.2.7", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.0.16", - "com.unity.settings-manager": "1.0.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.timeline": { - "version": "1.8.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.10", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10", - "com.unity.sysroot.linux-x86_64": "2.0.9" - }, - "url": "https://packages.unity.com" - }, - "com.unity.transport": { - "version": "2.6.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ugui": { - "version": "2.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0" - } - }, - "com.unity.visualscripting": { - "version": "1.9.7", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.xr.legacyinputhelpers": { - "version": "2.1.12", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.xr": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.modules.accessibility": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.ai": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.androidjni": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.animation": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.assetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.audio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.cloth": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.director": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.animation": "1.0.0" - } - }, - "com.unity.modules.hierarchycore": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imageconversion": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imgui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.jsonserialize": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.particlesystem": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics2d": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.screencapture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.subsystems": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.terrain": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.terrainphysics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.terrain": "1.0.0" - } - }, - "com.unity.modules.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics2d": "1.0.0" - } - }, - "com.unity.modules.ui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.uielements": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0" - } - }, - "com.unity.modules.umbra": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unityanalytics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.unitywebrequest": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unitywebrequestassetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestaudio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.audio": "1.0.0" - } - }, - "com.unity.modules.unitywebrequesttexture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestwww": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.vehicles": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.video": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.vr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.xr": "1.0.0" - } - }, - "com.unity.modules.wind": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.xr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.subsystems": "1.0.0" - } - } - } -} diff --git a/Examples/OverridingScenesAndPrefabs/ProjectSettings/ProjectVersion.txt b/Examples/OverridingScenesAndPrefabs/ProjectSettings/ProjectVersion.txt index 47df254537..ba577c5bf5 100644 --- a/Examples/OverridingScenesAndPrefabs/ProjectSettings/ProjectVersion.txt +++ b/Examples/OverridingScenesAndPrefabs/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.61f1 -m_EditorVersionWithRevision: 6000.0.61f1 (74a0adb02c31) +m_EditorVersion: 6000.0.72f1 +m_EditorVersionWithRevision: 6000.0.72f1 (b731fd3ae857) diff --git a/Examples/PingTool/Packages/manifest.json b/Examples/PingTool/Packages/manifest.json index 0c5e1bb258..b1e4adc52f 100644 --- a/Examples/PingTool/Packages/manifest.json +++ b/Examples/PingTool/Packages/manifest.json @@ -2,25 +2,25 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ads": "4.4.2", - "com.unity.ai.navigation": "2.0.9", + "com.unity.ads": "4.16.4", + "com.unity.ai.navigation": "2.0.12", "com.unity.analytics": "3.8.1", - "com.unity.collab-proxy": "2.10.0", + "com.unity.collab-proxy": "2.12.4", "com.unity.feature.development": "1.0.2", - "com.unity.ide.rider": "3.0.38", - "com.unity.ide.visualstudio": "2.0.25", + "com.unity.ide.rider": "3.0.39", + "com.unity.ide.visualstudio": "2.0.27", "com.unity.multiplayer.center": "1.0.0", - "com.unity.multiplayer.playmode": "1.6.1", - "com.unity.multiplayer.tools": "2.2.6", + "com.unity.multiplayer.playmode": "1.6.3", + "com.unity.multiplayer.tools": "2.2.8", "com.unity.netcode.gameobjects": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "com.unity.purchasing": "4.12.2", + "com.unity.purchasing": "4.14.2", "com.unity.services.multiplayer": "1.2.0", "com.unity.test-framework": "1.6.0", - "com.unity.timeline": "1.8.9", + "com.unity.timeline": "1.8.10", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.10", - "com.unity.transport": "2.6.0", + "com.unity.transport": "2.7.2", "com.unity.ugui": "2.0.0", - "com.unity.visualscripting": "1.9.7", + "com.unity.visualscripting": "1.9.11", "com.unity.xr.legacyinputhelpers": "2.1.12", "com.unity.modules.accessibility": "1.0.0", "com.unity.modules.ai": "1.0.0", diff --git a/Examples/PingTool/Packages/packages-lock.json b/Examples/PingTool/Packages/packages-lock.json deleted file mode 100644 index f76bd76778..0000000000 --- a/Examples/PingTool/Packages/packages-lock.json +++ /dev/null @@ -1,674 +0,0 @@ -{ - "dependencies": { - "com.unity.2d.sprite": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.2d.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.tilemap": "1.0.0", - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.ads": { - "version": "4.4.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ai.navigation": { - "version": "2.0.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.ai": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.analytics": { - "version": "3.8.1", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.analytics": "1.0.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.burst": { - "version": "1.8.25", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.collab-proxy": { - "version": "2.10.0", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.collections": { - "version": "2.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", - "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" - }, - "com.unity.editorcoroutines": { - "version": "1.0.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.ext.nunit": { - "version": "2.0.5", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.feature.development": { - "version": "1.0.2", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ide.visualstudio": "2.0.25", - "com.unity.ide.rider": "3.0.38", - "com.unity.editorcoroutines": "1.0.1", - "com.unity.performance.profile-analyzer": "1.2.4", - "com.unity.test-framework": "1.6.0", - "com.unity.testtools.codecoverage": "1.2.7" - } - }, - "com.unity.ide.rider": { - "version": "3.0.38", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ide.visualstudio": { - "version": "2.0.25", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.31" - }, - "url": "https://packages.unity.com" - }, - "com.unity.mathematics": { - "version": "1.3.2", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.multiplayer.center": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.uielements": "1.0.0" - } - }, - "com.unity.multiplayer.playmode": { - "version": "1.6.1", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.nuget.newtonsoft-json": "2.0.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.multiplayer.tools": { - "version": "2.2.6", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.18", - "com.unity.collections": "2.5.1", - "com.unity.mathematics": "1.3.2", - "com.unity.profiling.core": "1.0.2", - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.modules.uielements": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.netcode.gameobjects": { - "version": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git?path=com.unity.netcode.gameobjects#develop-2.0.0", - "depth": 0, - "source": "git", - "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.6.0" - }, - "hash": "37bdf528127a9ae3e104d76b9a13343bffd653cb" - }, - "com.unity.nuget.mono-cecil": { - "version": "1.11.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.performance.profile-analyzer": { - "version": "1.2.4", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.profiling.core": { - "version": "1.0.2", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.purchasing": { - "version": "4.12.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.5", - "com.unity.modules.androidjni": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.analytics": { - "version": "6.1.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.12.4", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.authentication": { - "version": "3.5.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.15.1", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.core": { - "version": "1.15.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.modules.androidjni": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment": { - "version": "1.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.15.1", - "com.unity.services.deployment.api": "1.1.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment.api": { - "version": "1.1.2", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.services.multiplayer": { - "version": "1.2.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.transport": "2.5.0", - "com.unity.collections": "2.2.1", - "com.unity.services.qos": "1.3.0", - "com.unity.services.core": "1.15.1", - "com.unity.services.wire": "1.4.0", - "com.unity.services.deployment": "1.6.2", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "3.5.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.qos": { - "version": "1.3.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.services.core": "1.12.4", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "2.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.wire": { - "version": "1.4.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.12.5", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.authentication": "2.7.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.settings-manager": { - "version": "2.1.0", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot": { - "version": "2.0.10", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.9", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10" - }, - "url": "https://packages.unity.com" - }, - "com.unity.test-framework": { - "version": "1.6.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ext.nunit": "2.0.3", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.test-framework.performance": { - "version": "3.2.0", - "depth": 2, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.33", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.testtools.codecoverage": { - "version": "1.2.7", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.0.16", - "com.unity.settings-manager": "1.0.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.timeline": { - "version": "1.8.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.10", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10", - "com.unity.sysroot.linux-x86_64": "2.0.9" - }, - "url": "https://packages.unity.com" - }, - "com.unity.transport": { - "version": "2.6.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ugui": { - "version": "2.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0" - } - }, - "com.unity.visualscripting": { - "version": "1.9.7", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.xr.legacyinputhelpers": { - "version": "2.1.12", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.xr": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.modules.accessibility": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.ai": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.androidjni": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.animation": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.assetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.audio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.cloth": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.director": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.animation": "1.0.0" - } - }, - "com.unity.modules.hierarchycore": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imageconversion": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imgui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.jsonserialize": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.particlesystem": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics2d": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.screencapture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.subsystems": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.terrain": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.terrainphysics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.terrain": "1.0.0" - } - }, - "com.unity.modules.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics2d": "1.0.0" - } - }, - "com.unity.modules.ui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.uielements": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0" - } - }, - "com.unity.modules.umbra": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unityanalytics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.unitywebrequest": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unitywebrequestassetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestaudio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.audio": "1.0.0" - } - }, - "com.unity.modules.unitywebrequesttexture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestwww": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.vehicles": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.video": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.vr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.xr": "1.0.0" - } - }, - "com.unity.modules.wind": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.xr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.subsystems": "1.0.0" - } - } - } -} diff --git a/Examples/PingTool/ProjectSettings/ProjectVersion.txt b/Examples/PingTool/ProjectSettings/ProjectVersion.txt index 47df254537..ba577c5bf5 100644 --- a/Examples/PingTool/ProjectSettings/ProjectVersion.txt +++ b/Examples/PingTool/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.61f1 -m_EditorVersionWithRevision: 6000.0.61f1 (74a0adb02c31) +m_EditorVersion: 6000.0.72f1 +m_EditorVersionWithRevision: 6000.0.72f1 (b731fd3ae857) diff --git a/Examples/PingTool/ProjectSettings/VirtualProjectsConfig.json b/Examples/PingTool/ProjectSettings/VirtualProjectsConfig.json index 4ea16eb5a7..602c3b1ec3 100644 --- a/Examples/PingTool/ProjectSettings/VirtualProjectsConfig.json +++ b/Examples/PingTool/ProjectSettings/VirtualProjectsConfig.json @@ -1,4 +1,4 @@ { "PlayerTags": [], - "version": "1.6.1" + "version": "1.6.3" } \ No newline at end of file diff --git a/minimalproject/Packages/manifest.json b/minimalproject/Packages/manifest.json index 6556fd319a..ed404b0448 100644 --- a/minimalproject/Packages/manifest.json +++ b/minimalproject/Packages/manifest.json @@ -1,7 +1,7 @@ { "disableProjectUpdate": false, "dependencies": { - "com.unity.ide.rider": "3.0.38", + "com.unity.ide.rider": "3.0.39", "com.unity.netcode.gameobjects": "file:../../com.unity.netcode.gameobjects" }, "testables": [ diff --git a/minimalproject/Packages/packages-lock.json b/minimalproject/Packages/packages-lock.json deleted file mode 100644 index 5b95287ae1..0000000000 --- a/minimalproject/Packages/packages-lock.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - "dependencies": { - "com.unity.burst": { - "version": "1.8.25", - "depth": 2, - "source": "registry", - "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.collections": { - "version": "2.6.2", - "depth": 2, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", - "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ext.nunit": { - "version": "2.0.5", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.ide.rider": { - "version": "3.0.38", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, - "url": "https://packages.unity.com" - }, - "com.unity.mathematics": { - "version": "1.3.2", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.netcode.gameobjects": { - "version": "file:../../com.unity.netcode.gameobjects", - "depth": 0, - "source": "local", - "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.6.0" - } - }, - "com.unity.nuget.mono-cecil": { - "version": "1.11.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.test-framework": { - "version": "1.6.0", - "depth": 3, - "source": "builtin", - "dependencies": { - "com.unity.ext.nunit": "2.0.3", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.test-framework.performance": { - "version": "3.2.0", - "depth": 3, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.33", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.transport": { - "version": "2.6.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.modules.imgui": { - "version": "1.0.0", - "depth": 4, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.jsonserialize": { - "version": "1.0.0", - "depth": 3, - "source": "builtin", - "dependencies": {} - } - } -} diff --git a/minimalproject/ProjectSettings/ProjectVersion.txt b/minimalproject/ProjectSettings/ProjectVersion.txt index 47df254537..ba577c5bf5 100644 --- a/minimalproject/ProjectSettings/ProjectVersion.txt +++ b/minimalproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.0.61f1 -m_EditorVersionWithRevision: 6000.0.61f1 (74a0adb02c31) +m_EditorVersion: 6000.0.72f1 +m_EditorVersionWithRevision: 6000.0.72f1 (b731fd3ae857) diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index f7832aaad0..6c563a7a55 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -12,7 +12,7 @@ "com.unity.package-validation-suite": "0.49.0-preview", "com.unity.services.authentication": "3.5.2", "com.unity.services.multiplayer": "1.2.0", - "com.unity.test-framework": "1.6.0", + "com.unity.test-framework": "1.4.6", "com.unity.test-framework.performance": "3.2.0", "com.unity.timeline": "1.8.9", "com.unity.toolchain.win-x86_64-linux-x86_64": "2.0.11", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json deleted file mode 100644 index 675de94bf2..0000000000 --- a/testproject/Packages/packages-lock.json +++ /dev/null @@ -1,570 +0,0 @@ -{ - "dependencies": { - "com.unity.addressables": { - "version": "2.7.4", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.profiling.core": "1.0.2", - "com.unity.test-framework": "1.4.5", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.scriptablebuildpipeline": "2.4.3", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ai.navigation": { - "version": "2.0.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.ai": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.burst": { - "version": "1.8.25", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.mathematics": "1.2.1", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.collab-proxy": { - "version": "2.10.1", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.collections": { - "version": "2.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.23", - "com.unity.mathematics": "1.3.2", - "com.unity.test-framework": "1.4.6", - "com.unity.nuget.mono-cecil": "1.11.5", - "com.unity.test-framework.performance": "3.0.3" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ext.nunit": { - "version": "2.0.5", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.ide.rider": { - "version": "3.0.38", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ide.visualstudio": { - "version": "2.0.25", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.31" - }, - "url": "https://packages.unity.com" - }, - "com.unity.mathematics": { - "version": "1.3.3", - "depth": 0, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.multiplayer.tools": { - "version": "2.2.6", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.18", - "com.unity.collections": "2.5.1", - "com.unity.mathematics": "1.3.2", - "com.unity.profiling.core": "1.0.2", - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.modules.uielements": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.netcode.gameobjects": { - "version": "file:../../com.unity.netcode.gameobjects", - "depth": 0, - "source": "local", - "dependencies": { - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.transport": "2.6.0" - } - }, - "com.unity.nuget.mono-cecil": { - "version": "1.11.5", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.nuget.newtonsoft-json": { - "version": "3.2.1", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.package-validation-suite": { - "version": "0.49.0-preview", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.nuget.mono-cecil": "0.1.6-preview.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.profiling.core": { - "version": "1.0.2", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.scriptablebuildpipeline": { - "version": "2.4.3", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.4.5", - "com.unity.modules.assetbundle": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.authentication": { - "version": "3.5.2", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.ugui": "1.0.0", - "com.unity.services.core": "1.15.1", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.core": { - "version": "1.15.1", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.modules.androidjni": "1.0.0", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment": { - "version": "1.6.2", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.15.1", - "com.unity.services.deployment.api": "1.1.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.deployment.api": { - "version": "1.1.2", - "depth": 2, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.services.multiplayer": { - "version": "1.2.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.transport": "2.5.0", - "com.unity.collections": "2.2.1", - "com.unity.services.qos": "1.3.0", - "com.unity.services.core": "1.15.1", - "com.unity.services.wire": "1.4.0", - "com.unity.services.deployment": "1.6.2", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "3.5.1" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.qos": { - "version": "1.3.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.collections": "1.2.4", - "com.unity.services.core": "1.12.4", - "com.unity.nuget.newtonsoft-json": "3.0.2", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.services.authentication": "2.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.services.wire": { - "version": "1.4.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.services.core": "1.12.5", - "com.unity.nuget.newtonsoft-json": "3.2.1", - "com.unity.services.authentication": "2.7.4" - }, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot": { - "version": "2.0.10", - "depth": 1, - "source": "registry", - "dependencies": {}, - "url": "https://packages.unity.com" - }, - "com.unity.sysroot.linux-x86_64": { - "version": "2.0.9", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10" - }, - "url": "https://packages.unity.com" - }, - "com.unity.test-framework": { - "version": "1.6.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.ext.nunit": "2.0.3", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.test-framework.performance": { - "version": "3.2.0", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.test-framework": "1.1.33", - "com.unity.modules.jsonserialize": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.timeline": { - "version": "1.8.9", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0" - }, - "url": "https://packages.unity.com" - }, - "com.unity.toolchain.win-x86_64-linux-x86_64": { - "version": "2.0.11", - "depth": 0, - "source": "registry", - "dependencies": { - "com.unity.sysroot": "2.0.10", - "com.unity.sysroot.linux-x86_64": "2.0.9" - }, - "url": "https://packages.unity.com" - }, - "com.unity.transport": { - "version": "2.6.0", - "depth": 1, - "source": "registry", - "dependencies": { - "com.unity.burst": "1.8.24", - "com.unity.collections": "2.2.1", - "com.unity.mathematics": "1.3.2" - }, - "url": "https://packages.unity.com" - }, - "com.unity.ugui": { - "version": "2.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0" - } - }, - "com.unity.modules.accessibility": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.ai": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.androidjni": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.animation": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.assetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.audio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.cloth": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.director": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.animation": "1.0.0" - } - }, - "com.unity.modules.hierarchycore": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imageconversion": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.imgui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.jsonserialize": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.particlesystem": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.physics2d": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.screencapture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.subsystems": { - "version": "1.0.0", - "depth": 1, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.terrain": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.terrainphysics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.terrain": "1.0.0" - } - }, - "com.unity.modules.tilemap": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics2d": "1.0.0" - } - }, - "com.unity.modules.ui": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.uielements": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0", - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.umbra": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unityanalytics": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0" - } - }, - "com.unity.modules.unitywebrequest": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.unitywebrequestassetbundle": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestaudio": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.audio": "1.0.0" - } - }, - "com.unity.modules.unitywebrequesttexture": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.unitywebrequestwww": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0" - } - }, - "com.unity.modules.vehicles": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0" - } - }, - "com.unity.modules.video": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0" - } - }, - "com.unity.modules.vr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.xr": "1.0.0" - } - }, - "com.unity.modules.wind": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": {} - }, - "com.unity.modules.xr": { - "version": "1.0.0", - "depth": 0, - "source": "builtin", - "dependencies": { - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.subsystems": "1.0.0" - } - } - } -} diff --git a/testproject/ProjectSettings/ProjectVersion.txt b/testproject/ProjectSettings/ProjectVersion.txt index c31f18a127..ba577c5bf5 100644 --- a/testproject/ProjectSettings/ProjectVersion.txt +++ b/testproject/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 6000.2.12f1 -m_EditorVersionWithRevision: 6000.2.12f1 (e89d5df0e333) +m_EditorVersion: 6000.0.72f1 +m_EditorVersionWithRevision: 6000.0.72f1 (b731fd3ae857) From 3d9f18a6065ae2c6038cba7fc12f17cc642b345e Mon Sep 17 00:00:00 2001 From: Emma Date: Tue, 11 Aug 2026 16:03:46 -0400 Subject: [PATCH 43/64] fix: Message sending GC allocations (#4119) * fix: GC allocations * Add message receive allocations test * Revert the fix to check ILPP build fails the test * Revert forced failure * Remove gc checks on NetworkListTests --- .../Messages/NetworkVariableDeltaMessage.cs | 8 +- .../Runtime/Messaging/Messages/RpcMessages.cs | 8 +- .../Messaging/NetworkMessageManager.cs | 11 +- .../Runtime/Metrics/MetricHooks.cs | 23 +++- .../Collections/NetworkList.cs | 33 +++--- .../Runtime/Serialization/BitReader.cs | 2 +- .../Runtime/Serialization/FastBufferReader.cs | 2 +- .../Editor/Messaging/MessageReceivingTests.cs | 103 ++++++++--------- .../Editor/Serialization/BytePackerTests.cs | 2 +- .../Serialization/FastBufferReaderTests.cs | 5 +- .../Tests/Runtime/Helpers/MessageCatcher.cs | 8 +- .../MessageReceiveAllocationTests.cs | 106 ++++++++++++++++++ .../MessageReceiveAllocationTests.cs.meta | 2 + .../NetworkVariable/NetworkVariableTests.cs | 2 +- .../Tests/Runtime/Rpc/RpcInvocationTests.cs | 8 ++ .../Tests/Runtime/Rpc/RpcManyClientsTests.cs | 7 ++ 16 files changed, 240 insertions(+), 90 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs.meta diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index 11fe2e2a7e..3e4c6a0541 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -47,7 +47,7 @@ internal struct NetworkVariableDeltaMessage : INetworkMessage private Dictionary> m_ForwardUpdates; - private List m_UpdatedNetworkVariables; + private NativeList m_UpdatedNetworkVariables; [MethodImpl(MethodImplOptions.AggressiveInlining)] private void WriteNetworkVariable(ref FastBufferWriter writer, ref NetworkVariableBase networkVariable, bool ensureNetworkVariableLengthSafety, int nonfragmentedSize, int fragmentedSize) @@ -217,7 +217,7 @@ public void Handle(ref NetworkContext context) var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer; var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer; - m_UpdatedNetworkVariables = new List(); + m_UpdatedNetworkVariables = new NativeList(Allocator.Temp); if (networkBehaviour == null) { @@ -396,9 +396,9 @@ public void Handle(ref NetworkContext context) // When a server forwards delta updates to connected clients, it needs to preserve the previous value // until it is done serializing all valid NetworkVariable field deltas (relative to each client). This // is invoked after it is done forwarding the deltas. - foreach (var fieldIndex in m_UpdatedNetworkVariables) + for (int i = 0; i < m_UpdatedNetworkVariables.Length; i++) { - networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead(); + networkBehaviour.NetworkVariableFields[m_UpdatedNetworkVariables[i]].PostDeltaRead(); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs index b7f1320788..0dbb3b6bf6 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/RpcMessages.cs @@ -83,12 +83,8 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, { networkManager.Log.Exception(ex, new Context(LogLevel.Error, "Unhandled RPC exception!").AddNetworkBehaviour(networkBehaviour)); - var methodId = metadata.NetworkRpcMethodId; - networkManager.Log.Info(new Context(LogLevel.Developer, "RPC Table Contents").AddCollection(rpcsForBehaviour, entry => - { - var invokePermission = NetworkBehaviour.__rpc_permission_table[networkBehaviour.GetType()][methodId]; - return $"{entry.Key} | {entry.Value.Method.Name} | {invokePermission}"; - })); + var invokePermission = permission; + networkManager.Log.Info(new Context(LogLevel.Developer, "RPC Table Contents").AddCollection(rpcsForBehaviour, entry => $"{entry.Key} | {entry.Value.Method.Name} | {invokePermission}")); } } } diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs index 343cec9961..21df4c7e4b 100644 --- a/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/Messaging/NetworkMessageManager.cs @@ -503,9 +503,9 @@ internal void CleanupDisconnectedClients() m_DisconnectedClients.Clear(); } - public static int CreateMessageAndGetVersion() where T : INetworkMessage, new() + public static int CreateMessageAndGetVersion() where T : struct, INetworkMessage { - return new T().Version; + return default(T).Version; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -538,10 +538,10 @@ internal int GetMessageVersion(Type type, ulong clientId, bool forReceive = fals - public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager) where T : INetworkMessage, new() + public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, NetworkMessageManager manager) where T : struct, INetworkMessage { var messageType = typeof(T); - var message = new T(); + var message = default(T); var messageVersion = 0; // Special cases because these are the messages that carry the version info - thus the version info isn't @@ -633,8 +633,9 @@ internal int SendMessage(ref TMessageType messa return largestSerializedSize; } - internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList clientIds, int messageVersionFilter) + internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds, int messageVersionFilter) where TMessageType : INetworkMessage + where TClientIdListType : IReadOnlyList { using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp); diff --git a/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs index fd12bc5adc..c7badc20a0 100644 --- a/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs +++ b/com.unity.netcode.gameobjects/Runtime/Metrics/MetricHooks.cs @@ -1,10 +1,12 @@ using System; +using System.Collections.Generic; namespace Unity.Netcode { internal class MetricHooks : INetworkHooks { private readonly NetworkManager m_NetworkManager; + private readonly Dictionary m_CachedTypeNames = new(); public MetricHooks(NetworkManager networkManager) { @@ -17,12 +19,12 @@ public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDeliver public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage { - m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, typeof(T).Name, messageSizeBytes); + m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, GetNameForType(typeof(T)), messageSizeBytes); } public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) { - m_NetworkManager.NetworkMetrics.TrackNetworkMessageReceived(senderId, messageType.Name, messageSizeBytes); + m_NetworkManager.NetworkMetrics.TrackNetworkMessageReceived(senderId, GetNameForType(messageType), messageSizeBytes); } public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) @@ -66,5 +68,22 @@ public void OnAfterHandleMessage(ref T message, ref NetworkContext context) w { // TODO: Per-message metrics recording moved here } + + /// + /// Gets the Name from a given type. + /// + private string GetNameForType(Type type) + { + if (m_CachedTypeNames.TryGetValue(type, out var cachedName)) + { + return cachedName; + } + + // type.Name does a reflection lookup that does a GC allocation + // Grab the name once and save to a cache. + var name = type.Name; + m_CachedTypeNames.Add(type, name); + return name; + } } } diff --git a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs index 9c0cb4bd83..acf139b0d1 100644 --- a/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/com.unity.netcode.gameobjects/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -137,6 +137,15 @@ public override void WriteDelta(FastBufferWriter writer) } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private T ReadValue(FastBufferReader reader) + { + // T is constrained to unmanaged, use default rather than new() to avoid an allocation. + var value = default(T); + NetworkVariableSerialization.Serializer.Read(reader, ref value); + return value; + } + /// public override void WriteField(FastBufferWriter writer) { @@ -154,9 +163,7 @@ public override void ReadField(FastBufferReader reader) reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { - var value = new T(); - NetworkVariableSerialization.Serializer.Read(reader, ref value); - m_List.Add(value); + m_List.Add(ReadValue(reader)); } } @@ -178,8 +185,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { case NetworkListEvent.EventType.Add: { - var value = new T(); - NetworkVariableSerialization.Serializer.Read(reader, ref value); + var value = ReadValue(reader); m_List.Add(value); if (OnListChanged != null) @@ -188,7 +194,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { Type = eventType, Index = m_List.Length - 1, - Value = m_List[m_List.Length - 1] + Value = value }); } @@ -198,7 +204,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { Type = eventType, Index = m_List.Length - 1, - Value = m_List[m_List.Length - 1] + Value = value }); // Preserve the legacy way of handling this if (keepDirtyDelta) @@ -211,8 +217,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Insert: { ByteUnpacker.ReadValueBitPacked(reader, out int index); - var value = new T(); - NetworkVariableSerialization.Serializer.Read(reader, ref value); + var value = ReadValue(reader); if (index < m_List.Length) { @@ -230,7 +235,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { Type = eventType, Index = index, - Value = m_List[index] + Value = value }); } @@ -240,7 +245,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) { Type = eventType, Index = index, - Value = m_List[index] + Value = value }); // Preserve the legacy way of handling this if (keepDirtyDelta) @@ -252,8 +257,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) break; case NetworkListEvent.EventType.Remove: { - var value = new T(); - NetworkVariableSerialization.Serializer.Read(reader, ref value); + var value = ReadValue(reader); int index = m_List.IndexOf(value); if (index == -1) { @@ -323,8 +327,7 @@ public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta) case NetworkListEvent.EventType.Value: { ByteUnpacker.ReadValueBitPacked(reader, out int index); - var value = new T(); - NetworkVariableSerialization.Serializer.Read(reader, ref value); + var value = ReadValue(reader); 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/Serialization/BitReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs index ab8c9bc5ce..92d1951a7a 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/BitReader.cs @@ -170,7 +170,7 @@ public unsafe void ReadBit(out bool bit) [MethodImpl(MethodImplOptions.AggressiveInlining)] private unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetBytes = 0) where T : unmanaged { - var val = new T(); + var val = default(T); byte* ptr = ((byte*)&val) + offsetBytes; byte* bufferPointer = m_BufferPointer + BytePosition; UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); diff --git a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs index d132f775fd..522e9c4bad 100644 --- a/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs +++ b/com.unity.netcode.gameobjects/Runtime/Serialization/FastBufferReader.cs @@ -707,7 +707,7 @@ public unsafe void ReadPartialValue(out T value, int bytesToRead, int offsetB } #endif - var val = new T(); + var val = default(T); byte* ptr = ((byte*)&val) + offsetBytes; byte* bufferPointer = Handle->BufferPointer + Handle->Position; UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs index d77bf4ea72..4d3df172cf 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Messaging/MessageReceivingTests.cs @@ -1,9 +1,10 @@ using System; using System.Collections.Generic; using NUnit.Framework; -using NUnit.Framework.Internal; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; +using UnityEngine.TestTools.Constraints; +using Is = NUnit.Framework.Is; namespace Unity.Netcode.EditorTests { @@ -95,22 +96,23 @@ public void WhenHandlingAMessage_ReceiveMethodIsCalled() }; var message = GetMessage(); - var writer = new FastBufferWriter(1300, Allocator.Temp); - using (writer) - { - writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message)); - writer.WriteValue(message); + using var writer = new FastBufferWriter(1300, Allocator.Temp); + writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message)); + writer.WriteValue(message); - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - m_MessageManager.HandleMessage(messageHeader, reader, 0, 0, 0); - Assert.IsTrue(TestMessage.Deserialized); - Assert.IsTrue(TestMessage.Handled); - Assert.AreEqual(1, TestMessage.DeserializedValues.Count); - Assert.AreEqual(message, TestMessage.DeserializedValues[0]); - } - } + using var reader = new FastBufferReader(writer, Allocator.Temp); + m_MessageManager.HandleMessage(messageHeader, reader, 0, 0, 0); + Assert.IsTrue(TestMessage.Deserialized); + Assert.IsTrue(TestMessage.Handled); + Assert.AreEqual(1, TestMessage.DeserializedValues.Count); + Assert.AreEqual(message, TestMessage.DeserializedValues[0]); + + // Check for GC Allocations + Assert.That(() => + { + reader.Seek(0); + m_MessageManager.HandleMessage(messageHeader, reader, 0, 0, 0); + }, Is.Not.AllocatingGCMemory()); } [Test] @@ -220,44 +222,45 @@ public unsafe void WhenReceivingMultipleMessagesAndProcessingMessageQueue_Receiv var message = GetMessage(); var message2 = GetMessage(); - var writer = new FastBufferWriter(1300, Allocator.Temp); - using (writer) + using var writer = new FastBufferWriter(1300, Allocator.Temp); + writer.WriteValueSafe(batchHeader); + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType); + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize); + writer.WriteValueSafe(message); + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType); + BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize); + writer.WriteValueSafe(message2); + + // Fill out the rest of the batch header + writer.Seek(0); + batchHeader = new NetworkBatchHeader { - writer.WriteValueSafe(batchHeader); - BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType); - BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize); - writer.WriteValueSafe(message); - BytePacker.WriteValueBitPacked(writer, messageHeader.MessageType); - BytePacker.WriteValueBitPacked(writer, messageHeader.MessageSize); - writer.WriteValueSafe(message2); + Magic = NetworkBatchHeader.MagicValue, + BatchSize = writer.Length, + BatchHash = XXHash.Hash64(writer.GetUnsafePtr() + sizeof(NetworkBatchHeader), writer.Length - sizeof(NetworkBatchHeader)), + BatchCount = 2 + }; + writer.WriteValue(batchHeader); - // Fill out the rest of the batch header - writer.Seek(0); - batchHeader = new NetworkBatchHeader - { - Magic = NetworkBatchHeader.MagicValue, - BatchSize = writer.Length, - BatchHash = XXHash.Hash64(writer.GetUnsafePtr() + sizeof(NetworkBatchHeader), writer.Length - sizeof(NetworkBatchHeader)), - BatchCount = 2 - }; - writer.WriteValue(batchHeader); + var data = new ArraySegment(writer.ToArray()); + m_MessageManager.HandleIncomingData(0, data, 0); + Assert.IsFalse(TestMessage.Deserialized); + Assert.IsFalse(TestMessage.Handled); + Assert.IsEmpty(TestMessage.DeserializedValues); - var reader = new FastBufferReader(writer, Allocator.Temp); - using (reader) - { - m_MessageManager.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); - Assert.IsFalse(TestMessage.Deserialized); - Assert.IsFalse(TestMessage.Handled); - Assert.IsEmpty(TestMessage.DeserializedValues); + m_MessageManager.ProcessIncomingMessageQueue(); + Assert.IsTrue(TestMessage.Deserialized); + Assert.IsTrue(TestMessage.Handled); + Assert.AreEqual(2, TestMessage.DeserializedValues.Count); + Assert.AreEqual(message, TestMessage.DeserializedValues[0]); + Assert.AreEqual(message2, TestMessage.DeserializedValues[1]); - m_MessageManager.ProcessIncomingMessageQueue(); - Assert.IsTrue(TestMessage.Deserialized); - Assert.IsTrue(TestMessage.Handled); - Assert.AreEqual(2, TestMessage.DeserializedValues.Count); - Assert.AreEqual(message, TestMessage.DeserializedValues[0]); - Assert.AreEqual(message2, TestMessage.DeserializedValues[1]); - } - } + // Check for GC Allocations + Assert.That(() => + { + m_MessageManager.HandleIncomingData(0, data, 0); + m_MessageManager.ProcessIncomingMessageQueue(); + }, Is.Not.AllocatingGCMemory()); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs index 1bd197997f..8c1be03140 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/BytePackerTests.cs @@ -92,7 +92,7 @@ private unsafe void RunTypeTest(T value) where T : unmanaged using (reader) { - var outVal = new T(); + var outVal = default(T); MethodInfo method; if (value is Enum) { diff --git a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs index d38c10e0a7..d63b7bb067 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -259,7 +259,7 @@ private void RunReadMethod(string methodName, FastBufferReader reader, out T } } } - value = new T(); + value = default; Assert.NotNull(method); @@ -766,8 +766,7 @@ public unsafe void RunFixedStringTest(T fixedStringValue, int numBytesWritten { VerifyPositionAndLength(reader, writer.Length); - var result = new T(); - reader.ReadValueSafe(out result); + reader.ReadValueSafe(out T result); Assert.AreEqual(fixedStringValue, result); VerifyCheckBytes(reader, serializedValueSize); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Helpers/MessageCatcher.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Helpers/MessageCatcher.cs index f408532871..63955c1acc 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Helpers/MessageCatcher.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Helpers/MessageCatcher.cs @@ -25,7 +25,6 @@ private struct TriggerData public void ReleaseMessages() { - foreach (var caughtSpawn in m_CaughtMessages) { // Reader will be disposed within HandleMessage @@ -33,6 +32,13 @@ public void ReleaseMessages() } } + public void HandleCaughtMessage(int index) + { + var caughtMessage = m_CaughtMessages[index]; + // Reader will be disposed within HandleMessage + m_OwnerNetworkManager.ConnectionManager.MessageManager.HandleMessage(caughtMessage.Header, caughtMessage.Reader, caughtMessage.SenderId, caughtMessage.Timestamp, caughtMessage.SerializedHeaderSize); + } + public int CaughtMessageCount => m_CaughtMessages.Count; public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs new file mode 100644 index 0000000000..bcd768ac9c --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs @@ -0,0 +1,106 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; +using UnityEngine.TestTools.Constraints; +using Is = NUnit.Framework.Is; + +namespace Unity.Netcode.RuntimeTests +{ + internal class AllocationTestBehaviour : NetworkBehaviour + { + internal int RpcReceivedCount; + + public NetworkVariable TestVariable = new(); + + [Rpc(SendTo.NotMe)] + public void NotMeRpc() + { + RpcReceivedCount++; + } + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class MessageReceiveAllocationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + public MessageReceiveAllocationTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + private GameObject m_Prefab; + + protected override void OnServerAndClientsCreated() + { + m_Prefab = CreateNetworkObjectPrefab(nameof(AllocationTestBehaviour)); + m_Prefab.AddComponent(); + base.OnServerAndClientsCreated(); + } + + [UnityTest] + public IEnumerator NoAllocationsOnMessageReceive() + { + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + var authorityInstance = SpawnObject(m_Prefab, authority); + var authorityComponent = authorityInstance.GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(authorityInstance); + AssertOnTimeout("Timed out waiting for objects to spawn"); + + var nonAuthorityComponent = nonAuthority.SpawnManager.SpawnedObjects[authorityComponent.NetworkObjectId].GetComponent(); + + /* + * RpcMessage + */ + var rpcCatcher = new MessageCatcher(nonAuthority); + nonAuthority.ConnectionManager.MessageManager.Hook(rpcCatcher); + + // Send the same message twice: the first is replayed as a warm-up. + // The second will follow the identical code path and is checked for allocations. + authorityComponent.NotMeRpc(); + authorityComponent.NotMeRpc(); + yield return WaitForConditionOrTimeOut(() => rpcCatcher.CaughtMessageCount == 2); + AssertOnTimeout($"Timed out waiting to catch all expected {nameof(RpcMessage)} messages. Expected: 2, Actual: {rpcCatcher.CaughtMessageCount}"); + + // Unhook first so the replayed messages are handled instead of being caught again + nonAuthority.ConnectionManager.MessageManager.Unhook(rpcCatcher); + + rpcCatcher.HandleCaughtMessage(0); + Assert.AreEqual(1, nonAuthorityComponent.RpcReceivedCount); + + Assert.That(() => + { + rpcCatcher.HandleCaughtMessage(1); + }, Is.Not.AllocatingGCMemory()); + Assert.AreEqual(2, nonAuthorityComponent.RpcReceivedCount); + + /* + * NetworkVariableDeltaMessage + */ + var deltaCatcher = new MessageCatcher(nonAuthority); + nonAuthority.ConnectionManager.MessageManager.Hook(deltaCatcher); + + authorityComponent.TestVariable.Value = 1; + // Wait for the first change to be received client-side before sending the second change + yield return WaitForConditionOrTimeOut(() => deltaCatcher.CaughtMessageCount == 1); + authorityComponent.TestVariable.Value = 2; + yield return WaitForConditionOrTimeOut(() => deltaCatcher.CaughtMessageCount == 2); + AssertOnTimeout($"Timed out waiting to catch all expected {nameof(NetworkVariableDeltaMessage)} messages. Expected: 2, Actual: {deltaCatcher.CaughtMessageCount}"); + + // Unhook first so the replayed messages are handled instead of being caught again + nonAuthority.ConnectionManager.MessageManager.Unhook(deltaCatcher); + + deltaCatcher.HandleCaughtMessage(0); + Assert.AreEqual(1, nonAuthorityComponent.TestVariable.Value); + + Assert.That(() => + { + deltaCatcher.HandleCaughtMessage(1); + }, Is.Not.AllocatingGCMemory()); + Assert.AreEqual(2, nonAuthorityComponent.TestVariable.Value); + } + + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs.meta new file mode 100644 index 0000000000..88be098d8d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Messaging/MessageReceiveAllocationTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 52d657f70a8454deaa08d1de60e0fee1 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 85a72d9f19..4eb9611a75 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -1780,7 +1780,7 @@ public void WhenSerializingAndDeserializingValueTypeNativeArrayNetworkVariables_ public unsafe T RandGenBytes(System.Random rand) where T : unmanaged { - var t = new T(); + var t = default(T); T* tPtr = &t; var s = new Span(tPtr, sizeof(T)); rand.NextBytes(s); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs index 613b81f5e5..9f36890f2a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcInvocationTests.cs @@ -8,6 +8,8 @@ using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; +using UnityEngine.TestTools.Constraints; +using Is = NUnit.Framework.Is; namespace Unity.Netcode.RuntimeTests { @@ -335,6 +337,12 @@ public IEnumerator RpcInvocationOrderTests() Assert.IsTrue(ValidateInvocationOrder(errorLog), $"[Has nested][nonAuthority][{testType}] Rpcs were invoked in an incorrect order\n {errorLog}"); errorLog.Clear(); } + + // Safety check for GC allocations + Assert.That(() => + { + nonAuthorityInstance.EveryoneInvokePermissionRpc(); + }, Is.Not.AllocatingGCMemory()); } private void ResetAllExpectedInvocations() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs index 62fb94bbe5..99f88ecf85 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcManyClientsTests.cs @@ -4,6 +4,7 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.TestTools.Constraints; namespace Unity.Netcode.RuntimeTests { @@ -150,6 +151,12 @@ public void RpcManyClientsTest() var possibility1 = new List { m_ClientNetworkManagers[1].LocalClientId, m_ClientNetworkManagers[2].LocalClientId }; var possibility2 = new List { m_ClientNetworkManagers[2].LocalClientId, m_ClientNetworkManagers[1].LocalClientId }; Debug.Assert(Enumerable.SequenceEqual(rpcManyClientsObject.ReceivedFrom, possibility1) || Enumerable.SequenceEqual(rpcManyClientsObject.ReceivedFrom, possibility2)); + + // Safety check for GC allocations + Assert.That(() => + { + rpcManyClientsObject.WithParamsClientRpc(param); + }, NUnit.Framework.Is.Not.AllocatingGCMemory()); } } } From 77a25d7312ccf575a913aa6265469c172c1096c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Thu, 13 Aug 2026 13:23:21 +0200 Subject: [PATCH 44/64] ci: re-enable PS5 and Switch jobs (#4117) * Re-enabled ps5 and webgl * ci: Add WebGL PlayMode test job (MTT-15569) Split the WebGL CI into a build phase and a run phase. webgl-build.yml now exposes the built player as a dedicated 'players' artifact, and a new webgl-test.yml run job consumes it and executes the PlayMode tests inside Firefox on a GPU-backed Ubuntu agent (Unity::VM::GPU, rtx2080). The Firefox browser flags are passed explicitly so UTR does not attempt to download the browser from Stevedore. The run job is wired into the Nightly and Weekly (QV) triggers only, not into PR triggers, since WebGL failures are infrequent and the build is slow. Runtime RuntimePlatform.WebGLPlayer exclusions are intentionally deferred to be added reactively after the first real CI run. * corrected images * disabled webgl test * adjustments * Removed WebGL changes * reverted change * added diagnostic for switch * corrected Switch sdk version * temporarily disabled switch * corrected ticket description --- .yamato/console-standalone-test.yml | 23 +++++++++--------- .yamato/project.metafile | 36 ++++++++++++++--------------- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/.yamato/console-standalone-test.yml b/.yamato/console-standalone-test.yml index dba4a796a1..9574e07e1b 100644 --- a/.yamato/console-standalone-test.yml +++ b/.yamato/console-standalone-test.yml @@ -31,7 +31,6 @@ # Each console requires specific SDK paths and tools # QUALITY THOUGHTS-------------------------------------------------------------------- - # TODO: consider adding all projects that have tests # To see where this job is included (in trigger job definitions) look into _triggers.yml file @@ -51,16 +50,16 @@ console_standalone_build_{{ project.name }}_{{ platform.name }}_{{ editor }}: {% endif %} commands: - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor -c il2cpp -c {{ platform.name }} - - UnifiedTestRunner --testproject={{ project.path }} --architecture={% if platform.name == "switch" %}arm64{% else %}x64{% endif %} --scripting-backend=il2cpp --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --testfilter="Unity.Netcode.RuntimeTests.*" --extra-editor-arg=-batchmode --extra-editor-arg=-nographics --reruncount=1 --clean-library-on-rerun --build-only --timeout={{ test_timeout}} + - UnifiedTestRunner --testproject={{ project.path }} --architecture={% if platform.name == "switch" %}arm64{% else %}x64{% endif %} --scripting-backend=il2cpp --suite=playmode --platform={{ platform.standalone }} --editor-location=.Editor --artifacts-path=artifacts --player-save-path=build/players --testfilter="Unity.Netcode.RuntimeTests.*" --extra-editor-arg=-batchmode --extra-editor-arg=-nographics{% if platform.name == "switch" %} --extra-editor-arg=-overrideTextureCompression --extra-editor-arg=ForceUncompressed{% endif %} --reruncount=1 --clean-library-on-rerun --build-only --timeout={{ test_timeout}} variables: # PS4 related SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' -# PS5 related --> THIS WAS DISABLED IN PROJECT.METAFILE. SEE MTT-12118 - SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\9.000' - SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\target\bins' +# PS5 related --> win10-ps5:v4 ships SDK 13.000 but defaults SCE_PROSPERO_SDK_DIR to an unsupported version, so set it explicitly. + SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\13.000' + SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\host_tools\bin' SCE_ROOT_DIR: 'C:\Program Files (x86)\SCE' -# Switch related - NINTENDO_SDK_ROOT: 'C:\Nintendo\nx_sdk-18_3_0\NintendoSDK' +# Switch related --> Switch is DISABLED in project.metafile (MTT-15636): 6000.x/trunk editors mandate NintendoSDK 22.2.x, which is not installed on win10-switch:v4 (only nintendosdk-en-21_4_0 is). This target path is correct for when PETS ships 22.2.x and Switch is re-enabled. + NINTENDO_SDK_ROOT: 'C:\Nintendo\nintendosdk-en-22_2_0\NintendoSDK' UNITY_NINTENDOSDK_CLI_TOOLS: '${NINTENDO_SDK_ROOT}\Tools\CommandLineTools' artifacts: players: @@ -94,12 +93,12 @@ console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: variables: # PS4 related SCE_ORBIS_SDK_DIR: 'C:\Users\bokken\SCE\ps4_sdk_12_00' -# PS5 related --> THIS WAS DISABLED IN PROJECT.METAFILE. SEE MTT-12118 - SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\9.000' - SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\target\bins' +# PS5 related --> win10-ps5:v4 ships SDK 13.000 but defaults SCE_PROSPERO_SDK_DIR to an unsupported version, so set it explicitly. + SCE_PROSPERO_SDK_DIR: 'C:\Program Files (x86)\SCE\Prospero SDKs\13.000' + SHADER_COMPILER_PATH: '${SCE_PROSPERO_SDK_DIR}\host_tools\bin' SCE_ROOT_DIR: 'C:\Program Files (x86)\SCE' -# Switch related - NINTENDO_SDK_ROOT: 'C:\Nintendo\nx_sdk-18_3_0\NintendoSDK' +# Switch related --> Switch is DISABLED in project.metafile (MTT-15636): 6000.x/trunk editors mandate NintendoSDK 22.2.x, which is not installed on win10-switch:v4 (only nintendosdk-en-21_4_0 is). This target path is correct for when PETS ships 22.2.x and Switch is re-enabled. + NINTENDO_SDK_ROOT: 'C:\Nintendo\nintendosdk-en-22_2_0\NintendoSDK' UNITY_NINTENDOSDK_CLI_TOOLS: '${NINTENDO_SDK_ROOT}\Tools\CommandLineTools' artifacts: logs: diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 9c52b691a8..518f8ec2ba 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -111,18 +111,15 @@ test_platforms: flavor: b1.large larger_flavor: b1.xlarge standalone: PS4 - # - name: ps5 --> SEE MTT-12118 - # type: Unity::VM - # image: package-ci/win10-ps5:v4 - # flavor: b1.large - # larger_flavor: b1.xlarge - # standalone: PS5 - # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 - # The NintendoSDK on the win10-switch:v4 Bokken image is incomplete for the SDK - # version the newer editor Playback Engines require: 18_3_0 is missing the - # nintendo-switch-support lib the new toolchain links against, and 21_4_3 is missing the - # Tools\Graphics\NvnTools (GraphicsConverter) libraries needed to package textures. - # Neither is fixable from .yamato; re-enable once Package CI ships a complete SDK on the image. + # ps5: win10-ps5:v4 defaults SCE_PROSPERO_SDK_DIR to an unsupported SDK, so it is overridden to 13.000 in console-standalone-test.yml. + - name: ps5 + type: Unity::VM + image: package-ci/win10-ps5:v4 + flavor: b1.large + larger_flavor: b1.xlarge + standalone: PS5 + # switch --> DISABLED. SEE MTT-15636 (split from MTT-12118). 6000.x/trunk editors mandate NintendoSDK 22.2.x, but package-ci/win10-switch:v4 currently ships only nintendosdk-en-21_4_0. That fixes texture packaging but the native IL2CPP link then fails (undefined symbol std::__1::__hash_memory, from the 22_02 SwitchPlayer.a). Re-enable once PETS publishes a win10-switch image with nintendosdk-en-22_2_x. + # - name: switch # type: Unity::VM # image: package-ci/win10-switch:v4 # flavor: b1.large @@ -147,13 +144,14 @@ test_platforms: flavor: b1.large larger_flavor: b1.xlarge standalone: PS4 - #- name: ps5 --> SEE MTT-12118 - # type: Unity::console::ps5 - # image: package-ci/win10-ps5:v4 - # flavor: b1.large - # larger_flavor: b1.xlarge - # standalone: PS5 - # - name: switch --> TEMPORARILY DISABLED. SEE MTT-12118 (incomplete NintendoSDK on win10-switch:v4 image) + - name: ps5 + type: Unity::console::ps5 + image: package-ci/win10-ps5:v4 + flavor: b1.large + larger_flavor: b1.xlarge + standalone: PS5 + # switch --> DISABLED. SEE MTT-15636 (needs NintendoSDK 22.2.x on the image; see console_build note). + # - name: switch # type: Unity::console::switch # image: package-ci/win10-switch:v4 # flavor: b1.large From e924812bfd09e3394ec0b38b2bb01b38affdef46 Mon Sep 17 00:00:00 2001 From: Netcode team bot Date: Tue, 18 Aug 2026 15:12:24 +0200 Subject: [PATCH 45/64] chore: Updated aspects of Netcode package in anticipation of v2.13.2 release (#4121) --- com.unity.netcode.gameobjects/CHANGELOG.md | 17 +++++++++++------ com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index db85aa2f2a..931ae821f3 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -14,8 +14,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - - ### Deprecated @@ -24,10 +22,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) -- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) -- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) - ### Security @@ -35,6 +29,17 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [2.13.2] - 2026-08-16 + + + +### Fixed + +- Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) +- Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) +- Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) + + ## [2.13.1] - 2026-07-19 ### Added diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 8c1951af95..5220ee5c17 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": "2.13.2", + "version": "2.13.3", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 95e472b475c45c43ff7fc27bd5dc30bd1cd3c8e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 18 Aug 2026 15:43:28 +0200 Subject: [PATCH 46/64] ci: Add pets-svc to pr-description-check exclusion list (#4125) Changde users exclusion to pattern exclusion in pr description check --- .github/workflows/pr-description-validation.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index 748e887db5..eabd029d6f 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -30,17 +30,17 @@ jobs: const pr = context.payload.pull_request; const body = pr.body || ''; - // List of users to skip description validation + // List of user patterns to skip description validation // This should be automations where we don't care that much about the description format - const skipUsersPrefixes = [ - 'unity-renovate', - 'svc-' + const skipUserPatterns = [ + /^unity-renovate/, + /(^|-)svc(-|$)/ ]; - - // If PR author is in the skip list, exit early + + // If PR author matches the skip list, exit early const author = pr.user.login; console.log(`PR author: ${author}`); - if (skipUsersPrefixes.some(prefix => author.startsWith(prefix))) { + if (skipUserPatterns.some(pattern => pattern.test(author))) { console.log(`Skipping PR description check for user: ${author}`); return; } From d64e191fcaf940376a0a56c47b3deea3269ed217 Mon Sep 17 00:00:00 2001 From: "pets-svc[bot]" <299490404+pets-svc[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 09:52:32 +0200 Subject: [PATCH 47/64] trunk shadow changes: com.unity.netcode.gameobjects (#4120) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated changelog and package version for Netcode in anticipation of v2.13.1 release * typo in build automation * trunk shadow changes: com.unity.netcode.gameobjects [skip ci] * Revert CHANGELOG changes * Added UNITY_TEST_FRAMEWORK_1_7_OR_NEWER guard and UnityCoreClrExplicitDisabledAttributeShim.cs to make attribute usage possible * corrected svc bot exclusion --------- Co-authored-by: netcode-automation Co-authored-by: Michał Chrobot Co-authored-by: PETS automation <299490404+pets-svc[bot]@users.noreply.github.com> Co-authored-by: Emma --- .../workflows/pr-description-validation.yml | 2 +- .../BuildAutomation/manifest_update.py | 2 +- .../DeferredDespawningTests.cs | 1 + .../NetworkClientAndPlayerObjectTests.cs | 1 + .../Configuration/SinglePlayerSessions.cs | 1 + .../CustomSerializationDocsTests.cs | 1 + .../NetworkObjectSynchronizationTests.cs | 3 +++ .../NetworkVariable/NetworkListTests.cs | 1 + .../NetworkVariableAnticipationTests.cs | 2 ++ ...NetworkVariableCollectionsChangingTests.cs | 1 + .../NetworkVariableCollectionsTests.cs | 1 + .../NetworkVariableGeneralTests.cs | 1 + .../NetworkVariablePermissionTests.cs | 1 + .../NetworkVariable/NetworkVariableTests.cs | 27 +++++++++++++++++++ .../NetworkVariableTraitsTests.cs | 1 + ...tworkVariableUserSerializableTypesTests.cs | 2 ++ .../NetworkVariable/OwnerPermissionTests.cs | 1 + .../Runtime/PeerDisconnectCallbackTests.cs | 1 + .../Tests/Runtime/Rpc/RpcTests.cs | 1 + .../Runtime/Rpc/RpcTypeSerializationTests.cs | 2 ++ .../NetworkBehaviourReferenceTests.cs | 2 ++ .../Transports/SinglePlayerTransportTests.cs | 1 + .../Unity.Netcode.Runtime.Tests.asmdef | 5 ++++ ...ityCoreClrExplicitDisabledAttributeShim.cs | 14 ++++++++++ ...reClrExplicitDisabledAttributeShim.cs.meta | 11 ++++++++ 25 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index eabd029d6f..fdced1b888 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -34,7 +34,7 @@ jobs: // This should be automations where we don't care that much about the description format const skipUserPatterns = [ /^unity-renovate/, - /(^|-)svc(-|$)/ + /svc/ ]; // If PR author matches the skip list, exit early diff --git a/Tools/scripts/BuildAutomation/manifest_update.py b/Tools/scripts/BuildAutomation/manifest_update.py index 0ad8913b8e..1597e2bc39 100644 --- a/Tools/scripts/BuildAutomation/manifest_update.py +++ b/Tools/scripts/BuildAutomation/manifest_update.py @@ -51,7 +51,7 @@ def main(): # Extract the version, providing a default if not found local_package_version = local_package_data.get('version', 'N/A') - print(f"--> Verified local '{args.package-name}' version is: {local_package_version}") + print(f"--> Verified local '{args.package_name}' version is: {local_package_version}") except FileNotFoundError: print(f"Warning: Could not find package.json at '{local_package_json_path}'") diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index 1902539113..dac15570f7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -53,6 +53,7 @@ protected override void OnServerAndClientsCreated() [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator DeferredDespawning() { // Setup for test diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index 929592a9b0..aabe7522c7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -155,6 +155,7 @@ private bool AllNetworkClientsValidated() /// Validates the same thing when a client late joins and when a client disconnects. ///
[UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149595", "ValidateNetworkClients throws duplicate-key ArgumentException on CoreCLR (DAHost)")] public IEnumerator ValidateNetworkClients() { // Validate the initial clients created diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs index 945807a06c..774debccf0 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs @@ -151,6 +151,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator SwitchTransportTest() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs index 1e6096b187..69dfae8be8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs @@ -173,6 +173,7 @@ private bool ValidateAllAreEqual(StringBuilder errorLog) } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator TestHealthCode() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index 7535e4ffce..b943be4883 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -94,6 +94,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public IEnumerator NetworkObjectDeserializationFailure() { m_CurrentLogLevel = LogLevel.Nothing; @@ -277,6 +278,7 @@ private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject authorit /// will still be initialized properly ///
[UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public IEnumerator NetworkBehaviourSynchronization() { var authority = GetAuthorityNetworkManager(); @@ -319,6 +321,7 @@ public IEnumerator NetworkBehaviourSynchronization() /// A basic validation for the NetworkBehaviour.OnSynchronize method ///
[UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public IEnumerator NetworkBehaviourOnSynchronize() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs index 5f3be93f57..98bb2e8f74 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs @@ -244,6 +244,7 @@ void TestForceUpdateCallback(NetworkListEvent _) // don't extend this please [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator LegacyPredicateTesting() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs index 09d37cb64a..b5b89d02f1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; +using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { @@ -58,6 +59,7 @@ public void SetReanticipateValueRpc(float f) } } + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] internal class NetworkVariableAnticipationTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs index ce0b7d378c..1b61c05bca 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs @@ -17,6 +17,7 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, CollectionTypes.Dictionary)] [TestFixture(HostOrServer.Server, CollectionTypes.List)] [TestFixture(HostOrServer.Server, CollectionTypes.Dictionary)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] internal class NetworkVariableCollectionsChangingTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs index 69e8727f27..c4cdf5e3e6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs @@ -22,6 +22,7 @@ namespace Unity.Netcode.RuntimeTests ///
[TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] internal class NetworkVariableCollectionsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs index b0f3e8be56..4623351b7b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs @@ -140,6 +140,7 @@ private bool ChangedValueMatches(StringBuilder errorLog) /// instances invoke . ///
[UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator ApplyValueDuringSpawnSequence() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs index 73481fd02c..f1c882685e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs @@ -11,6 +11,7 @@ namespace Unity.Netcode.RuntimeTests { [TestFixtureSource(nameof(TestDataSource))] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] internal class NetworkVariablePermissionTests : NetcodeIntegrationTest { public static IEnumerable TestDataSource() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 4eb9611a75..33178e9d27 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -349,6 +349,7 @@ private void InitializeServerAndClients(HostOrServer useHost) /// Runs generalized tests on all predefined NetworkVariable types ///
[Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void AllNetworkVariableTypes([Values] HostOrServer useHost) { var prefabToSpawn = CreateNetworkObjectPrefab("NetVarTest"); @@ -391,6 +392,7 @@ public void AllNetworkVariableTypes([Values] HostOrServer useHost) } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void ClientWritePermissionTest([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -405,6 +407,7 @@ public void ClientWritePermissionTest([Values] HostOrServer useHost) /// Runs tests that network variables sync on client whatever the local value of . ///
[Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void NetworkVariableSync_WithDifferentTimeScale([Values] HostOrServer useHost, [Values(0.0f, 1.0f, 2.0f)] float timeScale) { Time.timeScale = timeScale; @@ -419,6 +422,7 @@ public void NetworkVariableSync_WithDifferentTimeScale([Values] HostOrServer use } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void FixedString32Test([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -430,6 +434,7 @@ public void FixedString32Test([Values] HostOrServer useHost) } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -449,6 +454,7 @@ bool VerifyClass() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableTemplateClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -467,6 +473,7 @@ bool VerifyClass() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -485,6 +492,7 @@ bool VerifyStructure() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableTemplateStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -503,6 +511,7 @@ bool VerifyStructure() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableTemplateBehaviourClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -525,6 +534,7 @@ bool VerifyClass() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableTemplateBehaviourClassNotReferencedElsewhere([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -543,6 +553,7 @@ bool VerifyClass() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableTemplateBehaviourStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -561,6 +572,7 @@ bool VerifyClass() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableEnum([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -578,6 +590,7 @@ bool VerifyStructure() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestINetworkSerializableClassCallsNetworkSerialize([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -596,6 +609,7 @@ public void TestINetworkSerializableClassCallsNetworkSerialize([Values] HostOrSe } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -610,6 +624,7 @@ public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrS } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestCustomGenericSerialization() { // Just verifies that the ILPP codegen initialized these values for this type. @@ -663,6 +678,7 @@ public void TestUnsupportedManagedTypesThrowExceptions() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() { var variable = new NetworkVariable(); @@ -718,6 +734,7 @@ public void TestUnsupportedUnmanagedTypesThrowExceptions() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestTypesReferencedInSubclassSerializeSuccessfully() { var variable = new NetworkVariableSubclass>(); @@ -733,6 +750,7 @@ public void TestTypesReferencedInSubclassSerializeSuccessfully() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestUnsupportedUnmanagedTypesWithUserSerializationDoNotThrowExceptions() { var variable = new NetworkVariable(); @@ -806,6 +824,7 @@ public void WhenCreatingAnArrayOfNetVars_InitializingVariablesDoesNotThrowAnExce } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public void TestNetworkVariableChangeAndReturnInSameFrame([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -1363,6 +1382,7 @@ private void TestValueTypeNativeHashMap(NativeHashMap te } #endif [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingValueTypeNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1530,6 +1550,7 @@ public void WhenSerializingAndDeserializingValueTypeNetworkVariables_ValuesAreSe } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1873,6 +1894,7 @@ public string ArrayStr(NativeArray arr) where T : unmanaged [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2335,6 +2357,7 @@ public string DictionaryStr(Dictionary list) [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2530,6 +2553,7 @@ public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesA [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2694,6 +2718,7 @@ public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_Valu [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void WhenSerializingAndDeserializingVeryLargeDictionaryNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(ulong), typeof(Vector2), typeof(HashMapKeyClass))] Type keyType, @@ -4810,6 +4835,7 @@ public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeHashMapNetwor #endif [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() { var variable = new NetworkVariable @@ -4846,6 +4872,7 @@ public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() } [Test] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public void TestUnmnagedINetworkSerializableNetworkVariablesDeserializeInPlace() { var variable = new NetworkVariable diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs index 97d3e7a499..45a66c6670 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs @@ -15,6 +15,7 @@ internal class NetworkVariableTraitsComponent : NetworkBehaviour [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.DAHost)] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] internal class NetworkVariableTraitsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 3; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs index c261bdf032..cf9021f463 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs @@ -142,6 +142,7 @@ private bool CheckForClientInstance() where T : WorkingUserNetworkVariableCom } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerialization_ReplicationWorks() { UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in MyTypeOne value) => @@ -182,6 +183,7 @@ public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializatio } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializationViaExtensionMethod_ReplicationWorks() { UserNetworkVariableSerialization.WriteValue = NetworkVariableUserSerializableTypesTestsExtensionMethods.WriteValueSafe; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs index cee5a131c6..1879da0174 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs @@ -98,6 +98,7 @@ protected override void OnServerAndClientsCreated() } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149597", "Unexpected owner write-permission error logged on CoreCLR")] public IEnumerator OwnerPermissionTest() { // create 3 objects diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index a8d0c6a4ef..d273cf9f1f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -104,6 +104,7 @@ private void ClientToDisconnect_OnClientStopped(bool wasHost) } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clientDisconnectType, [Values(1ul, 2ul, 3ul)] ulong disconnectedClient) { m_TargetClientShutdown = false; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs index 23c546e4fa..0ec70b0e21 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs @@ -91,6 +91,7 @@ protected override void OnCreatePlayerPrefab() } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator TestRpcs() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs index ca1ce942ae..f26baeb5d1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs @@ -1125,6 +1125,7 @@ public IEnumerator TestValueTypeNativeList(NativeList firstTest, NativeLis #endif [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator WhenSendingAValueTypeOverAnRpc_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1539,6 +1540,7 @@ public IEnumerator WhenSendingAnArrayOfValueTypesOverAnRpc_ValuesAreSerializedCo } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator WhenSendingANativeArrayOfValueTypesOverAnRpc_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 20e27d6492..fcb3fa5fc7 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -22,6 +22,7 @@ public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrSe #region Tests using non-null NetworkBehaviours and RPCs [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator TestRpc() { yield return SpawnTestPrefabInstance(); @@ -36,6 +37,7 @@ public IEnumerator TestRpc() [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator TestRpcImplicitNetworkBehaviour() { yield return SpawnTestPrefabInstance(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs index 4f198c73d2..33c801cc12 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs @@ -176,6 +176,7 @@ protected override bool CanStartServerAndClients() } [UnityTest] + [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] public IEnumerator StartSinglePlayerAndSpawn() { m_CanStartHost = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef index cde139b07f..ce23c34a2c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef @@ -47,6 +47,11 @@ "name": "Unity", "expression": "6000.1.0a1", "define": "HOSTNAME_RESOLUTION_AVAILABLE" + }, + { + "name": "com.unity.test-framework", + "expression": "1.7.0", + "define": "UNITY_TEST_FRAMEWORK_1_7_OR_NEWER" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs new file mode 100644 index 0000000000..07baa43dc0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs @@ -0,0 +1,14 @@ +#if !UNITY_TEST_FRAMEWORK_1_7_OR_NEWER +using System; + +namespace UnityEngine.TestTools +{ + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + internal sealed class UnityCoreClrExplicitDisabledAttribute : Attribute + { + public UnityCoreClrExplicitDisabledAttribute(string jiraIssue, string reason = null) + { + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta new file mode 100644 index 0000000000..d3dc8d26c9 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b7f17392536340a8b607dd678b2ecd76 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 0f87cad01163a5eff68a42e6e7932517fd69f98a Mon Sep 17 00:00:00 2001 From: "unity-renovate[bot]" <120015202+unity-renovate[bot]@users.noreply.github.com> Date: Wed, 19 Aug 2026 10:49:11 +0200 Subject: [PATCH 48/64] chore(deps): update ci deps updates (#4085) Co-authored-by: unity-renovate[bot] <120015202+unity-renovate[bot]@users.noreply.github.com> --- .yamato/wrench/api-validation-jobs.yml | 4 +- .yamato/wrench/package-pack-jobs.yml | 2 +- .yamato/wrench/preview-a-p-v.yml | 62 +++++++++++++------------- .yamato/wrench/promotion-jobs.yml | 8 ++-- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 60 ++++++++++++------------- .yamato/wrench/wrench_config.json | 2 +- Tools/CI/NGO.Cookbook.csproj | 6 +-- 8 files changed, 73 insertions(+), 73 deletions(-) diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 611902cc9a..207c95dc49 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -60,8 +60,8 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index a40678465b..a5cc623aab 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -29,5 +29,5 @@ package_pack_-_netcode_gameobjects: UPMCI_ACK_LARGE_PACKAGE: 1 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index bafc711428..f660b39a15 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -27,7 +27,7 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -82,10 +82,10 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -140,10 +140,10 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -199,10 +199,10 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -257,10 +257,10 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -315,10 +315,10 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -374,10 +374,10 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (MacOS). preview_apv_-_6000_5_-_macos13: @@ -432,10 +432,10 @@ preview_apv_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). preview_apv_-_6000_5_-_ubuntu2204: @@ -490,10 +490,10 @@ preview_apv_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.5 manifest (Windows). preview_apv_-_6000_5_-_win10: @@ -549,10 +549,10 @@ preview_apv_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (MacOS). preview_apv_-_6000_6_-_macos13arm: @@ -608,10 +608,10 @@ preview_apv_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). preview_apv_-_6000_6_-_ubuntu2204: @@ -666,10 +666,10 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Windows). preview_apv_-_6000_6_-_win10: @@ -725,10 +725,10 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (MacOS). preview_apv_-_6000_7_-_macos13arm: @@ -784,10 +784,10 @@ preview_apv_-_6000_7_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (Ubuntu). preview_apv_-_6000_7_-_ubuntu2204: @@ -842,10 +842,10 @@ preview_apv_-_6000_7_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Functional tests for dependents found in the latest 6000.7 manifest (Windows). preview_apv_-_6000_7_-_win10: @@ -901,8 +901,8 @@ preview_apv_-_6000_7_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 06dda4cdcf..336bd6b080 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -183,10 +183,10 @@ publish_dry_run_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -365,9 +365,9 @@ publish_netcode_gameobjects: unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index 343a1a9b85..c4f798e8c2 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 88d03e589b..4a7b6ba365 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -69,10 +69,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -139,10 +139,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -209,10 +209,10 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -279,10 +279,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -349,10 +349,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -419,10 +419,10 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -489,10 +489,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -559,10 +559,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -629,10 +629,10 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -700,10 +700,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -770,10 +770,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -840,10 +840,10 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -911,10 +911,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -981,10 +981,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects @@ -1051,10 +1051,10 @@ validate_-_netcode_gameobjects_-_6000_7_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.2.1.0 + UPMPVP_CONTEXT_WRENCH: 3.3.1.0 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.2.1.0 + Wrench: 3.3.1.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index 96b58ebf4f..fb5e12dc7b 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "3.2.1.0", + "wrench_version": "3.3.1.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index e47057e13d..d77b3d4bdb 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,11 +8,11 @@ - - + + - + From 0761334c5074ccd94054308b021d2a2e7eab2097 Mon Sep 17 00:00:00 2001 From: Emma Date: Thu, 20 Aug 2026 22:25:12 -0400 Subject: [PATCH 49/64] chore: Enable U-PR bot (#4138) --- .pr_agent.toml | 9 +- .../SceneManagementSynchronizationTests.cs | 124 +++++++++--------- 2 files changed, 70 insertions(+), 63 deletions(-) diff --git a/.pr_agent.toml b/.pr_agent.toml index 49734d8c44..71d8783c0c 100644 --- a/.pr_agent.toml +++ b/.pr_agent.toml @@ -1,2 +1,9 @@ +# This is the configuration for the u-pr bot +# https://github.cds.internal.unity3d.com/unity/u-pr +# For configuration of this file: +# https://developer.portal.internal.unity.com/catalog/default/component/u-pr/docs/configuration/automatic_runs/ + [github_app] -pr_commands = [] +handle_pr_actions = ['opened', 'ready_for_review'] # PR events that auto-run pr_commands +pr_commands = ["/harness-review"] # what runs on those events +reviewer_commands = ["/harness-review"] # what runs when @u-pr is added as reviewer diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneManagementSynchronizationTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneManagementSynchronizationTests.cs index e5c4e6716a..0fb1ce61c3 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneManagementSynchronizationTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneManagementSynchronizationTests.cs @@ -6,6 +6,7 @@ using Unity.Collections; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; using UnityEngine.TestTools; namespace TestProject.RuntimeTests @@ -24,6 +25,7 @@ public SceneManagementSynchronizationTests(HostOrServer hostOrServer) : base(hos private struct ExpectedEvent { + public bool IsSceneEvent; public SceneEvent SceneEvent; public ConnectionEventData ConnectionEvent; } @@ -51,13 +53,19 @@ private void AssertEventMatchesExpectedEvent(Action predicate, if (m_ExpectedEventQueue.Count > 0) { var expectedEvent = m_ExpectedEventQueue.Dequeue(); - predicate(expectedEvent); + try + { + predicate(expectedEvent); + } + catch (Exception failure) + { + Debug.LogException(failure); + } } else { Assert.Fail($"Received unexpected event at index {s_NumEventsProcessed}: {eventType}"); } - s_NumEventsProcessed++; } @@ -100,42 +108,30 @@ public IEnumerator SynchronizationCallbacks_Authority() var expectedClientId = GetNonAuthorityNetworkManager().LocalClientId + 1; // Setup expected events - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new SceneEvent() { - SceneEvent = new SceneEvent() - { - SceneEventType = SceneEventType.Synchronize, - ClientId = expectedClientId - }, + SceneEventType = SceneEventType.Synchronize, + ClientId = expectedClientId }); - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new SceneEvent() { - SceneEvent = new SceneEvent() - { - SceneEventType = SceneEventType.SynchronizeComplete, - ClientId = expectedClientId, - }, + SceneEventType = SceneEventType.SynchronizeComplete, + ClientId = expectedClientId, }); - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new ConnectionEventData() { - ConnectionEvent = new ConnectionEventData() - { - EventType = ConnectionEvent.ClientConnected, - ClientId = expectedClientId, - } + EventType = ConnectionEvent.ClientConnected, + ClientId = expectedClientId, }); if (m_UseHost) { - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new ConnectionEventData() { - ConnectionEvent = new ConnectionEventData() - { - EventType = ConnectionEvent.PeerConnected, - ClientId = expectedClientId, - } + EventType = ConnectionEvent.PeerConnected, + ClientId = expectedClientId, }); } @@ -158,31 +154,22 @@ public IEnumerator SynchronizationCallbacks_NonAuthority() var expectedPeerClientIds = m_UseHost ? new[] { authorityId, peerClientId } : new[] { peerClientId }; // Setup expected events - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new SceneEvent() { - SceneEvent = new SceneEvent() - { - SceneEventType = SceneEventType.Synchronize, - ClientId = expectedClientId, - }, + SceneEventType = SceneEventType.Synchronize, + ClientId = expectedClientId, }); - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new ConnectionEventData() { - ConnectionEvent = new ConnectionEventData() - { - EventType = ConnectionEvent.ClientConnected, - ClientId = expectedClientId, - PeerClientIds = new NativeArray(expectedPeerClientIds.ToArray(), Allocator.Persistent), - } + EventType = ConnectionEvent.ClientConnected, + ClientId = expectedClientId, + PeerClientIds = new NativeArray(expectedPeerClientIds.ToArray(), Allocator.Persistent), }); - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + AddExpectedEvent(new SceneEvent() { - SceneEvent = new SceneEvent() - { - SceneEventType = SceneEventType.SynchronizeComplete, - ClientId = expectedClientId, - }, + SceneEventType = SceneEventType.SynchronizeComplete, + ClientId = expectedClientId, }); Assert.Null(m_ManagerToTest, "m_ManagerToTest should be null as we should be testing newly created client"); @@ -203,27 +190,22 @@ public IEnumerator LateJoiningClient_PeerCallbacks() var nonAuthority = GetNonAuthorityNetworkManager(); var expectedClientId = nonAuthority.LocalClientId + 1; SetManagerToTest(nonAuthority); + // Setup expected events - if (m_UseCmbService) + AddExpectedEvent(new ConnectionEventData() { - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() - { - SceneEvent = new SceneEvent() - { - SceneEventType = SceneEventType.SynchronizeComplete, - ClientId = expectedClientId, - }, - }); - } + EventType = ConnectionEvent.PeerConnected, + ClientId = expectedClientId, + }); - m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + if (m_UseCmbService) { - ConnectionEvent = new ConnectionEventData() + AddExpectedEvent(new SceneEvent() { - EventType = ConnectionEvent.PeerConnected, + SceneEventType = SceneEventType.SynchronizeComplete, ClientId = expectedClientId, - } - }); + }); + } ////////////////////////////////////////// // Testing event notifications @@ -233,16 +215,34 @@ public IEnumerator LateJoiningClient_PeerCallbacks() Assert.IsEmpty(m_ExpectedEventQueue, "Not all expected callbacks were received"); } + private void AddExpectedEvent(SceneEvent expectedEvent) + { + m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + { + IsSceneEvent = true, + SceneEvent = expectedEvent, + }); + } + + private void AddExpectedEvent(ConnectionEventData expectedEvent) + { + m_ExpectedEventQueue.Enqueue(new ExpectedEvent() + { + IsSceneEvent = false, + ConnectionEvent = expectedEvent, + }); + } + private static void ValidateSceneEventsAreEqual(ExpectedEvent expectedEvent, SceneEvent sceneEvent) { - Assert.NotNull(expectedEvent.SceneEvent, $"Received unexpected scene event {sceneEvent.SceneEventType} at index {s_NumEventsProcessed}"); + Assert.That(expectedEvent.IsSceneEvent, Is.True, $"Received unexpected scene event {sceneEvent.SceneEventType} at index {s_NumEventsProcessed}. Expected connection event: {expectedEvent.ConnectionEvent.EventType}"); AssertField(expectedEvent.SceneEvent.SceneEventType, sceneEvent.SceneEventType, nameof(sceneEvent.SceneEventType), sceneEvent.SceneEventType); AssertField(expectedEvent.SceneEvent.ClientId, sceneEvent.ClientId, nameof(sceneEvent.ClientId), sceneEvent.SceneEventType); } private static void ValidateConnectionEventsAreEqual(ExpectedEvent expectedEvent, ConnectionEventData eventData) { - Assert.NotNull(expectedEvent.ConnectionEvent, $"Received unexpected connection event {eventData.EventType} at index {s_NumEventsProcessed}"); + Assert.That(expectedEvent.IsSceneEvent, Is.False, $"Received unexpected connection event {eventData.EventType} at index {s_NumEventsProcessed}. Expected scene event: {expectedEvent.ConnectionEvent.EventType}"); AssertField(expectedEvent.ConnectionEvent.EventType, eventData.EventType, nameof(eventData.EventType), eventData.EventType); AssertField(expectedEvent.ConnectionEvent.ClientId, eventData.ClientId, nameof(eventData.ClientId), eventData.EventType); From 407d54f2a32153a3484f75c00dc2a56dee920f0d Mon Sep 17 00:00:00 2001 From: Simon Lemay Date: Mon, 24 Aug 2026 10:05:18 -0400 Subject: [PATCH 50/64] feat: Disable PMTUD if using DA (#4126) * feat: Disable PMTUD if using DA * Remove empty spaces --------- Co-authored-by: Emma --- .../Runtime/Transports/UTP/UnityTransport.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs index cac615f394..f8ce9d815c 100644 --- a/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs +++ b/com.unity.netcode.gameobjects/Runtime/Transports/UTP/UnityTransport.cs @@ -520,6 +520,14 @@ public NetworkSettings GetDefaultNetworkSettings() receiveQueueCapacity: m_MaxPacketQueueSize, heartbeatTimeoutMS: m_HeartbeatTimeoutMS); + // Disable PMTU discovery if using DA, leave it at its default otherwise. + if (OnCurrentTopology() == NetworkTopologyTypes.DistributedAuthority) + { + var config = settings.GetNetworkConfigParameters(); + config.performPathMtuDiscovery = false; + settings.AddRawParameterStruct(ref config); + } + // Configure Relay if in use. if (m_ProtocolType == ProtocolType.RelayUnityTransport) { From e96e76f66af76e6c2d7c2710689d8c66e94c3183 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= Date: Wed, 19 Aug 2026 13:05:57 +0200 Subject: [PATCH 51/64] Changelog update --- com.unity.netcode.gameobjects/CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 931ae821f3..84c388677c 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -31,10 +31,10 @@ Additional documentation and release notes are available at [Multiplayer Documen ## [2.13.2] - 2026-08-16 - - ### Fixed +- Issue where in a distributed authority session changing a NetworkVariable prior to changing ownership in the same call-stack would result in the NetworkVariable not being synchronized. (#4107) +- Resolved GC allocations in some hot paths. (#4119) - Issue with not being able to spawn initially disabled in-scene placed objects. (#4093) - Issue with pre-instantiated network prefab instances being marked as in-scene placed. Now pre-instantiated network prefabs are dynamically spawned. (#4093) - Issue where a user could spawn runtime created `NetworkObject` that has a GlobalObjectIdHash of zero. These are not valid instances and will no longer be allowed to spawn. (#4093) From 11f577642353d64dfc5c1345e3a245fcc85e565a Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 27 Aug 2026 09:11:52 -0500 Subject: [PATCH 52/64] test: Rewrite TransformInterpolationTests to remove its random failures (#4147) Overhauling this test since it has been problematic for a long time. The root cause is that the values applied ended forming a discontinuously-linear/saw-tooth motion which makes it very problematic to distinguish a fault from an actual valid interpolation. The actual solution is to use a smoothed sinusoidal wave that forms a continuous motion that is discernable between a fault and the motion being applied. There were additional order of operation issues that --- .../Runtime/TransformInterpolationTests.cs | 311 +++++++++--------- 1 file changed, 161 insertions(+), 150 deletions(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs index 4e29237a82..53fd7a48f6 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TransformInterpolationTests.cs @@ -5,230 +5,241 @@ using UnityEngine; using UnityEngine.TestTools; - namespace Unity.Netcode.RuntimeTests { + /// + /// Drives a continuous world space motion on the authority and samples the resulting world + /// space position on the non-authority. + /// + /// + /// The moving instance is parented to an object held at , + /// so the local and world space representations of the same point differ by ~1000 units. A + /// non-authority that applies a local space value as a world space value (or the reverse) lands + /// roughly that far from where it belongs, which is what the sampling below looks for. + /// internal class TransformInterpolationObject : NetworkTransform { - public static bool TestComplete = false; - // Set the minimum threshold which we will use as our margin of error -#if UNITY_EDITOR - public const float MinThreshold = 0.00555555f; -#else - // Add additional room for error on console tests - public const float MinThreshold = 0.009999f; -#endif - - private const int k_TargetLocalSpaceToggles = 10; + // The motion is a sine wave, which keeps the authority's position continuous. A + // discontinuous motion (i.e. a saw tooth) cannot be told apart from an interpolation fault, + // since both show up on the non-authority as a large single frame delta. + public const float Amplitude = 1.0f; + public const float MotionPeriod = 2.0f; + + // How far outside of the motion's band the non-authority is allowed to sit. Budget: + // - PositionThreshold, since the authority suppresses updates until it has moved that far. + // - Interpolation lag, as the non-authority renders several ticks behind the authority and + // so trails it by up to (peak speed * that time), the peak speed of the motion above + // being Amplitude * 2*pi / MotionPeriod. + // - A hitched frame under CI load, which adds another frame of trailing distance. + // A world/local space fault puts the instance ~1000 units out, so this stays three orders + // of magnitude below a real failure. + public const float PositionTolerance = 0.25f; + + // Deliberately not a factor of MotionPeriod, so the transitions land on a different point + // of the motion each time instead of repeatedly hitting the same phase. + private const float k_ToggleInterval = 0.35f; - public bool CheckPosition; public bool IsMoving; public bool IsFixed; + public bool CheckPosition; - private float m_FrameRateFractional; - private bool m_CurrentLocalSpace; - - private int m_LocalSpaceToggles; - private int m_LastFrameCount; - - public bool ReachedTargetLocalSpaceTransitionCount() - { - TestComplete = m_LocalSpaceToggles >= k_TargetLocalSpaceToggles; - return TestComplete; - } + public int LocalSpaceToggles; + public float MinimumSampled; + public float MaximumSampled; + public float WorstDeviation; + public int SamplesTaken; - protected override void OnInitialize(ref NetworkTransformState replicatedState) - { - m_LocalSpaceToggles = 0; - m_FrameRateFractional = 1.0f / Application.targetFrameRate; - PositionThreshold = MinThreshold; - SetMaxInterpolationBound(1.0f); - base.OnInitialize(ref replicatedState); - } - - private int m_StartFrameCount; + private bool m_IsToggling; + private double m_MotionStartTime; + private double m_NextToggleTime; public void StartMoving() { - m_StartFrameCount = Time.frameCount; + m_MotionStartTime = NetworkManager.LocalTime.Time; IsMoving = true; } - public void StopMoving() + /// + /// Kept separate from so that no transition happens while the + /// non-authority is still converging on the motion. + /// + public void StartToggling() { - IsMoving = false; + m_NextToggleTime = NetworkManager.LocalTime.Time + k_ToggleInterval; + m_IsToggling = true; } - private const int k_MaxThresholdFailures = 4; - private int m_ExceededThresholdCount; + public void StartSampling() + { + MinimumSampled = float.MaxValue; + MaximumSampled = float.MinValue; + WorstDeviation = 0.0f; + SamplesTaken = 0; + CheckPosition = true; + } + /// + /// Invoked by during + /// and only for non-authority instances, so the interpolated position for this frame has + /// already been applied by the time the sampling below runs. + /// public override void OnUpdate() { base.OnUpdate(); - - // Check the position of the nested object on the client - if (CheckPosition) + if (!CheckPosition) { - if (transform.position.y < -MinThreshold || transform.position.y > Application.targetFrameRate + MinThreshold) - { - // Temporary work around for this test. - // Really, this test needs to be completely re-written. - m_ExceededThresholdCount++; - // If we haven't corrected ourselves within the maximum number of updates then throw an error. - if (m_ExceededThresholdCount > k_MaxThresholdFailures) - { - Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}]."); - } - } - else - { - // If corrected, then reset our count - m_ExceededThresholdCount = 0; - } + return; } + + var positionY = transform.position.y; + MinimumSampled = Mathf.Min(MinimumSampled, positionY); + MaximumSampled = Mathf.Max(MaximumSampled, positionY); + WorstDeviation = Mathf.Max(WorstDeviation, Mathf.Abs(positionY) - Amplitude); + SamplesTaken++; } + /// + /// Authority side motion. Authority instances are excluded from the update loop's + /// NetworkTransform registration, so this cannot live in . + /// + /// + /// Never invoke from here. It advances the interpolators by + /// , which the update loop already does once per frame for a + /// non-authority instance. + /// private void Update() { - base.OnUpdate(); - - if (!IsSpawned || !CanCommitToTransform || TestComplete) + if (!IsSpawned || !CanCommitToTransform) { return; } + if (IsFixed) + { + transform.position = TransformInterpolationTests.ParentPosition; + return; + } - // Move the nested object on the server - if (IsMoving) + if (!IsMoving) { - Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!"); - - if (m_LastFrameCount == Time.frameCount) - { - Debug.Log($"Detected duplicate frame update count {Time.frameCount}. Ignoring this update."); - return; - } - - m_LastFrameCount = Time.frameCount; - - // Leaving this here for reference. - // If a system is running at a slower frame rate than expected, then the below code could toggle - // the local to world space value at a higher frequency which might not provide enough updates to - // handle interpolating between the transitions. - //var y = Time.realtimeSinceStartup % 10.0f; - //// change the space between local and global every second - //GetComponent().InLocalSpace = ((int)y % 2 == 0); - - // Reduce the total frame count down to the frame rate - var y = (Time.frameCount - m_StartFrameCount) % Application.targetFrameRate; - - // change the space between local and global every time we hit the expected number of frames - // (or every second if running at the target frame rate) - InLocalSpace = y == 0 ? !InLocalSpace : InLocalSpace; - - if (m_CurrentLocalSpace != InLocalSpace) - { - m_LocalSpaceToggles++; - m_CurrentLocalSpace = InLocalSpace; - } - - transform.position = new Vector3(0.0f, (y * m_FrameRateFractional), 0.0f); + return; } - // On the server, make sure to keep the parent object at a fixed position - if (IsFixed) + var localTime = NetworkManager.LocalTime.Time; + + if (m_IsToggling && localTime >= m_NextToggleTime) { - Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!"); - transform.position = new Vector3(1000.0f, 1000.0f, 1000.0f); + InLocalSpace = !InLocalSpace; + LocalSpaceToggles++; + m_NextToggleTime += k_ToggleInterval; } + + var elapsed = (float)(localTime - m_MotionStartTime); + transform.position = new Vector3(0.0f, Amplitude * Mathf.Sin(elapsed * 2.0f * Mathf.PI / MotionPeriod), 0.0f); } } - internal class TransformInterpolationTests : NetcodeIntegrationTest + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class TransformInterpolationTests : IntegrationTestWithApproximation { + internal static readonly Vector3 ParentPosition = new Vector3(1000.0f, 1000.0f, 1000.0f); + + private const int k_TargetLocalSpaceToggles = 10; + + // The non-authority has to cover most of the motion's range for the samples to mean + // anything. An instance that stopped updating sits at a single value and would otherwise + // satisfy every bounds check in the test. + private const float k_MinimumRangeCovered = 0.5f; + protected override int NumberOfClients => 1; private GameObject m_PrefabToSpawn; - private NetworkObject m_SpawnedAsNetworkObject; - private NetworkObject m_SpawnedObjectOnClient; - - private NetworkObject m_BaseAsNetworkObject; - private NetworkObject m_BaseOnClient; + private TransformInterpolationObject m_AuthorityParent; + private TransformInterpolationObject m_AuthorityChild; + private TransformInterpolationObject m_NonAuthorityChild; + public TransformInterpolationTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } protected override void OnServerAndClientsCreated() { m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject"); - var networkTransform = m_PrefabToSpawn.AddComponent(); + m_PrefabToSpawn.AddComponent(); } - private IEnumerator RefreshNetworkObjects() + private IEnumerator SpawnAndParent() { - var clientId = m_ClientNetworkManagers[0].LocalClientId; + var authority = GetAuthorityNetworkManager(); + var nonAuthority = GetNonAuthorityNetworkManager(); + + m_AuthorityParent = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + m_AuthorityChild = SpawnObject(m_PrefabToSpawn, authority).GetComponent(); + + var parentId = m_AuthorityParent.NetworkObject.NetworkObjectId; + var childId = m_AuthorityChild.NetworkObject.NetworkObjectId; + var clientId = nonAuthority.LocalClientId; + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(clientId) && - s_GlobalNetworkObjects[clientId].ContainsKey(m_BaseAsNetworkObject.NetworkObjectId) && - s_GlobalNetworkObjects[clientId].ContainsKey(m_SpawnedAsNetworkObject.NetworkObjectId)); + s_GlobalNetworkObjects[clientId].ContainsKey(parentId) && + s_GlobalNetworkObjects[clientId].ContainsKey(childId)); + AssertOnTimeout($"Timed out waiting for the non-authority to spawn both {nameof(NetworkObject)}s!"); - Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side {nameof(NetworkObject)} ID of {m_SpawnedAsNetworkObject.NetworkObjectId}"); + m_NonAuthorityChild = s_GlobalNetworkObjects[clientId][childId].GetComponent(); - m_BaseOnClient = s_GlobalNetworkObjects[clientId][m_BaseAsNetworkObject.NetworkObjectId]; - // make sure the objects are set with the right network manager - m_BaseOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0]; + Assert.True(m_AuthorityChild.NetworkObject.TrySetParent(m_AuthorityParent.NetworkObject), "Failed to parent the moving instance!"); - 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]; + yield return WaitForConditionOrTimeOut(() => m_NonAuthorityChild.transform.parent != null); + AssertOnTimeout("Timed out waiting for the non-authority instance to be parented!"); } [UnityTest] public IEnumerator TransformInterpolationTest() { - TransformInterpolationObject.TestComplete = false; - // create an object - var spawnedObject = Object.Instantiate(m_PrefabToSpawn); - var baseObject = Object.Instantiate(m_PrefabToSpawn); - baseObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; - baseObject.GetComponent().Spawn(); + yield return SpawnAndParent(); - m_SpawnedAsNetworkObject = spawnedObject.GetComponent(); - m_SpawnedAsNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + m_AuthorityParent.IsFixed = true; - m_BaseAsNetworkObject = baseObject.GetComponent(); - m_BaseAsNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + // The child's world space position only means anything once the parent has settled on + // both instances, since the non-authority reconstructs it through its own parent. + var nonAuthorityParent = m_NonAuthorityChild.transform.parent; + yield return WaitForConditionOrTimeOut(() => Approximately(m_AuthorityParent.transform.position, ParentPosition) && + Approximately(nonAuthorityParent.position, ParentPosition)); + AssertOnTimeout($"Timed out waiting for the parent instances to settle at {ParentPosition}!"); - m_SpawnedAsNetworkObject.TrySetParent(baseObject); + m_AuthorityChild.StartMoving(); - m_SpawnedAsNetworkObject.Spawn(); + // The child rides its parent out at ~1000 until the motion starts, so wait for the + // non-authority to close that distance before sampling. Its approach to the first + // authoritative position is not what this test measures. + yield return WaitForConditionOrTimeOut(() => Mathf.Abs(m_NonAuthorityChild.transform.position.y) <= TransformInterpolationObject.Amplitude); + AssertOnTimeout("Timed out waiting for the non-authority instance to converge on the authority's motion!"); - yield return RefreshNetworkObjects(); + m_NonAuthorityChild.StartSampling(); + m_AuthorityChild.StartToggling(); - m_SpawnedAsNetworkObject.TrySetParent(baseObject); - var spawnedObjectNetworkTransform = spawnedObject.GetComponent(); - baseObject.GetComponent().IsFixed = true; - spawnedObject.GetComponent().StartMoving(); + var timeOutHelper = new TimeoutFrameCountHelper(10); + yield return WaitForConditionOrTimeOut(() => m_AuthorityChild.LocalSpaceToggles >= k_TargetLocalSpaceToggles, timeOutHelper); + m_NonAuthorityChild.CheckPosition = false; + AssertOnTimeout($"Failed to reach {k_TargetLocalSpaceToggles} local to world space transitions in the given time!", timeOutHelper); - const float maxPlacementError = 0.01f; + VerboseDebug($"[{nameof(TransformInterpolationTest)}] Toggles ({m_AuthorityChild.LocalSpaceToggles}) | Samples ({m_NonAuthorityChild.SamplesTaken}) | " + + $"Range ({m_NonAuthorityChild.MinimumSampled} to {m_NonAuthorityChild.MaximumSampled}) | Worst deviation ({m_NonAuthorityChild.WorstDeviation}) | " + + $"Frames ({timeOutHelper.GetFrameCount()}) | Elapsed ({timeOutHelper.GetTimeElapsed()})"); - // Wait for the base object to place itself on both instances - while (m_BaseOnClient.transform.position.y < 1000 - maxPlacementError || - m_BaseOnClient.transform.position.y > 1000 + maxPlacementError || - baseObject.transform.position.y < 1000 - maxPlacementError || - baseObject.transform.position.y > 1000 + maxPlacementError) - { - yield return new WaitForSeconds(0.01f); - } + Assert.Greater(m_NonAuthorityChild.SamplesTaken, 0, "The non-authority instance was never updated!"); - m_SpawnedObjectOnClient.GetComponent().CheckPosition = true; + Assert.LessOrEqual(m_NonAuthorityChild.WorstDeviation, TransformInterpolationObject.PositionTolerance, + $"The non-authority instance left the expected world space band of [+/- {TransformInterpolationObject.Amplitude}] by " + + $"{m_NonAuthorityChild.WorstDeviation}, which exceeds the tolerance of {TransformInterpolationObject.PositionTolerance}. " + + $"Sampled range was {m_NonAuthorityChild.MinimumSampled} to {m_NonAuthorityChild.MaximumSampled}."); - // Test that interpolation works correctly for ~10 seconds or 10 local to world space transitions while moving - // Increasing this duration gives you the opportunity to go check in the Editor how the objects are setup - // and how they move - var timeOutHelper = new TimeoutFrameCountHelper(10); - yield return WaitForConditionOrTimeOut(spawnedObjectNetworkTransform.ReachedTargetLocalSpaceTransitionCount, timeOutHelper); - VerboseDebug($"[TransformInterpolationTest] Wait condition reached or timed out. Frame Count ({timeOutHelper.GetFrameCount()}) | Time Elapsed ({timeOutHelper.GetTimeElapsed()})"); - AssertOnTimeout($"Failed to reach desired local to world space transitions in the given time!", timeOutHelper); + var rangeCovered = (m_NonAuthorityChild.MaximumSampled - m_NonAuthorityChild.MinimumSampled) / (2.0f * TransformInterpolationObject.Amplitude); + Assert.GreaterOrEqual(rangeCovered, k_MinimumRangeCovered, + $"The non-authority instance only covered {rangeCovered:P0} of the authority's motion, so it was not tracking it."); } } } From 2ea75a36bf498658b2c95dc469307af1182b6880 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Thu, 27 Aug 2026 18:16:40 -0500 Subject: [PATCH 53/64] fix: Lerp smoothing frame rate dependence and freeze at maximum interpolation time (#4130) * fix: Lerp smoothing frame rate dependence and freeze at maximum interpolation time The lerp smoothing pass used by the Lerp and SmoothDampening interpolation types applied a fixed factor of 1.0 minus the maximum interpolation time once per frame, with no delta time. The wall clock smoothing rate therefore scaled with the frame rate, so the same setting smoothed by different amounts on different hardware. The factor is now raised to the number of 60fps reference frames elapsed, which makes the rate a function of elapsed time. Results at 60fps are unchanged for every legal setting. Separately, a maximum interpolation time of 1.0 (the upper bound of the inspector range) produced a factor of exactly 0, so the interpolated value never advanced and the transform stopped moving entirely on all three axes. The retained portion is now clamped just below 1.0. This is an independent defect, as raising 1.0 to any power is still 1.0. The LegacyLerp path was already frame rate correct and is unchanged. The documentation on the lerp smoothing fields described the LegacyLerp formula for all interpolation types and has been corrected. * update updating the PR number. * fix: Only substitute the retention limit at a maximum interpolation time of 1.0 Clamping to k_MaximumLerpSmoothRetention flattened everything above it, so 0.995 smoothed at the same rate as 0.99 and stopped honoring "higher is smoother". Clamp to the legal range first and substitute only at the freezing endpoint. Also corrects the lerp smoothing remarks: LegacyLerp still applies deltaTime / MaximumInterpolationTime per frame, so the frame rate independence claim only holds for Lerp and SmoothDampening. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../BufferedLinearInterpolator.cs | 41 +++++++- .../Runtime/Components/NetworkTransform.cs | 22 ++++- .../Tests/Editor/InterpolatorTests.cs | 98 +++++++++++++++++++ 4 files changed, 153 insertions(+), 10 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 84c388677c..dddabbfd3b 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) +- Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130) ### Security diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs index 8ee288b02f..4cee25748e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/Interpolator/BufferedLinearInterpolator.cs @@ -209,12 +209,23 @@ public void Reset(T currentValue) internal bool LerpSmoothEnabled; /// - /// Determines how much smoothing will be applied to the 2nd lerp when using the (i.e. lerping and not smooth dampening). + /// The frame rate that is relative to when lerp smoothing. + /// + private const float k_LerpSmoothReferenceFrameRate = 60.0f; + + /// + /// Keeps a of 1.0f from retaining the entire delta each frame, + /// which would stop the value from ever advancing towards the target. + /// + private const float k_MaximumLerpSmoothRetention = 0.99f; + + /// + /// Determines how much smoothing will be applied to the 2nd lerp. /// /// - /// There's two factors affecting interpolation:
- /// - Buffering: Which can be adjusted in set in the .
- /// - Interpolation time: The divisor applied to delta time where the quotient is used as the lerp time. + /// Higher values are smoother, lower values are more precise. The amount of smoothing applied is + /// frame rate independent.
+ /// Buffering also affects interpolation and can be adjusted via . ///
[Range(0.016f, 1.0f)] public float MaximumInterpolationTime = 0.1f; @@ -420,6 +431,26 @@ internal void ResetCurrentState() } } + /// + /// Calculates the frame rate independent lerp smoothing "t" for the current frame. + /// + /// + /// Raising the retained portion to the number of reference frames elapsed makes the smoothing rate + /// a function of elapsed time rather than of how often this is called. + /// + /// The last frame time. + /// The lerp smoothing time to apply for this frame. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private float GetLerpSmoothTime(float deltaTime) + { + var retained = Mathf.Clamp01(MaximumInterpolationTime); + if (retained >= 1.0f) + { + retained = k_MaximumLerpSmoothRetention; + } + return 1.0f - Mathf.Pow(retained, deltaTime * k_LerpSmoothReferenceFrameRate); + } + /// /// Interpolation Update to use when smooth dampening is enabled on a . /// @@ -459,7 +490,7 @@ internal T Update(float deltaTime, double tickLatencyAsTime, double minDeltaTime if (LerpSmoothEnabled) { // Apply the smooth lerp to the target to help smooth the final value. - InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, Mathf.Clamp(1.0f - MaximumInterpolationTime, 0.0f, 1.0f)); + InterpolateState.CurrentValue = Interpolate(InterpolateState.CurrentValue, InterpolateState.NextValue, GetLerpSmoothTime(deltaTime)); } else { diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 152cea793b..4a62bcee3e 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -1136,7 +1136,7 @@ public enum InterpolationTypes /// Uses a 1 to 2 phase interpolation approach where:
/// /// The first phase lerps from the previous state update value to the next state update value. - /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of 1.0 minus the respective maximum interpolation time. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time. /// /// /// @@ -1156,7 +1156,7 @@ public enum InterpolationTypes /// Uses a 1 to 2 phase smooth dampening approach where:
/// /// The first phase smooth dampens towards the current tick state update being processed by the accumulated delta time relative to the time to target. - /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a rate of delta time divided by the respective max interpolation time. + /// The second phase (optional) performs lerp smoothing where the current respective transform value is lerped towards the result of the first phase at a frame rate independent rate determined by the respective maximum interpolation time. /// /// /// @@ -1236,7 +1236,11 @@ public enum InterpolationTypes /// Controls position interpolation smoothing. /// /// - /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// When enabled, the will apply a final lerping pass towards + /// the interpolated result at a rate determined by .
+ /// This smoothing pass is frame rate independent under and + /// . keeps its + /// original frame rate dependent smoothing, so the same value does not produce the same result there. ///
public bool PositionLerpSmoothing = true; private bool m_PreviousPositionLerpSmoothing; @@ -1257,7 +1261,11 @@ public enum InterpolationTypes /// Controls rotation interpolation smoothing. /// /// - /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// When enabled, the will apply a final lerping pass towards + /// the interpolated result at a rate determined by .
+ /// This smoothing pass is frame rate independent under and + /// . keeps its + /// original frame rate dependent smoothing, so the same value does not produce the same result there. ///
public bool RotationLerpSmoothing = true; private bool m_PreviousRotationLerpSmoothing; @@ -1278,7 +1286,11 @@ public enum InterpolationTypes /// Controls scale interpolation smoothing. /// /// - /// When enabled, the will apply a final lerping pass where the "t" parameter is calculated by dividing the frame time divided by the . + /// When enabled, the will apply a final lerping pass towards + /// the interpolated result at a rate determined by .
+ /// This smoothing pass is frame rate independent under and + /// . keeps its + /// original frame rate dependent smoothing, so the same value does not produce the same result there. ///
public bool ScaleLerpSmoothing = true; private bool m_PreviousScaleLerpSmoothing; diff --git a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs index 666183abe6..a97c2272ed 100644 --- a/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Editor/InterpolatorTests.cs @@ -301,5 +301,103 @@ public void TestDuplicatedValues() Assert.That(interp, Is.EqualTo(2f)); // Since there is no extrapolation, the rest of this test was removed. } + + #region Lerp Smoothing + + // Deliberately not round numbers, so exactly representable values cannot mask a defect. + private const double k_SmoothTickInterval = 1.0d / 30.0d; + private const int k_SmoothTickLatency = 2; + private const float k_SmoothStartValue = 3.17f; + private const float k_SmoothVelocity = 2.3f; + private const double k_SmoothMoveDuration = 1.53d; + private const double k_SmoothTotalDuration = 2.11d; + + /// + /// Drives the lerp and smooth dampening interpolation path with lerp smoothing enabled, where an + /// authority moves at a constant velocity and then holds still while the non-authority renders at + /// . + /// + /// The interpolated value once has elapsed. + private float RunLerpSmoothing(float maximumInterpolationTime, float frameDeltaTime, bool lerp) + { + var interpolator = new BufferedLinearInterpolatorFloat + { + MaximumInterpolationTime = maximumInterpolationTime, + LerpSmoothEnabled = true, + }; + interpolator.ResetTo(k_SmoothStartValue, 0.0d); + + var restValue = k_SmoothStartValue + (float)(k_SmoothVelocity * k_SmoothMoveDuration); + var maxDeltaTime = k_SmoothTickLatency * k_SmoothTickInterval; + var nextTick = 1; + var currentValue = k_SmoothStartValue; + + for (var time = 0.0d; time < k_SmoothTotalDuration; time += frameDeltaTime) + { + // Deliver every state update whose send time has already passed. + while (nextTick * k_SmoothTickInterval <= time) + { + var sentTime = nextTick * k_SmoothTickInterval; + var sentValue = sentTime <= k_SmoothMoveDuration + ? k_SmoothStartValue + (float)(k_SmoothVelocity * sentTime) + : restValue; + interpolator.AddMeasurement(sentValue, sentTime); + nextTick++; + } + + currentValue = interpolator.Update(frameDeltaTime, time - maxDeltaTime, k_SmoothTickInterval, maxDeltaTime, lerp); + } + + return currentValue; + } + + /// + /// Lerp smoothing must still advance the value at 1.0f, the maximum legal value of the + /// family of fields. + /// + [Test] + public void LerpSmoothingDoesNotFreezeAtMaximumInterpolationTime([Values] bool lerp) + { + var result = RunLerpSmoothing(1.0f, 1.0f / 60.0f, lerp); + + Assert.That(result, Is.GreaterThan(k_SmoothStartValue + 1.0f), + $"Interpolated value only advanced {result - k_SmoothStartValue} from {k_SmoothStartValue} over " + + $"{k_SmoothTotalDuration}s of authority motion. The maximum interpolation time froze the transform."); + } + + /// + /// The rate at which lerp smoothing converges must not depend on the frame rate. + /// + [Test] + public void LerpSmoothingIsFrameRateIndependent() + { + // Heavier than the default, where the frame rate dependency is measurable. + const float maximumInterpolationTime = 0.87f; + + var atThirtyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 30.0f, true); + var atTwoFortyFps = RunLerpSmoothing(maximumInterpolationTime, 1.0f / 240.0f, true); + + Assert.That(atThirtyFps, Is.EqualTo(atTwoFortyFps).Within(0.01f), + $"The same elapsed time and interpolation settings produced {atThirtyFps} at 30fps but " + + $"{atTwoFortyFps} at 240fps. The smoothing rate is scaling with the frame rate."); + } + + /// + /// Only 1.0f is substituted for, so settings below it keep their own smoothing rate and a heavier + /// setting stays smoother than a lighter one. + /// + [Test] + public void LerpSmoothingPreservesSettingsBelowTheMaximum() + { + // 0.99f is the retention substituted for 1.0f, so clamping to it would collapse these two. + var lighter = RunLerpSmoothing(0.99f, 1.0f / 60.0f, true); + var heavier = RunLerpSmoothing(0.995f, 1.0f / 60.0f, true); + + Assert.That(heavier, Is.LessThan(lighter), + $"0.995 converged to {heavier} and 0.99 to {lighter} over the same motion. A higher maximum " + + "interpolation time has to retain more of the previous value, so it cannot converge first."); + } + + #endregion } } From 57f2ff63a3239963a833af75b0a1ff42e7740772 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 28 Aug 2026 12:55:06 -0500 Subject: [PATCH 54/64] fix: NetworkTransform interpolation render time (#4133) * test: Add NetworkTransform interpolation render time regression test Adds an integration test that measures how far behind the server clock the state a non-authority NetworkTransform is interpolating towards was sent. Only states sent at or before the render time are eligible to be interpolated towards, and the render time is the server clock minus the tick latency, so that measurement can never be less than the tick latency. It currently is, and goes negative, meaning the interpolator is chasing a state that the server clock says has not happened yet. An in-process integration test has effectively no round trip time, so the test first widens the client's local time buffer to separate LocalTime and ServerTime by a known amount and waits for that separation to take hold. Without it the two clocks sit close enough together that the test would pass regardless of which one the render time is derived from. This commit contains the test only, so it can be run against an unfixed tree. * fix: Derive NetworkTransform interpolation time from the server clock A NetworkTransform state's SentTime comes from its NetworkTick, which is a server tick, but the render time the interpolators were given was derived from LocalTime. That mixes two clocks. LocalTime leads ServerTime, so subtracting the tick latency from it lands the render time back at approximately ServerTime rather than a whole tick latency behind it, and a state's SentTime is floored to a tick boundary on top of that. The render time therefore sat at or ahead of the newest state that could exist and the interpolator had nothing to interpolate towards. Measuring from ServerTime makes the offset the whole tick latency instead of whatever is left of it, and is self correcting: as the round trip time grows the tick latency grows and the render time moves further back with it. This also matches the rest of the component, which already resets the interpolators using ServerTime. This is a no-op on a host or server, where the two clocks are the same, so it only affects clients. GetTickLatencyInSeconds returns an absolute time rather than a duration and had the same defect, so it now derives from ServerTime as well. GetTickLatency is left alone because it returns a tick count rather than a point in time. * docs: Condense interpolation render time comments and changelog Comment and changelog wording only, no behavioral or test logic changes. Trims the explanation in UpdateInterpolation from twenty one lines to six and drops the measurement anecdote and the unfilled Jira placeholder, keeping the reason the server clock is the correct one to measure from. Shortens the test's remarks and constant comments to match the density of the surrounding tests. The removed detail, the measurements behind the fix, and the metrics that were tried and rejected while building the test are recorded outside the repository. * update Adding PR number to changelog entries. * fix: Return a duration from NetworkTransform.GetTickLatencyInSeconds GetTickLatencyInSeconds returned TimeTicksAgo(...).Time, which is an absolute network timestamp rather than a duration, so the value grew for as long as the session ran. It is documented as returning the tick latency in seconds, and NetworkTimeSystem.TickLatency points at it as a way to inspect that latency, so the contract was misleading regardless of which clock it was measured from. It now returns the tick count multiplied by the tick interval. This also takes the clock question out of this method entirely, since a duration does not reference LocalTime or ServerTime. The change to derive interpolation render time from ServerTime now applies only to UpdateInterpolation. Adds integration tests covering the documented contract: the value tracks the tick latency rather than elapsed time, and lengthens by exactly the tick interval for each tick of additional buffering. Both fail against the previous implementation, the second regardless of how long the session has run, since buffering more ticks used to make the reported latency smaller. * style Removing using directive for UnityEngine as it is an unused namespace. * test: Tolerate an adaptive tick latency in the tick latency tests NetworkTimeSystem.TickLatency is recomputed from the averaged round trip time and can legitimately change mid-run. Both tests assumed it would not, and one failed on macOS when it moved from two ticks to three, reporting the value as having gone from 0.0666s to 0.1s. The duration is now only held to being unchanged across samples where the tick latency itself did not change, and the buffer offset test accounts for any tick latency movement between its two samples so that only the buffering is held to an exact figure. Both still fail against the previous absolute timestamp implementation. * test: Address review feedback on the render time and tick latency tests - Drop the redundant HostOrServer fixture argument and the UseCMBService override; Host is the default and a client-server fixture never runs under the CMB service. - Use WaitForSpawnedOnAllOrTimeOut, GetNonAuthorityNetworkManager and WaitForTicks instead of hand rolled equivalents. - There is only ever one connected client, so drop the collections and refer to the single non-authority instance directly. - Fold the two tick latency tests into one and drop the assertion that recomputed the implementation's own formula. What is left is what can actually regress: the value does not drift with session time, and it grows by exactly the ticks added to the interpolation buffer. --- com.unity.netcode.gameobjects/CHANGELOG.md | 2 + .../Runtime/Components/NetworkTransform.cs | 20 +-- ...rkTransformInterpolationRenderTimeTests.cs | 147 ++++++++++++++++++ ...nsformInterpolationRenderTimeTests.cs.meta | 2 + .../NetworkTransformTickLatencyTests.cs | 93 +++++++++++ .../NetworkTransformTickLatencyTests.cs.meta | 2 + 6 files changed, 257 insertions(+), 9 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index dddabbfd3b..cb36aa3858 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Issue where `NetworkTransform` interpolated towards a point in time taken from the local clock rather than the server clock that state updates are stamped on, which starved the interpolator on clients and reduced interpolation to snapping between state updates. (#4133) +- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4133) - Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) - Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130) diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 4a62bcee3e..46b7ef24e5 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -4242,14 +4242,13 @@ internal BufferedLinearInterpolatorQuaternion GetRotationInterpolator() // Non-Authority private void UpdateInterpolation() { - // 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; + // Use the server time, since that is the clock the states being interpolated between are stamped on + // (a state's SentTime is derived from its NetworkTick). Deriving the render time from LocalTime + // subtracts the tick latency from a clock that already leads ServerTime by roughly that much, which + // leaves the render time at or ahead of the newest state that can exist and starves the interpolator. + // Measuring from ServerTime is also self correcting, as the tick latency grows with the round trip + // time. This is a no-op on a host or server, where both clocks are the same. + var timeSystem = m_CachedNetworkManager.ServerTime; 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; @@ -4713,7 +4712,10 @@ internal static float GetTickLatencyInSeconds(NetworkManager networkManager) { if (networkManager.IsListening) { - return (float)networkManager.LocalTime.TimeTicksAgo(networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset).Time; + // The number of ticks the interpolators run behind, as a duration. This is not a point in time: + // it does not grow as the session runs. + var ticksBehind = networkManager.NetworkTimeSystem.TickLatency + InterpolationBufferTickOffset; + return (float)(ticksBehind * networkManager.ServerTime.FixedDeltaTimeAsDouble); } return 0f; } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs new file mode 100644 index 0000000000..02e666ac19 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs @@ -0,0 +1,147 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Validates that the render time a non-authority instance interpolates towards is derived from the same + /// clock that the state updates it is interpolating between are stamped on. + /// + /// + /// Measures how far behind ServerTime the state being interpolated towards was sent. The render time is + /// ServerTime minus the tick latency and only states sent at or before it are eligible, so that measurement + /// can never be less than the tick latency. Deriving the render time from LocalTime eats into that margin by + /// however far the two clocks are apart, and can push the target past ServerTime entirely. + /// + [TestFixture(NetworkTransform.InterpolationTypes.Lerp)] + [TestFixture(NetworkTransform.InterpolationTypes.SmoothDampening)] + internal class NetworkTransformInterpolationRenderTimeTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 1; + + // How far LocalTime is pushed ahead of ServerTime, in ticks. An in-process test has no round trip time + // to separate the two clocks, and this is large enough to exceed NetworkTimeSystem's hard reset + // threshold so the offset snaps instead of converging at its default adjustment ratio. + private const int k_LocalBufferTicks = 12; + + // The separation the clocks must actually reach before any measurement is taken. + private const double k_RequiredLeadTicks = 8.0d; + + // Ticks of authority motion after the clocks have separated, so the interpolator reaches steady state. + private const int k_WarmUpTicks = 20; + + private const int k_SampledFrames = 90; + + // Far enough each tick that every tick produces a state update rather than being filtered out by the + // position threshold. + private const float k_DistancePerTick = 1.37f; + + private readonly NetworkTransform.InterpolationTypes m_InterpolationType; + + private GameObject m_TestPrefab; + private NetworkManager m_AuthorityNetworkManager; + private NetworkTransform m_AuthorityInstance; + private Vector3 m_Direction; + + public NetworkTransformInterpolationRenderTimeTests(NetworkTransform.InterpolationTypes interpolationType) + { + m_InterpolationType = interpolationType; + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("RenderTimeTestObj"); + var networkTransform = m_TestPrefab.AddComponent(); + networkTransform.PositionInterpolationType = m_InterpolationType; + base.OnServerAndClientsCreated(); + } + + private static double GetTickInterval(NetworkManager networkManager) + { + return 1.0d / networkManager.NetworkTickSystem.TickRate; + } + + /// + /// How far LocalTime currently leads ServerTime, expressed in ticks. + /// + private static double GetClockLeadInTicks(NetworkManager networkManager) + { + return (networkManager.LocalTime.Time - networkManager.ServerTime.Time) / GetTickInterval(networkManager); + } + + /// + /// Moves the authority instance once per tick so that a state update is generated every tick. + /// + private void OnNetworkTick() + { + m_AuthorityInstance.transform.position += m_Direction * k_DistancePerTick; + } + + [UnityTest] + public IEnumerator RenderTimeTrailsTheServerClock() + { + m_AuthorityNetworkManager = GetAuthorityNetworkManager(); + m_AuthorityInstance = SpawnObject(m_TestPrefab, m_AuthorityNetworkManager).GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(m_AuthorityInstance.NetworkObject); + AssertOnTimeout($"Not all clients spawned {m_AuthorityInstance.name}!"); + + var nonAuthority = GetNonAuthorityNetworkManager(); + var nonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObject.NetworkObjectId].GetComponent(); + + // Separate the two clocks by a known amount so that which one the render time is derived from is + // actually distinguishable. + nonAuthority.NetworkTimeSystem.LocalBufferSec = k_LocalBufferTicks * GetTickInterval(nonAuthority); + + // Start continuous motion on the authority. + m_Direction = GetRandomVector3(-10, 10).normalized; + m_AuthorityNetworkManager.NetworkTickSystem.Tick += OnNetworkTick; + + // The offset only moves when the client next receives a time sync, so wait for the separation to + // actually take hold rather than assuming it has. + yield return WaitForConditionOrTimeOut(() => GetClockLeadInTicks(nonAuthority) >= k_RequiredLeadTicks); + AssertOnTimeout($"The nonAuthority clock never fell {k_RequiredLeadTicks} ticks behind, so this test " + + "cannot tell the two clocks apart and would pass regardless of which one is used."); + + // Let the interpolator settle at the new separation before measuring. + yield return WaitForTicks(m_AuthorityNetworkManager, k_WarmUpTicks); + + // Sample how far behind ServerTime the state being interpolated towards was sent. + var interpolator = nonAuthorityInstance.GetPositionInterpolator(); + var totalTargetLagTicks = 0.0d; + var totalBuffered = 0; + var samples = 0; + for (int frame = 0; frame < k_SampledFrames; frame++) + { + if (interpolator.InterpolateState.Target.HasValue) + { + var targetLag = nonAuthority.ServerTime.Time - interpolator.InterpolateState.Target.Value.TimeSent; + totalTargetLagTicks += targetLag / GetTickInterval(nonAuthority); + totalBuffered += interpolator.m_BufferQueue.Count; + samples++; + } + yield return null; + } + + m_AuthorityNetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + + Assert.Greater(samples, 0, $"{nonAuthorityInstance.name} never had a state to interpolate towards!"); + + var meanTargetLagTicks = totalTargetLagTicks / samples; + var meanBuffered = totalBuffered / (float)samples; + var tickLatency = nonAuthority.NetworkTimeSystem.TickLatency; + + // Anything less than the tick latency means the render time came from a clock that leads the one + // the states are stamped on. + Assert.GreaterOrEqual(meanTargetLagTicks, tickLatency, + $"[{m_InterpolationType}] {nonAuthorityInstance.name} was interpolating towards a state sent " + + $"{meanTargetLagTicks:F3} ticks behind the server clock, but the render time is the server " + + $"clock minus a tick latency of {tickLatency}, so it should never be less than that. " + + $"(clock lead {GetClockLeadInTicks(nonAuthority):F3} ticks, mean buffered {meanBuffered:F3})"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs.meta new file mode 100644 index 0000000000..2ddeccd2ba --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformInterpolationRenderTimeTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: acef3f08e52a1e143bbffdddb16cdfc5 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs new file mode 100644 index 0000000000..7baac183f0 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs @@ -0,0 +1,93 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Validates that returns what it is documented to + /// return: the tick latency as a duration in seconds. + /// + /// + /// It previously returned TimeTicksAgo(...).Time, which is an absolute network timestamp rather than a + /// duration, so the value grew for as long as the session ran. + /// + internal class NetworkTransformTickLatencyTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + // Ticks of additional buffering applied part way through the test to confirm the returned duration + // tracks the tick latency it is derived from. + private const int k_AddedBufferTicks = 3; + + // Seconds of tolerance when comparing against the expected duration. + private const float k_Tolerance = 0.0005f; + + // The number of samples taken while the session runs, to confirm the value does not drift with time. + private const int k_Samples = 30; + + private int m_OriginalBufferTickOffset; + + protected override IEnumerator OnSetup() + { + m_OriginalBufferTickOffset = NetworkTransform.InterpolationBufferTickOffset; + return base.OnSetup(); + } + + protected override IEnumerator OnTearDown() + { + // This is static, so leaving it modified would leak into every test that runs afterwards. + NetworkTransform.InterpolationBufferTickOffset = m_OriginalBufferTickOffset; + return base.OnTearDown(); + } + + [UnityTest] + public IEnumerator GetTickLatencyInSecondsReturnsADurationNotATimestamp() + { + var client = GetNonAuthorityNetworkManager(); + var tickInterval = (float)client.ServerTime.FixedDeltaTimeAsDouble; + + // A timestamp climbs by roughly a second per second, so sample while the session clock advances + // and hold the value to only moving when the tick latency it is derived from moves. That latency + // is adaptive and can legitimately change mid-run. + var previousTicksBehind = -1; + var previousValue = 0f; + for (int i = 0; i < k_Samples; i++) + { + var ticksBehind = client.NetworkTimeSystem.TickLatency + NetworkTransform.InterpolationBufferTickOffset; + var latency = NetworkTransform.GetTickLatencyInSeconds(client); + + Assert.Greater(latency, 0f, "A latency of zero or less is not a duration this can be measured against."); + if (ticksBehind == previousTicksBehind) + { + Assert.AreEqual(previousValue, latency, k_Tolerance, + $"The reported latency moved from {previousValue}s to {latency}s while the tick latency " + + $"stayed at {ticksBehind} ticks, so it is tracking elapsed time rather than latency."); + } + + previousTicksBehind = ticksBehind; + previousValue = latency; + yield return null; + } + + // Buffering more ticks has to lengthen the reported duration by exactly those ticks. + var latencyBefore = client.NetworkTimeSystem.TickLatency; + var before = NetworkTransform.GetTickLatencyInSeconds(client); + NetworkTransform.InterpolationBufferTickOffset = m_OriginalBufferTickOffset + k_AddedBufferTicks; + yield return null; + + var latencyAfter = client.NetworkTimeSystem.TickLatency; + var after = NetworkTransform.GetTickLatencyInSeconds(client); + + // The adaptive tick latency may also have moved in between, so only the buffering is held to an + // exact figure. + var expectedIncrease = (k_AddedBufferTicks + (latencyAfter - latencyBefore)) * tickInterval; + Assert.AreEqual(expectedIncrease, after - before, k_Tolerance, + $"Adding {k_AddedBufferTicks} ticks of buffering changed the reported latency by " + + $"{after - before}s when a tick is {tickInterval}s, so it should have changed by " + + $"{expectedIncrease}s (tick latency went from {latencyBefore} to {latencyAfter})."); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs.meta new file mode 100644 index 0000000000..51de85178d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformTickLatencyTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 36f3fd2271d60504cbf04455d7ad3908 \ No newline at end of file From 5493a5df817ef7b18165215eca5718529763ce13 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 28 Aug 2026 13:44:41 -0500 Subject: [PATCH 55/64] fix: server not tracking scene handles for pre-loaded scenes (#4145) Scenes that were already loaded when a session starts were added to ScenesLoaded but never to the server-to-client scene handle tables, so UnloadScene passed its ScenesLoaded check and then failed inside RemoveServerClientSceneHandle. The scene unloaded on every peer, but an error was logged and the ScenesLoaded entry was never removed. The client-server branch of the NetworkSceneManager constructor now registers those scenes the same way InitializeScenesLoaded does for distributed authority. NetcodeIntegrationTestHelpers registered the test runner scene in ServerSceneHandleToClientSceneHandle unconditionally, which now collides with the constructor already having added it, so that add is guarded. --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../SceneManagement/NetworkSceneManager.cs | 2 +- .../NetcodeIntegrationTestHelpers.cs | 4 ++- .../NetworkSceneManagerStartupTests.cs | 30 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index cb36aa3858..aec4867142 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed issue where scenes additively loaded before a session started were tracked as loaded on the server but had no scene handle entries, which caused `NetworkSceneManager.UnloadScene` to log an error and leave the scene registered as loaded even though it unloaded on all peers. (#4145) - Issue where `NetworkTransform` interpolated towards a point in time taken from the local clock rather than the server clock that state updates are stamped on, which starved the interpolator on clients and reduced interpolation to snapping between state updates. (#4133) - Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4133) - Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs index e3a5d2cfe4..c7ae07164f 100644 --- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs @@ -847,7 +847,7 @@ internal NetworkSceneManager(NetworkManager networkManager) for (int i = 0; i < SceneManager.sceneCount; i++) { var loadedScene = SceneManager.GetSceneAt(i); - ScenesLoaded.Add(loadedScene.handle, loadedScene); + UpdateServerClientSceneHandle(loadedScene.handle, loadedScene.handle, loadedScene); } SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, NetworkManager); } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs index 99c7f47afc..7abe3dd19e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs @@ -540,7 +540,9 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan } return; } - networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); + + // The server already registers every scene loaded prior to startup, so only add the test runner scene if it is not already there. + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.TryAdd(scene.handle, scene.handle); } } diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs index 28cd0b49d2..968f63e9dc 100644 --- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs +++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/NetworkSceneManagerStartupTests.cs @@ -21,6 +21,7 @@ internal class NetworkSceneManagerStartupTests : NetcodeIntegrationTest { private const string k_ActiveScene = "SessionSynchronize"; private const string k_AdditionalScene = "InSceneNetworkObjectMovesToDDOL"; + private const string k_PreLoadedScene = "EmptyScene1"; private readonly List m_ObjectsInScenes = new List(); private Scene m_OriginalActiveScene; @@ -141,6 +142,35 @@ public IEnumerator AllExistingObjectsAreSpawnedAtStartup([Values] LoadSceneMode AssertOnTimeout("Timed out waiting for objects to spawn on all clients!"); } + /// + /// Validates that a scene additively loaded before the session started is tracked well enough + /// to be unloaded through without error. + /// + [UnityTest] + public IEnumerator UnloadPreLoadedScene() + { + yield return PreLoadScene(k_PreLoadedScene); + var preLoadedScene = m_SceneLoaded; + + m_CanStart = true; + yield return StartServerAndClients(); + + // Scenes loaded before the session started are registered in both the loaded scenes and the + // scene handle tables, otherwise unloading them fails part way through and leaks the entry. + var sceneManager = GetAuthorityNetworkManager().SceneManager; + Assert.IsTrue(sceneManager.ScenesLoaded.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} is not in {nameof(NetworkSceneManager.ScenesLoaded)}!"); + Assert.IsTrue(sceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} is not in {nameof(NetworkSceneManager.ServerSceneHandleToClientSceneHandle)}!"); + + var status = sceneManager.UnloadScene(preLoadedScene); + Assert.AreEqual(SceneEventProgressStatus.Started, status, $"{nameof(NetworkSceneManager.UnloadScene)} returned {status}!"); + + yield return WaitForConditionOrTimeOut(() => !preLoadedScene.isLoaded); + AssertOnTimeout($"Timed out waiting for {k_PreLoadedScene} to unload!"); + + Assert.IsFalse(sceneManager.ScenesLoaded.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} was unloaded but is still in {nameof(NetworkSceneManager.ScenesLoaded)}!"); + Assert.IsFalse(sceneManager.ServerSceneHandleToClientSceneHandle.ContainsKey(preLoadedScene.handle), $"{k_PreLoadedScene} was unloaded but is still in {nameof(NetworkSceneManager.ServerSceneHandleToClientSceneHandle)}!"); + } + #region Scene loading and related methods /// From 95ba20799dd7fae6fb869a09c1e1ffe6262b8981 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Mon, 31 Aug 2026 18:02:34 -0500 Subject: [PATCH 56/64] fix: half float delta position dither (#4128) * fix: half float position encoding manufactures motion on resting objects NetworkDeltaPosition carries the half float rounding loss of each update into the next one, which keeps the average transmitted position accurate while a value is moving. The loss alone is enough to change the encoded delta, so once the value stops moving that mechanism keeps changing what is sent even though the position has not moved. The encoded value alternates between neighbouring representable values and a stationary object is transmitted as one that oscillates. The rounding loss is now only carried forward while the value moves by at least one representable step. MaxDeltaBeforeAdjustment also determined the transmitted resolution, since a half float's step size grows with its magnitude. At 64 the coarsest step was 31.25mm, so objects away from their base position were reproduced in ~3cm increments. At 2 it is 0.977mm. Folding the delta into the base more often costs no bandwidth with reliable deltas because both sides apply the same rule to the same value, and the reconstructed position is unchanged by the fold. UseUnreliableDeltas forces a full precision base synchronization per fold, so those projects will send those more often. Measured on 10 settling physics objects with half float enabled: 28-42mm of oscillation before, none after, matching the same scene with half float disabled. Objects in motion improve as well, peak error dropping from 12.5mm to 0.587mm. Sender and receiver must agree on MaxDeltaBeforeAdjustment, so this is not compatible across builds. NetworkConstants.PROTOCOL_VERSION already participates in the connection config hash, so mismatched versions cannot connect. Co-Authored-By: Claude Opus 5 * test: integration coverage for half float position encoding Two NetcodeIntegrationTest cases, one for an object moving in steps too small for the encoding to represent and one for an object at rest. Both move the authority forwards only and require non-authority instances to follow without ever moving backwards. Interpolation cannot overshoot, so movement opposite to the authority's has to have come from the encoding. That also avoids a tolerance that would need revisiting whenever the resolution changes. Two setup details are needed for these to detect anything. The object has to travel away from the base position established when it spawned, since resolution is fine near the base. It then has to step by an amount the encoding cannot represent before coming to rest, because a position a half float represents exactly leaves no rounding loss and so cannot exhibit the problem: resting on 30.0 produces no backwards movement at all while resting on 30.0007 produces 15.6mm. Verified in both directions. Without the fix all four cases fail on the intended assertion, reporting 7.9mm to 10.1mm of backwards movement. With the fix all four pass. These do not use the time travel harness because the behavior only appears over multiple real state update and interpolation cycles. Co-Authored-By: Claude Opus 5 * Update CHANGELOG for NetworkTransform precision changes Updated changelog entries for NetworkTransform.UseHalfFloatPrecision to reflect changes in issue tracking numbers. * test - update Adding better coverage and adjusting some of the test to better leverage from NetcodeIntegrationTest helper methods. * test - update Moving the NetworkDeltaPositionTests into its own file. * update Bumping the protocol version to assure legacy clients cannot connect to a session with the fixes. While the fixes aren't technically a "breaking change", any projects using the legacy lerp will end up with an offset from the expected final position. Since motion continually feeds the full position (half float or full precision) as deltas this would prevent from "long term drift". Either case, updating the protocol version only assures that clients of a previous version cannot connect to a session with the newer version. * test: Refer to the single non-authority instance directly There is one connected client, so the per instance dictionaries and the loops over m_NetworkManagers only ever held one entry. Resolve the non-authority instance once through GetNonAuthorityNetworkManager and name the two sampling frame counts that were inline literals. * update Removed note about why we don't use time travel (too verbose). Enabling this on the CMB service pass to validate this works as expected against a live session. * test: Drop two NetworkDeltaPosition tests that cover an already covered path ADeltaUnderTheThresholdIsLeftAsADelta asserted nothing the other tests do not: MovingFoldsThePreviousRoundingLossBackIn already requires the delta to stay under the threshold, and UnsynchronizedAxesAreLeftUntouched already requires the synchronized axis to hold the movement. QuantumDropsTheSignBecauseTheLatticeIsSymmetric ran its own value list to assert one thing, so it moves into the loop in QuantumIsTheSmallestChangeTheEncodingCanSee, which already walks the same kind of values. 300f joins that list so no input is lost. The masking it covers is a common path executed by every call, so the coverage score is unchanged either way. --------- Co-authored-by: Claude Opus 5 --- com.unity.netcode.gameobjects/CHANGELOG.md | 3 + .../Components/NetworkDeltaPosition.cs | 48 ++- .../Runtime/Configuration/NetworkConstants.cs | 2 +- .../Tests/Editor/NetworkDeltaPositionTests.cs | 371 ++++++++++++++++++ .../Editor/NetworkDeltaPositionTests.cs.meta | 2 + ...NetworkTransformHalfFloatPrecisionTests.cs | 232 +++++++++++ ...rkTransformHalfFloatPrecisionTests.cs.meta | 11 + 7 files changed, 665 insertions(+), 4 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs.meta create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index aec4867142..92bc6b7e0d 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 ### Changed +- Changed `NetworkTransform.UseHalfFloatPrecision` to synchronize position with a resolution of approximately 1mm regardless of how far an object has travelled. Previously the resolution could degrade to approximately 3cm. This does not increase bandwidth, but projects using `NetworkTransform.UseUnreliableDeltas` will send full precision position updates more often. (#4128) + ### Deprecated @@ -27,6 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen - Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4133) - Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) - Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130) +- Issue where objects using `NetworkTransform.UseHalfFloatPrecision` appeared to jitter on non-authority instances while they were stationary or coming to rest, even though the authority was not moving them. (#4128) ### Security diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs index a780a07230..3bae7eded3 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkDeltaPosition.cs @@ -11,7 +11,14 @@ namespace Unity.Netcode.Components [Serializable] public struct NetworkDeltaPosition : INetworkSerializable { - internal const float MaxDeltaBeforeAdjustment = 64f; + /// + /// How far the delta may grow before it is folded into the base position. + /// + /// + /// This determines the transmitted position resolution, since a half float's step size grows with its + /// magnitude. Keeping the delta small keeps that step small: at 2 the coarsest step is roughly 1mm. + /// + internal const float MaxDeltaBeforeAdjustment = 2f; /// /// The HalfVector3 used to synchronize the delta in position @@ -138,14 +145,29 @@ public void UpdateFrom(ref Vector3 vector3, int networkTick) { CollapsedDeltaIntoBase = false; NetworkTick = networkTick; - DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition; for (int i = 0; i < HalfVector3.Length; i++) { if (HalfVector3.AxisToSynchronize[i]) { + var rawDelta = vector3[i] - CurrentBasePosition[i]; + + // Adding the previous rounding loss back in keeps the average position accurate while the + // value is moving, but it also changes the value being sent. Once the value stops moving + // that is all it does, which makes a stationary object appear to oscillate. + var movedSinceLastSend = Mathf.Abs(vector3[i] - PreviousPosition[i]); + var applyPrecisionLoss = movedSinceLastSend >= HalfPrecisionQuantum(rawDelta); + + DeltaPosition[i] = applyPrecisionLoss ? rawDelta + PrecisionLossDelta[i] : rawDelta; + HalfVector3.Axis[i] = math.half(DeltaPosition[i]); HalfDeltaConvertedBack[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value); - PrecisionLossDelta[i] = DeltaPosition[i] - HalfDeltaConvertedBack[i]; + + // Left unchanged when skipped so it is still applied once movement resumes. + if (applyPrecisionLoss) + { + PrecisionLossDelta[i] = DeltaPosition[i] - HalfDeltaConvertedBack[i]; + } + if (Mathf.Abs(HalfDeltaConvertedBack[i]) >= MaxDeltaBeforeAdjustment) { CurrentBasePosition[i] += HalfDeltaConvertedBack[i]; @@ -165,6 +187,26 @@ public void UpdateFrom(ref Vector3 vector3, int networkTick) } } + /// + /// The smallest change a half float can represent at the magnitude of the value passed in. + /// + /// The value to get the step size for. + /// The distance to the next representable half float value. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static float HalfPrecisionQuantum(float value) + { + // The step size is symmetric about zero, so the sign is dropped. + var magnitude = (ushort)(math.half(value).value & 0x7FFF); + + // Guard only: stepping past the largest finite half float would give infinity. + if (magnitude >= 0x7BFF) + { + return MaxDeltaBeforeAdjustment; + } + + return Mathf.HalfToFloat((ushort)(magnitude + 1)) - Mathf.HalfToFloat(magnitude); + } + /// /// Constructor /// diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConstants.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConstants.cs index b9eed28790..bc862c8fe2 100644 --- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConstants.cs +++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkConstants.cs @@ -5,6 +5,6 @@ namespace Unity.Netcode /// internal static class NetworkConstants { - internal const string PROTOCOL_VERSION = "15.0.0"; + internal const string PROTOCOL_VERSION = "15.1.0"; } } diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs b/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs new file mode 100644 index 0000000000..c93832d030 --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs @@ -0,0 +1,371 @@ +using NUnit.Framework; +using Unity.Collections; +using Unity.Mathematics; +using Unity.Netcode.Components; +using UnityEngine; + +namespace Unity.Netcode.EditorTests +{ + /// + /// Branch coverage for 's encoding math. + /// + internal class NetworkDeltaPositionTests + { + private const int k_Tick = 100; + + // Lossy as a half float, and two of them still fit under the collapse threshold. + private const float k_LossyStep = 0.7f; + + // Past the threshold and exactly representable, so the collapse cannot hinge on rounding. + private const float k_CollapsingStep = NetworkDeltaPosition.MaxDeltaBeforeAdjustment + 0.5f; + + // Off the half float lattice on every axis, so each conversion leaves rounding loss behind. + private static readonly Vector3 k_Base = new Vector3(30.0007f, -12.0003f, 5.0009f); + + private static Vector3 Offset(float amount) + { + return k_Base + new Vector3(amount, amount, amount); + } + + // The transmitted form, so comparisons are against what actually goes on the wire. + private static ushort[] Encoded(NetworkDeltaPosition deltaPosition) + { + return new[] + { + deltaPosition.HalfVector3.Axis.x.value, + deltaPosition.HalfVector3.Axis.y.value, + deltaPosition.HalfVector3.Axis.z.value, + }; + } + + [Test] + public void ConstructorOverloadsProduceTheSameInitialState() + { + var position = k_Base; + var allAxes = math.bool3(true); + + var instances = new[] + { + new NetworkDeltaPosition(position, k_Tick), + new NetworkDeltaPosition(position, k_Tick, allAxes), + new NetworkDeltaPosition(position.x, position.y, position.z, k_Tick), + new NetworkDeltaPosition(position.x, position.y, position.z, k_Tick, allAxes), + }; + + foreach (var instance in instances) + { + Assert.AreEqual(position, instance.GetCurrentBasePosition(), "The base position should be where the object started."); + Assert.AreEqual(Vector3.zero, instance.GetDeltaPosition(), "Nothing has moved yet, so there is no delta."); + Assert.AreEqual(Vector3.zero, instance.PrecisionLossDelta, "No conversion has lost anything yet."); + Assert.AreEqual(k_Tick, instance.NetworkTick, "The construction tick should be recorded."); + Assert.IsFalse(instance.CollapsedDeltaIntoBase, "A zero delta cannot have collapsed."); + Assert.IsFalse(instance.SynchronizeBase, "The base is only synchronized explicitly."); + Assert.AreEqual(allAxes, instance.HalfVector3.AxisToSynchronize, "All axes should be synchronized by default."); + } + } + + [Test] + public void AccessorsReportTheUnderlyingState() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_LossyStep); + deltaPosition.UpdateFrom(ref moved, k_Tick + 1); + + Assert.AreEqual(deltaPosition.CurrentBasePosition, deltaPosition.GetCurrentBasePosition()); + Assert.AreEqual(deltaPosition.DeltaPosition, deltaPosition.GetDeltaPosition()); + Assert.AreEqual(deltaPosition.HalfDeltaConvertedBack, deltaPosition.GetConvertedDelta()); + Assert.AreEqual(deltaPosition.CurrentBasePosition + deltaPosition.DeltaPosition, deltaPosition.GetFullPosition()); + + Assert.AreNotEqual(deltaPosition.GetDeltaPosition().x, deltaPosition.GetConvertedDelta().x, + "The converted delta is the lossy one and should not match the full precision delta."); + } + + [Test] + public void MovingFoldsThePreviousRoundingLossBackIn() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick); + + var firstMove = Offset(k_LossyStep); + deltaPosition.UpdateFrom(ref firstMove, k_Tick + 1); + + var carriedLoss = deltaPosition.PrecisionLossDelta; + Assert.AreNotEqual(0.0f, carriedLoss.x, "A step off the lattice has to leave rounding loss behind."); + + var basePosition = deltaPosition.GetCurrentBasePosition(); + var secondMove = Offset(k_LossyStep * 2.0f); + deltaPosition.UpdateFrom(ref secondMove, k_Tick + 2); + + Assert.IsFalse(deltaPosition.CollapsedDeltaIntoBase, + "Both steps together have to stay under the collapse threshold, or the delta asserted on below is reset to zero."); + + // Folding the loss in is what keeps the average position accurate instead of drifting by a + // fraction of a step per send. + var rawDelta = secondMove.x - basePosition.x; + Assert.AreEqual(rawDelta + carriedLoss.x, deltaPosition.GetDeltaPosition().x, 1e-7f, + "The delta being sent should have the carried rounding loss added to it."); + Assert.AreNotEqual(math.half(rawDelta).value, deltaPosition.HalfVector3.Axis.x.value, + "Folding the loss in has to change the transmitted value, or it would have no effect."); + Assert.AreNotEqual(carriedLoss.x, deltaPosition.PrecisionLossDelta.x, + "The carried loss should be recomputed from the conversion that just happened."); + } + + [Test] + public void StandingStillDoesNotChangeWhatIsSent() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick); + + // Arrive off the lattice, which is where a settling object ends up. + var arrived = Offset(k_LossyStep); + deltaPosition.UpdateFrom(ref arrived, k_Tick + 1); + + var encodedOnArrival = Encoded(deltaPosition); + var lossOnArrival = deltaPosition.PrecisionLossDelta; + Assert.AreNotEqual(0.0f, lossOnArrival.x, "The arrival conversion has to leave rounding loss behind."); + + var currentTick = k_Tick + 2; + var maxTick = k_Tick + 5; + + // Folding the loss back in while stationary is what made resting objects jitter. + while (currentTick <= maxTick) + { + deltaPosition.UpdateFrom(ref arrived, currentTick); + + Assert.AreEqual(encodedOnArrival, Encoded(deltaPosition), + $"The transmitted delta changed on tick {currentTick} while the position did not move."); + Assert.AreEqual(lossOnArrival, deltaPosition.PrecisionLossDelta, + $"The carried loss should be untouched on tick {currentTick} so it still applies once movement resumes."); + currentTick++; + } + } + + [Test] + public void DeltaCollapsesIntoTheBaseAtTheThreshold() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick); + var originalBase = deltaPosition.GetCurrentBasePosition(); + + var moved = Offset(k_CollapsingStep); + deltaPosition.UpdateFrom(ref moved, k_Tick + 1); + + Assert.IsTrue(deltaPosition.CollapsedDeltaIntoBase, "A delta at the threshold should have been folded into the base."); + Assert.AreEqual(0.0f, deltaPosition.GetDeltaPosition().x, "The delta should be reset once it is folded in."); + Assert.AreEqual(0.0f, deltaPosition.GetConvertedDelta().x, "The converted delta should be reset along with it."); + Assert.AreNotEqual(originalBase.x, deltaPosition.GetCurrentBasePosition().x, "The base should have absorbed the delta."); + Assert.AreEqual(moved.x, deltaPosition.GetFullPosition().x, 1e-3f, + "Folding the delta into the base must not move the object it describes."); + } + + [Test] + public void UnsynchronizedAxesAreLeftUntouched() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick, math.bool3(true, false, false)); + + var moved = Offset(k_LossyStep); + deltaPosition.UpdateFrom(ref moved, k_Tick + 1); + + Assert.AreNotEqual(0.0f, deltaPosition.GetDeltaPosition().x, "The synchronized axis should track the movement."); + Assert.AreEqual(0.0f, deltaPosition.GetDeltaPosition().y, "An unsynchronized axis should not produce a delta."); + Assert.AreEqual(0.0f, deltaPosition.GetDeltaPosition().z, "An unsynchronized axis should not produce a delta."); + + // A stale reference here would break the comparison if the axis is synchronized later. + Assert.AreEqual(moved.x, deltaPosition.PreviousPosition.x, "The synchronized axis should record where it was sent from."); + Assert.AreEqual(k_Base.y, deltaPosition.PreviousPosition.y, "An unsynchronized axis should keep its original reference."); + Assert.AreEqual(k_Base.z, deltaPosition.PreviousPosition.z, "An unsynchronized axis should keep its original reference."); + } + + [Test] + public void DecodingOnTheSameTickDoesNotReadTheEncodedAxes() + { + var deltaPosition = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_LossyStep); + deltaPosition.UpdateFrom(ref moved, k_Tick + 1); + + var expected = deltaPosition.GetFullPosition(); + + // Overwriting the encoded axes proves this path returns the already-decoded value rather than + // decoding again, which would apply the same delta twice. + deltaPosition.HalfVector3.Axis = math.half3(new float3(1.9f, 1.9f, 1.9f)); + + Assert.AreEqual(expected, deltaPosition.ToVector3(k_Tick + 1), + "Decoding the tick that was just written should return the position already held."); + } + + [Test] + public void DecodingANewTickAppliesTheDelta() + { + var authority = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_LossyStep); + authority.UpdateFrom(ref moved, k_Tick + 1); + + var receiver = new NetworkDeltaPosition(k_Base, k_Tick) + { + HalfVector3 = authority.HalfVector3, + }; + + var decoded = receiver.ToVector3(k_Tick + 1); + + Assert.AreEqual(authority.GetConvertedDelta().x, receiver.GetDeltaPosition().x, + "The receiver should decode the same delta the authority encoded."); + Assert.AreEqual(k_Base.x + authority.GetConvertedDelta().x, decoded.x, 1e-4f, + "The decoded position should be the base plus the transmitted delta."); + } + + [Test] + public void DecodingCollapsesIntoTheBaseAtTheThreshold() + { + var authority = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_CollapsingStep); + authority.UpdateFrom(ref moved, k_Tick + 1); + + // The send side folds the delta into its own base but leaves the encoded axes holding it, so the + // receiving side has to perform the same fold to end up on the same base. + var receiver = new NetworkDeltaPosition(k_Base, k_Tick) + { + HalfVector3 = authority.HalfVector3, + }; + + var decoded = receiver.ToVector3(k_Tick + 1); + + Assert.AreEqual(0.0f, receiver.GetDeltaPosition().x, "The delta should be reset once it is folded into the base."); + Assert.AreEqual(0, receiver.HalfVector3.Axis.x.value, "The encoded axis should be cleared along with it."); + Assert.AreEqual(authority.GetCurrentBasePosition().x, receiver.GetCurrentBasePosition().x, 1e-4f, + "Both sides must end up on the same base position or they will disagree from here on."); + Assert.AreEqual(moved.x, decoded.x, 1e-3f, "Folding the delta into the base must not move the object."); + } + + [Test] + public void DecodingIgnoresUnsynchronizedAxes() + { + var axesToSynchronize = math.bool3(true, false, false); + var authority = new NetworkDeltaPosition(k_Base, k_Tick, axesToSynchronize); + var moved = Offset(k_LossyStep); + authority.UpdateFrom(ref moved, k_Tick + 1); + + var receiver = new NetworkDeltaPosition(k_Base, k_Tick, axesToSynchronize) + { + HalfVector3 = authority.HalfVector3, + }; + + var decoded = receiver.ToVector3(k_Tick + 1); + + Assert.AreNotEqual(k_Base.x, decoded.x, "The synchronized axis should have moved."); + Assert.AreEqual(k_Base.y, decoded.y, "An unsynchronized axis should stay at the base value."); + Assert.AreEqual(k_Base.z, decoded.z, "An unsynchronized axis should stay at the base value."); + } + + [Test] + public void HalfDeltaRoundTripsWhenTheBaseIsNotSynchronized() + { + var source = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_LossyStep); + source.UpdateFrom(ref moved, k_Tick + 1); + + var result = RoundTrip(source, synchronizeBase: false); + + Assert.AreEqual(Encoded(source), Encoded(result), "The encoded axes should survive the round trip."); + + // Only the half float axes go on the wire here, so the receiver keeps whatever base it had. + Assert.AreEqual(Vector3.zero, result.GetCurrentBasePosition(), "The base should not be transmitted in this mode."); + } + + [Test] + public void FullPrecisionRoundTripsWhenTheBaseIsSynchronized() + { + var source = new NetworkDeltaPosition(k_Base, k_Tick); + var moved = Offset(k_LossyStep); + source.UpdateFrom(ref moved, k_Tick + 1); + + var result = RoundTrip(source, synchronizeBase: true); + + // Synchronizing sends both values at full precision, so this path has to be lossless. + Assert.AreEqual(source.GetDeltaPosition(), result.GetDeltaPosition(), "The delta should round trip exactly."); + Assert.AreEqual(source.GetCurrentBasePosition(), result.GetCurrentBasePosition(), "The base should round trip exactly."); + } + + [Test] + public void QuantumIsTheSmallestChangeTheEncodingCanSee() + { + // Exactly representable, so "one step away" is unambiguous. + var values = new[] { 0.5f, 1.0f, -1.0f, 2.0f, 300.0f, 1024.0f }; + + foreach (var value in values) + { + var quantum = NetworkDeltaPosition.HalfPrecisionQuantum(value); + Assert.Greater(quantum, 0.0f, $"The step size at {value} should be positive."); + + Assert.AreNotEqual(math.half(value).value, math.half(value + quantum).value, + $"A full step from {value} should encode differently, or it is not the step size."); + Assert.AreEqual(math.half(value).value, math.half(value + (quantum * 0.25f)).value, + $"A quarter step from {value} should encode identically, or the step size is too large."); + + // The lattice is symmetric about zero, which is why the sign is dropped. + Assert.AreEqual(quantum, NetworkDeltaPosition.HalfPrecisionQuantum(-value), + $"The step size at {value} and {-value} should be the same."); + } + } + + [Test] + public void QuantumIsGuardedAtTheTopOfTheRange() + { + // 70000f is the finite one: the conversion itself rounds to infinity, which reaches the guard + // by a different path than handing it an infinity outright. + var values = new[] + { + 65504.0f, -65504.0f, 70000.0f, + float.PositiveInfinity, float.NegativeInfinity, float.NaN, + }; + + foreach (var value in values) + { + Assert.AreEqual(NetworkDeltaPosition.MaxDeltaBeforeAdjustment, + NetworkDeltaPosition.HalfPrecisionQuantum(value), + $"{value} is at or past the largest finite half float and should fall back to the maximum delta."); + } + } + + [Test] + public void QuantumIsNeverNonFiniteOrZero() + { + // Why the guard exists: an infinite step size would make the "has it moved?" comparison in + // UpdateFrom false for every input, silently stopping the rounding loss from being applied. + var unguarded = Mathf.HalfToFloat(0x7BFF + 1) - Mathf.HalfToFloat(0x7BFF); + Assert.IsTrue(float.IsInfinity(unguarded) || float.IsNaN(unguarded), + "The unguarded computation at the top of the range should be non-finite, which is why the guard exists."); + + var values = new[] + { + 0.0f, float.Epsilon, 1e-7f, 0.5f, 1.0f, 100.0f, 65503.0f, 65504.0f, -65504.0f, 70000.0f, + float.PositiveInfinity, float.NegativeInfinity, float.NaN, + }; + + foreach (var value in values) + { + var quantum = NetworkDeltaPosition.HalfPrecisionQuantum(value); + Assert.IsFalse(float.IsNaN(quantum) || float.IsInfinity(quantum), $"The step size at {value} should be finite."); + Assert.Greater(quantum, 0.0f, $"The step size at {value} should be positive."); + } + } + + private static NetworkDeltaPosition RoundTrip(NetworkDeltaPosition source, bool synchronizeBase) + { + source.SynchronizeBase = synchronizeBase; + + using var writer = new FastBufferWriter(256, Allocator.Temp); + var writeSerializer = new BufferSerializer(new BufferSerializerWriter(writer)); + source.NetworkSerialize(writeSerializer); + + // Starts from a different state, so a value that failed to arrive shows up as a mismatch. + var result = new NetworkDeltaPosition(Vector3.zero, 0) + { + SynchronizeBase = synchronizeBase, + HalfVector3 = { AxisToSynchronize = source.HalfVector3.AxisToSynchronize }, + }; + + using var reader = new FastBufferReader(writer, Allocator.Temp); + var readSerializer = new BufferSerializer(new BufferSerializerReader(reader)); + result.NetworkSerialize(readSerializer); + + return result; + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs.meta new file mode 100644 index 0000000000..8ef2b73daf --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Editor/NetworkDeltaPositionTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: 4c273f2393c37e64980677d134606e15 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs new file mode 100644 index 0000000000..de3843b7be --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs @@ -0,0 +1,232 @@ +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// Validates that does not introduce motion of its own. + /// + /// + /// Both tests move the authority in one direction only and require the non-authority instance to follow + /// without ever moving backwards. Interpolation cannot overshoot, so any movement opposite to the direction + /// the authority moved came from how the position was encoded. + /// + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.DAHost)] + internal class NetworkTransformHalfFloatPrecisionTests : IntegrationTestWithApproximation + { + protected override int NumberOfClients => 1; + + // Half float resolution gets coarser the further the object is from the base position established when it + // spawned, so it has to travel away from that base for the resolution to be worth testing. + private const float k_TravelDistance = 30.0f; + + private const float k_TravelStep = 1.5f; + + // Moves the object off a position that a half float can represent exactly, which leaves no rounding loss + // behind and so cannot show the problem being tested for. + private const float k_UnrepresentableOffset = 0.0007f; + + // Small enough per update that the encoding cannot represent the change on its own. + private const float k_CreepStep = 0.0005f; + + private const int k_CreepTicks = 60; + + // Frames sampled after the creep, while the last states sent are still being interpolated. + private const int k_SettleFrames = 30; + + // Frames sampled with nothing moving at all. + private const int k_StationaryFrames = 120; + + // Tolerated backwards movement, which is float noise only. Well below the roughly 1mm resolution. + private const float k_MonotonicEpsilon = 1e-5f; + + private GameObject m_TestPrefab; + private NetworkManager m_AuthorityNetworkManager; + private NetworkTransform m_AuthorityInstance; + private NetworkTransform m_NonAuthorityInstance; + + private float m_WorstRegression; + private float m_LastObserved; + + private int m_TicksApplied; + private float m_StepThisPhase; + + public NetworkTransformHalfFloatPrecisionTests(HostOrServer hostOrServer) : base(hostOrServer) + { + } + + protected override void OnServerAndClientsCreated() + { + m_TestPrefab = CreateNetworkObjectPrefab("HalfFloatObj"); + var networkTransform = m_TestPrefab.AddComponent(); + + networkTransform.UseHalfFloatPrecision = true; + networkTransform.Interpolate = true; + + // Lerp smoothing would filter out the movement being tested for. + networkTransform.PositionInterpolationType = NetworkTransform.InterpolationTypes.Lerp; + networkTransform.PositionLerpSmoothing = false; + + // No threshold, so the very small movements used below are actually sent. + networkTransform.PositionThreshold = 0.0f; + + networkTransform.SyncRotAngleX = false; + networkTransform.SyncRotAngleY = false; + networkTransform.SyncRotAngleZ = false; + networkTransform.SyncScaleX = false; + networkTransform.SyncScaleY = false; + networkTransform.SyncScaleZ = false; + + base.OnServerAndClientsCreated(); + } + + private bool NonAuthorityCaughtUp() + { + return Approximately(m_NonAuthorityInstance.transform.position, m_AuthorityInstance.transform.position); + } + + /// + /// Records any movement opposite to the direction the authority is moving. + /// + /// + /// Sampled once per frame rather than once per tick, since the position applied to the transform is what + /// needs to be checked. + /// + private void SampleForRegression() + { + var current = m_NonAuthorityInstance.transform.position.x; + m_WorstRegression = Mathf.Max(m_WorstRegression, m_LastObserved - current); + m_LastObserved = current; + } + + private void BeginSampling() + { + m_WorstRegression = 0.0f; + m_LastObserved = m_NonAuthorityInstance.transform.position.x; + } + + private void AssertNoRegression(string phase) + { + Assert.LessOrEqual(m_WorstRegression, k_MonotonicEpsilon, + $"[{phase}] {m_NonAuthorityInstance.NetworkManager.name} moved {m_WorstRegression} backwards along " + + $"X while the authority only ever moved forwards. Interpolation cannot overshoot, so this motion " + + $"was introduced by the half float position encoding rather than reproduced from the authority."); + } + + /// + /// Advances the authority one step per tick along +X. + /// + /// + /// Driven from the tick event so the position written is the one captured for that same tick. + /// + private void OnNetworkTick() + { + m_TicksApplied++; + var position = m_AuthorityInstance.transform.position; + position.x += m_StepThisPhase; + m_AuthorityInstance.transform.position = position; + } + + private IEnumerator DriveAuthority(float stepPerTick, int ticks) + { + m_TicksApplied = 0; + m_StepThisPhase = stepPerTick; + m_AuthorityNetworkManager.NetworkTickSystem.Tick += OnNetworkTick; + yield return WaitForConditionOrTimeOut(() => m_TicksApplied >= ticks); + m_AuthorityNetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + AssertOnTimeout($"Timed out waiting for {ticks} authority updates (applied {m_TicksApplied})."); + } + + /// + /// Spawns the test object and travels it away from the base position established when it spawned. + /// + private IEnumerator SpawnAndTravel() + { + m_AuthorityNetworkManager = GetAuthorityNetworkManager(); + m_AuthorityInstance = SpawnObject(m_TestPrefab, m_AuthorityNetworkManager).GetComponent(); + + yield return WaitForSpawnedOnAllOrTimeOut(m_AuthorityInstance.NetworkObject); + AssertOnTimeout($"Not all clients spawned {m_AuthorityInstance.name}!"); + + var nonAuthority = GetNonAuthorityNetworkManager(); + m_NonAuthorityInstance = nonAuthority.SpawnManager.SpawnedObjects[m_AuthorityInstance.NetworkObjectId].GetComponent(); + + yield return DriveAuthority(k_TravelStep, (int)(k_TravelDistance / k_TravelStep)); + } + + /// + /// Moves an object away from its base position and then moves it forward in very small steps, requiring + /// the non-authority instance to follow without ever moving backwards. + /// + /// An for the test coroutine. + [UnityTest] + public IEnumerator HalfFloatPrecisionDoesNotInvertMotion() + { + yield return SpawnAndTravel(); + + yield return WaitForConditionOrTimeOut(NonAuthorityCaughtUp); + AssertOnTimeout("The non-authority instance did not catch up to the authority after the travel phase."); + + BeginSampling(); + m_TicksApplied = 0; + m_StepThisPhase = k_CreepStep; + m_AuthorityNetworkManager.NetworkTickSystem.Tick += OnNetworkTick; + while (m_TicksApplied < k_CreepTicks) + { + SampleForRegression(); + yield return null; + } + m_AuthorityNetworkManager.NetworkTickSystem.Tick -= OnNetworkTick; + + // Keep sampling while the last sent states are still being interpolated. + for (var i = 0; i < k_SettleFrames; i++) + { + SampleForRegression(); + yield return null; + } + + AssertNoRegression("creep"); + + // Small movements still have to arrive rather than be discarded. + yield return WaitForConditionOrTimeOut(NonAuthorityCaughtUp); + AssertOnTimeout($"The non-authority instance did not converge on the authority position " + + $"{m_AuthorityInstance.transform.position} after creeping, which means slow motion is being " + + $"discarded rather than transmitted."); + } + + /// + /// Requires a stationary authority to produce a stationary non-authority. + /// + /// An for the test coroutine. + [UnityTest] + public IEnumerator HalfFloatPrecisionHoldsStillWhenStationary() + { + yield return SpawnAndTravel(); + + // A position that a half float happens to represent exactly leaves no rounding loss behind, and with + // no rounding loss there is nothing that could move the object. Offsetting by less than the encoding + // can represent guarantees there is some, which is the state a settling object is normally left in. + yield return DriveAuthority(k_UnrepresentableOffset, 1); + + yield return WaitForConditionOrTimeOut(NonAuthorityCaughtUp); + AssertOnTimeout("The non-authority instance did not catch up to the authority after the travel phase."); + + // Nothing moves for the rest of the test, so the last direction the authority moved was forwards. + // Checking for backwards movement rather than for drift from a starting point means the instance is + // still free to finish interpolating towards the authority without that counting against it. + BeginSampling(); + for (var i = 0; i < k_StationaryFrames; i++) + { + SampleForRegression(); + yield return null; + } + + AssertNoRegression("stationary"); + } + } +} diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs.meta new file mode 100644 index 0000000000..b380ca173d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformHalfFloatPrecisionTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9130626724ce4dddcfcd533d630aa6b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From cbaa81bac0700f6385ec11f9b9d5ed2e8c75ac1d Mon Sep 17 00:00:00 2001 From: "pets-svc[bot]" <299490404+pets-svc[bot]@users.noreply.github.com> Date: Mon, 7 Sep 2026 11:09:09 +0200 Subject: [PATCH 57/64] trunk shadow changes: com.unity.netcode.gameobjects (#4136) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Updated changelog and package version for Netcode in anticipation of v2.13.1 release * typo in build automation * trunk shadow changes: com.unity.netcode.gameobjects [skip ci] * typo --------- Co-authored-by: netcode-automation Co-authored-by: Michał Chrobot Co-authored-by: PETS automation <299490404+pets-svc[bot]@users.noreply.github.com> --- .../DeferredDespawningTests.cs | 4 +- .../NetworkClientAndPlayerObjectTests.cs | 4 +- .../Configuration/SinglePlayerSessions.cs | 4 +- .../CustomSerializationDocsTests.cs | 5 +- .../NetworkObjectSynchronizationTests.cs | 12 +- .../NetworkVariable/NetworkListTests.cs | 4 +- .../NetworkVariableAnticipationTests.cs | 4 +- ...NetworkVariableCollectionsChangingTests.cs | 4 +- .../NetworkVariableCollectionsTests.cs | 4 +- .../NetworkVariableGeneralTests.cs | 4 +- .../NetworkVariablePermissionTests.cs | 4 +- .../NetworkVariable/NetworkVariableTests.cs | 108 +++++++++++++----- .../NetworkVariableTraitsTests.cs | 4 +- ...tworkVariableUserSerializableTypesTests.cs | 8 +- .../NetworkVariable/OwnerPermissionTests.cs | 5 +- .../Runtime/PeerDisconnectCallbackTests.cs | 4 +- .../Tests/Runtime/Rpc/RpcTests.cs | 4 +- .../Runtime/Rpc/RpcTypeSerializationTests.cs | 8 +- .../NetworkBehaviourReferenceTests.cs | 8 +- .../Transports/SinglePlayerTransportTests.cs | 4 +- .../Unity.Netcode.Runtime.Tests.asmdef | 5 - ...ityCoreClrExplicitDisabledAttributeShim.cs | 14 --- ...reClrExplicitDisabledAttributeShim.cs.meta | 11 -- 23 files changed, 155 insertions(+), 81 deletions(-) delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs delete mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs index dac15570f7..262d78dd0a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/DeferredDespawningTests.cs @@ -53,7 +53,9 @@ protected override void OnServerAndClientsCreated() [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator DeferredDespawning() { // Setup for test diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs index aabe7522c7..fd0723e111 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DistributedAuthority/NetworkClientAndPlayerObjectTests.cs @@ -155,7 +155,9 @@ private bool AllNetworkClientsValidated() /// Validates the same thing when a client late joins and when a client disconnects. /// [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149595", "ValidateNetworkClients throws duplicate-key ArgumentException on CoreCLR (DAHost)")] +#if ENABLE_CORECLR + [Explicit("ValidateNetworkClients throws duplicate-key ArgumentException on CoreCLR (DAHost), see https://jira.unity3d.com/browse/UUM-149595")] +#endif public IEnumerator ValidateNetworkClients() { // Validate the initial clients created diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs index 774debccf0..7b46e03916 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/Configuration/SinglePlayerSessions.cs @@ -151,7 +151,9 @@ protected override void OnNewClientCreated(NetworkManager networkManager) } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator SwitchTransportTest() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs index 69dfae8be8..d56813dd3a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Text; +using NUnit.Framework; using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -173,7 +174,9 @@ private bool ValidateAllAreEqual(StringBuilder errorLog) } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestHealthCode() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs index b943be4883..f0deca9593 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkObject/NetworkObjectSynchronizationTests.cs @@ -94,7 +94,9 @@ protected override void OnNewClientCreated(NetworkManager networkManager) } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public IEnumerator NetworkObjectDeserializationFailure() { m_CurrentLogLevel = LogLevel.Nothing; @@ -278,7 +280,9 @@ private void ValidateNetworkBehaviourWithNetworkVariables(NetworkObject authorit /// will still be initialized properly /// [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public IEnumerator NetworkBehaviourSynchronization() { var authority = GetAuthorityNetworkManager(); @@ -321,7 +325,9 @@ public IEnumerator NetworkBehaviourSynchronization() /// A basic validation for the NetworkBehaviour.OnSynchronize method /// [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public IEnumerator NetworkBehaviourOnSynchronize() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs index 98bb2e8f74..c310d55781 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs @@ -244,7 +244,9 @@ void TestForceUpdateCallback(NetworkListEvent _) // don't extend this please [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator LegacyPredicateTesting() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs index b5b89d02f1..52f76f04c1 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs @@ -59,7 +59,9 @@ public void SetReanticipateValueRpc(float f) } } - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif internal class NetworkVariableAnticipationTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs index 1b61c05bca..cf9e0e103c 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsChangingTests.cs @@ -17,7 +17,9 @@ namespace Unity.Netcode.RuntimeTests [TestFixture(HostOrServer.Host, CollectionTypes.Dictionary)] [TestFixture(HostOrServer.Server, CollectionTypes.List)] [TestFixture(HostOrServer.Server, CollectionTypes.Dictionary)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif internal class NetworkVariableCollectionsChangingTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs index c4cdf5e3e6..fb3d2a72bb 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableCollectionsTests.cs @@ -22,7 +22,9 @@ namespace Unity.Netcode.RuntimeTests /// [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.Server)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif internal class NetworkVariableCollectionsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 2; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs index 4623351b7b..8e3e954879 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableGeneralTests.cs @@ -140,7 +140,9 @@ private bool ChangedValueMatches(StringBuilder errorLog) /// instances invoke . /// [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator ApplyValueDuringSpawnSequence() { var authority = GetAuthorityNetworkManager(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs index f1c882685e..90f3f774ed 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariablePermissionTests.cs @@ -11,7 +11,9 @@ namespace Unity.Netcode.RuntimeTests { [TestFixtureSource(nameof(TestDataSource))] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif internal class NetworkVariablePermissionTests : NetcodeIntegrationTest { public static IEnumerable TestDataSource() diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs index 33178e9d27..a8aa926709 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTests.cs @@ -349,7 +349,9 @@ private void InitializeServerAndClients(HostOrServer useHost) /// Runs generalized tests on all predefined NetworkVariable types /// [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void AllNetworkVariableTypes([Values] HostOrServer useHost) { var prefabToSpawn = CreateNetworkObjectPrefab("NetVarTest"); @@ -392,7 +394,9 @@ public void AllNetworkVariableTypes([Values] HostOrServer useHost) } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void ClientWritePermissionTest([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -407,7 +411,9 @@ public void ClientWritePermissionTest([Values] HostOrServer useHost) /// Runs tests that network variables sync on client whatever the local value of . /// [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void NetworkVariableSync_WithDifferentTimeScale([Values] HostOrServer useHost, [Values(0.0f, 1.0f, 2.0f)] float timeScale) { Time.timeScale = timeScale; @@ -422,7 +428,9 @@ public void NetworkVariableSync_WithDifferentTimeScale([Values] HostOrServer use } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void FixedString32Test([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -434,7 +442,9 @@ public void FixedString32Test([Values] HostOrServer useHost) } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -454,7 +464,9 @@ bool VerifyClass() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableTemplateClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -473,7 +485,9 @@ bool VerifyClass() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -492,7 +506,9 @@ bool VerifyStructure() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableTemplateStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -511,7 +527,9 @@ bool VerifyStructure() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableTemplateBehaviourClass([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -534,7 +552,9 @@ bool VerifyClass() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableTemplateBehaviourClassNotReferencedElsewhere([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -553,7 +573,9 @@ bool VerifyClass() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableTemplateBehaviourStruct([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -572,7 +594,9 @@ bool VerifyClass() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableEnum([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -590,7 +614,9 @@ bool VerifyStructure() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestINetworkSerializableClassCallsNetworkSerialize([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -609,7 +635,9 @@ public void TestINetworkSerializableClassCallsNetworkSerialize([Values] HostOrSe } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -624,7 +652,9 @@ public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrS } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestCustomGenericSerialization() { // Just verifies that the ILPP codegen initialized these values for this type. @@ -678,7 +708,9 @@ public void TestUnsupportedManagedTypesThrowExceptions() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestUnsupportedManagedTypesWithUserSerializationDoNotThrowExceptions() { var variable = new NetworkVariable(); @@ -734,7 +766,9 @@ public void TestUnsupportedUnmanagedTypesThrowExceptions() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestTypesReferencedInSubclassSerializeSuccessfully() { var variable = new NetworkVariableSubclass>(); @@ -750,7 +784,9 @@ public void TestTypesReferencedInSubclassSerializeSuccessfully() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestUnsupportedUnmanagedTypesWithUserSerializationDoNotThrowExceptions() { var variable = new NetworkVariable(); @@ -824,7 +860,9 @@ public void WhenCreatingAnArrayOfNetVars_InitializingVariablesDoesNotThrowAnExce } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public void TestNetworkVariableChangeAndReturnInSameFrame([Values] HostOrServer useHost) { InitializeServerAndClients(useHost); @@ -1382,7 +1420,9 @@ private void TestValueTypeNativeHashMap(NativeHashMap te } #endif [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingValueTypeNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1550,7 +1590,9 @@ public void WhenSerializingAndDeserializingValueTypeNetworkVariables_ValuesAreSe } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1894,7 +1936,9 @@ public string ArrayStr(NativeArray arr) where T : unmanaged [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeArrayNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2357,7 +2401,9 @@ public string DictionaryStr(Dictionary list) [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2553,7 +2599,9 @@ public void WhenSerializingAndDeserializingVeryLargeListNetworkVariables_ValuesA [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -2718,7 +2766,9 @@ public void WhenSerializingAndDeserializingVeryLargeHashSetNetworkVariables_Valu [Test] [UnityPlatform(exclude = new[] { RuntimePlatform.Android, RuntimePlatform.IPhonePlayer })] // Ignored test tracked in MTT-11343 [Repeat(5)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void WhenSerializingAndDeserializingVeryLargeDictionaryNetworkVariables_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(ulong), typeof(Vector2), typeof(HashMapKeyClass))] Type keyType, @@ -4835,7 +4885,9 @@ public void WhenSerializingAndDeserializingVeryLargeValueTypeNativeHashMapNetwor #endif [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() { var variable = new NetworkVariable @@ -4872,7 +4924,9 @@ public void TestManagedINetworkSerializableNetworkVariablesDeserializeInPlace() } [Test] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public void TestUnmnagedINetworkSerializableNetworkVariablesDeserializeInPlace() { var variable = new NetworkVariable diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs index 45a66c6670..0672b50077 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableTraitsTests.cs @@ -15,7 +15,9 @@ internal class NetworkVariableTraitsComponent : NetworkBehaviour [TestFixture(HostOrServer.Host)] [TestFixture(HostOrServer.DAHost)] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif internal class NetworkVariableTraitsTests : NetcodeIntegrationTest { protected override int NumberOfClients => 3; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs index cf9021f463..2b5700d3b8 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableUserSerializableTypesTests.cs @@ -142,7 +142,9 @@ private bool CheckForClientInstance() where T : WorkingUserNetworkVariableCom } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerialization_ReplicationWorks() { UserNetworkVariableSerialization.WriteValue = (FastBufferWriter writer, in MyTypeOne value) => @@ -183,7 +185,9 @@ public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializatio } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator WhenUsingAUserSerializableNetworkVariableWithUserSerializationViaExtensionMethod_ReplicationWorks() { UserNetworkVariableSerialization.WriteValue = NetworkVariableUserSerializableTypesTestsExtensionMethods.WriteValueSafe; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs index 1879da0174..2bfbf2874b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Generic; +using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; @@ -98,7 +99,9 @@ protected override void OnServerAndClientsCreated() } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149597", "Unexpected owner write-permission error logged on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("Unexpected owner write-permission error logged on CoreCLR, see https://jira.unity3d.com/browse/UUM-149597")] +#endif public IEnumerator OwnerPermissionTest() { // create 3 objects diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs index d273cf9f1f..2daaaf7a5f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/PeerDisconnectCallbackTests.cs @@ -104,7 +104,9 @@ private void ClientToDisconnect_OnClientStopped(bool wasHost) } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149591", "NGO multi-instance test sessions fail to start/connect or time out on CoreCLR")] +#if ENABLE_CORECLR + [Explicit("NGO multi-instance test sessions fail to start/connect or time out on CoreCLR, see https://jira.unity3d.com/browse/UUM-149591")] +#endif public IEnumerator TestPeerDisconnectCallback([Values] ClientDisconnectType clientDisconnectType, [Values(1ul, 2ul, 3ul)] ulong disconnectedClient) { m_TargetClientShutdown = false; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs index 0ec70b0e21..306d329bbf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTests.cs @@ -91,7 +91,9 @@ protected override void OnCreatePlayerPrefab() } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestRpcs() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs index f26baeb5d1..964cacac4a 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Rpc/RpcTypeSerializationTests.cs @@ -1125,7 +1125,9 @@ public IEnumerator TestValueTypeNativeList(NativeList firstTest, NativeLis #endif [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator WhenSendingAValueTypeOverAnRpc_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), @@ -1540,7 +1542,9 @@ public IEnumerator WhenSendingAnArrayOfValueTypesOverAnRpc_ValuesAreSerializedCo } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator WhenSendingANativeArrayOfValueTypesOverAnRpc_ValuesAreSerializedCorrectly( [Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint), diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index fcb3fa5fc7..1306b4a803 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -22,7 +22,9 @@ public NetworkBehaviourReferenceTests(HostOrServer hostOrServer) : base(hostOrSe #region Tests using non-null NetworkBehaviours and RPCs [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestRpc() { yield return SpawnTestPrefabInstance(); @@ -37,7 +39,9 @@ public IEnumerator TestRpc() [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestRpcImplicitNetworkBehaviour() { yield return SpawnTestPrefabInstance(); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs index 33c801cc12..466a4e5a6b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Transports/SinglePlayerTransportTests.cs @@ -176,7 +176,9 @@ protected override bool CanStartServerAndClients() } [UnityTest] - [UnityCoreClrExplicitDisabled("https://jira.unity3d.com/browse/UUM-149592", "NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer)")] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator StartSinglePlayerAndSpawn() { m_CanStartHost = true; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef index ce23c34a2c..cde139b07f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef @@ -47,11 +47,6 @@ "name": "Unity", "expression": "6000.1.0a1", "define": "HOSTNAME_RESOLUTION_AVAILABLE" - }, - { - "name": "com.unity.test-framework", - "expression": "1.7.0", - "define": "UNITY_TEST_FRAMEWORK_1_7_OR_NEWER" } ], "noEngineReferences": false diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs deleted file mode 100644 index 07baa43dc0..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs +++ /dev/null @@ -1,14 +0,0 @@ -#if !UNITY_TEST_FRAMEWORK_1_7_OR_NEWER -using System; - -namespace UnityEngine.TestTools -{ - [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - internal sealed class UnityCoreClrExplicitDisabledAttribute : Attribute - { - public UnityCoreClrExplicitDisabledAttribute(string jiraIssue, string reason = null) - { - } - } -} -#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta deleted file mode 100644 index d3dc8d26c9..0000000000 --- a/com.unity.netcode.gameobjects/Tests/Runtime/UnityCoreClrExplicitDisabledAttributeShim.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: b7f17392536340a8b607dd678b2ecd76 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: From 3f5d7a8f8816c888880c7f21b63c911ccdcdbdce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Tue, 8 Sep 2026 00:47:56 +0200 Subject: [PATCH 58/64] chore: Corrections for standards check (#4155) * corrected using statements for standards check * yaml fix * yaml fix --- .../NetworkVariable/CustomSerializationDocsTests.cs | 2 ++ .../Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs | 1 - .../Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs index d56813dd3a..fc87bd93af 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/DocumentationCodeSamples/NetworkVariable/CustomSerializationDocsTests.cs @@ -1,6 +1,8 @@ using System.Collections; using System.Text; +#if ENABLE_CORECLR using NUnit.Framework; +#endif using Unity.Netcode; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs index 52f76f04c1..aa56a4ef40 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkVariableAnticipationTests.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; -using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests { diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs index 2bfbf2874b..84edb4035e 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/OwnerPermissionTests.cs @@ -1,6 +1,8 @@ using System.Collections; using System.Collections.Generic; +#if ENABLE_CORECLR using NUnit.Framework; +#endif using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; using UnityEngine.TestTools; From 50c9e1994596da7a8ca51ff077bd8b38166a6e96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= Date: Mon, 14 Sep 2026 13:49:14 +0200 Subject: [PATCH 59/64] chore: Updated aspects of Netcode package in anticipation of v2.13.3 release --- com.unity.netcode.gameobjects/CHANGELOG.md | 23 ++++++++++++++-------- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 92bc6b7e0d..fffb65c904 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -13,8 +13,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed -- Changed `NetworkTransform.UseHalfFloatPrecision` to synchronize position with a resolution of approximately 1mm regardless of how far an object has travelled. Previously the resolution could degrade to approximately 3cm. This does not increase bandwidth, but projects using `NetworkTransform.UseUnreliableDeltas` will send full precision position updates more often. (#4128) - ### Deprecated @@ -24,12 +22,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where scenes additively loaded before a session started were tracked as loaded on the server but had no scene handle entries, which caused `NetworkSceneManager.UnloadScene` to log an error and leave the scene registered as loaded even though it unloaded on all peers. (#4145) -- Issue where `NetworkTransform` interpolated towards a point in time taken from the local clock rather than the server clock that state updates are stamped on, which starved the interpolator on clients and reduced interpolation to snapping between state updates. (#4133) -- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4133) -- Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) -- Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130) -- Issue where objects using `NetworkTransform.UseHalfFloatPrecision` appeared to jitter on non-authority instances while they were stationary or coming to rest, even though the authority was not moving them. (#4128) ### Security @@ -37,6 +29,21 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [2.13.4] - 2026-09-14 + +### Changed + +- Changed `NetworkTransform.UseHalfFloatPrecision` to synchronize position with a resolution of approximately 1mm regardless of how far an object has travelled. Previously the resolution could degrade to approximately 3cm. This does not increase bandwidth, but projects using `NetworkTransform.UseUnreliableDeltas` will send full precision position updates more often. (#4128) + +### Fixed + +- Fixed issue where scenes additively loaded before a session started were tracked as loaded on the server but had no scene handle entries, which caused `NetworkSceneManager.UnloadScene` to log an error and leave the scene registered as loaded even though it unloaded on all peers. (#4145) +- Issue where `NetworkTransform` interpolated towards a point in time taken from the local clock rather than the server clock that state updates are stamped on, which starved the interpolator on clients and reduced interpolation to snapping between state updates. (#4133) +- Issue where `NetworkTransform.GetTickLatencyInSeconds` returned an absolute network timestamp that grew for as long as the session ran, rather than the tick latency as a duration in seconds that it is documented to return. (#4133) +- Issue where lerp smoothing was applied per frame instead of over time, which caused the `Lerp` and `SmoothDampening` interpolation types to smooth by different amounts at different frame rates. Results at 60fps are unchanged. (#4130) +- Issue where setting a maximum interpolation time of 1.0 would stop a `NetworkTransform` from interpolating at all when using the `Lerp` or `SmoothDampening` interpolation types. (#4130) +- Issue where objects using `NetworkTransform.UseHalfFloatPrecision` appeared to jitter on non-authority instances while they were stationary or coming to rest, even though the authority was not moving them. (#4128) + ## [2.13.2] - 2026-08-16 ### Fixed diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index 5220ee5c17..e257255fcc 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": "2.13.3", + "version": "2.13.4", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4", From 00c5d07c8aa108f6b946c8e025b356a0aff88ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= Date: Mon, 14 Sep 2026 20:26:40 +0200 Subject: [PATCH 60/64] changelog typo --- 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 fffb65c904..7247324fe1 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -29,7 +29,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete -## [2.13.4] - 2026-09-14 +## [2.13.3] - 2026-09-14 ### Changed From 47bd4345d582361cb3958aaf57dfc45417d0fcae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:47:15 +0200 Subject: [PATCH 61/64] ci: [NGOv2.X] Adjusting CI after 3.X release (#4162) * Added Wrench cap on supported editors * CI adjustments for new editor setup * Updated Wrench and regenerated recipes * typo * Disabled more CoreCLR tests * Adjusted pr-supervisor * Added branch to github workflows --- .github/workflows/autoupdate.yaml | 1 + .github/workflows/conventional-pr.yml | 1 + .../workflows/pr-description-validation.yml | 1 + .github/workflows/pr-supervisor.yaml | 35 +- .yamato/_run-all.yml | 187 ++---- .yamato/_triggers.yml | 83 +-- .yamato/code-coverage.yml | 18 +- ...nfrastructure-instability-detection-mac.sh | 7 + ...frastructure-instability-detection-win.cmd | 2 +- .yamato/package-tests.yml | 2 +- .yamato/project.metafile | 10 +- .yamato/wrench/api-validation-jobs.yml | 7 +- .yamato/wrench/package-pack-jobs.yml | 3 +- .yamato/wrench/preview-a-p-v.yml | 440 ++------------ .yamato/wrench/promotion-jobs.yml | 134 +---- .yamato/wrench/recipe-regeneration.yml | 2 +- .yamato/wrench/validation-jobs.yml | 540 +++--------------- .yamato/wrench/wrench_config.json | 4 +- Tools/CI/NGO.Cookbook.csproj | 10 +- Tools/CI/Settings/NGOSettings.cs | 1 + .../NetworkBehaviourReferenceTests.cs | 6 + .../NetworkObjectReferenceTests.cs | 9 + 22 files changed, 321 insertions(+), 1182 deletions(-) diff --git a/.github/workflows/autoupdate.yaml b/.github/workflows/autoupdate.yaml index 764b52b373..298c87bc37 100644 --- a/.github/workflows/autoupdate.yaml +++ b/.github/workflows/autoupdate.yaml @@ -4,6 +4,7 @@ on: branches: - develop - develop-2.0.0 + - develop-3.x.x jobs: autoupdate: name: auto-update diff --git a/.github/workflows/conventional-pr.yml b/.github/workflows/conventional-pr.yml index 30274254e2..e5176d4912 100644 --- a/.github/workflows/conventional-pr.yml +++ b/.github/workflows/conventional-pr.yml @@ -6,6 +6,7 @@ on: branches: - develop - develop-2.0.0 + - develop-3.x.x # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" diff --git a/.github/workflows/pr-description-validation.yml b/.github/workflows/pr-description-validation.yml index fdced1b888..b117ff21ef 100644 --- a/.github/workflows/pr-description-validation.yml +++ b/.github/workflows/pr-description-validation.yml @@ -14,6 +14,7 @@ on: branches: - develop - develop-2.0.0 + - develop-3.x.x - release/* jobs: diff --git a/.github/workflows/pr-supervisor.yaml b/.github/workflows/pr-supervisor.yaml index 2326b8da4e..b1660c2522 100644 --- a/.github/workflows/pr-supervisor.yaml +++ b/.github/workflows/pr-supervisor.yaml @@ -2,8 +2,8 @@ # We are using https://cli.github.com/manual/gh_pr_checks # The aim is to ensure that conditionally triggered Yamato jobs are completed successfully before allowing merges -# This job will be required in branch protection rules for develop, develop-2.0.0, and release/* branches. It's only goal will be to ensure that Yamato jobs are completed successfully before allowing Pr to merge. -# Note that conditional jobs will have 30s to show which is always the cas since they are showing up as soon as in distribution stage. +# This job will be required in branch protection rules for develop and release/* branches. It's only goal will be to ensure that Yamato jobs are completed successfully before allowing Pr to merge. +# Note that conditional jobs will have 30s to show which is always the case since they are showing up as soon as in distribution stage. name: Yamato PR Supervisor @@ -13,8 +13,9 @@ on: branches: - develop - develop-2.0.0 + - develop-3.x.x - release/* - + concurrency: group: pr-${{ github.event.pull_request.number }} cancel-in-progress: true @@ -23,13 +24,20 @@ jobs: yamato-supervisor: runs-on: ubuntu-latest timeout-minutes: 720 + permissions: + actions: read + checks: read + statuses: read + pull-requests: read + contents: read steps: - name: Checkout repository - uses: actions/checkout@v7 - + uses: actions/checkout@v4 + + - name: Wait and Verify Yamato Job Status env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} + GH_TOKEN: ${{ github.token }} PR_NUMBER: ${{ github.event.pull_request.number }} run: | set -e @@ -42,18 +50,25 @@ jobs: for ((i=1;i<=MAX_ATTEMPTS;i++)); do echo "Polling PR checks (attempt $i/$MAX_ATTEMPTS)..." - # We want to watch for pending checks beside this check - checks=$(gh pr checks $PR_NUMBER --json name,state --jq '[ .[] | select(.name != "yamato-supervisor") ]') - + # gh pr checks exits non-zero while checks are pending (8) or failing (1); we evaluate state ourselves, so don't let that abort the loop. + checks=$(gh pr checks $PR_NUMBER --json name,state --jq '[ .[] | select(.name != "yamato-supervisor") ]') || true + + if [[ -z "$checks" ]]; then + echo "No non-supervisor checks reported yet; waiting..." + sleep $INTERVAL + continue + fi + pending=$(echo "$checks" | jq '[.[] | select(.state == "PENDING")] | length') skipping=$(echo "$checks" | jq '[.[] | select(.state == "SKIPPED")] | length') passed=$(echo "$checks" | jq '[.[] | select(.state == "SUCCESS")] | length') failed=$(echo "$checks" | jq '[.[] | select(.state == "FAILURE")] | length') - echo "Pending checks: $pending, Skipping checks: $skipping", Passed checks: $passed, Failed checks: $failed + echo "Pending checks: $pending; Skipping checks: $skipping, Passed checks: $passed, Failed checks: $failed" if [[ "$failed" -gt 0 ]]; then echo "A check has failed! Failing fast." + echo "If you rerun the job and everything is green then just rerun this job as well." exit 1 fi diff --git a/.yamato/_run-all.yml b/.yamato/_run-all.yml index 5f9e320670..fad7923322 100644 --- a/.yamato/_run-all.yml +++ b/.yamato/_run-all.yml @@ -27,33 +27,25 @@ run_all_package_tests: dependencies: {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} - - .yamato/package-tests.yml#package_test_-_ngo_{{ editor }}_{{ platform.name }} + - .yamato/package-tests.yml#package_test_-_ngo_{{ platform.name }}_{{ editor }} {% endfor -%} {% endfor -%} -# Runs all package tests on trunk editor -run_all_package_tests_trunk: - name: Run All Package Tests [Trunk only] +# Runs all package tests on latest supported editor +run_all_package_tests_{{ validation_editors.latest }}: + name: Run All Package Tests [{{ validation_editors.latest }} only] dependencies: {% for platform in test_platforms.desktop -%} - - .yamato/package-tests.yml#package_test_-_ngo_trunk_{{ platform.name }} + - .yamato/package-tests.yml#package_test_-_ngo_{{ platform.name }}_{{ validation_editors.latest }} {% endfor -%} -# Runs all package tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_package_tests_pinnedTrunk: - name: Run All Package Tests [Pinned Trunk only] +# Runs all package tests on minimal supported editor +run_all_package_tests_{{ validation_editors.minimal }}: + name: Run All Package Tests [{{ validation_editors.minimal }}] dependencies: {% for platform in test_platforms.desktop -%} - - .yamato/package-tests.yml#package_test_-_ngo_{{ pinnedTrunk }}_{{ platform.name }} -{% endfor -%} - -# Runs all package tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_package_tests_6000: - name: Run All Package Tests [6000.0] - dependencies: -{% for platform in test_platforms.desktop -%} - - .yamato/package-tests.yml#package_test_-_ngo_6000.0_{{ platform.name }} + - .yamato/package-tests.yml#package_test_-_ngo_{{ platform.name }}_{{ validation_editors.minimal }} {% endfor -%} @@ -72,38 +64,26 @@ run_all_project_tests: {% endfor -%} -# Runs all projects tests on trunk editor -run_all_project_tests_trunk: - name: Run All Project Tests [Trunk only] - dependencies: -{% for project in projects.all -%} -{% if project.has_tests == "true" -%} -{% for platform in test_platforms.desktop -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_trunk -{% endfor -%} -{% endif -%} -{% endfor -%} - -# Runs all projects tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_project_tests_pinnedTrunk: - name: Run All Project Tests [Pinned Trunk only] +# Runs all projects tests on latest supported editor +run_all_project_tests_{{ validation_editors.latest }}: + name: Run All Project Tests [{{ validation_editors.latest }} only] dependencies: {% for project in projects.all -%} {% if project.has_tests == "true" -%} {% for platform in test_platforms.desktop -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_{{ pinnedTrunk }} + - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.latest }} {% endfor -%} {% endif -%} {% endfor -%} -# Runs all projects tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_project_tests_6000: - name: Run All Project Tests [6000.0] +# Runs all projects tests on minimal supported editor +run_all_project_tests_{{ validation_editors.minimal }}: + name: Run All Project Tests [{{ validation_editors.minimal }}] dependencies: {% for project in projects.all -%} {% if project.has_tests == "true" -%} {% for platform in test_platforms.desktop -%} - - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_6000.0 + - .yamato/project-tests.yml#test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.minimal }} {% endfor -%} {% endif -%} {% endfor -%} @@ -135,34 +115,24 @@ run_all_webgl_builds: {% endfor -%} -# Runs all WebGL builds on trunk editor -run_all_webgl_builds_trunk: - name: Run All WebGl Build [Trunk only] +# Runs all WebGL builds on latest supported editor +run_all_webgl_builds_{{ validation_editors.latest }}: + name: Run All WebGl Build [{{ validation_editors.latest }} only] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} - - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_trunk + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ validation_editors.latest }} {% endfor -%} {% endfor -%} -# Runs all WebGL builds on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_webgl_builds_pinnedTrunk: - name: Run All WebGl Build [Pinned Trunk only] +# Runs all WebGL builds on minimal supported editor +run_all_webgl_builds_{{ validation_editors.minimal }}: + name: Run All WebGl Build [{{ validation_editors.minimal }}] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} - - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ pinnedTrunk }} -{% endfor -%} -{% endfor -%} - -# Runs all WebGL builds on 6000.0 editor -run_all_webgl_builds_6000: - name: Run All WebGl Build [6000.0] - dependencies: -{% for project in projects.default -%} -{% for platform in test_platforms.desktop -%} - - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_6000.0 + - .yamato/webgl-build.yml#webgl_build_{{ project.name }}_{{ platform.name }}_{{ validation_editors.minimal }} {% endfor -%} {% endfor -%} @@ -182,38 +152,26 @@ run_all_project_tests_desktop_standalone: {% endfor -%} -# Runs all Desktop tests on trunk editor -run_all_project_tests_desktop_standalone_trunk: - name: Run All Standalone Tests - Desktop [Trunk only] +# Runs all Desktop tests on latest supported editor +run_all_project_tests_desktop_standalone_{{ validation_editors.latest }}: + name: Run All Standalone Tests - Desktop [{{ validation_editors.latest }} only] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for backend in scripting_backends -%} - - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_trunk + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ validation_editors.latest }} {% endfor -%} {% endfor -%} {% endfor -%} -# Runs all Desktop tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_project_tests_desktop_standalone_pinnedTrunk: - name: Run All Standalone Tests - Desktop [Pinned Trunk only] +# Runs all Desktop tests on minimal supported editor +run_all_project_tests_desktop_standalone_{{ validation_editors.minimal }}: + name: Run All Standalone Tests - Desktop [{{ validation_editors.minimal }}] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.desktop -%} {% for backend in scripting_backends -%} - - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ pinnedTrunk }} -{% endfor -%} -{% endfor -%} -{% endfor -%} - -# Runs all Desktop tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_project_tests_desktop_standalone_6000: - name: Run All Standalone Tests - Desktop [6000.0] - dependencies: -{% for project in projects.default -%} -{% for platform in test_platforms.desktop -%} -{% for backend in scripting_backends -%} - - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_6000.0 + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ validation_editors.minimal }} {% endfor -%} {% endfor -%} {% endfor -%} @@ -231,33 +189,24 @@ run_all_project_tests_mobile_standalone: {% endfor -%} -# Runs all Mobile tests on trunk editor -run_all_project_tests_mobile_standalone_trunk: - name: Run All Standalone Tests - Mobile [Trunk only] +# Runs all Mobile tests on latest supported editor +run_all_project_tests_mobile_standalone_{{ validation_editors.latest }}: + name: Run All Standalone Tests - Mobile [{{ validation_editors.latest }} only] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.mobile_test -%} - - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_trunk + - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.latest }} {% endfor -%} {% endfor -%} -# Runs all Mobile tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_project_tests_mobile_standalone_pinnedTrunk: - name: Run All Standalone Tests - Mobile [Pinned Trunk only] - dependencies: -{% for project in projects.default -%} -{% for platform in test_platforms.mobile_test -%} - - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ pinnedTrunk }} -{% endfor -%} -{% endfor -%} -# Runs all Mobile tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_project_tests_mobile_standalone_6000: - name: Run All Standalone Tests - Mobile [6000.0] +# Runs all Mobile tests on minimal supported editor +run_all_project_tests_mobile_standalone_{{ validation_editors.minimal }}: + name: Run All Standalone Tests - Mobile [{{ validation_editors.minimal }}] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.mobile_test -%} - - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_6000.0 + - .yamato/mobile-standalone-test.yml#mobile_standalone_test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.minimal }} {% endfor -%} {% endfor -%} @@ -275,33 +224,23 @@ run_all_project_tests_console_standalone: {% endfor -%} -# Runs all Console tests on trunk editor -run_all_project_tests_console_standalone_trunk: - name: Run All Standalone Tests - Console [Trunk only] - dependencies: -{% for project in projects.default -%} -{% for platform in test_platforms.console_test -%} - - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_trunk -{% endfor -%} -{% endfor -%} - -# Runs all Console tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_project_tests_console_standalone_pinnedTrunk: - name: Run All Standalone Tests - Console [Pinned Trunk only] +# Runs all Console tests on latest supported editor +run_all_project_tests_console_standalone_{{ validation_editors.latest }}: + name: Run All Standalone Tests - Console [{{ validation_editors.latest }} only] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.console_test -%} - - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ pinnedTrunk }} + - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.latest }} {% endfor -%} {% endfor -%} -# Runs all Console tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_project_tests_console_standalone_6000: - name: Run All Standalone Tests - Console [6000.0] +# Runs all Console tests on minimal supported editor +run_all_project_tests_console_standalone_{{ validation_editors.minimal }}: + name: Run All Standalone Tests - Console [{{ validation_editors.minimal }}] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.console_test -%} - - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_6000.0 + - .yamato/console-standalone-test.yml#console_standalone_test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.minimal }} {% endfor -%} {% endfor -%} @@ -320,38 +259,26 @@ run_all_project_tests_cmb_service: {% endfor -%} {% endfor -%} -# Runs all CMB service tests on trunk editor -run_all_project_tests_cmb_service_trunk: - name: Run All CMB Service Tests [Trunk only] - dependencies: -{% for project in projects.default -%} -{% for platform in test_platforms.default -%} -{% for backend in scripting_backends -%} - - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_trunk -{% endfor -%} -{% endfor -%} -{% endfor -%} - -# Runs all CMB service tests on pinned trunk editor (This is used for PR testing as we want to avoid blocking failures) -run_all_project_tests_cmb_service_pinnedTrunk: - name: Run All CMB Service Tests [Pinned Trunk only] +# Runs all CMB service tests on latest supported editor +run_all_project_tests_cmb_service_{{ validation_editors.latest }}: + name: Run All CMB Service Tests [{{ validation_editors.latest }} only] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.default -%} {% for backend in scripting_backends -%} - - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ pinnedTrunk }} + - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ validation_editors.latest }} {% endfor -%} {% endfor -%} {% endfor -%} -# Runs all CMB service tests on mimimum supported editor (6000.0 in case of NGOv2.X) -run_all_project_tests_cmb_service_6000: - name: Run All CMB Service Tests [6000.0] +# Runs all CMB service tests on minimal supported editor +run_all_project_tests_cmb_service_{{ validation_editors.minimal }}: + name: Run All CMB Service Tests [{{ validation_editors.minimal }}] dependencies: {% for project in projects.default -%} {% for platform in test_platforms.default -%} {% for backend in scripting_backends -%} - - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_6000.0 + - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_{{ project.name }}_{{ platform.name }}_{{ backend }}_{{ validation_editors.minimal }} {% endfor -%} {% endfor -%} {% endfor -%} diff --git a/.yamato/_triggers.yml b/.yamato/_triggers.yml index 3b292603b4..380d07b997 100644 --- a/.yamato/_triggers.yml +++ b/.yamato/_triggers.yml @@ -17,7 +17,7 @@ # 1) Minimal PR checks that run on all PRs (even if only docs are changed) # 2) More extensive pr_code_changes_checks that run if code is changed. This test validates Standards, Package tests, Project tests and Desktop standalone tests to ensure that main platforms are covered # By default pr_minimal_required_checks it's triggered if - # 1) PR targets develop, develop-2.0.0 or release branches + # 1) PR targets develop, develop-2.0., develop-3.x.x or release branches # 2) PR is not a draft # Then pr_code_changes_checks it's triggered if the same conditions apply plus: # 1) PR changes code in com.unity.netcode.gameobjects package (Editor, Runtime or Tests folders) or in testproject folder or package.json file @@ -58,6 +58,7 @@ pr_minimal_required_checks: (pull_request.comment eq "ngo" OR (pull_request.target eq "develop" OR pull_request.target eq "develop-2.0.0" OR + pull_request.target eq "develop-3.x.x" OR pull_request.target match "release/*")) AND NOT pull_request.draft cancel_old_ci: true @@ -75,27 +76,28 @@ pr_code_changes_checks: # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test - # Run package EditMode and Playmode package tests on trunk and an older supported editor (6000.0) - - .yamato/package-tests.yml#package_test_-_ngo_{{ pinnedTrunk }}_mac - - .yamato/package-tests.yml#package_test_-_ngo_6000.0_win + # Run package EditMode and Playmode package tests + - .yamato/package-tests.yml#package_test_-_ngo_mac_{{ validation_editors.default }} + - .yamato/package-tests.yml#package_test_-_ngo_win_{{ validation_editors.minimal }} - # Run testproject EditMode and Playmode project tests on trunk and an older supported editor (6000.0) - - .yamato/project-tests.yml#test_testproject_win_{{ pinnedTrunk }} - - .yamato/project-tests.yml#test_testproject_mac_6000.0 + # Run testproject EditMode and Playmode project tests + - .yamato/project-tests.yml#test_testproject_win_{{ validation_editors.default }} + - .yamato/project-tests.yml#test_testproject_mac_{{ validation_editors.minimal }} # Run standalone test. We run it only on Ubuntu since it's the fastest machine, and it was noted that for example distribution on macOS is taking 40m since we switched to Apple Silicon # Coverage on other standalone machines is present in Nightly job so it's enough to not run all of them for PRs # desktop_standalone_test and cmb_service_standalone_test are both reusing desktop_standalone_build dependency so we run those in the same configuration on PRs to reduce waiting time. # Note that our daily tests will anyway run both test configurations in "minimal supported" and "trunk" configurations - - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ pinnedTrunk }} - # Run code coverage test (PRs use the pinned "safe" trunk) - - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ pinnedTrunk }} + - .yamato/desktop-standalone-tests.yml#desktop_standalone_test_testproject_ubuntu_il2cpp_{{ validation_editors.default }} + - .yamato/cmb-service-standalone-tests.yml#cmb_service_standalone_test_testproject_ubuntu_il2cpp_{{ validation_editors.default }} + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ validation_editors.default }} triggers: expression: |- (pull_request.comment eq "ngo" OR (pull_request.target eq "develop" OR pull_request.target eq "develop-2.0.0" OR + pull_request.target eq "develop-3.x.x" OR pull_request.target match "release/*")) AND NOT pull_request.draft AND pull_request.changes.any match [ @@ -118,11 +120,10 @@ pr_code_changes_checks: -# Run all tests on trunk on nightly basis. +# Run all tests on minimal and latest editors on nightly basis (rest of editors runs weekly) # Same subset as pull_request_trigger with addition of mobile/desktop/console tests and webgl builds -# Those tests are all running on trunk editor (since it's daily and running all of them would add a lot of overhead) develop_nightly: - name: "\U0001F319 [Nightly] Run All Tests [Trunk and 6000]" + name: "\U0001F319 [Nightly] Run All Tests [Latest Editor and Minimal Editor]" triggers: recurring: - branch: develop-2.0.0 @@ -133,40 +134,40 @@ develop_nightly: - .yamato/project-standards.yml#standards_ubuntu_testproject_{{ validation_editors.standards }} # Run APV jobs to make sure the change won't break any dependants - .yamato/wrench/preview-a-p-v.yml#all_preview_apv_jobs - # Run package EditMode and Playmode tests on desktop platforms on trunk and 6000.0 - - .yamato/_run-all.yml#run_all_package_tests_trunk - - .yamato/_run-all.yml#run_all_package_tests_6000 - # Run project EditMode and PLaymode tests on desktop platforms on trunk and 6000.0 - - .yamato/_run-all.yml#run_all_project_tests_trunk - - .yamato/_run-all.yml#run_all_project_tests_6000 - # Run Runtime tests on desktop players on trunk and 6000.0 editors - - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone_trunk - - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone_6000 - # Run Runtime tests on mobile players on trunk and 6000.0 editors - - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone_trunk - - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone_6000 - # Run Runtime tests on console players on trunk and 6000.0 editors - - .yamato/_run-all.yml#run_all_project_tests_console_standalone_trunk - - .yamato/_run-all.yml#run_all_project_tests_console_standalone_6000 - # Build player for webgl platform on trunk and 6000.0 editors - - .yamato/_run-all.yml#run_all_webgl_builds_trunk - - .yamato/_run-all.yml#run_all_webgl_builds_6000 - # Run Runtime tests against cmb service on trunk and 6000.0 editors - - .yamato/_run-all.yml#run_all_project_tests_cmb_service_trunk - - .yamato/_run-all.yml#run_all_project_tests_cmb_service_6000 - # Build player for webgl platform on trunk and 6000.0 editors - - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_ubuntu_trunk - - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_6000.0 + # Run package EditMode and Playmode tests on desktop platforms + - .yamato/_run-all.yml#run_all_package_tests_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_package_tests_{{ validation_editors.minimal }} + # Run project EditMode and PLaymode tests on desktop platforms + - .yamato/_run-all.yml#run_all_project_tests_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_project_tests_{{ validation_editors.minimal }} + # Run Runtime tests on desktop players + - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_project_tests_desktop_standalone_{{ validation_editors.minimal }} + # Run Runtime tests on mobile players + - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_project_tests_mobile_standalone_{{ validation_editors.minimal }} + # Run Runtime tests on console players + - .yamato/_run-all.yml#run_all_project_tests_console_standalone_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_project_tests_console_standalone_{{ validation_editors.minimal }} + # Build player for webgl platform + - .yamato/_run-all.yml#run_all_webgl_builds_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_webgl_builds_{{ validation_editors.minimal }} + # Run Runtime tests against cmb service + - .yamato/_run-all.yml#run_all_project_tests_cmb_service_{{ validation_editors.latest }} + - .yamato/_run-all.yml#run_all_project_tests_cmb_service_{{ validation_editors.minimal }} + # Build player for webgl platform on trunk + - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_ubuntu_{{ validation_editors.latest }} + - .yamato/project-updated-dependencies-test.yml#updated-dependencies_testproject_NGO_win_{{ validation_editors.minimal }} # Run API validation to early-detect all new APIs that would force us to release new minor version of the package. Note that for this to work the package version in package.json must correspond to "actual package state" which means that it should be higher than last released version - .yamato/vetting-test.yml#vetting_test - # Run code coverage test (nightly uses actual trunk) - - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_trunk + # Run code coverage test + - .yamato/code-coverage.yml#code_coverage_project_test_testproject_ubuntu_{{ validation_editors.default }} # Run all tests on weekly bases # Same subset as develop_nightly but runs per all supported editors as well as executes code coverage test and runs project standards per project # It's not running wrench jobs since those will run either way in nightly test run -# This in contrast to nightly checks will run tests on all editors (not only trunk). Running those on weekly basis and trunk tests nightly should be a good balance between making sure that tests are passing and overhead of running lots of tests +# This in contrast to nightly checks will run tests on all editors. Running those on weekly basis and trunk tests nightly should be a good balance between making sure that tests are passing and overhead of running lots of tests develop_weekly_trunk: name: "\U0001F319 [Weekly] Run All Tests" triggers: diff --git a/.yamato/code-coverage.yml b/.yamato/code-coverage.yml index 18adbef416..3b2062b399 100644 --- a/.yamato/code-coverage.yml +++ b/.yamato/code-coverage.yml @@ -13,8 +13,7 @@ # CONFIGURATION STRUCTURE-------------------------------------------------------------- # Jobs are generated for: # 1. Default platform only (Ubuntu) since coverage would not vary between platforms (no need for checks on more platforms) - # 2. Two editors: actual "trunk" (used by nightly) and the "pinnedTrunk" safe version (used by PRs). - # Occasionally trunk breaks our tests, so PRs run against a pinned "safe" trunk while nightly runs actual trunk. + # 2. Default (most used) editor which is 6000.3 #TECHNICAL CONSIDERATIONS--------------------------------------------------------------- # Requires Unity Editor installation @@ -23,16 +22,12 @@ # QUALITY CONSIDERATIONS-------------------------------------------------------------------- # To see where this job is included (in trigger job definitions) look into _triggers.yml file. Currently: - # [Code Coverage] Project Test runs on PR changes (pr_code_changes_checks, pinnedTrunk) and nightly (develop_nightly, trunk) - -{% assign coverage_editors = "trunk," | append: pinnedTrunk | split: "," -%} + # [Code Coverage] Project Test runs on PR changes (pr_code_changes_checks) and nightly (develop_nightly) {% for platform in test_platforms.default -%} {% for project in projects.default -%} -{% for editor in coverage_editors -%} -{% if editor == "trunk" -%}{% assign editor_label = "trunk" -%}{% else -%}{% assign editor_label = "pinnedTrunk" -%}{% endif -%} -code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: - name: '[Code Coverage] Project Test - NGO {{ project.name }} [{{ platform.name }}, {{ editor }}]' +code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ validation_editors.default }}: + name: '[Code Coverage] Project Test - NGO {{ project.name }} [{{ platform.name }}, {{ validation_editors.default }}]' agent: type: {{ platform.type }} image: {{ platform.image }} @@ -41,8 +36,8 @@ code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: model: {{ platform.model }} # This is set only in platforms where we want non-default model to use (more information in project.metafile) {% endif %} commands: - - unity-downloader-cli --fast --wait -u {{ editor }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models - - UnifiedTestRunner --testproject={{ project.path }} --suite=editor --suite=playmode --editor-location=.Editor --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};flags:NGOv2_project_{{ project.name }}_{{ platform.name }}_{{ editor_label }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results + - unity-downloader-cli --fast --wait -u {{ validation_editors.default }} -c Editor {% if platform.name == "mac" %} --arch arm64 {% endif %} # For macOS we use ARM64 models + - UnifiedTestRunner --testproject={{ project.path }} --suite=editor --suite=playmode --editor-location=.Editor --enable-code-coverage --coverage-upload-options="reportsDir:$PWD/test-results/CoverageResults;name:NGOv2_project_{{ project.name }}_{{ platform.name }};flags:NGOv2_project_{{ project.name }}_{{ platform.name }};verbose" --coverage-results-path=$PWD/test-results/CoverageResults --coverage-options="generateHtmlReport;generateAdditionalMetrics;assemblyFilters:+Unity.Netcode.Editor,+Unity.Netcode.Runtime" --extra-editor-arg=--burst-disable-compilation --timeout={{ test_timeout }} --rerun-strategy=Test --retry={{ num_test_retries }} --clean-library-on-rerun --artifacts-path=test-results artifacts: logs: paths: @@ -52,4 +47,3 @@ code_coverage_project_test_{{ project.name }}_{{ platform.name }}_{{ editor }}: - .yamato/package-pack.yml#package_pack_-_ngo_{{ platform.name }} {% endfor -%} {% endfor -%} -{% endfor -%} diff --git a/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh b/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh index aef156dad9..1f7b566191 100644 --- a/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh +++ b/.yamato/generated-scripts/infrastructure-instability-detection-mac.sh @@ -2,6 +2,13 @@ # This is an auto-generated script. Do not edit manually! set -x +crashReportDir="${TMPDIR%/}/BugReporterCrashReportJson" +if [ -d "$crashReportDir" ]; then + for f in "$crashReportDir"/CrashReport_*.json; do + [ -e "$f" ] || continue + curl -X POST --connect-timeout 5 --max-time 10 -H "Content-Type: application/json" -T "$f" "https://internal-crash-collector.prd.cds.internal.unity3d.com/api/crash" || echo "Failed to upload $f. Ignoring..." + done +fi set -e if [ -f "infrastructure_instability_detection_standalone.zip" ]; then echo "removed existing archive infrastructure_instability_detection_standalone.zip" diff --git a/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd b/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd index 40d3bc8b3d..2397b45ae0 100644 --- a/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd +++ b/.yamato/generated-scripts/infrastructure-instability-detection-win.cmd @@ -1,7 +1,7 @@ @echo on rem This is an auto-generated script. Do not edit manually! -if exist "%TEMP%\BugReporterCrashReportJson" for /f "delims=" %%i in ('dir /b /a-d "%TEMP%\BugReporterCrashReportJson\*.json"') do curl -X POST -H "Content-Type: application/json" -T "%TEMP%\BugReporterCrashReportJson\%%i" "https://internal-crash-collector.prd.cds.internal.unity3d.com/api/crash" || echo Failed to upload %%i. Ignoring... +if exist "%TEMP%\BugReporterCrashReportJson" for /f "delims=" %%i in ('dir /b /a-d "%TEMP%\BugReporterCrashReportJson\CrashReport_*.json"') do curl -X POST --connect-timeout 5 --max-time 10 -H "Content-Type: application/json" -T "%TEMP%\BugReporterCrashReportJson\%%i" "https://internal-crash-collector.prd.cds.internal.unity3d.com/api/crash" || echo Failed to upload %%i. Ignoring... curl -fs "https://artifactory-slo.bf.unity3d.com/artifactory/automation-and-tooling/infrastructure-instability-detection/standalone/1.2.2/windows.zip" --output "infrastructure_instability_detection_standalone.zip" --retry 5 IF EXIST "infrastructure_instability_detection" rmdir /s /q infrastructure_instability_detection powershell.exe -nologo -noprofile -command "& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('infrastructure_instability_detection_standalone.zip', '.'); }" && DEL "infrastructure_instability_detection_standalone.zip" diff --git a/.yamato/package-tests.yml b/.yamato/package-tests.yml index 50799533dd..5d6364c526 100644 --- a/.yamato/package-tests.yml +++ b/.yamato/package-tests.yml @@ -26,7 +26,7 @@ {% for platform in test_platforms.desktop -%} {% for editor in validation_editors.all -%} -package_test_-_ngo_{{ editor }}_{{ platform.name }}: +package_test_-_ngo_{{ platform.name }}_{{ editor }}: name : Package Test - NGO [{{ platform.name }}, {{ editor }}] agent: type: {{ platform.type }} diff --git a/.yamato/project.metafile b/.yamato/project.metafile index 518f8ec2ba..c1e846d489 100644 --- a/.yamato/project.metafile +++ b/.yamato/project.metafile @@ -179,18 +179,16 @@ test_platforms: validation_editors: default: - 6000.3 + minimal: + - 6000.0 + latest: + - 6000.6 all: - 6000.0 - 6000.3 - - 6000.5 - 6000.6 - - trunk - - 5fe7931aab8c4fff9274e15ef0800125c68b8d6a - minimal: - - 6000.0 standards: - 6000.0.72f1 -pinnedTrunk: 5fe7931aab8c4fff9274e15ef0800125c68b8d6a # Scripting backends used by Standalone RunTimeTests--------------------------------------------------- diff --git a/.yamato/wrench/api-validation-jobs.yml b/.yamato/wrench/api-validation-jobs.yml index 207c95dc49..b92db75f1f 100644 --- a/.yamato/wrench/api-validation-jobs.yml +++ b/.yamato/wrench/api-validation-jobs.yml @@ -18,7 +18,7 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -60,8 +60,9 @@ api_validation_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 diff --git a/.yamato/wrench/package-pack-jobs.yml b/.yamato/wrench/package-pack-jobs.yml index a5cc623aab..768dd701e6 100644 --- a/.yamato/wrench/package-pack-jobs.yml +++ b/.yamato/wrench/package-pack-jobs.yml @@ -27,7 +27,8 @@ package_pack_-_netcode_gameobjects: - upm-ci~/packages/**/* variables: UPMCI_ACK_LARGE_PACKAGE: 1 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 diff --git a/.yamato/wrench/preview-a-p-v.yml b/.yamato/wrench/preview-a-p-v.yml index f660b39a15..3e6e67e31f 100644 --- a/.yamato/wrench/preview-a-p-v.yml +++ b/.yamato/wrench/preview-a-p-v.yml @@ -16,18 +16,12 @@ all_preview_apv_jobs: - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_macos13 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_3_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_macos13 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_ubuntu2204 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_5_-_win10 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_macos13arm - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_ubuntu2204 - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_6_-_win10 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_macos13arm - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_ubuntu2204 - - path: .yamato/wrench/preview-a-p-v.yml#preview_apv_-_6000_7_-_win10 metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (MacOS). preview_apv_-_6000_0_-_macos13: @@ -37,7 +31,7 @@ preview_apv_-_6000_0_-_macos13: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -47,7 +41,7 @@ preview_apv_-_6000_0_-_macos13: - command: unity-downloader-cli -u 6000.0/staging -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.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --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 @@ -82,10 +76,11 @@ preview_apv_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Ubuntu). preview_apv_-_6000_0_-_ubuntu2204: @@ -95,7 +90,7 @@ preview_apv_-_6000_0_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -105,7 +100,7 @@ preview_apv_-_6000_0_-_ubuntu2204: - command: unity-downloader-cli -u 6000.0/staging -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.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --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 @@ -140,10 +135,11 @@ preview_apv_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.0 manifest (Windows). preview_apv_-_6000_0_-_win10: @@ -154,7 +150,7 @@ preview_apv_-_6000_0_-_win10: 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -164,7 +160,7 @@ preview_apv_-_6000_0_-_win10: - command: unity-downloader-cli -u 6000.0/staging -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.0 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.0 --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.0 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd @@ -199,10 +195,11 @@ preview_apv_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (MacOS). preview_apv_-_6000_3_-_macos13: @@ -212,7 +209,7 @@ preview_apv_-_6000_3_-_macos13: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -222,7 +219,7 @@ preview_apv_-_6000_3_-_macos13: - command: unity-downloader-cli -u 6000.3/staging -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.3 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.3 --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 @@ -257,10 +254,11 @@ preview_apv_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Ubuntu). preview_apv_-_6000_3_-_ubuntu2204: @@ -270,7 +268,7 @@ preview_apv_-_6000_3_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -280,7 +278,7 @@ preview_apv_-_6000_3_-_ubuntu2204: - command: unity-downloader-cli -u 6000.3/staging -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.3 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.3 --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 @@ -315,10 +313,11 @@ preview_apv_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.3 manifest (Windows). preview_apv_-_6000_3_-_win10: @@ -329,7 +328,7 @@ preview_apv_-_6000_3_-_win10: 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -339,7 +338,7 @@ preview_apv_-_6000_3_-_win10: - command: unity-downloader-cli -u 6000.3/staging -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.3 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.3 --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.3 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd @@ -374,185 +373,11 @@ preview_apv_-_6000_3_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.5 manifest (MacOS). -preview_apv_-_6000_5_-_macos13: - name: Preview APV - 6000.5 - macos13 - agent: - image: package-ci/macos-13:v4 - type: Unity::VM::osx - flavor: b1.xlarge - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.5 manifest (Ubuntu). -preview_apv_-_6000_5_-_ubuntu2204: - name: Preview APV - 6000.5 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.5 manifest (Windows). -preview_apv_-_6000_5_-_win10: - name: Preview APV - 6000.5 - win10 - agent: - image: package-ci/win10:v4 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (MacOS). preview_apv_-_6000_6_-_macos13arm: @@ -563,7 +388,7 @@ preview_apv_-_6000_6_-_macos13arm: flavor: m1.mac model: M1 commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -573,7 +398,7 @@ preview_apv_-_6000_6_-_macos13arm: - command: unity-downloader-cli -u 6000.6/staging -c editor --path .Editor --fast --arch arm64 timeout: 10 retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --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 @@ -608,10 +433,11 @@ preview_apv_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Ubuntu). preview_apv_-_6000_6_-_ubuntu2204: @@ -621,7 +447,7 @@ preview_apv_-_6000_6_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -631,7 +457,7 @@ preview_apv_-_6000_6_-_ubuntu2204: - command: unity-downloader-cli -u 6000.6/staging -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --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 @@ -666,10 +492,11 @@ preview_apv_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Functional tests for dependents found in the latest 6000.6 manifest (Windows). preview_apv_-_6000_6_-_win10: @@ -680,7 +507,7 @@ preview_apv_-_6000_6_-_win10: 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -690,7 +517,7 @@ preview_apv_-_6000_6_-_win10: - command: unity-downloader-cli -u 6000.6/staging -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.6 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ + - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.6 --artifacts-path=PreviewApvArtifacts~ - command: python PythonScripts/editor_manifest_validator.py --version=6000.6 --wrench-config=.yamato/wrench/wrench_config.json after: - command: .yamato\generated-scripts\infrastructure-instability-detection-win.cmd @@ -725,184 +552,9 @@ preview_apv_-_6000_6_-_win10: UNITY_LICENSING_SERVER_DELETE_NUL: 0 UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.7 manifest (MacOS). -preview_apv_-_6000_7_-_macos13arm: - name: Preview APV - 6000.7 - macos13arm - agent: - image: package-ci/macos-13-arm64:v4 - type: Unity::VM::osx - flavor: m1.mac - model: M1 - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 --arch arm64 - timeout: 10 - retries: 3 - - command: python PythonScripts/preview_apv.py --wrench-config=.yamato/wrench/wrench_config.json --editor-version=6000.7 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.7 manifest (Ubuntu). -preview_apv_-_6000_7_-_ubuntu2204: - name: Preview APV - 6000.7 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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.7 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - -# Functional tests for dependents found in the latest 6000.7 manifest (Windows). -preview_apv_-_6000_7_-_win10: - name: Preview APV - 6000.7 - win10 - agent: - image: package-ci/win10:v4 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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.7 --testsuite=editor,playmode --artifacts-path=PreviewApvArtifacts~ - - command: python PythonScripts/editor_manifest_validator.py --version=6000.7 --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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 diff --git a/.yamato/wrench/promotion-jobs.yml b/.yamato/wrench/promotion-jobs.yml index 336bd6b080..ed5c138ff1 100644 --- a/.yamato/wrench/promotion-jobs.yml +++ b/.yamato/wrench/promotion-jobs.yml @@ -14,7 +14,7 @@ publish_dry_run_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -91,36 +91,6 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-win10 - unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: @@ -151,42 +121,13 @@ publish_dry_run_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-win10 - unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 # Publish for netcode.gameobjects to https://artifactory-slo.bf.unity3d.com/artifactory/api/npm/upm-npm publish_netcode_gameobjects: @@ -196,7 +137,7 @@ publish_netcode_gameobjects: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -273,36 +214,6 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.3-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_macos13 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-macos13 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-macos13 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_5_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.5-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.5-win10 - unzip: true - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_6_-_macos13arm specific_options: packages: @@ -333,41 +244,12 @@ publish_netcode_gameobjects: UTR: location: results/UTR/validate-netcode.gameobjects-6000.6-win10 unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_macos13arm - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-macos13arm - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-macos13arm - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-ubuntu2204 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-ubuntu2204 - unzip: true - - path: .yamato/wrench/validation-jobs.yml#validate_-_netcode_gameobjects_-_6000_7_-_win10 - specific_options: - packages: - ignore_artifact: true - pvp-results: - location: results/pvp/validate-netcode.gameobjects-6000.7-win10 - unzip: true - UTR: - location: results/UTR/validate-netcode.gameobjects-6000.7-win10 - unzip: true variables: UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip allow_on: branch match "^release/.*" metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 diff --git a/.yamato/wrench/recipe-regeneration.yml b/.yamato/wrench/recipe-regeneration.yml index c4f798e8c2..1529d50b33 100644 --- a/.yamato/wrench/recipe-regeneration.yml +++ b/.yamato/wrench/recipe-regeneration.yml @@ -31,5 +31,5 @@ test_-_wrench_jobs_up_to_date: cancel_old_ci: true metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 diff --git a/.yamato/wrench/validation-jobs.yml b/.yamato/wrench/validation-jobs.yml index 4a7b6ba365..ff46e2c4ac 100644 --- a/.yamato/wrench/validation-jobs.yml +++ b/.yamato/wrench/validation-jobs.yml @@ -14,7 +14,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -28,13 +28,19 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -69,10 +75,11 @@ validate_-_netcode_gameobjects_-_6000_0_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -84,7 +91,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -98,13 +105,19 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -139,10 +152,11 @@ validate_-_netcode_gameobjects_-_6000_0_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -154,7 +168,7 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -168,13 +182,19 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -209,10 +229,11 @@ validate_-_netcode_gameobjects_-_6000_0_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -224,7 +245,7 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: type: Unity::VM::osx flavor: b1.xlarge commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -238,13 +259,19 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -279,10 +306,11 @@ validate_-_netcode_gameobjects_-_6000_3_-_macos13: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -294,7 +322,7 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -308,13 +336,19 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -349,10 +383,11 @@ validate_-_netcode_gameobjects_-_6000_3_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -364,7 +399,7 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -378,223 +413,19 @@ validate_-_netcode_gameobjects_-_6000_3_-_win10: - 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 + - command: python PythonScripts/ignore_clean_console_failures.py 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - macos13 (6000.5 - MacOS). -validate_-_netcode_gameobjects_-_6000_5_-_macos13: - name: Validate - netcode.gameobjects - 6000.5 - macos13 - agent: - image: package-ci/macos-13:v4 - type: Unity::VM::osx - flavor: b1.xlarge - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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}" ' + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - ubuntu2204 (6000.5 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_5_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.5 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.5 - win10 (6000.5 - Windows). -validate_-_netcode_gameobjects_-_6000_5_-_win10: - name: Validate - netcode.gameobjects - 6000.5 - win10 - agent: - image: package-ci/win10:v4 - type: Unity::VM - flavor: b1.large - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 6000.5/staging -c editor --path .Editor --fast - timeout: 20 - 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 - - 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}" ' + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -629,10 +460,11 @@ validate_-_netcode_gameobjects_-_6000_5_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -645,7 +477,7 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: flavor: m1.mac model: M1 commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -659,13 +491,19 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -700,10 +538,11 @@ validate_-_netcode_gameobjects_-_6000_6_-_macos13arm: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -715,7 +554,7 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "$WRENCH_LOCALAPV_URL" -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 @@ -729,13 +568,19 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: - command: upm-pvp test --unity .Editor --packages "upm-ci~/packages/*.tgz" --results upm-ci~/pvp timeout: 40 retries: 0 + - command: python PythonScripts/ignore_clean_console_failures.py + timeout: 5 + 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}" ' + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--ff={ops.upmpvpevidence.enable=true}" ' + timeout: 40 + retries: 1 + - command: 'UnifiedTestRunner --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -770,10 +615,11 @@ validate_-_netcode_gameobjects_-_6000_6_-_ubuntu2204: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects @@ -785,7 +631,7 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: type: Unity::VM flavor: b1.large commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.zip -o wrench-localapv.zip + - command: curl "%WRENCH_LOCALAPV_URL%" -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 @@ -799,224 +645,19 @@ validate_-_netcode_gameobjects_-_6000_6_-_win10: - 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - macos13arm (6000.7 - MacOS). -validate_-_netcode_gameobjects_-_6000_7_-_macos13arm: - name: Validate - netcode.gameobjects - 6000.7 - macos13arm - agent: - image: package-ci/macos-13-arm64:v4 - type: Unity::VM::osx - flavor: m1.mac - model: M1 - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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 --arch arm64 - timeout: 20 - 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 - - 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 + - command: python PythonScripts/ignore_clean_console_failures.py 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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - ubuntu2204 (6000.7 - Ubuntu). -validate_-_netcode_gameobjects_-_6000_7_-_ubuntu2204: - name: Validate - netcode.gameobjects - 6000.7 - ubuntu2204 - 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-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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: 20 - 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 - - 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}" ' + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/editor --suite=editor "--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: - UNITY_LICENSING_SERVER_BASE_URL: http://unity-ci-licenses.hq.unity3d.com:8080/ - UNITY_LICENSING_SERVER_DELETE_NUL: 0 - UNITY_LICENSING_SERVER_DELETE_ULF: 0 - UNITY_LICENSING_SERVER_TOOLSET: pro - UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 - metadata: - Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 - labels: - - Packages:netcode.gameobjects - -# PVP Editor and Playmode tests for Validate - netcode.gameobjects - 6000.7 - win10 (6000.7 - Windows). -validate_-_netcode_gameobjects_-_6000_7_-_win10: - name: Validate - netcode.gameobjects - 6000.7 - win10 - agent: - image: package-ci/win10:v4 - type: Unity::VM - flavor: b1.large - commands: - - command: curl https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-3_51b4e6affb4c10b90f92db9551b9dc80c5520794983600cff4fa925111db116d.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: 20 - 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 - - 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}" ' + - command: 'UnifiedTestRunner.exe --testproject=testproject --editor-location=.Editor --clean-library --reruncount=1 --clean-library-on-rerun --artifacts-path=artifacts/playmode --suite=playmode "--ff={ops.upmpvpevidence.enable=true}" ' timeout: 40 retries: 1 after: @@ -1051,10 +692,11 @@ validate_-_netcode_gameobjects_-_6000_7_-_win10: UNITY_LICENSING_SERVER_DELETE_ULF: 0 UNITY_LICENSING_SERVER_TOOLSET: pro UPMPVP_ACK_UPMPVP_DOES_NO_API_VALIDATION: 1 - UPMPVP_CONTEXT_WRENCH: 3.3.1.0 + UPMPVP_CONTEXT_WRENCH: 3.12.1.0 + WRENCH_LOCALAPV_URL: https://artifactory.prd.it.unity3d.com/artifactory/stevedore-unity-internal/wrench-localapv/1-3-20_6ace78b5de8cf4671628832afa80f64bddc76883d89b9781fa19478b4bb33732.zip metadata: Job Maintainers: '#rm-packageworks' - Wrench: 3.3.1.0 + Wrench: 3.12.1.0 labels: - Packages:netcode.gameobjects diff --git a/.yamato/wrench/wrench_config.json b/.yamato/wrench/wrench_config.json index fb5e12dc7b..b48ed0d37c 100644 --- a/.yamato/wrench/wrench_config.json +++ b/.yamato/wrench/wrench_config.json @@ -18,7 +18,7 @@ }, "InternalOnly": false, "NeverPublish": false, - "MaxEditorVersion": "", + "MaxEditorVersion": "6000.6", "coverageEnabled": false, "coverageCommands": [ "generateAdditionalMetrics;generateHtmlReport;assemblyFilters:ASSEMBLY_NAME;" @@ -39,7 +39,7 @@ }, "publishing_job": ".yamato/wrench/promotion-jobs.yml#publish_netcode_gameobjects", "branch_pattern": "ReleaseSlash", - "wrench_version": "3.3.1.0", + "wrench_version": "3.12.1.0", "pvp_exemption_path": ".yamato/wrench/pvp-exemptions.json", "cs_project_path": "Tools/CI/NGO.Cookbook.csproj" } \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index d77b3d4bdb..b57525b8f2 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -8,11 +8,11 @@ - - - - - + + + + + diff --git a/Tools/CI/Settings/NGOSettings.cs b/Tools/CI/Settings/NGOSettings.cs index aab47ee96b..5b9ade0cfe 100644 --- a/Tools/CI/Settings/NGOSettings.cs +++ b/Tools/CI/Settings/NGOSettings.cs @@ -24,6 +24,7 @@ public class NGOSettings : AnnotatedSettingsBase new PackageOptions() { ReleaseOptions = new ReleaseOptions() { IsReleasing = true }, + MaximumEditorVersion = "6000.6", // NGOv2.X will be deprecated in 6000.7+ so we won't run tests on higher editor versions ValidationOptions = validationOptions } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index 1306b4a803..9248ed159f 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -57,6 +57,9 @@ public IEnumerator TestRpcImplicitNetworkBehaviour() #region Tests using non-null NetworkBehaviours and NetworkVariable [UnityTest] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestNetworkVariable() { yield return SpawnTestPrefabInstance(); @@ -75,6 +78,9 @@ public IEnumerator TestNetworkVariable() #region Validating using NULL as a NetworkBehaviourReference [UnityTest] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestSerializeNull() { yield return SpawnTestPrefabInstance(true); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 699f4a21f9..da4675655b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -32,6 +32,9 @@ protected override void OnOneTimeSetup() } [UnityTest] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestSerializeNetworkObject() { yield return SpawnTestPrefabInstance(); @@ -59,6 +62,9 @@ public IEnumerator TestSerializeNetworkObject() } [UnityTest] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestSerializeNull() { yield return SpawnTestPrefabInstance(true); @@ -106,6 +112,9 @@ public IEnumerator TestSerializeNull() } [UnityTest] +#if ENABLE_CORECLR + [Explicit("NGO NetworkVariable serialization codegen not generated for some types on CoreCLR (falls back to FallbackSerializer), see https://jira.unity3d.com/browse/UUM-149592")] +#endif public IEnumerator TestGetReferenceAndConversion() { yield return SpawnTestPrefabInstance(); From bd121632f3c7b311f3c0746b920d7d8621e63d6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Chrobot?= <124174716+michalChrobot@users.noreply.github.com> Date: Fri, 25 Sep 2026 10:33:04 +0200 Subject: [PATCH 62/64] ci: Initial BuildAutomation module implementation (#4168) * Initial BuildAutomation module implementation * Renamed Wrench settings for beter readibility * Updated MinSupportedEditorBySample values --- .../build-automation/standalone-projects.yml | 113 ++++++ .yamato/project-builders/builder.metafile | 22 -- .yamato/project-builders/project-builders.yml | 97 ----- Tools/CI/NGO.Cookbook.csproj | 7 +- Tools/CI/Program.cs | 5 +- .../CI/Settings/NgoBuildAutomationSettings.cs | 40 ++ .../{NGOSettings.cs => NgoWrenchSettings.cs} | 4 +- .../scripts/BuildAutomation/BuilderScripts.cs | 164 ++++++++ .../Unity.ProjectBuild.Editor.asmdef | 0 .../extend_source_sparse_checkout.py | 65 ++++ .../BuildAutomation/get_build_method.py | 70 ++++ .../BuildAutomation/manifest_update.py | 154 ++++++++ .../BuildAutomation/package_parsing.py | 23 ++ .../BuildAutomation/print_build_summary.py | 226 +++++++++++ .../Asteroids.patterns | 2 + .../BossRoom.patterns | 2 + .../SocialHub.patterns | 2 + .../resolve_sparse_file_references.py | 165 ++++++++ .../BuildAutomation/setup_build_scripts.py | 38 ++ .../BuildAutomation/validate_params.py | 366 ++++++++++++++++++ .../scripts/BuildAutomation/BuilderScripts.cs | 181 --------- Tools/scripts/BuildAutomation/FileCopy.py | 59 --- .../BuildAutomation/connect_services.py | 46 --- .../BuildAutomation/disable-enable-burst.py | 120 ------ .../BuildAutomation/manifest_update.py | 62 --- .../BuildAutomation/validate_params.py | 58 --- 26 files changed, 1442 insertions(+), 649 deletions(-) create mode 100644 .yamato/build-automation/standalone-projects.yml delete mode 100644 .yamato/project-builders/builder.metafile delete mode 100644 .yamato/project-builders/project-builders.yml create mode 100644 Tools/CI/Settings/NgoBuildAutomationSettings.cs rename Tools/CI/Settings/{NGOSettings.cs => NgoWrenchSettings.cs} (94%) create mode 100644 Tools/CI/scripts/BuildAutomation/BuilderScripts.cs rename Tools/{ => CI}/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef (100%) create mode 100644 Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py create mode 100644 Tools/CI/scripts/BuildAutomation/get_build_method.py create mode 100644 Tools/CI/scripts/BuildAutomation/manifest_update.py create mode 100644 Tools/CI/scripts/BuildAutomation/package_parsing.py create mode 100644 Tools/CI/scripts/BuildAutomation/print_build_summary.py create mode 100644 Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/Asteroids.patterns create mode 100644 Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/BossRoom.patterns create mode 100644 Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/SocialHub.patterns create mode 100644 Tools/CI/scripts/BuildAutomation/resolve_sparse_file_references.py create mode 100644 Tools/CI/scripts/BuildAutomation/setup_build_scripts.py create mode 100644 Tools/CI/scripts/BuildAutomation/validate_params.py delete mode 100644 Tools/scripts/BuildAutomation/BuilderScripts.cs delete mode 100644 Tools/scripts/BuildAutomation/FileCopy.py delete mode 100644 Tools/scripts/BuildAutomation/connect_services.py delete mode 100644 Tools/scripts/BuildAutomation/disable-enable-burst.py delete mode 100644 Tools/scripts/BuildAutomation/manifest_update.py delete mode 100644 Tools/scripts/BuildAutomation/validate_params.py diff --git a/.yamato/build-automation/standalone-projects.yml b/.yamato/build-automation/standalone-projects.yml new file mode 100644 index 0000000000..2d06d4f69b --- /dev/null +++ b/.yamato/build-automation/standalone-projects.yml @@ -0,0 +1,113 @@ +####################################################### +### ### +### Auto-generated by Recipe Engine, DO NOT EDIT. ### +### ### +####################################################### +# Source: RecipeEngine.Modules.BuildAutomation.Recipes.ProjectBuildersRecipe +# Generated by RecipeEngine.Modules.BuildAutomation +build_asteroids_project: + name: Asteroids + agent: + image: package-ci/win10:v4 + type: Unity::VM + flavor: b1.xlarge + commands: + - command: pip install --upgrade unity-downloader-cli + - command: python Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py --source-dir %YAMATO_SOURCE_DIR% --local-tested-packages-path . + - command: python Tools/CI/scripts/BuildAutomation/validate_params.py --build-machine-os windows --check-sample-branch https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git --check-unity-version --check-local-tested-packages %YAMATO_SOURCE_DIR%/. --min-supported-editor 6000.3 + - command: git clone --single-branch --branch %SAMPLE_BRANCH% https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git C:/ClonedProject + - command: python Tools/CI/scripts/BuildAutomation/manifest_update.py --manifest-path C:/ClonedProject/./Packages/manifest.json --local-tested-packages-path %YAMATO_SOURCE_DIR%/. + - command: python Tools/CI/scripts/BuildAutomation/print_build_summary.py --project-name Asteroids --build-machine-os windows --sample-repo-url https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git --project-path C:/ClonedProject/. + - command: unity-downloader-cli --unity-version %UNITY_VERSION% --path C:/TestingEditor --wait --fast -c editor -c il2cpp -c android -c macos + - command: python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path C:/ClonedProject/. + - command: python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path C:/ClonedProject/. --unity-path C:/TestingEditor/Unity.exe + - command: python -c "import os, shutil; dst = './PlayerArtifacts/Asteroids'; src = r'C:/ClonedProject/./build'; os.makedirs(dst, exist_ok=True); shutil.copytree(src, dst, dirs_exist_ok=True)" + artifacts: + logs: + paths: + - artifacts/**/* + players: + paths: + - PlayerArtifacts/**/* + variables: + LOCAL_TESTED_PACKAGES_NAMES: com.unity.netcode.gameobjects + PLATFORM_(WIN64_MAC_ANDROID): win64 + SAMPLE_BRANCH: main + SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR): il2cpp + UNITY_VERSION: 6000.3 + source: + files: + sparse_checkout_rules: Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/Asteroids.patterns + checkout_mode: sparse +build_bossroom_project: + name: BossRoom + agent: + image: package-ci/win10:v4 + type: Unity::VM + flavor: b1.xlarge + commands: + - command: pip install --upgrade unity-downloader-cli + - command: python Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py --source-dir %YAMATO_SOURCE_DIR% --local-tested-packages-path . + - command: python Tools/CI/scripts/BuildAutomation/validate_params.py --build-machine-os windows --check-sample-branch https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git --check-unity-version --check-local-tested-packages %YAMATO_SOURCE_DIR%/. --min-supported-editor 6000.3 + - command: git clone --single-branch --branch %SAMPLE_BRANCH% https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git C:/ClonedProject + - command: python Tools/CI/scripts/BuildAutomation/manifest_update.py --manifest-path C:/ClonedProject/./Packages/manifest.json --local-tested-packages-path %YAMATO_SOURCE_DIR%/. + - command: python Tools/CI/scripts/BuildAutomation/print_build_summary.py --project-name BossRoom --build-machine-os windows --sample-repo-url https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git --project-path C:/ClonedProject/. + - command: unity-downloader-cli --unity-version %UNITY_VERSION% --path C:/TestingEditor --wait --fast -c editor -c il2cpp -c android -c macos + - command: python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path C:/ClonedProject/. + - command: python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path C:/ClonedProject/. --unity-path C:/TestingEditor/Unity.exe + - command: python -c "import os, shutil; dst = './PlayerArtifacts/BossRoom'; src = r'C:/ClonedProject/./build'; os.makedirs(dst, exist_ok=True); shutil.copytree(src, dst, dirs_exist_ok=True)" + artifacts: + logs: + paths: + - artifacts/**/* + players: + paths: + - PlayerArtifacts/**/* + variables: + LOCAL_TESTED_PACKAGES_NAMES: com.unity.netcode.gameobjects + PLATFORM_(WIN64_MAC_ANDROID): win64 + SAMPLE_BRANCH: ngo-playtest-update + SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR): il2cpp + UNITY_VERSION: 6000.3 + source: + files: + sparse_checkout_rules: Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/BossRoom.patterns + checkout_mode: sparse +build_socialhub_project: + name: SocialHub + agent: + image: package-ci/win10:v4 + type: Unity::VM + flavor: b1.xlarge + commands: + - command: pip install --upgrade unity-downloader-cli + - command: python Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py --source-dir %YAMATO_SOURCE_DIR% --local-tested-packages-path . + - command: python Tools/CI/scripts/BuildAutomation/validate_params.py --build-machine-os windows --check-sample-branch https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git --check-unity-version --check-local-tested-packages %YAMATO_SOURCE_DIR%/. --min-supported-editor 6000.0 + - command: git clone --filter=blob:none --no-checkout --single-branch --branch %SAMPLE_BRANCH% https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git C:/ClonedProject + - command: git -C C:/ClonedProject sparse-checkout set Basic/DistributedAuthoritySocialHub + - command: git -C C:/ClonedProject checkout %SAMPLE_BRANCH% + - command: python Tools/CI/scripts/BuildAutomation/resolve_sparse_file_references.py --cloned-root C:/ClonedProject --project-path Basic/DistributedAuthoritySocialHub --branch %SAMPLE_BRANCH% + - command: python Tools/CI/scripts/BuildAutomation/manifest_update.py --manifest-path C:/ClonedProject/Basic/DistributedAuthoritySocialHub/Packages/manifest.json --local-tested-packages-path %YAMATO_SOURCE_DIR%/. + - command: python Tools/CI/scripts/BuildAutomation/print_build_summary.py --project-name DistributedAuthoritySocialHub --build-machine-os windows --sample-repo-url https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git --project-path C:/ClonedProject/Basic/DistributedAuthoritySocialHub + - command: unity-downloader-cli --unity-version %UNITY_VERSION% --path C:/TestingEditor --wait --fast -c editor -c il2cpp -c android -c macos + - command: python Tools/CI/scripts/BuildAutomation/setup_build_scripts.py --project-path C:/ClonedProject/Basic/DistributedAuthoritySocialHub + - command: python Tools/CI/scripts/BuildAutomation/get_build_method.py --project-path C:/ClonedProject/Basic/DistributedAuthoritySocialHub --unity-path C:/TestingEditor/Unity.exe + - command: python -c "import os, shutil; dst = './PlayerArtifacts/SocialHub'; src = r'C:/ClonedProject/Basic/DistributedAuthoritySocialHub/build'; os.makedirs(dst, exist_ok=True); shutil.copytree(src, dst, dirs_exist_ok=True)" + artifacts: + logs: + paths: + - artifacts/**/* + players: + paths: + - PlayerArtifacts/**/* + variables: + LOCAL_TESTED_PACKAGES_NAMES: com.unity.netcode.gameobjects + PLATFORM_(WIN64_MAC_ANDROID): win64 + SAMPLE_BRANCH: main + SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR): il2cpp + UNITY_VERSION: 6000.0 + source: + files: + sparse_checkout_rules: Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/SocialHub.patterns + checkout_mode: sparse + diff --git a/.yamato/project-builders/builder.metafile b/.yamato/project-builders/builder.metafile deleted file mode 100644 index b261d28dea..0000000000 --- a/.yamato/project-builders/builder.metafile +++ /dev/null @@ -1,22 +0,0 @@ -# https://internaldocs.unity.com/yamato_continuous_integration/usage/templating/ - -NetcodeProjects: - # Note that we are using internal Unity repo. This means that we may test with newest changes that are not yet released to our users (there are also public versions) - # The parameters specify repo location, default branch to use (since you can modify it via job variable), path to manifest.json file so we can replace package entry and path to the project root folder since it differs between projects - # Note that for BossRoom 'main' branch supports NGOv1.X and 'develop' branch supports NGOv2.X - # TODO: For BossRoom correct branch to develop when PR-3736 is merged in their repo - BossRoom: - GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git" - defaultBranch: ngo-playtest-update - manifestPath: Packages/manifest.json - projectPath: '.' - Asteroids: - GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git" - defaultBranch: main - manifestPath: Packages/manifest.json - projectPath: '.' - SocialHub: - GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git" - defaultBranch: main - manifestPath: Basic/DistributedAuthoritySocialHub/Packages/manifest.json - projectPath: 'Basic/DistributedAuthoritySocialHub' diff --git a/.yamato/project-builders/project-builders.yml b/.yamato/project-builders/project-builders.yml deleted file mode 100644 index 327598d9ea..0000000000 --- a/.yamato/project-builders/project-builders.yml +++ /dev/null @@ -1,97 +0,0 @@ -{% metadata_file .yamato/project-builders/builder.metafile %} ---- -# The above line corresponds to https://internaldocs.unity.com/yamato_continuous_integration/usage/templating/ - -# Yamato paths disclaimer: -# Note that artifacts can get only local paths and since cloned project is in different location, you need to be careful with paths -# All artifact paths are defined relative to the Yamato source directory (YAMATO_SOURCE_DIR) and are case-sensitive. -# !!! important “Artifact directory locations” All artifact globs are defined relative to the source directory (YAMATO_SOURCE_DIR). Yamato can’t capture artifacts from outside this directory, so if you need files from elsewhere, you should copy them into your source directory as part of job commands. - -# Those jobs were created in order to (in most cases) speed up playtesting and development time. -# The aim is to collect all possible projects that use Netcode for Entities and create a job that will build the project with the given version of N4E package. -# The package is taken directly from the branch from which the job was triggered. So image triggering the job from release/2.0.0 and release/2.1.0 branch to compare differences -# Example use case would be to trigger the build job on Sunday so by Monday morning all builds are ready for playtesting. This limits the time a dev/QA has to spend building projects for different configurations (win, mac, Android, scripting backends, burst etc) or simply time necessary for building huge projects (Megacity). - -# This job takes parameters as scriptable backend configuration, burst compilation, unity editor version and platform. -# Since Yamato variables can't be restricted to only specific values, the job will validate the parameters passed to it and fail quickly if those are incorrect. In order to see what options are available, please look at the variable name. - -# Note that for now all of those builds are being made on Windows machine (so for example combination of macOS + il2cpp is expected to fail) -# TODO: for now all builds are being made on Windows machine, but it would be nice to have a Mac build as well. -# TODO: add iOS support -{% for netcodeProject in NetcodeProjects -%} -build_{{ netcodeProject[0] }}_project: - name: {{ netcodeProject[0] }} - agent: - type: Unity::VM - image: package-ci/win10:v4 - flavor: b1.xlarge - variables: - UNITY_VERSION: trunk - SCRIPTING_BACKEND_IL2CPP_MONO: il2cpp - BURST_ON_OFF: on - PLATFORM_WIN64_MAC_ANDROID: win64 - SAMPLE_BRANCH: {{ netcodeProject[1].defaultBranch }} - commands: - # Validate inputs passed via Yamato variables - - python Tools/scripts/BuildAutomation/validate_params.py - - echo Building {{ netcodeProject[0] }} project from branch %SAMPLE_BRANCH% with Unity version of %UNITY_VERSION%, Scripting backend %SCRIPTING_BACKEND_IL2CPP_MONO%, Burst %BURST_ON_OFF% for platform %PLATFORM_WIN64_MAC_ANDROID% - - # Clone the external project repository into a specific directory. Notice that branch is also specified. - - git clone --single-branch --branch %SAMPLE_BRANCH% {{ netcodeProject[1].GithubRepo }} C:/ClonedProject - - # Modify the external project's manifest to use the local N4E package from current branch on which this Yamato job is running. (requires python that should be preinstalled in the image) - - python Tools/scripts/BuildAutomation/manifest_update.py --manifest-path C:/ClonedProject/{{ netcodeProject[1].manifestPath }} --local-package-path %YAMATO_SOURCE_DIR%/com.unity.netcode.gameobjects - - # Run python script to update ProjectSettings.asset in order to connect the project to Unity Services/set proper values. - # Notice that if a project has this already set up then in theory we don't need to run this script. - - python Tools/scripts/BuildAutomation/connect_services.py --project-settings-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}/ProjectSettings/ProjectSettings.asset - - # Enable or disable Burst compilation. This step is specific to Netcode package (or any package that uses Burst) - - IF "%BURST_ON_OFF%"=="on" (python Tools/scripts/BuildAutomation/disable-enable-burst.py --enable-burst --project-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}) - ELSE (python Tools/scripts/BuildAutomation/disable-enable-burst.py --disable-burst --project-path C:/ClonedProject/{{ netcodeProject[1].projectPath }}) - - # Download the Unity Editor version specified in the UNITY_VERSION variable. Il2cpp component is downloaded only if the SCRIPTING_BACKEND_IL2CPP_MONO is set to "il2cpp". - # TODO: we could download components only if needed - - unity-downloader-cli --fast --wait -u %UNITY_VERSION% -p C:/TestingEditor -c Editor -c il2cpp -c Android -c macOS - - # Add BuilderScript.cs to the project so we can modify and build the project using Unity Editor. - # This is a bit tricky step, notice that we also need to include proper assembly definition in order for those scripts to compile properly. - # TODO: the asmdef file can be simplified - - python Tools/scripts/BuildAutomation/FileCopy.py "C:/ClonedProject/{{ netcodeProject[1].projectPath }}" - - # Build the project using Unity Editor. This will call the given static BuilderScripts method. - # Ideally, it would be nice to parametrize the BuilderScripts (for example to pass scripting backend as parameter) but -executeMethod only calls static methods without parameters so for now we will have multiple configurations - # Notice that for Android platform even if mono is selected, il2cpp will be used since mono is not supported for Android builds. - - IF /I "%PLATFORM_WIN64_MAC_ANDROID%"=="win64" ( - IF /I "%SCRIPTING_BACKEND_IL2CPP_MONO%"=="il2cpp" ( - C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget win64 -executeMethod BuilderScripts.BuildWinIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit - ) ELSE ( - C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget win64 -executeMethod BuilderScripts.BuildWinMono -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit - ) - ) - ELSE IF /I "%PLATFORM_WIN64_MAC_ANDROID%"=="mac" ( - IF /I "%SCRIPTING_BACKEND_IL2CPP_MONO%"=="il2cpp" ( - C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget osx -executeMethod BuilderScripts.BuildMacIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit - ) ELSE ( - C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget osx -executeMethod BuilderScripts.BuildMacMono -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit - ) - ) - ELSE IF /I "%PLATFORM_WIN64_MAC_ANDROID%"=="android" ( - C:/TestingEditor/Unity.exe -projectPath C:/ClonedProject/{{ netcodeProject[1].projectPath }} -buildTarget android -executeMethod BuilderScripts.BuildAndroidIl2cpp -batchmode -logFile ./artifacts/UnityLog.txt -automated -crash-report-folder ./artifacts/CrashArtifacts -quit - ) - - # Because of this we need to ensure that all files are copied to the source directory. - # TODO: this can be omitted if I can somehow build the project in the source directory (YAMATO_SOURCE_DIR) instead of C:/CompetitiveAction - - python -c "import os; os.makedirs('./build', exist_ok=True)" # --> Create the build directory if it doesn't exist - - python -c "import shutil; shutil.copytree('C:/ClonedProject/{{ netcodeProject[1].projectPath }}/build', './build', dirs_exist_ok=True)" # --> Copy the build directory to the source directory (YAMATO_SOURCE_DIR). Remember to copy entire directory and not only exe file - - artifacts: - logs: - paths: - - '*.log' - - '*.xml' - - artifacts/**/* - players: - paths: - - build/**/* -{% endfor -%} \ No newline at end of file diff --git a/Tools/CI/NGO.Cookbook.csproj b/Tools/CI/NGO.Cookbook.csproj index b57525b8f2..9296598611 100644 --- a/Tools/CI/NGO.Cookbook.csproj +++ b/Tools/CI/NGO.Cookbook.csproj @@ -7,12 +7,17 @@ enable + + + + - + + diff --git a/Tools/CI/Program.cs b/Tools/CI/Program.cs index 176215bdff..913e04d496 100644 --- a/Tools/CI/Program.cs +++ b/Tools/CI/Program.cs @@ -1,5 +1,6 @@ using NGO.Cookbook.Settings; using RecipeEngine; +using RecipeEngine.Modules.BuildAutomation; using RecipeEngine.Modules.Wrench.Helpers; @@ -8,13 +9,15 @@ public static class Program { public static int Main(string[] args) { - var settings = new NGOSettings(); + var settings = new NgoWrenchSettings(); + var buildAutomationSettings = new NgoBuildAutomationSettings(); // ReSharper disable once UnusedVariable var engine = EngineFactory .Create() .ScanAll() .WithWrenchModule(settings.Wrench) + .WithBuildAutomation(buildAutomationSettings.BuildAutomation) .GenerateAsync().Result; return engine; } diff --git a/Tools/CI/Settings/NgoBuildAutomationSettings.cs b/Tools/CI/Settings/NgoBuildAutomationSettings.cs new file mode 100644 index 0000000000..a81474cdcd --- /dev/null +++ b/Tools/CI/Settings/NgoBuildAutomationSettings.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using RecipeEngine.Modules.BuildAutomation.Models; +using RecipeEngine.Modules.BuildAutomation.Settings; + +namespace NGO.Cookbook.Settings +{ + public class NgoBuildAutomationSettings + { + public NgoBuildAutomationSettings() + { + BuildAutomation = new BuildAutomationSettings( + projects: new Dictionary + { + ["BossRoom"] = new( + GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.coop.git", + ProjectPath: ".", + DefaultSampleBranch: "ngo-playtest-update", + MinSupportedEditorBySample: "6000.3", + LocalTestedPackagesNames: new[] { "com.unity.netcode.gameobjects" }, + LocalTestedPackagesPath: "."), + ["Asteroids"] = new( + GithubRepo: "https://github.cds.internal.unity3d.com/unity/Asteroids-CMB-NGO-Sample.git", + ProjectPath: ".", + DefaultSampleBranch: "main", + MinSupportedEditorBySample: "6000.3", + LocalTestedPackagesNames: new[] { "com.unity.netcode.gameobjects" }, + LocalTestedPackagesPath: "."), + ["SocialHub"] = new( + GithubRepo: "https://github.com/Unity-Technologies/com.unity.multiplayer.samples.bitesize.git", + ProjectPath: "Basic/DistributedAuthoritySocialHub", + DefaultSampleBranch: "main", + MinSupportedEditorBySample: "6000.0", + LocalTestedPackagesNames: new[] { "com.unity.netcode.gameobjects" }, + LocalTestedPackagesPath: "."), + }); + } + + public BuildAutomationSettings BuildAutomation { get; } + } +} diff --git a/Tools/CI/Settings/NGOSettings.cs b/Tools/CI/Settings/NgoWrenchSettings.cs similarity index 94% rename from Tools/CI/Settings/NGOSettings.cs rename to Tools/CI/Settings/NgoWrenchSettings.cs index 5b9ade0cfe..a82cd79895 100644 --- a/Tools/CI/Settings/NGOSettings.cs +++ b/Tools/CI/Settings/NgoWrenchSettings.cs @@ -5,7 +5,7 @@ namespace NGO.Cookbook.Settings; -public class NGOSettings : AnnotatedSettingsBase +public class NgoWrenchSettings : AnnotatedSettingsBase { // Path from the root of the repository where packages are located. readonly string[] packagesRootPaths = {"."}; @@ -30,7 +30,7 @@ public class NGOSettings : AnnotatedSettingsBase } }; - public NGOSettings() + public NgoWrenchSettings() { Wrench = new WrenchSettings(packagesRootPaths, PackageOptions); Wrench.PvpProfilesToCheck = new HashSet() { "supported" }; diff --git a/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs b/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs new file mode 100644 index 0000000000..646a31d06f --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/BuilderScripts.cs @@ -0,0 +1,164 @@ +/////////////////////////////////////////////////////////////////////////////// +// Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. // +// You may override this file locally, but any changes will be overwritten // +// the next time CI is regenerated. Permanent fixes must be made inside: // +// RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ // +/////////////////////////////////////////////////////////////////////////////// + +using System; +using System.Linq; +using UnityEditor; +using UnityEditor.Build; +using UnityEngine; +using UnityEngine.Rendering; + +/// +/// Main build script materialized into the sample project. It builds the player with ALL and ONLY the enabled +/// scenes; to build a different scene set, branch off in the sample repo and clone from that branch. +/// +public class BuilderScripts : MonoBehaviour +{ + static string[] GetEnabledScenes() + { + return EditorBuildSettings.scenes + .Where(scene => scene.enabled) + .Select(scene => scene.path) + .ToArray(); + } + + static void BuildPlayer(BuildConfig config) + { + PlayerSettings.SetScriptingBackend(config.NamedBuildTarget, config.ScriptingBackend); + + if (config.GraphicsApis != null && config.GraphicsApis.Length > 0) + { + PlayerSettings.SetUseDefaultGraphicsAPIs(config.BuildTarget, false); + PlayerSettings.SetGraphicsAPIs(config.BuildTarget, config.GraphicsApis); + } + + if (config.ApplicationId != null) + PlayerSettings.SetApplicationIdentifier(config.NamedBuildTarget, config.ApplicationId); + + if (config.Architecture.HasValue) + PlayerSettings.SetArchitecture(config.NamedBuildTarget, config.Architecture.Value); + + AssetDatabase.SaveAssets(); + + var buildOptions = new BuildPlayerOptions + { + locationPathName = config.OutputPath, + target = config.BuildTarget, + options = BuildOptions.Development, + scenes = GetEnabledScenes() + }; + + BuildPipeline.BuildPlayer(buildOptions); + } + + struct BuildConfig + { + public NamedBuildTarget NamedBuildTarget; + public ScriptingImplementation ScriptingBackend; + public BuildTarget BuildTarget; + public GraphicsDeviceType[] GraphicsApis; + public string OutputPath; + // Not 'string?': materialized into projects with nullable disabled, where that annotation is a hard CS8632 error. + public string ApplicationId; + public int? Architecture; + } + + [MenuItem("Tools/Builder/Build Development Windows Il2cpp")] + static void BuildWinIl2cpp() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = ScriptingImplementation.IL2CPP, + BuildTarget = BuildTarget.StandaloneWindows64, + GraphicsApis = new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12 }, + OutputPath = "./build/Windows_Il2cpp/PlaytestBuild.exe" + }); + + [MenuItem("Tools/Builder/Build Development Windows Mono")] + static void BuildWinMono() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = ScriptingImplementation.Mono2x, + BuildTarget = BuildTarget.StandaloneWindows64, + GraphicsApis = new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12 }, + OutputPath = "./build/Windows_Mono/PlaytestBuild.exe" + }); + + [MenuItem("Tools/Builder/Build Development Mac Mono")] + static void BuildMacMono() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = ScriptingImplementation.Mono2x, + BuildTarget = BuildTarget.StandaloneOSX, + GraphicsApis = new[] { GraphicsDeviceType.Metal }, + OutputPath = "./build/macOS_Mono/PlaytestBuild.app" + }); + + [MenuItem("Tools/Builder/Build Development Mac Il2cpp")] + static void BuildMacIl2cpp() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = ScriptingImplementation.IL2CPP, + BuildTarget = BuildTarget.StandaloneOSX, + GraphicsApis = new[] { GraphicsDeviceType.Metal }, + OutputPath = "./build/macOS_Il2cpp/PlaytestBuild.app" + }); + + [MenuItem("Tools/Builder/Build Development Android Il2cpp")] + static void BuildAndroidIl2cpp() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Android, + ScriptingBackend = ScriptingImplementation.IL2CPP, + BuildTarget = BuildTarget.Android, + GraphicsApis = new[] { GraphicsDeviceType.Vulkan }, + OutputPath = "./build/Android_Il2cpp_Vulkan/PlaytestBuild.apk", + ApplicationId = "com.UnityTestRunner.UnityTestRunner", + Architecture = 1 + }); + +#if UNITY_6000_6_OR_NEWER + // ScriptingImplementation.CoreCLR is marked [Obsolete] ("work in progress"), so suppress CS0618 to compile under warnings-as-errors. +#pragma warning disable CS0618 + static ScriptingImplementation CoreClrBackend => ScriptingImplementation.CoreCLR; +#pragma warning restore CS0618 + + [MenuItem("Tools/Builder/Build Development Windows CoreCLR")] + static void BuildWinCoreClr() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = CoreClrBackend, + BuildTarget = BuildTarget.StandaloneWindows64, + GraphicsApis = new[] { GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12 }, + OutputPath = "./build/Windows_CoreClr/PlaytestBuild.exe" + }); + + [MenuItem("Tools/Builder/Build Development Mac CoreCLR")] + static void BuildMacCoreClr() => BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Standalone, + ScriptingBackend = CoreClrBackend, + BuildTarget = BuildTarget.StandaloneOSX, + GraphicsApis = new[] { GraphicsDeviceType.Metal }, + OutputPath = "./build/macOS_CoreClr/PlaytestBuild.app" + }); + + [MenuItem("Tools/Builder/Build Development Android CoreCLR")] + static void BuildAndroidCoreClr() + { + // CoreCLR on Android is 64-bit only; ARMv7 (32-bit) is unsupported, so force ARM64. + PlayerSettings.Android.targetArchitectures = AndroidArchitecture.ARM64; + BuildPlayer(new BuildConfig + { + NamedBuildTarget = NamedBuildTarget.Android, + ScriptingBackend = CoreClrBackend, + BuildTarget = BuildTarget.Android, + GraphicsApis = new[] { GraphicsDeviceType.Vulkan }, + OutputPath = "./build/Android_CoreClr/PlaytestBuild.apk", + ApplicationId = "com.UnityTestRunner.UnityTestRunner" + }); + } +#endif +} diff --git a/Tools/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef b/Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef similarity index 100% rename from Tools/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef rename to Tools/CI/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef diff --git a/Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py b/Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py new file mode 100644 index 0000000000..ccdfcc6eaa --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/extend_source_sparse_checkout.py @@ -0,0 +1,65 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Extends the job's own (source) sparse checkout to include the local tested-package folders named at +runtime by LOCAL_TESTED_PACKAGES_NAMES. + +The generated rules file materializes a baseline set at checkout time, but the packages to test are read +from the environment here so that a package added (or changed) via the LOCAL_TESTED_PACKAGES_NAMES trigger +variable is checked out on demand rather than failing because it was absent from the pre-baked rules. Adding +a path that does not exist in the repository materializes nothing and is not an error; validate_params.py is +what reports a genuinely missing package. +""" + +import argparse +import os +import subprocess +import sys + +from package_parsing import parse_package_names + +ENV_LOCAL_TESTED_PACKAGES_NAMES = "LOCAL_TESTED_PACKAGES_NAMES" + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Add local tested-package folders to the source repo's sparse checkout." + ) + parser.add_argument("--source-dir", required=True, help="Absolute path to the job's source checkout (git repo root).") + parser.add_argument( + "--local-tested-packages-path", + required=True, + help="Repo-relative base directory containing the local packages (e.g. 'Packages').", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + + package_names = parse_package_names(os.environ.get(ENV_LOCAL_TESTED_PACKAGES_NAMES, "")) + if not package_names: + print(f"[INFO] {ENV_LOCAL_TESTED_PACKAGES_NAMES} is empty; no source paths to add.") + return + + base_path = args.local_tested_packages_path.strip().replace("\\", "/").strip("/") + paths = [f"{base_path}/{name}" for name in package_names] + + result = subprocess.run( + ["git", "-C", args.source_dir, "sparse-checkout", "add", *paths], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False, + ) + if result.returncode != 0: + print(f"Error extending source sparse checkout: {result.stderr.strip()}", file=sys.stderr) + sys.exit(1) + + print(f"[INFO] Added to source sparse checkout: {paths}") + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/get_build_method.py b/Tools/CI/scripts/BuildAutomation/get_build_method.py new file mode 100644 index 0000000000..5625861edd --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/get_build_method.py @@ -0,0 +1,70 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Helper script that determines the appropriate BuilderScripts method and +runs Unity with the correct parameters. +""" + +import argparse +import os +import subprocess +import sys + +BUILD_CONFIGS = { + ("win64", "il2cpp"): {"method": "BuilderScripts.BuildWinIl2cpp", "buildTarget": "win64"}, + ("win64", "mono"): {"method": "BuilderScripts.BuildWinMono", "buildTarget": "win64"}, + ("win64", "coreclr"): {"method": "BuilderScripts.BuildWinCoreClr", "buildTarget": "win64"}, + ("mac", "il2cpp"): {"method": "BuilderScripts.BuildMacIl2cpp", "buildTarget": "osx"}, + ("mac", "mono"): {"method": "BuilderScripts.BuildMacMono", "buildTarget": "osx"}, + ("mac", "coreclr"): {"method": "BuilderScripts.BuildMacCoreClr", "buildTarget": "osx"}, + ("android", "il2cpp"): {"method": "BuilderScripts.BuildAndroidIl2cpp", "buildTarget": "android"}, + ("android", "coreclr"): {"method": "BuilderScripts.BuildAndroidCoreClr", "buildTarget": "android"}, +} + + +def resolve_backend(): + return os.environ.get("SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR)", "").lower() + +def execute_unity_build(project_path, unity_path): + platform = os.environ.get("PLATFORM_(WIN64_MAC_ANDROID)", "").lower() + backend = resolve_backend() + config = BUILD_CONFIGS.get((platform, backend)) + if not config: + print(f"ERROR: Invalid combination: platform={platform}, backend={backend}", file=sys.stderr) + sys.exit(1) + + cmd = [ + unity_path, + "-projectPath", + project_path, + "-buildTarget", + config["buildTarget"], + "-executeMethod", + config["method"], + "-batchmode", + "-logFile", + "./artifacts/UnityLog.txt", + "-automated", + "-crash-report-folder", + "./artifacts/CrashArtifacts", + "-quit", + ] + result = subprocess.run(cmd, check=False) + sys.exit(result.returncode) + + +def main(): + parser = argparse.ArgumentParser(description="Execute Unity build command.") + parser.add_argument("--project-path", required=True, help="Path to Unity project.") + parser.add_argument("--unity-path", default="C:/TestingEditor/Unity.exe", help="Path to Unity executable.") + args = parser.parse_args() + execute_unity_build(args.project_path, args.unity_path) + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/manifest_update.py b/Tools/CI/scripts/BuildAutomation/manifest_update.py new file mode 100644 index 0000000000..6c712811cc --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/manifest_update.py @@ -0,0 +1,154 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Updates the cloned project's manifest.json to point tested packages at local file: sources, so the +changes on the current branch are exercised against the sample project. + +!!! IMPORTANT !!! — UPM embedded package priority: +Any package folder placed directly inside the project's Packages/ directory is treated as an +embedded package and takes the absolute highest priority in UPM dependency resolution. + +To work around this, the script scans the Packages/ directory of the cloned project and removes any +embedded package whose package.json declares a name matching a target package, before writing the +local file: reference into the manifest. +""" + +import argparse +import json +import os +import shutil +import sys + +from package_parsing import parse_package_names + +SEPARATOR = "=" * 80 +ENV_LOCAL_TESTED_PACKAGES_NAMES = "LOCAL_TESTED_PACKAGES_NAMES" + + +def parse_args(): + parser = argparse.ArgumentParser(description="Update a Unity project manifest to use local package versions.") + parser.add_argument("--manifest-path", required=True, help="Absolute path to project manifest.json file.") + parser.add_argument( + "--local-tested-packages-path", + required=True, + help="Absolute path to the local packages root directory (e.g. the source dir's Packages folder).", + ) + return parser.parse_args() + + +def remove_embedded_package(packages_dir, package_name): + """ + Scans subfolders of packages_dir and removes any folder whose package.json + declares a 'name' matching package_name. + """ + if not os.path.isdir(packages_dir): + return + + for entry in os.scandir(packages_dir): + if not entry.is_dir(): + continue + package_json_path = os.path.join(entry.path, "package.json") + if not os.path.isfile(package_json_path): + continue + try: + with open(package_json_path, "r", encoding="utf-8") as f: + name = json.load(f).get("name", "") + except Exception: + continue + + if name == package_name: + shutil.rmtree(entry.path) + print(f"[INFO] Removed embedded package '{package_name}' from '{entry.path}'") + return + + +def update_manifest(manifest_data, package_name, local_packages_root): + old_value = manifest_data.get("dependencies", {}).get(package_name, "") + local_package_path = os.path.join(local_packages_root, package_name) + local_path_normalized = local_package_path.replace(os.sep, "/") + manifest_data.setdefault("dependencies", {})[package_name] = f"file:{local_path_normalized}" + return old_value, f"file:{local_path_normalized}" + + +def print_summary(manifest_data, substitutions): + print() + print(SEPARATOR) + print("TARGET PACKAGE SUBSTITUTION") + print(SEPARATOR) + for substitution in substitutions: + print(f"Package: {substitution['package_name']}") + print(f" Old value: {substitution['old_value']}") + print(f" New value: {substitution['new_value']}") + local_version = substitution["local_version"] or "" + print(f"Verified local '{substitution['package_name']}' version is: {local_version}") + print() + print(SEPARATOR) + print("FINAL MANIFEST CONTENT") + print(SEPARATOR) + dependencies = manifest_data.get("dependencies", {}) + for dep_name, dep_value in sorted(dependencies.items()): + print(f" {dep_name}: {dep_value}") + print(SEPARATOR) + print() + + +def get_package_version(package_path): + package_json_path = os.path.join(package_path, "package.json") + try: + with open(package_json_path, "r", encoding="utf-8") as f: + return json.load(f).get("version", "N/A") + except Exception: + return None + + +def main(): + args = parse_args() + + try: + package_names = parse_package_names(os.environ.get(ENV_LOCAL_TESTED_PACKAGES_NAMES, "")) + # Clearing the variable at trigger time opts out of substitution; leave the manifest untouched (plain build). + if not package_names: + print(f"[INFO] {ENV_LOCAL_TESTED_PACKAGES_NAMES} is empty; skipping local package substitution.") + return + + with open(args.manifest_path, "r", encoding="utf-8") as manifest_file: + manifest_data = json.load(manifest_file) + + packages_dir = os.path.dirname(args.manifest_path) + + substitutions = [] + for package_name in package_names: + remove_embedded_package(packages_dir, package_name) + + old_value, new_value = update_manifest( + manifest_data, + package_name, + args.local_tested_packages_path, + ) + local_package_path = os.path.join(args.local_tested_packages_path, package_name) + local_version = get_package_version(local_package_path) + substitutions.append( + { + "package_name": package_name, + "old_value": old_value, + "new_value": new_value, + "local_version": local_version, + } + ) + + with open(args.manifest_path, "w", encoding="utf-8", newline="\n") as manifest_file: + json.dump(manifest_data, manifest_file, indent=4) + + print_summary(manifest_data, substitutions) + except Exception as error: + print(f"Error updating manifest: {error}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/package_parsing.py b/Tools/CI/scripts/BuildAutomation/package_parsing.py new file mode 100644 index 0000000000..8bd9183b9d --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/package_parsing.py @@ -0,0 +1,23 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Shared parsing for the LOCAL_TESTED_PACKAGES_NAMES variable, so every build-automation script splits it the +same way. Package names may be separated by commas, semicolons, and/or whitespace in any combination, so +values entered as "a,b", "a; b", or "a b" all yield the same list. +""" + +import re + +_SEPARATORS = re.compile(r"[,;\s]+") + + +def parse_package_names(raw_value): + """Split a LOCAL_TESTED_PACKAGES_NAMES value into package names on commas, semicolons, or whitespace.""" + if not raw_value: + return [] + return [name for name in _SEPARATORS.split(raw_value.strip()) if name] diff --git a/Tools/CI/scripts/BuildAutomation/print_build_summary.py b/Tools/CI/scripts/BuildAutomation/print_build_summary.py new file mode 100644 index 0000000000..4065b252ff --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/print_build_summary.py @@ -0,0 +1,226 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Prints a formatted build configuration summary table from Yamato environment variables. +Emitted after the sample is cloned and unity-downloader-cli is installed, but before the slow editor +download, so the configuration is visible early in the Yamato log while still being able to resolve the +editor actually used by the build. +""" + +import argparse +import os +import platform +import re +import subprocess + +from package_parsing import parse_package_names + +SEP = "=" * 72 +PLATFORM_MAC = "mac" +ENV_SCRIPTING_BACKEND = "SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR)" +ENV_PLATFORM = "PLATFORM_(WIN64_MAC_ANDROID)" +ENV_SAMPLE_BRANCH = "SAMPLE_BRANCH" +ENV_UNITY_VERSION = "UNITY_VERSION" +ENV_LOCAL_TESTED_PACKAGES_NAMES = "LOCAL_TESTED_PACKAGES_NAMES" +ENV_GIT_BRANCH = "GIT_BRANCH" + +# Friendly labels for the build agent OS the job ran on (the value passed by --build-machine-os). +BUILD_MACHINE_DISPLAY = {"windows": "Windows"} + +# Length to truncate a full Git revision to, matching the form ProjectVersion.txt records (e.g. +# "6000.5.0a5 (4928cd185deb)"). +SHORT_REVISION_LENGTH = 12 + + +def run_git(source_dir, *args, check=False): + try: + result = subprocess.run( + ["git", "-C", source_dir, *args], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=check, + ) + return result.stdout.strip() if result.stdout else "" + except Exception: + return "" + + +def get_package_branch_info(): + source_dir = os.environ.get("YAMATO_SOURCE_DIR", ".") + # Yamato checks the source out at a detached HEAD, so git can't report the branch; GIT_BRANCH carries the + # branch the job runs against (the PR source branch for PR-triggered jobs). + branch = os.environ.get(ENV_GIT_BRANCH, "").strip() + commit = run_git(source_dir, "rev-parse", "--short", "HEAD") + if branch and commit: + return f"{branch} ({commit})" + if branch: + return branch + if commit: + return f" ({commit})" + return "N/A" + + +def get_sample_branch_commit(sample_repo_url, sample_branch): + try: + result = subprocess.run( + ["git", "ls-remote", sample_repo_url, f"refs/heads/{sample_branch}"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False, + ) + if result.returncode == 0 and result.stdout.strip(): + full_sha = result.stdout.strip().split()[0] + return full_sha[:10] + except Exception: + pass + return None + + +def get_editor_version_from_downloader(unity_version): + try: + # --fast resolves branch-style versions (e.g. "trunk") against cached builds; --wait falls back to a + # pending build when none is cached, so resolution does not flake. Mirrors the download/validate calls. + result = subprocess.run( + ["unity-downloader-cli", "--fast", "--wait", "-u", unity_version, "-c", "editor", "--skip-download"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=False, + ) + combined = result.stderr + result.stdout + match = re.search(r"Grabbing unity release (\S+) which is revision: (\w+)", combined) + if match: + return f"{match.group(1)} ({match.group(2)})" + version_match = re.search(r"Version:\s*(\S+)", result.stderr) + sha = result.stdout.strip() + if version_match: + return f"{version_match.group(1)}{f' ({sha[:SHORT_REVISION_LENGTH]})' if sha else ''}" + except Exception: + pass + return unity_version + + +def get_editor_version_from_project(project_path): + """Read the editor the cloned sample was authored with, preferring the version+revision form.""" + try: + version_file = os.path.join(project_path, "ProjectSettings", "ProjectVersion.txt") + with open(version_file, "r", encoding="utf-8") as f: + content = f.read() + # e.g. "m_EditorVersionWithRevision: 6000.5.0a5 (4928cd185deb)" + match = re.search(r"^m_EditorVersionWithRevision:\s*(.+?)\s*$", content, re.MULTILINE) + if match: + return match.group(1).strip() + match = re.search(r"^m_EditorVersion:\s*(\S+)\s*$", content, re.MULTILINE) + if match: + return match.group(1) + except Exception: + pass + return "N/A" + + +def get_updated_editor(): + """Resolve the editor actually used by the build, as opposed to the one the sample was authored with.""" + unity_version = os.environ.get(ENV_UNITY_VERSION) + if unity_version: + return get_editor_version_from_downloader(unity_version) + return None + + +def print_mac_on_windows_note(target_platform): + """ + A macOS player cross-compiled on a Windows agent loses its executable bit and carries a quarantine + attribute, so the .app may refuse to open on the Mac. Surface the recovery commands in the job log + when (and only when) a mac build is produced on a Windows host. + """ + if target_platform.strip().lower() != PLATFORM_MAC or platform.system() != "Windows": + return + + lines = [ + "", + SEP, + " Note for Mac users (mac build produced on a Windows agent)", + SEP, + " If you are unable to open the build, run the following on the Mac:", + " xattr -c ", + " chmod -R +x ", + SEP, + "", + ] + print("\n".join(lines)) + + +def main(): + parser = argparse.ArgumentParser(description="Print build configuration summary.") + parser.add_argument("--project-name", required=True, help="Project key used in CI.") + parser.add_argument( + "--sample-repo-url", + help="GitHub URL of sample repo, used to resolve sample branch commit.", + ) + parser.add_argument( + "--project-path", + help="Path to the cloned Unity project root, used to read the sample's editor from ProjectVersion.txt.", + ) + parser.add_argument( + "--build-machine-os", + choices=sorted(BUILD_MACHINE_DISPLAY.keys()), + help="OS of the build agent the job ran on; shown in the summary.", + ) + args = parser.parse_args() + + rows = [("Project Name", args.project_name)] + + # Editor the sample was authored with vs. the editor the build actually uses. + original_editor = get_editor_version_from_project(args.project_path) if args.project_path else None + updated_editor = get_updated_editor() + + git_package_branch = get_package_branch_info() + + # The package under test is the job's own source checkout, so report its branch. + rows.append(("Package Branch", git_package_branch)) + + # SAMPLE_BRANCH env var holds the external project branch. + sample_branch = os.environ.get(ENV_SAMPLE_BRANCH, "").strip() + if sample_branch: + commit = get_sample_branch_commit(args.sample_repo_url, sample_branch) if args.sample_repo_url else None + branch_display = f"{sample_branch} ({commit})" if commit else sample_branch + else: + branch_display = f" resolved to {git_package_branch}" + rows.append(("Sample Branch", branch_display)) + + # Lead with the editor the build uses; note the project's authored editor (ProjectVersion.txt) when known. + project_editor = original_editor if original_editor and original_editor != "N/A" else None + if updated_editor and project_editor: + editor_display = f"{updated_editor} (project's ProjectVersion.txt: {project_editor})" + else: + editor_display = updated_editor or original_editor + if editor_display: + rows.append(("Editor Version", editor_display)) + + local_tested_packages = parse_package_names(os.environ.get(ENV_LOCAL_TESTED_PACKAGES_NAMES, "")) + if local_tested_packages: + rows.append(("Local Tested Packages", ", ".join(local_tested_packages))) + + rows.append(("Scripting Backend", os.environ.get(ENV_SCRIPTING_BACKEND, "N/A"))) + + target_platform = os.environ.get(ENV_PLATFORM, "N/A") + rows.append(("Target Platform", target_platform)) + + # The agent OS the build ran on, distinct from the target platform (a Windows agent can cross-build a mac + # player). Best-effort: only shown when the recipe passed --build-machine-os. + if args.build_machine_os: + rows.append(("Build Machine", BUILD_MACHINE_DISPLAY[args.build_machine_os])) + + label_width = max(len(label) for label, _ in rows) + lines = ["", SEP, " Build Configuration Summary", SEP] + for label, value in rows: + lines.append(f" {label:<{label_width}} : {value}") + lines += [SEP, ""] + + print("\n".join(lines)) + + print_mac_on_windows_note(target_platform) + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/Asteroids.patterns b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/Asteroids.patterns new file mode 100644 index 0000000000..52d79c4688 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/Asteroids.patterns @@ -0,0 +1,2 @@ +Tools/CI/scripts/BuildAutomation +./com.unity.netcode.gameobjects diff --git a/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/BossRoom.patterns b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/BossRoom.patterns new file mode 100644 index 0000000000..52d79c4688 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/BossRoom.patterns @@ -0,0 +1,2 @@ +Tools/CI/scripts/BuildAutomation +./com.unity.netcode.gameobjects diff --git a/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/SocialHub.patterns b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/SocialHub.patterns new file mode 100644 index 0000000000..52d79c4688 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/project-sparse-checkouts/SocialHub.patterns @@ -0,0 +1,2 @@ +Tools/CI/scripts/BuildAutomation +./com.unity.netcode.gameobjects diff --git a/Tools/CI/scripts/BuildAutomation/resolve_sparse_file_references.py b/Tools/CI/scripts/BuildAutomation/resolve_sparse_file_references.py new file mode 100644 index 0000000000..d529aaddcf --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/resolve_sparse_file_references.py @@ -0,0 +1,165 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Extends a sparse-checked-out sample clone to include the repo-relative package folders it +references via UPM `file:` dependencies. + +A sample project is cloned with a blob-less partial clone whose working tree is restricted to + (see CloneProject). The project's /Packages/manifest.json can list +`file:` dependencies whose paths (relative to the manifest's own Packages/ directory) resolve to +folders OUTSIDE -- for example NetcodeSamples references the repo-root Packages/ +folder via "file:../../../Packages/com.unity.entities". Those folders are never materialized by the +initial sparse checkout, so UPM cannot resolve the packages and the build fails. + +This script parses the cloned manifest, resolves each `file:` reference to a repo-relative +directory, and `git sparse-checkout add`s the ones that fall outside . References that +escape the repository root, that already live inside , or a missing manifest are +skipped without failing the build. + +A single pass is sufficient -- no recursion is needed. The sample author lists every repo-local +sibling package as a `file:` reference in the PROJECT manifest (NetcodeSamples lists +entities/transport/netcode/URP there). A pulled-in package's own package.json declares its +dependencies as registry version refs, not `file:` references into the monorepo, so transitive +dependencies resolve from the package registry rather than via repo-local paths. + +This must run BEFORE manifest_update.py: that script rewrites the tested packages to +`file:%YAMATO_SOURCE_DIR%/...` refs pointing at the source checkout (not the sample repo), so the +resolver must see the sample's ORIGINAL manifest and act only on the sample repo's own internal refs. +""" + +import argparse +import json +import posixpath +import subprocess +import sys + +SEPARATOR = "=" * 80 + + +def parse_args(): + parser = argparse.ArgumentParser( + description="Add repo-relative file: package folders to a sample clone's sparse checkout." + ) + parser.add_argument("--cloned-root", required=True, help="Absolute path to the cloned sample repo (e.g. C:/ClonedProject).") + parser.add_argument("--project-path", required=True, help="Repo-relative project path (e.g. Workspace/NetcodeSamples).") + parser.add_argument("--branch", required=True, help="Branch to re-checkout so newly included blobs are fetched.") + return parser.parse_args() + + +def resolve_reference(project_path, target): + """ + Resolves a manifest `file:` target (relative to /Packages) to a repo-relative, + forward-slash directory. Returns one of: + ("escape", combined) -- the path escapes the repository root; warn and skip. + ("inside", combined) -- the path is inside ; already checked out, skip. + ("candidate", combined) -- a repo-relative dir outside ; add to the checkout. + """ + combined = posixpath.normpath(posixpath.join(project_path + "/Packages", target)) + + if combined == ".." or combined.startswith("../"): + return "escape", combined + if combined == project_path or combined.startswith(project_path + "/"): + return "inside", combined + return "candidate", combined + + +def collect_candidates(dependencies, project_path): + candidates = [] + skipped_inside = [] + skipped_escape = [] + seen = set() + + for ref in dependencies.values(): + if not str(ref).startswith("file:"): + continue + + target = str(ref)[len("file:"):] + kind, combined = resolve_reference(project_path, target) + if kind == "escape": + skipped_escape.append(combined) + elif kind == "inside": + skipped_inside.append(combined) + elif combined not in seen: + seen.add(combined) + candidates.append(combined) + + return candidates, skipped_inside, skipped_escape + + +def print_summary(candidates, skipped_inside, skipped_escape): + print() + print(SEPARATOR) + print("SPARSE CHECKOUT FILE: REFERENCE RESOLUTION") + print(SEPARATOR) + print(f"Added (outside project): {candidates if candidates else ''}") + print(f"Skipped (already in project): {skipped_inside if skipped_inside else ''}") + print(f"Skipped (escapes repo root): {skipped_escape if skipped_escape else ''}") + print(SEPARATOR) + print() + + +def main(): + args = parse_args() + + project_path = args.project_path.replace("\\", "/").strip("/") + if not project_path or project_path == ".": + print("[INFO] Project path is the repository root; no sparse checkout to extend.") + return + + manifest_path = posixpath.join(args.cloned_root.replace("\\", "/"), project_path, "Packages/manifest.json") + + try: + with open(manifest_path, "r", encoding="utf-8") as manifest_file: + manifest_data = json.load(manifest_file) + except FileNotFoundError: + print(f"[INFO] No manifest at '{manifest_path}'; nothing to resolve.") + return + except Exception as error: + print(f"Error reading manifest '{manifest_path}': {error}", file=sys.stderr) + sys.exit(1) + + dependencies = manifest_data.get("dependencies", {}) + candidates, skipped_inside, skipped_escape = collect_candidates(dependencies, project_path) + + for escaped in skipped_escape: + print(f"[WARN] file: reference resolves outside the repository root and was skipped: '{escaped}'", file=sys.stderr) + + if not candidates: + print("[INFO] No external file: references to add to the sparse checkout.") + print_summary(candidates, skipped_inside, skipped_escape) + return + + add_result = subprocess.run( + ["git", "-C", args.cloned_root, "sparse-checkout", "add", *candidates], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + if add_result.returncode != 0: + print(f"Error adding paths to sparse checkout: {add_result.stderr.strip()}", file=sys.stderr) + sys.exit(1) + + # The blob:none partial clone fetches the newly included blobs lazily on checkout, mirroring + # CloneProject's final checkout (robust across git versions). + checkout_result = subprocess.run( + ["git", "-C", args.cloned_root, "checkout", args.branch], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + if checkout_result.returncode != 0: + print(f"Error checking out '{args.branch}' after extending sparse checkout: {checkout_result.stderr.strip()}", file=sys.stderr) + sys.exit(1) + + print_summary(candidates, skipped_inside, skipped_escape) + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/setup_build_scripts.py b/Tools/CI/scripts/BuildAutomation/setup_build_scripts.py new file mode 100644 index 0000000000..6926e1341d --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/setup_build_scripts.py @@ -0,0 +1,38 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Helper script that copies build scripts into the cloned Unity project to execute build action from within +""" + +import argparse +import os +import shutil + + +def parse_args(): + parser = argparse.ArgumentParser(description="Set up build scripts in cloned project.") + parser.add_argument("--project-path", required=True, help="Path to the cloned project root.") + return parser.parse_args() + + +def main(): + args = parse_args() + target_dir = os.path.join(args.project_path, "Assets", "CI", "Scripts", "Editor") + # BuilderScripts.cs and the asmdef are materialized into this script's own directory, so resolve them + # relative to __file__. This stays correct regardless of the consumer's configured ScriptsRelativeDir. + source_dir = os.path.dirname(os.path.abspath(__file__)) + os.makedirs(target_dir, exist_ok=True) + + for filename in ["Unity.ProjectBuild.Editor.asmdef", "BuilderScripts.cs"]: + source_path = os.path.join(source_dir, filename) + target_path = os.path.join(target_dir, filename) + shutil.copy(source_path, target_path) + + +if __name__ == "__main__": + main() diff --git a/Tools/CI/scripts/BuildAutomation/validate_params.py b/Tools/CI/scripts/BuildAutomation/validate_params.py new file mode 100644 index 0000000000..1d6c03ae38 --- /dev/null +++ b/Tools/CI/scripts/BuildAutomation/validate_params.py @@ -0,0 +1,366 @@ +############################################################################### +### Auto-generated by RecipeEngine.Modules.BuildAutomation. DO NOT EDIT. ### +### You may override this file locally, but any changes will be overwritten ### +### the next time CI is regenerated. Permanent fixes must be made inside: ### +### RecipeEngine.Modules.BuildAutomation/EmbeddedScripts/ ### +############################################################################### + +""" +Validates Yamato environment variables based on predefined rules. +Checks always-required values and optional repo/feature-specific values. +Exits with non-zero status if validation fails, halting the build process early. +""" + +import argparse +import os +import re +import subprocess +import sys + +from package_parsing import parse_package_names + +ENV_SCRIPTING_BACKEND = "SCRIPTING_BACKEND_(IL2CPP_MONO_CORECLR)" +ENV_PLATFORM = "PLATFORM_(WIN64_MAC_ANDROID)" +ENV_SAMPLE_BRANCH = "SAMPLE_BRANCH" +ENV_UNITY_VERSION = "UNITY_VERSION" +ENV_LOCAL_TESTED_PACKAGES_NAMES = "LOCAL_TESTED_PACKAGES_NAMES" + +ALLOWED_SCRIPTING_BACKENDS = {"il2cpp", "mono", "coreclr"} +ALLOWED_PLATFORMS = {"win64", "mac", "android"} + +CORECLR_BACKEND = "coreclr" +# coreclr is only a supported scripting backend from this editor onward; older editors must use il2cpp/mono. +MIN_CORECLR_EDITOR_VERSION = (6000, 6) +MIN_CORECLR_EDITOR_VERSION_DISPLAY = "6000.6" + +BUILD_MACHINE_WINDOWS = "windows" +ALLOWED_BUILD_MACHINE_OS = {BUILD_MACHINE_WINDOWS} + +# Platform/scripting-backend combinations that are invalid regardless of the build machine OS. +INVALID_COMBINATIONS = [ + ( + "android", + "mono", + "Android platform with mono is not supported. " + "Mobile builds require the il2cpp or coreclr scripting backend.", + ), +] + +# Combinations that depend on which OS the build agent runs. il2cpp produces a native player, so a Windows +# IL2CPP player needs a Windows agent; a mac il2cpp player cannot be produced on the Windows agent (macOS +# agents are a planned follow-up). +INVALID_COMBINATIONS_BY_BUILD_MACHINE = { + BUILD_MACHINE_WINDOWS: [ + ( + "mac", + "il2cpp", + "Mac il2cpp players cannot be built on a Windows agent. " + "Use mono to cross-build mac on Windows.", + ), + ], +} + + +def validate_required_enum(var_name, allowed_values): + value = os.environ.get(var_name, "").strip().lower() + if not value: + return [f"ERROR: Missing required environment variable: {var_name}"] + + if value not in allowed_values: + return [ + f"ERROR: Invalid {var_name}: '{value}'. " + f"Allowed values: {sorted(list(allowed_values))}" + ] + + return [] + + +def validate_core_variables(): + errors = [] + errors.extend(validate_required_enum(ENV_PLATFORM, ALLOWED_PLATFORMS)) + errors.extend(validate_required_enum(ENV_SCRIPTING_BACKEND, ALLOWED_SCRIPTING_BACKENDS)) + return errors + + +def parse_major_minor(version): + """Parses the leading MAJOR.MINOR of a Unity version string (e.g. '6000.6.0a7' -> (6000, 6)). + Returns None when the two leading numeric components cannot be determined.""" + parts = version.split(".") + if len(parts) < 2: + return None + + numbers = [] + for part in parts[:2]: + match = re.match(r"\d+", part.strip()) + if not match: + return None + numbers.append(int(match.group())) + return tuple(numbers) + + +def parse_version_tuple(version): + """Extracts the leading numeric MAJOR[.MINOR[.PATCH]] of a Unity version string as a tuple, padding + missing trailing components with 0 (e.g. '6000.5.0a5' -> (6000, 5, 0), '6000.6' -> (6000, 6, 0)). + Returns None when no leading major number is present.""" + numbers = [] + for part in str(version).strip().split(".")[:3]: + match = re.match(r"\d+", part.strip()) + if not match: + break + numbers.append(int(match.group())) + + if not numbers: + return None + + while len(numbers) < 3: + numbers.append(0) + return tuple(numbers) + + +def parse_resolved_release(downloader_output): + """Parses the editor version unity-downloader-cli reports it resolved to, from its combined output + (e.g. 'Grabbing unity release 6000.5.0a5 which is revision: ...'). Returns None when not present.""" + match = re.search(r"Grabbing unity release (\S+)", downloader_output) + return match.group(1) if match else None + + +def validate_min_supported_editor(unity_version, downloader_output, min_supported_editor): + """Fails when the editor unity-downloader-cli actually resolved is older than the sample's minimum + supported editor. Best-effort: if either version can't be parsed, warn and pass (the floor is advisory).""" + resolved = parse_resolved_release(downloader_output) + if not resolved: + print( + f"WARNING: could not determine the resolved editor version for {ENV_UNITY_VERSION} " + f"'{unity_version}'; skipping the minimum supported editor check.", + file=sys.stderr, + ) + return [] + + resolved_parsed = parse_version_tuple(resolved) + min_parsed = parse_version_tuple(min_supported_editor) + if resolved_parsed is None or min_parsed is None: + print( + f"WARNING: could not compare resolved editor '{resolved}' against minimum supported " + f"'{min_supported_editor}'; skipping the minimum supported editor check.", + file=sys.stderr, + ) + return [] + + if resolved_parsed < min_parsed: + return [ + f"ERROR: resolved editor '{resolved}' (from {ENV_UNITY_VERSION} '{unity_version}') is older than " + f"the sample's minimum supported editor '{min_supported_editor}'." + ] + return [] + + +def coreclr_editor_error(editor_major_minor, editor_display): + """Returns an error when the coreclr backend is selected but the given editor MAJOR.MINOR is older than the + minimum. `editor_major_minor` is a (major, minor) tuple, or None to skip (undeterminable version).""" + scripting_backend = os.environ.get(ENV_SCRIPTING_BACKEND, "").strip().lower() + if scripting_backend != CORECLR_BACKEND or editor_major_minor is None: + return [] + + if editor_major_minor < MIN_CORECLR_EDITOR_VERSION: + return [ + f"ERROR: coreclr scripting backend requires editor " + f"{MIN_CORECLR_EDITOR_VERSION_DISPLAY} or newer, but the editor is '{editor_display}'." + ] + return [] + + +def validate_coreclr_editor_version(): + """coreclr is only supported from editor 6000.6 onward. Checked here against the *requested* UNITY_VERSION + when it is a concrete MAJOR.MINOR; branch-style values (e.g. "trunk", "6000.6/staging") can't be judged from + the env text, so they are deferred to the resolved-editor check in validate_unity_version. Skipped when + UNITY_VERSION is absent.""" + scripting_backend = os.environ.get(ENV_SCRIPTING_BACKEND, "").strip().lower() + if scripting_backend != CORECLR_BACKEND: + return [] + + unity_version = os.environ.get(ENV_UNITY_VERSION, "").strip() + if not unity_version: + return [] + + parsed = parse_major_minor(unity_version) + if parsed is None: + # Branch-style version: the resolved-editor coreclr check in validate_unity_version handles it. + return [] + + return coreclr_editor_error(parsed, unity_version) + + +def validate_combinations(build_machine_os): + errors = [] + platform = os.environ.get(ENV_PLATFORM, "").strip().lower() + scripting_backend = os.environ.get(ENV_SCRIPTING_BACKEND, "").strip().lower() + + combinations = INVALID_COMBINATIONS + INVALID_COMBINATIONS_BY_BUILD_MACHINE.get(build_machine_os, []) + for invalid_platform, invalid_backend, message in combinations: + if platform == invalid_platform and scripting_backend == invalid_backend: + errors.append(f"ERROR: Invalid Configuration: {message}") + return errors + + +def validate_sample_branch(github_repo_url): + errors = [] + sample_branch = os.environ.get(ENV_SAMPLE_BRANCH, "").strip() + if not sample_branch: + return [f"ERROR: Missing required environment variable: {ENV_SAMPLE_BRANCH}"] + + result = subprocess.run( + ["git", "ls-remote", "--heads", "--exit-code", github_repo_url, sample_branch], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + if result.returncode != 0: + errors.append( + f"ERROR: Branch '{sample_branch}' was not found in repository '{github_repo_url}'." + ) + return errors + + +def validate_unity_version(min_supported_editor=None): + errors = [] + unity_version = os.environ.get(ENV_UNITY_VERSION, "").strip() + if not unity_version: + return [f"ERROR: Missing required environment variable: {ENV_UNITY_VERSION}"] + + try: + # --fast resolves branch-style versions (e.g. "trunk", "6000.6/staging") against already-cached builds; + # --wait is required alongside it because --fast alone exits non-zero when nothing is cached yet (trunk + # builds expire), which would spuriously reject a valid branch. Mirrors the download invocation + # (--fast --wait). --skip-download still resolves and prints the revision without fetching the editor. + result = subprocess.run( + ["unity-downloader-cli", "--fast", "--wait", "-u", unity_version, "-c", "editor", "--skip-download"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + check=False, + ) + except FileNotFoundError: + return [ + "ERROR: unity-downloader-cli is not installed; " + f"cannot validate {ENV_UNITY_VERSION}. Install it before running this check." + ] + if result.returncode != 0: + errors.append( + f"ERROR: unity-downloader-cli does not recognize {ENV_UNITY_VERSION} '{unity_version}'." + ) + return errors + + resolved_revision = result.stdout.strip() + if not resolved_revision: + errors.append( + "ERROR: unity-downloader-cli did not return a resolved revision for " + f"{ENV_UNITY_VERSION} '{unity_version}'." + ) + + combined_output = result.stderr + result.stdout + + # coreclr floor against the RESOLVED editor, so branch-style versions (e.g. "trunk", "6000.6/staging") that + # the raw env text can't reveal are still gated once the downloader resolves them. + resolved_release = parse_resolved_release(combined_output) + if resolved_release: + errors.extend(coreclr_editor_error(parse_major_minor(resolved_release), resolved_release)) + + if min_supported_editor: + errors.extend( + validate_min_supported_editor(unity_version, combined_output, min_supported_editor) + ) + return errors + + +def validate_local_tested_packages(base_path): + package_names = parse_package_names(os.environ.get(ENV_LOCAL_TESTED_PACKAGES_NAMES, "")) + errors = [] + + # Clearing the variable at trigger time opts out of substitution (plain build); nothing to validate. + if not package_names: + return [] + + for package_name in package_names: + package_dir = os.path.join(base_path, package_name) + package_json = os.path.join(package_dir, "package.json") + + if not os.path.isdir(package_dir): + errors.append( + f"ERROR: Local tested package directory does not exist: '{package_dir}'" + ) + continue + + if not os.path.isfile(package_json): + errors.append( + f"ERROR: package.json was not found for local tested package '{package_name}' " + f"at '{package_json}'" + ) + + if errors: + errors.append( + f"HINT: {ENV_LOCAL_TESTED_PACKAGES_NAMES} was parsed as {package_names}. Separate package names " + "with a comma, semicolon, or space (e.g. 'com.unity.netcode,com.unity.entities'); each must be a " + f"folder under '{base_path}' that contains a package.json." + ) + + return errors + + +def parse_args(): + parser = argparse.ArgumentParser(description="Validate Yamato variables for build automation.") + parser.add_argument( + "--check-sample-branch", + dest="sample_branch_repo_url", + help="Validate that SAMPLE_BRANCH exists in this repository URL.", + ) + parser.add_argument( + "--check-unity-version", + action="store_true", + help="Validate UNITY_VERSION by resolving it through unity-downloader-cli.", + ) + parser.add_argument( + "--check-local-tested-packages", + dest="local_tested_packages_path", + help="Validate LOCAL_TESTED_PACKAGES_NAMES under this base package path.", + ) + parser.add_argument( + "--min-supported-editor", + dest="min_supported_editor", + help="Sample's minimum supported editor; the editor resolved for UNITY_VERSION must be at least " + "this version (checked only with --check-unity-version).", + ) + parser.add_argument( + "--build-machine-os", + choices=sorted(ALLOWED_BUILD_MACHINE_OS), + default=BUILD_MACHINE_WINDOWS, + help="OS of the build agent; gates platform/scripting-backend combinations (e.g. mac+il2cpp).", + ) + return parser.parse_args() + + +def main(): + args = parse_args() + errors = [] + errors.extend(validate_core_variables()) + errors.extend(validate_combinations(args.build_machine_os)) + errors.extend(validate_coreclr_editor_version()) + + if args.sample_branch_repo_url: + errors.extend(validate_sample_branch(args.sample_branch_repo_url)) + + if args.check_unity_version: + errors.extend(validate_unity_version(args.min_supported_editor)) + + if args.local_tested_packages_path: + errors.extend(validate_local_tested_packages(args.local_tested_packages_path)) + + if errors: + for error in errors: + print(error, file=sys.stderr) + print("\nOne or more parameters failed validation. Halting build.", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/Tools/scripts/BuildAutomation/BuilderScripts.cs b/Tools/scripts/BuildAutomation/BuilderScripts.cs deleted file mode 100644 index 159dd619e9..0000000000 --- a/Tools/scripts/BuildAutomation/BuilderScripts.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System.Collections.Generic; -using UnityEditor; -using UnityEditor.Build; -using UnityEngine; -using System; -using System.Collections; -using UnityEngine.Rendering; -using UnityEngine.SceneManagement; - -// This script is used to automate the build process for different platforms. -// When included in the project, it can be triggered from the script the game for teh given configuration. -// Note that it's possible to have those as a button in the editor (see https://github.cds.internal.unity3d.com/unity/Megacity-Metro/blob/c3b1b16ff1f04f96fbfbcc3267696679ad4e8396/Megacity-Metro/Assets/Scripts/Utils/Editor/BuilderScript.cs) -// Ideally we would like to pass scripting backend and platform as parameters instead of having different script per each combintation but the nature of calling this nmethod via script (via -executeMethod) is that it needs to be static and the parameters are not passed in a way that we can use them. -// TODO: add iOS support -public class BuilderScripts : MonoBehaviour -{ - [MenuItem("Tools/Builder/Build Development Windows Il2cpp")] - static void BuildWinIl2cpp() - { - // This part modifies Player Settings. We only use it (for our case) to modify scripting backend - PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.IL2CPP); - PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows64, false); // disable auto graphic to use our own custom list - PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. - - // Below you can see additional settings that you can apply to the build: - //PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Direct3D12}); - //PlayerSettings.SetArchitecture(NamedBuildTarget.Standalone,0); - - // The settings that we applied above need to be saved. - AssetDatabase.SaveAssets(); - - // We want to build all scenes in build settings, so we collect them here. - // If you want to build only specific scenes, then you could just use something like buildPlayerOptions.scenes = new[] { "Assets/Scenes/Menu.unity","Assets/Scenes/Main.unity" }; below. - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) - { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } - } - - // This is an equivalent of BuildPlayerOptions in the Unity Editor. - // We want to choose development build, what platform are we targetting, where to save the build and which scenes to include. - // Some of those options can be omitted when triggering this script from withing GUI since more implicit context is provided (targetGroup, subtarget) - // subtarget = (int)StandaloneBuildSubtarget.Player - // targetGroup = BuildTargetGroup.Standalone - // extraScriptingDefines = new[] { "NETCODE_DEBUG", "UNITY_CLIENT" }, - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/Windows_Il2cpp/PlaytestBuild.exe", - target = BuildTarget.StandaloneWindows64, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } - - [MenuItem("Tools/Builder/Build Development Windows Mono")] - static void BuildWinMono() - { - PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.Mono2x); - PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneWindows64, false); // disable auto graphic to use our own custom list - PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneWindows64, new []{GraphicsDeviceType.Vulkan, GraphicsDeviceType.Direct3D11, GraphicsDeviceType.Direct3D12}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. - - AssetDatabase.SaveAssets(); - - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) - { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } - } - - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/Windows_Mono/PlaytestBuild.exe", - target = BuildTarget.StandaloneWindows64, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } - - [MenuItem("Tools/Builder/Build Development Mac Mono")] - static void BuildMacMono() - { - PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.Mono2x); - PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.StandaloneOSX, false); // disable auto graphic to use our own custom list - PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneOSX, new []{GraphicsDeviceType.Metal}); // enforcing Metal Graphics API. Without this there will be shader errors in the final build. - - AssetDatabase.SaveAssets(); - - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) - { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } - } - - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/macOS_Mono/PlaytestBuild.app", - target = BuildTarget.StandaloneOSX, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } - - [MenuItem("Tools/Builder/Build Development Mac Il2cpp")] - static void BuildMacIl2cpp() - { - PlayerSettings.SetScriptingBackend(NamedBuildTarget.Standalone, ScriptingImplementation.IL2CPP); - PlayerSettings.SetGraphicsAPIs(BuildTarget.StandaloneOSX, new []{GraphicsDeviceType.Metal}); // enforcing Metal Graphics API. Without this there will be shader errors in the final build. - - AssetDatabase.SaveAssets(); - - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) - { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } - } - - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/macOS_Il2cpp/PlaytestBuild.app", - target = BuildTarget.StandaloneOSX, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } - - [MenuItem("Tools/Builder/Build Development Android Il2cpp")] - static void BuildAndroidIl2cpp() - { - PlayerSettings.SetScriptingBackend(NamedBuildTarget.Android, ScriptingImplementation.IL2CPP); - PlayerSettings.SetApplicationIdentifier(NamedBuildTarget.Android, "com.UnityTestRunner.UnityTestRunner"); // This is needed only for mobiles since by default the application identifier quite often contains invalid characters like spaces so we wan't to make sure that this has a valid value. It's needed only for mobile since that's an app store requirement - PlayerSettings.SetUseDefaultGraphicsAPIs(BuildTarget.Android, false); // disable auto graphic to use our own custom list - PlayerSettings.SetGraphicsAPIs(BuildTarget.Android, new []{GraphicsDeviceType.Vulkan}); // We need to specify the graphics API for Android builds to ensure proper shader compilation. Vulkan is recommended for modern devices. - PlayerSettings.SetArchitecture(BuildTargetGroup.Android,1); // An integer value associated with the architecture of the build target. 0 - None, 1 - ARM64, 2 - Universal. most modern Android devices use the ARM64 architecture - - AssetDatabase.SaveAssets(); - - List scenesToAdd = new List(); - foreach (var scene in EditorBuildSettings.scenes) - { - if (scene.enabled) - { - Debug.Log("Adding scene to build: " + scene.path); - scenesToAdd.Add(scene.path); - } - } - - var buildPlayerOptions = new BuildPlayerOptions - { - locationPathName = "./build/Android_Il2cpp_Vulkan/PlaytestBuild.apk", - target = BuildTarget.Android, - options = BuildOptions.Development, - scenes = scenesToAdd.ToArray() - }; - - BuildPipeline.BuildPlayer(buildPlayerOptions); - } -} diff --git a/Tools/scripts/BuildAutomation/FileCopy.py b/Tools/scripts/BuildAutomation/FileCopy.py deleted file mode 100644 index 5204fbb6b8..0000000000 --- a/Tools/scripts/BuildAutomation/FileCopy.py +++ /dev/null @@ -1,59 +0,0 @@ -import os -import shutil -import sys - -def main(): - """ - Cleans and prepares the 'Assets/Scripts/Editor' directory for build automation scripts. - It deletes the directory if it exists, recreates it, and copies in the necessary - assembly definition and C# script files. - """ - # --- 1. Argument Validation --- - if len(sys.argv) < 2: - print("Error: Missing required argument.") - print("Usage: python prepare_build_scripts.py ") - sys.exit(1) - - project_root = sys.argv[1] - - # --- 2. Define File Paths --- - # The target directory inside the Unity project - target_dir = os.path.join(project_root, 'Assets', 'Scripts', 'Editor') - - # The source files for build automation - source_asmdef = 'Tools/scripts/BuildAutomation/Unity.ProjectBuild.Editor.asmdef' - source_script = 'Tools/scripts/BuildAutomation/BuilderScripts.cs' - - print(f"Preparing build scripts for project at: {project_root}") - print(f"Target editor script directory: {target_dir}") - - # --- 3. Clean and Recreate Directory --- - try: - if os.path.exists(target_dir): - print(f"Directory '{target_dir}' exists. Removing it.") - shutil.rmtree(target_dir) - - print(f"Creating directory: {target_dir}") - os.makedirs(target_dir) - - except OSError as e: - print(f"Error managing directory: {e}") - sys.exit(1) - - # --- 4. Copy Build Automation Files --- - try: - print(f"Copying '{source_asmdef}' to '{target_dir}'") - shutil.copy(source_asmdef, target_dir) - - print(f"Copying '{source_script}' to '{target_dir}'") - shutil.copy(source_script, target_dir) - - except IOError as e: - print(f"Error copying files: {e}") - sys.exit(1) - - print("\nSuccessfully prepared build automation scripts.") - -if __name__ == "__main__": - main() - diff --git a/Tools/scripts/BuildAutomation/connect_services.py b/Tools/scripts/BuildAutomation/connect_services.py deleted file mode 100644 index db11eeff13..0000000000 --- a/Tools/scripts/BuildAutomation/connect_services.py +++ /dev/null @@ -1,46 +0,0 @@ -# This script modifies ProjectSettings.asset in order to connect the project to Services (like Relay). This is needed for Netcode related builds and can be probably skipped in other cases -# As a Netcode team we usually use (during Playtesting) the following settings - -# Note that cloudProjectId, projectName and organizationId are defined as secrets - -# Notice that those parameters are included as defaults but in your yml file you can override them with your own values. - -import sys -import re -import argparse -import os - -def parse_args(): - global args - parser = argparse.ArgumentParser(description='Update ProjectSettings.asset in order to properly connect to services.') - parser.add_argument('--project-settings-path', required=True, help='The absolute path to the project ProjectSettings.asset file that contains all of the services settings.') - parser.add_argument('--cloud-project-ID', default=os.getenv("CLOUDPROJECTID"), help='ID of a cloud project to which we want to connect to.') - parser.add_argument('--organization-ID', default=os.getenv("ORGANIZATIONID"), help="ID of the organization to which the cloud project belongs.") - parser.add_argument('--project-name', default=os.getenv("PROJECTNAME"), help='Name of the project to which we want to connect to.') - args = parser.parse_args() - -def main(): - """ - Modifies ProjectSettings.asset in order to connect the project to Services - """ - parse_args() - - with open(args.project_settings_path, 'r') as f: - content = f.read() - - # Use regex to replace the values. This is safer than simple string replacement. - content = re.sub(r"cloudProjectId:.*", f"cloudProjectId: {args.cloud_project_ID}", content) - content = re.sub(r"organizationId:.*", f"organizationId: {args.organization_ID}", content) - content = re.sub(r"projectName:.*", f"projectName: {args.project_name}", content) - # Ensure the project is marked as connected - content = re.sub(r"cloudEnabled:.*", "cloudEnabled: 1", content) - - with open(args.project_settings_path, 'w', encoding='UTF-8', newline='\n') as f: - f.write(content) - - print(f"[Linker] Successfully updated {args.project_settings_path} with Project ID: {args.cloud_project_ID}, Org ID: {args.organization_ID}, Project Name: {args.project_name}") - -if __name__ == "__main__": - main() - - diff --git a/Tools/scripts/BuildAutomation/disable-enable-burst.py b/Tools/scripts/BuildAutomation/disable-enable-burst.py deleted file mode 100644 index d9cd0b29e6..0000000000 --- a/Tools/scripts/BuildAutomation/disable-enable-burst.py +++ /dev/null @@ -1,120 +0,0 @@ -# An example usage would be "- python Tools/CI/Netcode/BuildAutomations/disable-enable-burst.py --project-path {{ project.path }} --platform WebGL" -# This file aims to modify BurstAotSettings file which should be present under ProjectSettings folder. -# Note that this requires Burst package to be installed as well as you need to specify the platform for which you are building since there are different settings for each. (this is taken from environment variable) -# This script is not overriding existing settings file but completely replacing it - -import argparse -import json -import os - -# Function that parses arguments of the script -def parse_args(): - global args - parser = argparse.ArgumentParser(description="Enable or disable Burst compilation and specify Unity project details.") - - # Add the mutually exclusive group for --disable-burst and --enable-burst - group = parser.add_mutually_exclusive_group(required=True) - group.add_argument('--disable-burst', action='store_true', help='Disable Burst compilation.') - group.add_argument('--enable-burst', action='store_true', help='Enable Burst compilation.') - - # Add additional arguments - parser.add_argument('--project-path', required=True, help='Specify the location of the Unity project.') - - args = parser.parse_args() - - -# This function creates a new burst settings file with default values. Notice that this should almost always not be used since assumption is that in our case we have projects with Burst preinstalled -# For the "default" values I used values from NetcodeSamples project in DOTS-monorepo -def create_config(settings_path): - config_name = os.path.join(settings_path, 'BurstAotSettings_{}.json'.format(resolve_target())) - monobehaviour = { - 'Version': 4, - 'EnableBurstCompilation': True, - 'EnableOptimisations': True, - 'EnableSafetyChecks': False, - 'EnableDebugInAllBuilds': False, - 'CpuMinTargetX32': 0, - 'CpuMaxTargetX32': 0, - 'CpuMinTargetX64': 0, - 'CpuMaxTargetX64': 0, - 'CpuTargetsX32': 6, - 'CpuTargetsX64': 72, - 'OptimizeFor': 0 - } - - data = {'MonoBehaviour': monobehaviour} - with open(config_name, 'w', encoding='UTF-8', newline='\n') as f: - json.dump(data, f) - return config_name - - -# Burst has specific files for each platform, so we need to resolve the target platform to get the correct settings file. -# Note that this jobs uses environment variables to pass parameters to the script. -def resolve_target(): - # Get the platform value from the environment variable - platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID').lower() - - resolved_target = platform_key - if 'win64' == platform_key: - resolved_target = 'StandaloneWindows' - elif 'mac' == platform_key: - resolved_target = 'StandaloneOSX' - elif 'android' == platform_key: - resolved_target = 'Android' - else: - raise ValueError("Unsupported platform: {}".format(platform) + "Check if you are passing correct argument for one of the supported platforms: StandaloneWindows or StandaloneLinux") - - return resolved_target - - -# This function either returns existing burst settings or creates new if a file was not found -def get_or_create_burst_AOT_config(): - settings_path = os.path.join(args.project_path, 'ProjectSettings') - if not os.path.isdir(settings_path): - os.mkdir(settings_path) - config_names = [os.path.join(settings_path, filename) for filename in os.listdir(settings_path) if filename.startswith("BurstAotSettings_{}".format(resolve_target()))] - if not config_names: - return [create_config(settings_path)] - return config_names - - -# Function that sets the AOT status in the burst settings file (essentially enables or disables burst compilation) -def set_burst_AOT(config_file, status): - config = None - with open(config_file, 'r') as f: - config = json.load(f) - - assert config is not None, 'AOT settings not found; did the burst-enabled build finish successfully?' - - config['MonoBehaviour']['EnableBurstCompilation'] = status - with open(config_file, 'w', encoding='UTF-8', newline='\n') as f: - json.dump(config, f) - - -def main(): - parse_args() - config_names = get_or_create_burst_AOT_config() - - platform_key = os.environ.get('PLATFORM_WIN64_MAC_ANDROID').lower() - print(f"Burst compilation script: Unity project path is {args.project_path}") - print(f"Burst compilation script: Target platform is {platform_key}") - - if args.disable_burst: - print('BURST COMPILATION: DISABLED') - - for config_name in config_names: - set_burst_AOT(config_name, False) - - elif args.enable_burst: - print('BURST COMPILATION: ENABLED') - - for config_name in config_names: - set_burst_AOT(config_name, True) - - else: - sys.exit('BURST COMPILATION: unexpected value: {}'.format(args.enable_burst)) - - - -if __name__ == '__main__': - main() diff --git a/Tools/scripts/BuildAutomation/manifest_update.py b/Tools/scripts/BuildAutomation/manifest_update.py deleted file mode 100644 index 1597e2bc39..0000000000 --- a/Tools/scripts/BuildAutomation/manifest_update.py +++ /dev/null @@ -1,62 +0,0 @@ -# This script is used to replace a package from the project manifest.json with a local version. -# The goal is that you will trigger the build process from your branch (for example release/1.2.3) and package from this branch will be use in the project manifest.1 -# Note that for now this script is assuming that such package already has an entry in the manifest -# TODO: consider if it makes sense to just add new manifest entry (to test what?) - -import json -import argparse -import os - -def parse_args(): - global args - parser = argparse.ArgumentParser(description='Update a Unity project manifest to point to the local package version.') - parser.add_argument('--manifest-path', required=True, help='The absolute path to the project manifest.json file.') - parser.add_argument('--package-name', default="com.unity.netcode.gameobjects", help="The name of the package to modify in the manifest.") - parser.add_argument('--local-package-path', required=True, help='The absolute file path to the local package source directory.') - args = parser.parse_args() - -def main(): - """ - Updates a project's manifest.json to use package under passed local-package-path, - and then prints the version of that local package. - """ - parse_args() - - # Update the target project's manifest - try: - with open(args.manifest_path, 'r') as f: - manifest_data = json.load(f) - - local_path_normalized = args.local_package_path.replace(os.sep, '/') - manifest_data["dependencies"][args.package_name] = f"file:{local_path_normalized}" - - with open(args.manifest_path, 'w', encoding='UTF-8', newline='\n') as f: - json.dump(manifest_data, f, indent=4) - - print(f"Successfully updated manifest at '{args.manifest_path}'") - print(f"Set '{args.package_name}' to use local package at '{args.local_package_path}'") - except Exception as e: - print(f"Error updating manifest: {e}") - exit(1) - - # --- Read and report the local package's version for log confirmation--- - # This is only for debug purposes - try: - # Construct the path to the local package's package.json file - local_package_json_path = os.path.join(args.local_package_path, 'package.json') - - with open(local_package_json_path, 'r') as f: - local_package_data = json.load(f) - - # Extract the version, providing a default if not found - local_package_version = local_package_data.get('version', 'N/A') - - print(f"--> Verified local '{args.package_name}' version is: {local_package_version}") - - except FileNotFoundError: - print(f"Warning: Could not find package.json at '{local_package_json_path}'") - except Exception as e: - print(f"Error reading local package version: {e}") - -if __name__ == "__main__": - main() diff --git a/Tools/scripts/BuildAutomation/validate_params.py b/Tools/scripts/BuildAutomation/validate_params.py deleted file mode 100644 index 4c105836cc..0000000000 --- a/Tools/scripts/BuildAutomation/validate_params.py +++ /dev/null @@ -1,58 +0,0 @@ -import os -import sys - -# --- Configuration --- -# A dictionary that maps each environment variable to a set of its allowed values. -# This is the single source of truth for all validation. -VALIDATION_RULES = { - 'SCRIPTING_BACKEND_IL2CPP_MONO': {'il2cpp', 'mono'}, - 'BURST_ON_OFF': {'on', 'off'}, - 'PLATFORM_WIN64_MAC_ANDROID': {'win64', 'mac', 'android'} -} - -def main(): - """ - Validates Yamato environment variables using a rule-based dictionary. - Exits with 1 if any variable is invalid, otherwise exits with 0. - """ - all_params_valid = True - - for var_name, allowed_values in VALIDATION_RULES.items(): - actual_value = os.environ.get(var_name, '').lower() - allowed_values_lower = {v.lower() for v in allowed_values} - - if actual_value not in allowed_values_lower: - print( - f"ERROR: Invalid {var_name}: '{actual_value}'. " - f"Allowed values are: {list(allowed_values)}", - file=sys.stderr - ) - all_params_valid = False - - platform = os.environ.get('PLATFORM_WIN64_MAC_ANDROID', '').lower() - scripting_backend = os.environ.get('SCRIPTING_BACKEND_IL2CPP_MONO', '').lower() - - if platform == 'mac' and scripting_backend == 'il2cpp': - print( - "ERROR: Invalid Configuration: The 'mac' platform with the 'il2cpp' " - "Note that for now windows machine is used for building project and it's a known limitation that mac builds (via windows machine) can be done only with mono", - file=sys.stderr - ) - all_params_valid = False - - if platform == 'android' and scripting_backend == 'mono': - print( - "ERROR: Invalid Configuration: The 'android' platform with the 'mono' " - "Note that mobile builds are not supporting mono and need il2cpp scripting backend", - file=sys.stderr - ) - all_params_valid = False - - if not all_params_valid: - print("\nOne or more parameters failed validation. Halting build.", file=sys.stderr) - sys.exit(1) - - print("All parameters are valid. Proceeding with the build.") - -if __name__ == "__main__": - main() \ No newline at end of file From fdadb7dac5dfd610f28e8146c23e4797d5440d48 Mon Sep 17 00:00:00 2001 From: Noel Stephens Date: Fri, 2 Oct 2026 11:13:53 -0500 Subject: [PATCH 63/64] fix: mixed authority nested NetworkTransform stops updating non-authority nested NetworkTransforms (#4169) * fix Fixing issue where a `NetworkObject` with nested `NetworkTransform` components using different `NetworkTransform.AuthorityMode` settings stop updating non-authority instances, because the first authority instance removes the entire `NetworkObject` from the update registration. * test The tests that validate this fix. * update Adding changelog entry * style Updating comments for clarity * fix Addressing the u-pr bot's findings. * test Adding a test to validate the, now fixed, gap u-pr bot found. * update Applying u-pr's suggestion to this test. * test: collapse the mixed authority fixtures and add GetManagersInstance The root authority mode moved from a TestFixture argument into an array walked inside the test, which takes the fixture count from 4 to 2 and the case count from 8 to 2. Only HostOrServer still needs a session per value. That required moving the nested NetworkTransform components off the player prefab and onto two prefabs spawned and despawned per case, so the owner is now an explicit client rather than every player in turn. NetcodeIntegrationTest gains GetManagersInstance for resolving a NetworkObject relative to a NetworkManager, which asserts rather than throwing KeyNotFoundException when the instance is missing. * Update com.unity.netcode.gameobjects/CHANGELOG.md Co-authored-by: u-pr[bot] <205906871+u-pr[bot]@users.noreply.github.com> * update --------- Co-authored-by: u-pr[bot] <205906871+u-pr[bot]@users.noreply.github.com> --- com.unity.netcode.gameobjects/CHANGELOG.md | 1 + .../Runtime/Components/NetworkTransform.cs | 40 ++++- .../NetworkTransformMixedAuthorityTests.cs | 144 ++++++++++++------ .../NetworkTransformMixedMotionModelTests.cs | 67 ++++++++ ...workTransformMixedMotionModelTests.cs.meta | 2 + .../TestHelpers/NetcodeIntegrationTest.cs | 40 +++++ 6 files changed, 242 insertions(+), 52 deletions(-) create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs create mode 100644 com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs.meta diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 7247324fe1..2b5a367421 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,6 +22,7 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed +- Fixed an issue where mixed authority nested `NetworkTransform` instances could stop child/nested instances from updating because an authoritative `NetworkTransform` (root or otherwise) would remove the `NetworkObject` from the update group, preventing non-authoritative instances from updating their state on the authority side. (#4169) ### Security diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs index 46b7ef24e5..9ac503a926 100644 --- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs +++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs @@ -3715,6 +3715,38 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState() m_ScaleInterpolator.ResetTo(transform.parent, transform.localScale, serverTime); } + /// + /// Determines if this has any instances that are non-authority and are updated during the same update stage. + /// + /// + /// See to better understand how the parameter is used to determine which update stage to check for non-authority instances. + /// + /// true to check the instances updated during the standard update and false to check the instances updated during the fixed update. + /// true if a non-authority NetworkTransform exists on this NetworkObject and false if there are none. + private bool HasNonAuthorityNetworkTransform(bool forUpdate) + { + var networkTransforms = NetworkObject.NetworkTransforms; + for (int i = 0; i < networkTransforms.Count; i++) + { + var networkTransform = networkTransforms[i]; +#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D + // If the update stages don't match, then skip this instance. + // Reference: + // forUpdate is true for the standard update and false for the fixed update. + // m_UseRigidbodyForMotion is false for the standard update and true for the fixed update. + if (forUpdate == networkTransform.m_UseRigidbodyForMotion) + { + continue; + } +#endif + if (!(networkTransform.IsServerAuthoritative() ? networkTransform.IsServer : networkTransform.IsOwner)) + { + return true; + } + } + return false; + } + /// /// The internal initialization method to allow for internal API adjustments /// @@ -3777,8 +3809,12 @@ private void InternalInitialization(bool isOwnershipChange = false) if (CanCommitToTransform) { - // Make sure authority doesn't get added to updates (no need to do this on the authority side) - m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); + // If there are no non-authority NetworkTransform instances on this NetworkObject using this update, then remove this instance from the NetworkManager's update list. + // Otherwise, we need to keep it registered for updates so the non-authority instances will process their received state updates and apply them to the transform. + if (!HasNonAuthorityNetworkTransform(forUpdate)) + { + m_CachedNetworkManager.NetworkTransformRegistration(NetworkObject, forUpdate, false); + } if (UseHalfFloatPrecision) { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, m_CachedNetworkManager.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs index 760e916bda..c8d9d144bf 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedAuthorityTests.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Text; +using NUnit.Framework; using Unity.Netcode.Components; using Unity.Netcode.TestHelpers.Runtime; using UnityEngine; @@ -7,6 +8,8 @@ namespace Unity.Netcode.RuntimeTests { + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] internal class NetworkTransformMixedAuthorityTests : IntegrationTestWithApproximation { private const float k_MotionMagnitude = 5.5f; @@ -14,65 +17,79 @@ internal class NetworkTransformMixedAuthorityTests : IntegrationTestWithApproxim protected override int NumberOfClients => 2; - private StringBuilder m_ErrorMsg = new StringBuilder(); - - protected override void OnCreatePlayerPrefab() + /// + /// The root's authority mode for each case. The nested child uses the inverse. + /// + private static readonly NetworkTransform.AuthorityModes[] k_RootAuthorityModes = { - m_PlayerPrefab.AddComponent(); + NetworkTransform.AuthorityModes.Server, + NetworkTransform.AuthorityModes.Owner, + }; + + private GameObject[] m_MixedAuthorityPrefabs; - var childGameObject = new GameObject(); - childGameObject.transform.parent = m_PlayerPrefab.transform; - var childNetworkTransform = childGameObject.AddComponent(); - childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner; - childNetworkTransform.InLocalSpace = true; + private StringBuilder m_ErrorMsg = new StringBuilder(); - base.OnCreatePlayerPrefab(); + public NetworkTransformMixedAuthorityTests(HostOrServer hostOrServer) : base(hostOrServer) + { } - private void MovePlayers() + protected override void OnServerAndClientsCreated() { - foreach (var networkManager in m_NetworkManagers) + m_MixedAuthorityPrefabs = new GameObject[k_RootAuthorityModes.Length]; + for (int i = 0; i < k_RootAuthorityModes.Length; i++) { - var direction = GetRandomVector3(-1.0f, 1.0f); - var playerObject = networkManager.LocalClient.PlayerObject; - var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId; - // Server authoritative - var serverPlayerClone = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId]; - serverPlayerClone.transform.position += direction * k_MotionMagnitude; - // Owner authoritative - var childTransform = networkManager.LocalClient.PlayerObject.transform.GetChild(0); - childTransform.localPosition += direction * k_MotionMagnitude; + var rootAuthorityMode = k_RootAuthorityModes[i]; + var prefab = CreateNetworkObjectPrefab($"MixedAuthority-{rootAuthorityMode}Root"); + prefab.AddComponent().AuthorityMode = rootAuthorityMode; + + var childGameObject = new GameObject(); + childGameObject.transform.parent = prefab.transform; + var childNetworkTransform = childGameObject.AddComponent(); + childNetworkTransform.AuthorityMode = InverseOf(rootAuthorityMode); + childNetworkTransform.InLocalSpace = true; + + m_MixedAuthorityPrefabs[i] = prefab; } + + base.OnServerAndClientsCreated(); + } + + private static NetworkTransform.AuthorityModes InverseOf(NetworkTransform.AuthorityModes authorityMode) + { + return authorityMode == NetworkTransform.AuthorityModes.Server ? NetworkTransform.AuthorityModes.Owner : NetworkTransform.AuthorityModes.Server; + } + + /// + /// Returns the instance with authority over a set to the given authority mode. + /// + private NetworkObject GetAuthorityInstance(NetworkObject instance, NetworkManager owner, NetworkTransform.AuthorityModes authorityMode) + { + return GetManagersInstance(authorityMode == NetworkTransform.AuthorityModes.Server ? m_ServerNetworkManager : owner, instance); } - private bool AllInstancePositionsMatch() + private bool AllInstancePositionsMatch(NetworkObject instance, NetworkManager owner, NetworkTransform.AuthorityModes rootAuthorityMode) { m_ErrorMsg.Clear(); + var authorityRootPosition = GetAuthorityInstance(instance, owner, rootAuthorityMode).transform.position; + var authorityChildPosition = GetAuthorityInstance(instance, owner, InverseOf(rootAuthorityMode)).transform.GetChild(0).localPosition; + + // The authority instances are compared too. An instance with authority over one nested + // NetworkTransform is still non-authority for the other. foreach (var networkManager in m_NetworkManagers) { - var playerObject = networkManager.LocalClient.PlayerObject; - var playerObjectId = networkManager.LocalClient.PlayerObject.NetworkObjectId; - var serverRootPosition = m_ServerNetworkManager.SpawnManager.SpawnedObjects[playerObjectId].transform.position; - var ownerChildPosition = networkManager.LocalClient.PlayerObject.transform.GetChild(0).localPosition; - foreach (var client in m_NetworkManagers) - { - if (client == networkManager) - { - continue; - } - var playerClone = client.SpawnManager.SpawnedObjects[playerObjectId]; - var cloneRootPosition = playerClone.transform.position; - var cloneChildPosition = playerClone.transform.GetChild(0).localPosition; + var clone = GetManagersInstance(networkManager, instance); + var cloneRootPosition = clone.transform.position; + var cloneChildPosition = clone.transform.GetChild(0).localPosition; - if (!Approximately(serverRootPosition, cloneRootPosition)) - { - m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Root mismatch ({GetVector3Values(serverRootPosition)})({GetVector3Values(cloneRootPosition)})!"); - } + if (!Approximately(authorityRootPosition, cloneRootPosition)) + { + m_ErrorMsg.AppendLine($"[{rootAuthorityMode}Root][{GetDisplayName(networkManager)}] Root mismatch ({GetVector3Values(authorityRootPosition)})({GetVector3Values(cloneRootPosition)})!"); + } - if (!Approximately(ownerChildPosition, cloneChildPosition)) - { - m_ErrorMsg.AppendLine($"[{playerObject.name}][{playerClone.name}] Child mismatch ({GetVector3Values(ownerChildPosition)})({GetVector3Values(cloneChildPosition)})!"); - } + if (!Approximately(authorityChildPosition, cloneChildPosition)) + { + m_ErrorMsg.AppendLine($"[{rootAuthorityMode}Root][{GetDisplayName(networkManager)}] Child mismatch ({GetVector3Values(authorityChildPosition)})({GetVector3Values(cloneChildPosition)})!"); } } return m_ErrorMsg.Length == 0; @@ -80,18 +97,45 @@ private bool AllInstancePositionsMatch() /// /// Client-Server Only - /// Validates that mixed authority is working properly - /// Root -- Server Authoritative - /// |--Child -- Owner Authoritative + /// Validates that mixed authority is working properly for both arrangements: + /// Root -- Server or Owner authoritative + /// |--Child -- The inverse of the root's authority mode /// [UnityTest] public IEnumerator MixedAuthorityTest() { - for (int i = 0; i < k_Iterations; i++) + // A client owns the instance so the owner authoritative half is never also the server. + var owner = m_ClientNetworkManagers[0]; + for (int i = 0; i < k_RootAuthorityModes.Length; i++) { - MovePlayers(); - yield return WaitForConditionOrTimeOut(AllInstancePositionsMatch); - AssertOnTimeout($"Transforms failed to synchronize!"); + var rootAuthorityMode = k_RootAuthorityModes[i]; + var instance = SpawnObject(m_MixedAuthorityPrefabs[i], owner).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(instance); + AssertOnTimeout($"[{rootAuthorityMode}Root] Failed to spawn {instance.name} on all clients!"); + + // An instance stays registered for updates while any of its nested NetworkTransform components is non-authority. + foreach (var networkManager in m_NetworkManagers) + { + var clone = GetManagersInstance(networkManager, instance); + var hasNonAuthority = false; + foreach (var networkTransform in clone.NetworkTransforms) + { + hasNonAuthority |= !networkTransform.CanCommitToTransform; + } + Assert.AreEqual(hasNonAuthority, networkManager.NetworkTransformUpdate.ContainsKey(instance.NetworkObjectId), $"[{rootAuthorityMode}Root][{GetDisplayName(networkManager)}] Unexpected update registration!"); + } + + for (int iteration = 0; iteration < k_Iterations; iteration++) + { + var direction = GetRandomVector3(-1.0f, 1.0f); + GetAuthorityInstance(instance, owner, rootAuthorityMode).transform.position += direction * k_MotionMagnitude; + GetAuthorityInstance(instance, owner, InverseOf(rootAuthorityMode)).transform.GetChild(0).localPosition += direction * k_MotionMagnitude; + + yield return WaitForConditionOrTimeOut(() => AllInstancePositionsMatch(instance, owner, rootAuthorityMode)); + AssertOnTimeout($"[{rootAuthorityMode}Root] Transforms failed to synchronize!\n{m_ErrorMsg}"); + } + + instance.Despawn(); } } } diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs new file mode 100644 index 0000000000..25ec9894eb --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs @@ -0,0 +1,67 @@ +#if COM_UNITY_MODULES_PHYSICS +using System.Collections; +using NUnit.Framework; +using Unity.Netcode.Components; +using Unity.Netcode.TestHelpers.Runtime; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Unity.Netcode.RuntimeTests +{ + internal class NetworkTransformMixedMotionModelTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + + private GameObject m_MixedMotionModelPrefab; + + protected override void OnServerAndClientsCreated() + { + m_MixedMotionModelPrefab = CreateNetworkObjectPrefab("MixedMotionModel"); + + // The root is owner authoritative and driven by the rigidbody, which places it in the fixed update registration + var rootNetworkTransform = m_MixedMotionModelPrefab.AddComponent(); + rootNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Owner; + var rigidbody = m_MixedMotionModelPrefab.AddComponent(); + rigidbody.useGravity = false; + rigidbody.detectCollisions = false; + m_MixedMotionModelPrefab.AddComponent().UseRigidBodyForMotion = true; + + // The nested child is server authoritative and driven by the transform, which places it in the update registration + var childGameObject = new GameObject(); + childGameObject.transform.parent = m_MixedMotionModelPrefab.transform; + var childNetworkTransform = childGameObject.AddComponent(); + childNetworkTransform.AuthorityMode = NetworkTransform.AuthorityModes.Server; + childNetworkTransform.InLocalSpace = true; + + base.OnServerAndClientsCreated(); + } + + /// + /// A NetworkObject that mixes both the authority motion model and the rigidbody motion model has each nested + /// NetworkTransform registered under a different update. Gaining authority over the instance in one update + /// should not leave it registered for the other. + /// + [UnityTest] + public IEnumerator UpdateRegistrationFollowsMotionModel() + { + var instance = SpawnObject(m_MixedMotionModelPrefab, m_ServerNetworkManager).GetComponent(); + yield return WaitForSpawnedOnAllOrTimeOut(instance); + AssertOnTimeout($"Failed to spawn {instance.name} on all clients!"); + + var newOwner = m_ClientNetworkManagers[0]; + + // Establish the baseline before ownership is transferred, otherwise the check below would still pass if this instance was never registered for the fixed update to begin with. + Assert.True(newOwner.NetworkTransformFixedUpdate.ContainsKey(instance.NetworkObjectId), $"Client-{newOwner.LocalClientId} should initially be registered for the fixed update!"); + + instance.ChangeOwnership(newOwner.LocalClientId); + yield return WaitForConditionOrTimeOut(() => newOwner.SpawnManager.SpawnedObjects[instance.NetworkObjectId].OwnerClientId == newOwner.LocalClientId); + AssertOnTimeout($"Client-{newOwner.LocalClientId} never gained ownership of {instance.name}!"); + + // The new owner is the authority for the rigidbody driven root, so nothing on this instance needs the fixed + // update any longer. The server authoritative child still needs the standard update. + Assert.False(newOwner.NetworkTransformFixedUpdate.ContainsKey(instance.NetworkObjectId), $"Client-{newOwner.LocalClientId} is still registered for the fixed update!"); + Assert.True(newOwner.NetworkTransformUpdate.ContainsKey(instance.NetworkObjectId), $"Client-{newOwner.LocalClientId} is not registered for the update!"); + } + } +} +#endif diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs.meta new file mode 100644 index 0000000000..8b1683109d --- /dev/null +++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/NetworkTransformMixedMotionModelTests.cs.meta @@ -0,0 +1,2 @@ +fileFormatVersion: 2 +guid: b12b5cdba06a3814fbe6cf7c4df188f7 \ No newline at end of file diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs index 5b2f5a986b..fe1185462b 100644 --- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs +++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs @@ -322,6 +322,46 @@ protected NetworkManager GetNonAuthorityNetworkManager(int nonAuthorityIndex) return null; } + /// + /// Gets the name of the given to use in assertion messages: + /// - Dedicated server: "Server" + /// - Host: "Host" + /// - Client: "Client-" followed by its + /// + /// The to get the name of + /// The name of the + protected string GetDisplayName(NetworkManager networkManager) + { + if (networkManager.IsServer) + { + return networkManager.IsHost ? "Host" : "Server"; + } + return $"Client-{networkManager.LocalClientId}"; + } + + /// + /// Gets the relative instance of the with the given identifier. + /// + /// The to get the relative instance from + /// The identifier of the wanted + /// The relative instance + protected NetworkObject GetManagersInstance(NetworkManager networkManager, ulong networkObjectId) + { + Assert.True(networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId), $"{GetDisplayName(networkManager)} has no spawned {nameof(NetworkObject)} with an identifier of {networkObjectId}!"); + return networkManager.SpawnManager.SpawnedObjects[networkObjectId]; + } + + /// + /// Gets the relative instance of the given . + /// + /// The to get the relative instance from + /// Any instance of the wanted + /// The relative instance + protected NetworkObject GetManagersInstance(NetworkManager networkManager, NetworkObject networkObject) + { + return GetManagersInstance(networkManager, networkObject.NetworkObjectId); + } + /// /// Contains each client relative set of player NetworkObject instances /// [Client Relative set of player instances][The player instance ClientId][The player instance's NetworkObject] From 351178dcd4c589d8cce520b81fa78ca43b19e251 Mon Sep 17 00:00:00 2001 From: netcode-automation Date: Sun, 4 Oct 2026 00:20:14 +0000 Subject: [PATCH 64/64] chore: Updated aspects of Netcode package in anticipation of v3.1.0 release --- com.unity.netcode.gameobjects/CHANGELOG.md | 7 ++++++- com.unity.netcode.gameobjects/package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/com.unity.netcode.gameobjects/CHANGELOG.md b/com.unity.netcode.gameobjects/CHANGELOG.md index 2b5a367421..72344cc2f9 100644 --- a/com.unity.netcode.gameobjects/CHANGELOG.md +++ b/com.unity.netcode.gameobjects/CHANGELOG.md @@ -22,7 +22,6 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed an issue where mixed authority nested `NetworkTransform` instances could stop child/nested instances from updating because an authoritative `NetworkTransform` (root or otherwise) would remove the `NetworkObject` from the update group, preventing non-authoritative instances from updating their state on the authority side. (#4169) ### Security @@ -30,6 +29,12 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Obsolete +## [3.1.0] - 2026-10-04 + +### Fixed + +- Fixed an issue where mixed authority nested `NetworkTransform` instances could stop child/nested instances from updating because an authoritative `NetworkTransform` (root or otherwise) would remove the `NetworkObject` from the update group, preventing non-authoritative instances from updating their state on the authority side. (#4169) + ## [2.13.3] - 2026-09-14 ### Changed diff --git a/com.unity.netcode.gameobjects/package.json b/com.unity.netcode.gameobjects/package.json index e257255fcc..b3f3c53e4b 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": "2.13.4", + "version": "2.13.5", "unity": "6000.0", "dependencies": { "com.unity.nuget.mono-cecil": "1.11.4",