From 5b4aaa8b590bd2b21a6a5a90897be369e93cca5a Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Wed, 2 Mar 2022 00:00:00 +0000 Subject: [PATCH] com.unity.netcode.gameobjects@1.0.0-pre.6 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). ## [1.0.0-pre.6] - 2022-03-02 ### Added - NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765) - Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735) ### Changed ### Fixed - Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683) - Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683) - Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683) - Disallowed async keyword in RPCs (#1681) - Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678) - Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen) - Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694) - Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685) - Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720) - Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680) - Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682) - Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725) - Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721) - Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724) - Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728) - Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731) - Improved performance in NetworkAnimator (#1735) - Removed the "always sync" network animator (aka "autosend") parameters (#1746) --- CHANGELOG.md | 33 +- .../BufferedLinearInterpolator.cs | 7 + Components/NetworkAnimator.cs | 363 ++++----- Components/NetworkTransform.cs | 81 +- .../com.unity.netcode.components.asmdef | 10 +- Editor/CodeGen/CodeGenHelpers.cs | 4 + Editor/CodeGen/INetworkMessageILPP.cs | 57 +- Editor/CodeGen/NetworkBehaviourILPP.cs | 363 ++++----- Editor/CodeGen/RuntimeAccessModifiersILPP.cs | 6 +- .../DontShowInTransportDropdownAttribute.cs | 8 - ...ntShowInTransportDropdownAttribute.cs.meta | 3 - Editor/NetworkAnimatorEditor.cs | 88 +-- Editor/NetworkManagerEditor.cs | 2 +- Editor/NetworkManagerHelper.cs | 2 +- Editor/com.unity.netcode.editor.asmdef | 9 +- README.md | 2 + Runtime/AssemblyInfo.cs | 6 +- Runtime/Configuration/NetworkConfig.cs | 2 + Runtime/Core/NetworkBehaviour.cs | 168 ++-- Runtime/Core/NetworkManager.cs | 89 ++- Runtime/Core/NetworkObject.cs | 9 +- Runtime/Core/SnapshotSystem.cs | 19 +- Runtime/Logging/NetworkLog.cs | 3 +- Runtime/Messaging/CustomMessageManager.cs | 16 +- Runtime/Messaging/INetworkHooks.cs | 26 +- Runtime/Messaging/INetworkMessage.cs | 8 +- .../Messages/ChangeOwnershipMessage.cs | 26 +- .../Messages/ConnectionApprovedMessage.cs | 49 +- .../Messages/ConnectionRequestMessage.cs | 40 +- .../Messaging/Messages/CreateObjectMessage.cs | 21 +- .../Messages/DestroyObjectMessage.cs | 19 +- Runtime/Messaging/Messages/NamedMessage.cs | 18 +- .../Messages/NetworkVariableDeltaMessage.cs | 80 +- .../Messaging/Messages/ParentSyncMessage.cs | 41 +- Runtime/Messaging/Messages/RpcMessage.cs | 109 --- Runtime/Messaging/Messages/RpcMessages.cs | 157 ++++ ...RpcMessage.cs.meta => RpcMessages.cs.meta} | 0 .../Messaging/Messages/SceneEventMessage.cs | 12 +- .../Messaging/Messages/ServerLogMessage.cs | 18 +- .../Messaging/Messages/SnapshotDataMessage.cs | 90 +-- Runtime/Messaging/Messages/TimeSyncMessage.cs | 13 +- Runtime/Messaging/Messages/UnnamedMessage.cs | 15 +- Runtime/Messaging/MessagingSystem.cs | 73 +- Runtime/Messaging/NetworkContext.cs | 5 + Runtime/Metrics/INetworkMetrics.cs | 6 + Runtime/Metrics/MetricHooks.cs | 17 +- Runtime/Metrics/NetworkMetrics.cs | 64 +- Runtime/Metrics/NullNetworkMetrics.cs | 12 + .../Collections/NetworkList.cs | 20 +- Runtime/NetworkVariable/NetworkVariable.cs | 8 +- .../NetworkVariable/NetworkVariableBase.cs | 2 +- Runtime/Profiling/ProfilingHooks.cs | 18 +- .../SceneManagement/ISceneManagerHandler.cs | 28 + .../ISceneManagerHandler.cs.meta | 11 + .../SceneManagement/NetworkSceneManager.cs | 237 +++--- Runtime/SceneManagement/SceneEventData.cs | 14 +- Runtime/Serialization/FastBufferReader.cs | 24 +- Runtime/Spawning/NetworkSpawnManager.cs | 40 +- Runtime/Transports/NetworkTransport.cs | 12 +- Runtime/Transports/UNET/UNetChannel.cs | 2 + Runtime/Transports/UNET/UNetTransport.cs | 10 +- Runtime/com.unity.netcode.runtime.asmdef | 23 +- .../Runtime/Helpers.meta => TestHelpers.meta | 2 +- .../Utility.meta => TestHelpers/Runtime.meta | 2 +- TestHelpers/Runtime/AssemblyInfo.cs | 6 + TestHelpers/Runtime/AssemblyInfo.cs.meta | 11 + TestHelpers/Runtime/Components.meta | 8 + .../Components/ObjectNameIdentifier.cs | 93 +++ .../Components/ObjectNameIdentifier.cs.meta | 11 + TestHelpers/Runtime/ConditionalPredicate.cs | 60 ++ .../Runtime/ConditionalPredicate.cs.meta | 11 + .../Runtime/IntegrationTestSceneHandler.cs | 115 +++ .../IntegrationTestSceneHandler.cs.meta | 11 + TestHelpers/Runtime/Metrics.meta | 8 + .../Runtime/Metrics}/MetricTestBase.cs | 49 +- .../Runtime/Metrics}/MetricTestBase.cs.meta | 0 .../Metrics}/NetworkVariableComponent.cs | 2 +- .../Metrics}/NetworkVariableComponent.cs.meta | 0 .../Runtime/Metrics}/RpcTestComponent.cs | 2 +- .../Runtime/Metrics}/RpcTestComponent.cs.meta | 0 .../Metrics/WaitForCounterMetricValue.cs | 50 ++ .../Metrics/WaitForCounterMetricValue.cs.meta | 3 + .../Metrics/WaitForEventMetricValues.cs | 60 ++ .../Metrics/WaitForEventMetricValues.cs.meta | 3 + .../Metrics/WaitForGaugeMetricValues.cs | 55 ++ .../Metrics/WaitForGaugeMetricValues.cs.meta | 3 + .../Runtime/Metrics}/WaitForMetricValues.cs | 98 +-- .../Metrics}/WaitForMetricValues.cs.meta | 0 TestHelpers/Runtime/NetcodeIntegrationTest.cs | 727 ++++++++++++++++++ .../Runtime/NetcodeIntegrationTest.cs.meta | 0 .../Runtime/NetcodeIntegrationTestHelpers.cs | 440 ++++++++--- .../NetcodeIntegrationTestHelpers.cs.meta | 0 .../Runtime}/NetworkManagerHelper.cs | 24 +- .../Runtime}/NetworkManagerHelper.cs.meta | 0 .../Runtime}/NetworkVariableHelper.cs | 6 +- .../Runtime}/NetworkVariableHelper.cs.meta | 0 TestHelpers/Runtime/TimeoutHelper.cs | 42 + TestHelpers/Runtime/TimeoutHelper.cs.meta | 11 + TestHelpers/Runtime/Transport.meta | 8 + TestHelpers/Runtime/Transport/MessageHooks.cs | 70 ++ .../Runtime/Transport/MessageHooks.cs.meta | 11 + .../Transport/MessageHooksConditional.cs | 95 +++ .../Transport/MessageHooksConditional.cs.meta | 11 + .../Runtime/Transport/SIPTransport.cs | 12 +- .../Runtime/Transport/SIPTransport.cs.meta | 0 ...m.unity.netcode.testhelpers.runtime.asmdef | 32 + ...ty.netcode.testhelpers.runtime.asmdef.meta | 7 + .../Editor/Messaging/MessageReceivingTests.cs | 23 +- .../Messaging/MessageRegistrationTests.cs | 58 +- Tests/Editor/Messaging/MessageSendingTests.cs | 44 +- Tests/Editor/Serialization.meta | 9 +- .../BaseFastBufferReaderWriterTest.cs | 2 +- .../Serialization/FastBufferReaderTests.cs | 46 ++ Tests/Editor/SnapshotTests.cs | 25 +- .../com.unity.netcode.editortests.asmdef | 15 +- Tests/Runtime/BaseMultiInstanceTest.cs | 178 ----- .../BufferDataValidationComponent.cs | 1 + .../NetworkVariableTestComponent.cs | 37 +- .../Components/NetworkVisibilityComponent.cs | 13 + .../NetworkVisibilityComponent.cs.meta | 11 + Tests/Runtime/ConnectionApproval.cs | 1 + Tests/Runtime/DisconnectTests.cs | 13 +- Tests/Runtime/HiddenVariableTests.cs | 57 +- Tests/Runtime/IntegrationTestExamples.cs | 184 +++++ Tests/Runtime/IntegrationTestExamples.cs.meta | 11 + Tests/Runtime/Messaging/NamedMessageTests.cs | 12 +- .../Runtime/Messaging/UnnamedMessageTests.cs | 5 +- .../Runtime/Metrics/MessagingMetricsTests.cs | 40 +- Tests/Runtime/Metrics/MetricsDispatchTests.cs | 2 + .../Metrics/NetworkObjectMetricsTests.cs | 85 +- .../Metrics/NetworkVariableMetricsTests.cs | 14 +- .../Metrics/OwnershipChangeMetricsTests.cs | 14 +- Tests/Runtime/Metrics/PacketMetricsTests.cs | 62 ++ .../Metrics/PacketMetricsTests.cs.meta | 3 + Tests/Runtime/Metrics/RpcMetricsTests.cs | 43 +- Tests/Runtime/Metrics/RttMetricsTests.cs | 101 +++ Tests/Runtime/Metrics/RttMetricsTests.cs.meta | 3 + .../Runtime/Metrics/ServerLogsMetricTests.cs | 13 +- .../Metrics/TransportBytesMetricsTests.cs | 6 +- Tests/Runtime/NestedNetworkManagerTests.cs | 33 +- Tests/Runtime/NetworkAnimator.meta | 8 + .../NetworkAnimator/NetworkAnimatorTests.cs | 237 ++++++ .../NetworkAnimatorTests.cs.meta | 11 + Tests/Runtime/NetworkAnimator/Resources.meta | 8 + .../Resources/AlphaAnimation.anim | 53 ++ .../Resources/AlphaAnimation.anim.meta | 8 + .../Resources/DefaultAnimation.anim | 53 ++ .../Resources/DefaultAnimation.anim.meta | 8 + .../Resources/Layer2Animation.anim | 53 ++ .../Resources/Layer2Animation.anim.meta | 8 + .../Resources/OverrideAlphaAnimation.anim | 53 ++ .../OverrideAlphaAnimation.anim.meta | 8 + .../Resources/OverrideDefaultAnimation.anim | 53 ++ .../OverrideDefaultAnimation.anim.meta | 8 + .../TestAnimatorController.controller | 449 +++++++++++ .../TestAnimatorController.controller.meta | 8 + ...matorOverrideController.overrideController | 15 + Tests/Runtime/NetworkBehaviourGenericTests.cs | 61 ++ .../NetworkBehaviourGenericTests.cs.meta | 11 + Tests/Runtime/NetworkBehaviourUpdaterTests.cs | 460 +++++++---- ...NetworkManagerCustomMessageManagerTests.cs | 7 +- ...rkManagerCustomMessageManagerTests.cs.meta | 0 .../NetworkManagerSceneManagerTests.cs | 2 +- .../NetworkManagerSceneManagerTests.cs.meta | 0 .../NetworkObjectDestroyTests.cs | 35 +- .../NetworkObjectDontDestroyWithOwnerTests.cs | 19 +- ...orkObjectNetworkClientOwnedObjectsTests.cs | 58 ++ ...jectNetworkClientOwnedObjectsTests.cs.meta | 11 + .../NetworkObjectOnNetworkDespawnTests.cs | 19 +- .../NetworkObjectOnSpawnTests.cs | 130 ++-- .../NetworkObjectOwnershipTests.cs | 23 +- .../NetworkObjectSceneSerializationTests.cs | 1 + .../NetworkObjectSpawnManyObjectsTests.cs | 65 ++ ...NetworkObjectSpawnManyObjectsTests.cs.meta | 11 + Tests/Runtime/NetworkPrefabHandlerTests.cs | 3 +- Tests/Runtime/NetworkShowHideTests.cs | 41 +- Tests/Runtime/NetworkSpawnManagerTests.cs | 15 +- .../NetworkTransform/NetworkTransformTests.cs | 154 ++-- Tests/Runtime/NetworkVarBufferCopyTest.cs | 106 ++- Tests/Runtime/NetworkVariableTests.cs | 692 ++++++++++------- Tests/Runtime/NetworkVisibilityTests.cs | 55 ++ Tests/Runtime/NetworkVisibilityTests.cs.meta | 11 + .../Runtime/Physics/NetworkRigidbody2DTest.cs | 38 +- Tests/Runtime/Physics/NetworkRigidbodyTest.cs | 59 +- .../Profiling/NetworkVariableNameTests.cs | 1 + Tests/Runtime/RpcManyClientsTests.cs | 153 ++++ Tests/Runtime/RpcManyClientsTests.cs.meta | 11 + Tests/Runtime/RpcQueueTests.cs | 1 + Tests/Runtime/RpcTests.cs | 56 +- .../NetworkBehaviourReferenceTests.cs | 3 +- .../NetworkObjectReferenceTests.cs | 3 +- Tests/{Editor => Runtime}/StartStopTests.cs | 2 +- .../StartStopTests.cs.meta | 0 Tests/Runtime/StopStartRuntimeTests.cs | 13 +- .../Runtime/Timing/NetworkTimeSystemTests.cs | 29 +- .../Runtime/Timing/TimeInitializationTest.cs | 23 +- Tests/Runtime/Timing/TimeMultiInstanceTest.cs | 41 +- Tests/Runtime/TransformInterpolationTests.cs | 116 +++ .../TransformInterpolationTests.cs.meta | 11 + Tests/Runtime/Transport.meta | 2 +- .../Transport/DummyTransport.cs} | 10 +- .../Transport/DummyTransport.cs.meta} | 0 Tests/Runtime/Transport/SIPTransportTests.cs | 3 +- .../com.unity.netcode.runtimetests.asmdef | 37 +- package.json | 9 +- 205 files changed, 6971 insertions(+), 2722 deletions(-) delete mode 100644 Editor/DontShowInTransportDropdownAttribute.cs delete mode 100644 Editor/DontShowInTransportDropdownAttribute.cs.meta delete mode 100644 Runtime/Messaging/Messages/RpcMessage.cs create mode 100644 Runtime/Messaging/Messages/RpcMessages.cs rename Runtime/Messaging/Messages/{RpcMessage.cs.meta => RpcMessages.cs.meta} (100%) create mode 100644 Runtime/SceneManagement/ISceneManagerHandler.cs create mode 100644 Runtime/SceneManagement/ISceneManagerHandler.cs.meta rename Tests/Runtime/Helpers.meta => TestHelpers.meta (77%) rename Tests/Runtime/Metrics/Utility.meta => TestHelpers/Runtime.meta (77%) create mode 100644 TestHelpers/Runtime/AssemblyInfo.cs create mode 100644 TestHelpers/Runtime/AssemblyInfo.cs.meta create mode 100644 TestHelpers/Runtime/Components.meta create mode 100644 TestHelpers/Runtime/Components/ObjectNameIdentifier.cs create mode 100644 TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta create mode 100644 TestHelpers/Runtime/ConditionalPredicate.cs create mode 100644 TestHelpers/Runtime/ConditionalPredicate.cs.meta create mode 100644 TestHelpers/Runtime/IntegrationTestSceneHandler.cs create mode 100644 TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta create mode 100644 TestHelpers/Runtime/Metrics.meta rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/MetricTestBase.cs (57%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/MetricTestBase.cs.meta (100%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/NetworkVariableComponent.cs (88%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/NetworkVariableComponent.cs.meta (100%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/RpcTestComponent.cs (88%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/RpcTestComponent.cs.meta (100%) create mode 100644 TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs create mode 100644 TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs.meta create mode 100644 TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs create mode 100644 TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs.meta create mode 100644 TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs create mode 100644 TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs.meta rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/WaitForMetricValues.cs (51%) rename {Tests/Runtime/Metrics/Utility => TestHelpers/Runtime/Metrics}/WaitForMetricValues.cs.meta (100%) create mode 100644 TestHelpers/Runtime/NetcodeIntegrationTest.cs rename Tests/Runtime/BaseMultiInstanceTest.cs.meta => TestHelpers/Runtime/NetcodeIntegrationTest.cs.meta (100%) rename Tests/Runtime/MultiInstanceHelpers.cs => TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs (50%) rename Tests/Runtime/MultiInstanceHelpers.cs.meta => TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs.meta (100%) rename {Tests/Runtime/Helpers => TestHelpers/Runtime}/NetworkManagerHelper.cs (92%) rename {Tests/Runtime/Helpers => TestHelpers/Runtime}/NetworkManagerHelper.cs.meta (100%) rename {Tests/Runtime/Helpers => TestHelpers/Runtime}/NetworkVariableHelper.cs (96%) rename {Tests/Runtime/Helpers => TestHelpers/Runtime}/NetworkVariableHelper.cs.meta (100%) create mode 100644 TestHelpers/Runtime/TimeoutHelper.cs create mode 100644 TestHelpers/Runtime/TimeoutHelper.cs.meta create mode 100644 TestHelpers/Runtime/Transport.meta create mode 100644 TestHelpers/Runtime/Transport/MessageHooks.cs create mode 100644 TestHelpers/Runtime/Transport/MessageHooks.cs.meta create mode 100644 TestHelpers/Runtime/Transport/MessageHooksConditional.cs create mode 100644 TestHelpers/Runtime/Transport/MessageHooksConditional.cs.meta rename {Tests => TestHelpers}/Runtime/Transport/SIPTransport.cs (97%) rename {Tests => TestHelpers}/Runtime/Transport/SIPTransport.cs.meta (100%) create mode 100644 TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef create mode 100644 TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef.meta delete mode 100644 Tests/Runtime/BaseMultiInstanceTest.cs create mode 100644 Tests/Runtime/Components/NetworkVisibilityComponent.cs create mode 100644 Tests/Runtime/Components/NetworkVisibilityComponent.cs.meta create mode 100644 Tests/Runtime/IntegrationTestExamples.cs create mode 100644 Tests/Runtime/IntegrationTestExamples.cs.meta create mode 100644 Tests/Runtime/Metrics/PacketMetricsTests.cs create mode 100644 Tests/Runtime/Metrics/PacketMetricsTests.cs.meta create mode 100644 Tests/Runtime/Metrics/RttMetricsTests.cs create mode 100644 Tests/Runtime/Metrics/RttMetricsTests.cs.meta create mode 100644 Tests/Runtime/NetworkAnimator.meta create mode 100644 Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs create mode 100644 Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim create mode 100644 Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim create mode 100644 Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim create mode 100644 Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim create mode 100644 Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim create mode 100644 Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller create mode 100644 Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta create mode 100644 Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController create mode 100644 Tests/Runtime/NetworkBehaviourGenericTests.cs create mode 100644 Tests/Runtime/NetworkBehaviourGenericTests.cs.meta rename Tests/{Editor => Runtime}/NetworkManagerCustomMessageManagerTests.cs (92%) rename Tests/{Editor => Runtime}/NetworkManagerCustomMessageManagerTests.cs.meta (100%) rename Tests/{Editor => Runtime}/NetworkManagerSceneManagerTests.cs (96%) rename Tests/{Editor => Runtime}/NetworkManagerSceneManagerTests.cs.meta (100%) create mode 100644 Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs create mode 100644 Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs.meta create mode 100644 Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs create mode 100644 Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs.meta create mode 100644 Tests/Runtime/NetworkVisibilityTests.cs create mode 100644 Tests/Runtime/NetworkVisibilityTests.cs.meta create mode 100644 Tests/Runtime/RpcManyClientsTests.cs create mode 100644 Tests/Runtime/RpcManyClientsTests.cs.meta rename Tests/{Editor => Runtime}/StartStopTests.cs (98%) rename Tests/{Editor => Runtime}/StartStopTests.cs.meta (100%) create mode 100644 Tests/Runtime/TransformInterpolationTests.cs create mode 100644 Tests/Runtime/TransformInterpolationTests.cs.meta rename Tests/{Editor/NetworkManagerMessageHandlerTests.cs => Runtime/Transport/DummyTransport.cs} (77%) rename Tests/{Editor/NetworkManagerMessageHandlerTests.cs.meta => Runtime/Transport/DummyTransport.cs.meta} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e1835c..7a3f781 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [1.0.0-pre.6] - 2022-03-02 + +### Added +- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765) +- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735) + +### Changed + +### Fixed +- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683) +- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683) +- Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683) +- Disallowed async keyword in RPCs (#1681) +- Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678) +- Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen) +- Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694) +- Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685) +- Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720) +- Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680) +- Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682) +- Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725) +- Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721) +- Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724) +- Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728) +- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731) +- Improved performance in NetworkAnimator (#1735) +- Removed the "always sync" network animator (aka "autosend") parameters (#1746) + ## [1.0.0-pre.5] - 2022-01-26 ### Added @@ -15,12 +43,15 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Changed - NetworkManager's GameObject is no longer allowed to be nested under one or more GameObject(s).(#1484) -- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484) +- NetworkManager DontDestroy property was removed and now NetworkManager always is migrated into the DontDestroyOnLoad scene. (#1484)' ### Fixed - Fixed network tick value sometimes being duplicated or skipped. (#1614) - Fixed The ClientNetworkTransform sample script to allow for owner changes at runtime. (#1606) +- Fixed When the LogLevel is set to developer NetworkBehaviour generates warning messages when it should not (#1631) +- Fixed NetworkTransport Initialize now can receive the associated NetworkManager instance to avoid using NetworkManager.Singleton in transport layer (#1677) +- Fixed a bug where NetworkList.Contains value was inverted (#1363) ## [1.0.0-pre.4] - 2021-01-04 diff --git a/Components/Interpolator/BufferedLinearInterpolator.cs b/Components/Interpolator/BufferedLinearInterpolator.cs index d0e9026..9b8c9ea 100644 --- a/Components/Interpolator/BufferedLinearInterpolator.cs +++ b/Components/Interpolator/BufferedLinearInterpolator.cs @@ -69,6 +69,13 @@ namespace Unity.Netcode private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0; + public void Clear() + { + m_Buffer.Clear(); + m_EndTimeConsumed = 0.0d; + m_StartTimeConsumed = 0.0d; + } + public void ResetTo(T targetValue, double serverTime) { m_LifetimeConsumedCount = 1; diff --git a/Components/NetworkAnimator.cs b/Components/NetworkAnimator.cs index a30e3a0..8ab739a 100644 --- a/Components/NetworkAnimator.cs +++ b/Components/NetworkAnimator.cs @@ -1,6 +1,5 @@ using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; - using UnityEngine; namespace Unity.Netcode.Components @@ -14,24 +13,19 @@ namespace Unity.Netcode.Components { internal struct AnimationMessage : INetworkSerializable { - public int StateHash; // if non-zero, then Play() this animation, skipping transitions + // state hash per layer. if non-zero, then Play() this animation, skipping transitions + public int StateHash; public float NormalizedTime; + public int Layer; + public float Weight; public byte[] Parameters; public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter { serializer.SerializeValue(ref StateHash); serializer.SerializeValue(ref NormalizedTime); - serializer.SerializeValue(ref Parameters); - } - } - - internal struct AnimationParametersMessage : INetworkSerializable - { - public byte[] Parameters; - - public void NetworkSerialize(BufferSerializer serializer) where T : IReaderWriter - { + serializer.SerializeValue(ref Layer); + serializer.SerializeValue(ref Weight); serializer.SerializeValue(ref Parameters); } } @@ -49,8 +43,6 @@ namespace Unity.Netcode.Components } [SerializeField] private Animator m_Animator; - [SerializeField] private uint m_ParameterSendBits; - [SerializeField] private float m_SendRate = 0.1f; public Animator Animator { @@ -58,43 +50,17 @@ namespace Unity.Netcode.Components set { m_Animator = value; - ResetParameterOptions(); } } - /* - * AutoSend is the ability to select which parameters linked to this animator - * get replicated on a regular basis regardless of a state change. The thinking - * behind this is that many of the parameters people use are usually booleans - * which result in a state change and thus would cause a full sync of state. - * Thus if you really care about a parameter syncing then you need to be explict - * by selecting it in the inspector when an NetworkAnimator is selected. - */ - public void SetParameterAutoSend(int index, bool value) - { - if (value) - { - m_ParameterSendBits |= (uint)(1 << index); - } - else - { - m_ParameterSendBits &= (uint)(~(1 << index)); - } - } - - public bool GetParameterAutoSend(int index) - { - return (m_ParameterSendBits & (uint)(1 << index)) != 0; - } + private bool m_SendMessagesAllowed = false; // Animators only support up to 32 params public static int K_MaxAnimationParams = 32; - private int m_TransitionHash; - private double m_NextSendTime = 0.0f; - - private int m_AnimationHash; - public int AnimationHash { get => m_AnimationHash; } + private int[] m_TransitionHash; + private int[] m_AnimationHash; + private float[] m_LayerWeights; private unsafe struct AnimatorParamCache { @@ -103,11 +69,11 @@ namespace Unity.Netcode.Components public fixed byte Value[4]; // this is a max size of 4 bytes } - // 128bytes per Animator + // 128 bytes per Animator private FastBufferWriter m_ParameterWriter = new FastBufferWriter(K_MaxAnimationParams * sizeof(float), Allocator.Persistent); private NativeArray m_CachedAnimatorParameters; - // We cache these values because UnsafeUtility.EnumToInt use direct IL that allows a nonboxing conversion + // We cache these values because UnsafeUtility.EnumToInt uses direct IL that allows a non-boxing conversion private struct AnimationParamEnumWrapper { public static readonly int AnimatorControllerParameterInt; @@ -122,25 +88,6 @@ namespace Unity.Netcode.Components } } - internal void ResetParameterOptions() - { - - if (NetworkLog.CurrentLogLevel <= LogLevel.Developer) - { - NetworkLog.LogInfoServer("ResetParameterOptions"); - } - - m_ParameterSendBits = 0; - } - - private bool sendMessagesAllowed - { - get - { - return IsServer && NetworkObject.IsSpawned; - } - } - public override void OnDestroy() { if (m_CachedAnimatorParameters.IsCreated) @@ -153,25 +100,36 @@ namespace Unity.Netcode.Components public override void OnNetworkSpawn() { + if (IsServer) + { + m_SendMessagesAllowed = true; + int layers = m_Animator.layerCount; + + m_TransitionHash = new int[layers]; + m_AnimationHash = new int[layers]; + m_LayerWeights = new float[layers]; + } + var parameters = m_Animator.parameters; m_CachedAnimatorParameters = new NativeArray(parameters.Length, Allocator.Persistent); - m_AnimationHash = -1; - for (var i = 0; i < parameters.Length; i++) { var parameter = parameters[i]; if (m_Animator.IsParameterControlledByCurve(parameter.nameHash)) { - //we are ignoring parameters that are controlled by animation curves - syncing the layer states indirectly syncs the values that are driven by the animation curves + // we are ignoring parameters that are controlled by animation curves - syncing the layer + // states indirectly syncs the values that are driven by the animation curves continue; } - var cacheParam = new AnimatorParamCache(); + var cacheParam = new AnimatorParamCache + { + Type = UnsafeUtility.EnumToInt(parameter.type), + Hash = parameter.nameHash + }; - cacheParam.Type = UnsafeUtility.EnumToInt(parameter.type); - cacheParam.Hash = parameter.nameHash; unsafe { switch (parameter.type) @@ -199,115 +157,100 @@ namespace Unity.Netcode.Components } } + public override void OnNetworkDespawn() + { + m_SendMessagesAllowed = false; + } + private void FixedUpdate() { - if (!sendMessagesAllowed) + if (!m_SendMessagesAllowed || !m_Animator || !m_Animator.enabled) { return; } - int stateHash; - float normalizedTime; - if (!CheckAnimStateChanged(out stateHash, out normalizedTime)) + for (int layer = 0; layer < m_Animator.layerCount; layer++) { - // We only want to check and send if we don't have any other state to since - // as we will sync all params as part of the state sync - CheckAndSend(); - - return; - } - - var animMsg = new AnimationMessage(); - animMsg.StateHash = stateHash; - animMsg.NormalizedTime = normalizedTime; - - m_ParameterWriter.Seek(0); - m_ParameterWriter.Truncate(); - - WriteParameters(m_ParameterWriter, false); - animMsg.Parameters = m_ParameterWriter.ToArray(); - - SendAnimStateClientRpc(animMsg); - } - - private void CheckAndSend() - { - var networkTime = NetworkManager.ServerTime.Time; - if (sendMessagesAllowed && m_SendRate != 0 && m_NextSendTime < networkTime) - { - m_NextSendTime = networkTime + m_SendRate; - - m_ParameterWriter.Seek(0); - m_ParameterWriter.Truncate(); - - if (WriteParameters(m_ParameterWriter, true)) - { - // we then sync the params we care about - var animMsg = new AnimationParametersMessage() - { - Parameters = m_ParameterWriter.ToArray() - }; - - SendParamsClientRpc(animMsg); - } - } - } - - private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime) - { - stateHash = 0; - normalizedTime = 0; - - if (m_Animator.IsInTransition(0)) - { - AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(0); - if (tt.fullPathHash != m_TransitionHash) - { - // first time in this transition - m_TransitionHash = tt.fullPathHash; - m_AnimationHash = 0; - return true; - } - return false; - } - - AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(0); - if (st.fullPathHash != m_AnimationHash) - { - // first time in this animation state - if (m_AnimationHash != 0) - { - // came from another animation directly - from Play() - stateHash = st.fullPathHash; - normalizedTime = st.normalizedTime; - } - m_TransitionHash = 0; - m_AnimationHash = st.fullPathHash; - return true; - } - return false; - } - - /* $AS TODO: Right now we are not checking for changed values this is because - the read side of this function doesn't have similar logic which would cause - an overflow read because it doesn't know if the value is there or not. So - there needs to be logic to track which indexes changed in order for there - to be proper value change checking. Will revist in 1.1.0. - */ - private unsafe bool WriteParameters(FastBufferWriter writer, bool autoSend) - { - if (m_CachedAnimatorParameters == null) - { - return false; - } - - for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) - { - if (autoSend && !GetParameterAutoSend(i)) + int stateHash; + float normalizedTime; + if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer)) { continue; } + var animMsg = new AnimationMessage + { + StateHash = stateHash, + NormalizedTime = normalizedTime, + Layer = layer, + Weight = m_LayerWeights[layer] + }; + + m_ParameterWriter.Seek(0); + m_ParameterWriter.Truncate(); + + WriteParameters(m_ParameterWriter); + animMsg.Parameters = m_ParameterWriter.ToArray(); + + SendAnimStateClientRpc(animMsg); + } + } + + private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer) + { + bool shouldUpdate = false; + stateHash = 0; + normalizedTime = 0; + + float layerWeightNow = m_Animator.GetLayerWeight(layer); + + if (!Mathf.Approximately(layerWeightNow, m_LayerWeights[layer])) + { + m_LayerWeights[layer] = layerWeightNow; + shouldUpdate = true; + } + if (m_Animator.IsInTransition(layer)) + { + AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer); + if (tt.fullPathHash != m_TransitionHash[layer]) + { + // first time in this transition for this layer + m_TransitionHash[layer] = tt.fullPathHash; + m_AnimationHash[layer] = 0; + shouldUpdate = true; + } + } + else + { + AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer); + if (st.fullPathHash != m_AnimationHash[layer]) + { + // first time in this animation state + if (m_AnimationHash[layer] != 0) + { + // came from another animation directly - from Play() + stateHash = st.fullPathHash; + normalizedTime = st.normalizedTime; + } + m_TransitionHash[layer] = 0; + m_AnimationHash[layer] = st.fullPathHash; + shouldUpdate = true; + } + } + + return shouldUpdate; + } + + /* $AS TODO: Right now we are not checking for changed values this is because + the read side of this function doesn't have similar logic which would cause + an overflow read because it doesn't know if the value is there or not. So + there needs to be logic to track which indexes changed in order for there + to be proper value change checking. Will revist in 1.1.0. + */ + private unsafe void WriteParameters(FastBufferWriter writer) + { + for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) + { ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), i); var hash = cacheValue.Hash; @@ -340,24 +283,12 @@ namespace Unity.Netcode.Components } } } - - // If we do not write any values to the writer then we should not send any data - return writer.Length > 0; } - private unsafe void ReadParameters(FastBufferReader reader, bool autoSend) + private unsafe void ReadParameters(FastBufferReader reader) { - if (m_CachedAnimatorParameters == null) - { - return; - } - for (int i = 0; i < m_CachedAnimatorParameters.Length; i++) { - if (autoSend && !GetParameterAutoSend(i)) - { - continue; - } ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef(m_CachedAnimatorParameters.GetUnsafePtr(), i); var hash = cacheValue.Hash; @@ -391,28 +322,20 @@ namespace Unity.Netcode.Components } } - [ClientRpc] - private unsafe void SendParamsClientRpc(AnimationParametersMessage animSnapshot, ClientRpcParams clientRpcParams = default) - { - if (animSnapshot.Parameters != null) - { - // We use a fixed value here to avoid the copy of data from the byte buffer since we own the data - fixed (byte* parameters = animSnapshot.Parameters) - { - var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length); - ReadParameters(reader, true); - } - } - } - + /// + /// Internally-called RPC client receiving function to update some animation parameters on a client when + /// the server wants to update them + /// + /// the payload containing the parameters to apply + /// unused [ClientRpc] private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default) { if (animSnapshot.StateHash != 0) { - m_AnimationHash = animSnapshot.StateHash; - m_Animator.Play(animSnapshot.StateHash, 0, animSnapshot.NormalizedTime); + m_Animator.Play(animSnapshot.StateHash, animSnapshot.Layer, animSnapshot.NormalizedTime); } + m_Animator.SetLayerWeight(animSnapshot.Layer, animSnapshot.Weight); if (animSnapshot.Parameters != null && animSnapshot.Parameters.Length != 0) { @@ -420,11 +343,17 @@ namespace Unity.Netcode.Components fixed (byte* parameters = animSnapshot.Parameters) { var reader = new FastBufferReader(parameters, Allocator.None, animSnapshot.Parameters.Length); - ReadParameters(reader, false); + ReadParameters(reader); } } } + /// + /// Internally-called RPC client receiving function to update a trigger when the server wants to forward + /// a trigger for a client to play / reset + /// + /// the payload containing the trigger data to apply + /// unused [ClientRpc] private void SendAnimTriggerClientRpc(AnimationTriggerMessage animSnapshot, ClientRpcParams clientRpcParams = default) { @@ -438,11 +367,24 @@ namespace Unity.Netcode.Components } } + /// + /// Sets the trigger for the associated animation + /// Note, triggers are special vs other kinds of parameters. For all the other parameters we watch for changes + /// in FixedUpdate and users can just set them normally off of Animator. But because triggers are transitory + /// and likely to come and go between FixedUpdate calls, we require users to set them here to guarantee us to + /// catch it...then we forward it to the Animator component + /// + /// The string name of the trigger to activate public void SetTrigger(string triggerName) { SetTrigger(Animator.StringToHash(triggerName)); } + /// + /// Sets the trigger for the associated animation. See note for SetTrigger(string) + /// + /// The hash for the trigger to activate + /// If true, resets the trigger public void SetTrigger(int hash, bool reset = false) { var animMsg = new AnimationTriggerMessage(); @@ -451,15 +393,38 @@ namespace Unity.Netcode.Components if (IsServer) { + // trigger the animation locally on the server... + if (reset) + { + m_Animator.ResetTrigger(hash); + } + else + { + m_Animator.SetTrigger(hash); + } + + // ...then tell all the clients to do the same SendAnimTriggerClientRpc(animMsg); } + else + { + Debug.LogWarning("Trying to call NetworkAnimator.SetTrigger on a client...ignoring"); + } } + /// + /// Resets the trigger for the associated animation. See note for SetTrigger(string) + /// + /// The string name of the trigger to reset public void ResetTrigger(string triggerName) { ResetTrigger(Animator.StringToHash(triggerName)); } + /// + /// Resets the trigger for the associated animation. See note for SetTrigger(string) + /// + /// The hash for the trigger to reset public void ResetTrigger(int hash) { SetTrigger(hash, true); diff --git a/Components/NetworkTransform.cs b/Components/NetworkTransform.cs index e7a4e79..d065835 100644 --- a/Components/NetworkTransform.cs +++ b/Components/NetworkTransform.cs @@ -260,8 +260,10 @@ namespace Unity.Netcode.Components /// [Tooltip("Sets whether this transform should sync in local space or in world space")] public bool InLocalSpace = false; + private bool m_LastInterpolateLocal = false; // was the last frame local public bool Interpolate = true; + private bool m_LastInterpolate = true; // was the last frame interpolated /// /// Used to determine who can write to this transform. Server only for this transform. @@ -367,7 +369,11 @@ namespace Unity.Netcode.Components private void CommitLocallyAndReplicate(NetworkTransformState networkState) { m_ReplicatedNetworkState.Value = networkState; - AddInterpolatedState(networkState); + + if (Interpolate) + { + AddInterpolatedState(networkState); + } } private void ResetInterpolatedStateToCurrentAuthoritativeState() @@ -532,7 +538,12 @@ namespace Unity.Netcode.Components // again, we should be using quats here if (SyncRotAngleX || SyncRotAngleY || SyncRotAngleZ) { - var eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; + var eulerAngles = new Vector3(); + if (Interpolate) + { + eulerAngles = m_RotationInterpolator.GetInterpolatedValue().eulerAngles; + } + if (SyncRotAngleX) { interpolatedRotAngles.x = networkState.IsTeleportingNextFrame || !Interpolate ? networkState.Rotation.x : eulerAngles.x; @@ -603,10 +614,46 @@ namespace Unity.Netcode.Components } } - private void AddInterpolatedState(NetworkTransformState newState) + private void AddInterpolatedState(NetworkTransformState newState, bool reset = false) { var sentTime = newState.SentTime; + if (reset) + { + if (newState.HasPositionX) + { + m_PositionXInterpolator.ResetTo(newState.PositionX, sentTime); + } + + if (newState.HasPositionY) + { + m_PositionYInterpolator.ResetTo(newState.PositionY, sentTime); + } + + if (newState.HasPositionZ) + { + m_PositionZInterpolator.ResetTo(newState.PositionZ, sentTime); + } + + m_RotationInterpolator.ResetTo(Quaternion.Euler(newState.Rotation), sentTime); + + if (newState.HasScaleX) + { + m_ScaleXInterpolator.ResetTo(newState.ScaleX, sentTime); + } + + if (newState.HasScaleY) + { + m_ScaleYInterpolator.ResetTo(newState.ScaleY, sentTime); + } + + if (newState.HasScaleZ) + { + m_ScaleZInterpolator.ResetTo(newState.ScaleZ, sentTime); + } + + return; + } if (newState.HasPositionX) { m_PositionXInterpolator.AddMeasurement(newState.PositionX, sentTime); @@ -656,7 +703,11 @@ namespace Unity.Netcode.Components Debug.DrawLine(newState.Position, newState.Position + Vector3.up + Vector3.left, Color.green, 10, false); - AddInterpolatedState(newState); + if (Interpolate) + { + AddInterpolatedState(newState, (newState.InLocalSpace != m_LastInterpolateLocal)); + } + m_LastInterpolateLocal = newState.InLocalSpace; if (m_CachedNetworkManager.LogLevel == LogLevel.Developer) { @@ -812,6 +863,17 @@ namespace Unity.Netcode.Components return; } + if (!Interpolate && m_LastInterpolate) + { + // if we just stopped interpolating, let's clear the interpolators + foreach (var interpolator in m_AllFloatInterpolators) + { + interpolator.Clear(); + } + } + + m_LastInterpolate = Interpolate; + if (CanCommitToTransform) { if (m_CachedIsServer) @@ -831,12 +893,15 @@ namespace Unity.Netcode.Components var cachedServerTime = serverTime.Time; var cachedRenderTime = serverTime.TimeTicksAgo(1).Time; - foreach (var interpolator in m_AllFloatInterpolators) + if (Interpolate) { - interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); - } + foreach (var interpolator in m_AllFloatInterpolators) + { + interpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + } - m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + m_RotationInterpolator.Update(cachedDeltaTime, cachedRenderTime, cachedServerTime); + } if (!CanCommitToTransform) { diff --git a/Components/com.unity.netcode.components.asmdef b/Components/com.unity.netcode.components.asmdef index 6c16972..3f4c4b4 100644 --- a/Components/com.unity.netcode.components.asmdef +++ b/Components/com.unity.netcode.components.asmdef @@ -5,13 +5,5 @@ "Unity.Netcode.Runtime", "Unity.Collections" ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], - "versionDefines": [], - "noEngineReferences": false + "allowUnsafeCode": true } \ No newline at end of file diff --git a/Editor/CodeGen/CodeGenHelpers.cs b/Editor/CodeGen/CodeGenHelpers.cs index 6e5e1ff..af66327 100644 --- a/Editor/CodeGen/CodeGenHelpers.cs +++ b/Editor/CodeGen/CodeGenHelpers.cs @@ -22,6 +22,10 @@ namespace Unity.Netcode.Editor.CodeGen public static readonly string ClientRpcAttribute_FullName = typeof(ClientRpcAttribute).FullName; public static readonly string ServerRpcParams_FullName = typeof(ServerRpcParams).FullName; public static readonly string ClientRpcParams_FullName = typeof(ClientRpcParams).FullName; + public static readonly string ClientRpcSendParams_FullName = typeof(ClientRpcSendParams).FullName; + public static readonly string ClientRpcReceiveParams_FullName = typeof(ClientRpcReceiveParams).FullName; + public static readonly string ServerRpcSendParams_FullName = typeof(ServerRpcSendParams).FullName; + public static readonly string ServerRpcReceiveParams_FullName = typeof(ServerRpcReceiveParams).FullName; public static readonly string INetworkSerializable_FullName = typeof(INetworkSerializable).FullName; public static readonly string UnityColor_FullName = typeof(Color).FullName; public static readonly string UnityColor32_FullName = typeof(Color32).FullName; diff --git a/Editor/CodeGen/INetworkMessageILPP.cs b/Editor/CodeGen/INetworkMessageILPP.cs index 336c1d2..a78a7e6 100644 --- a/Editor/CodeGen/INetworkMessageILPP.cs +++ b/Editor/CodeGen/INetworkMessageILPP.cs @@ -13,7 +13,6 @@ using MethodAttributes = Mono.Cecil.MethodAttributes; namespace Unity.Netcode.Editor.CodeGen { - internal sealed class INetworkMessageILPP : ILPPInterface { public override ILPPInterface GetInstance() => this; @@ -31,7 +30,6 @@ namespace Unity.Netcode.Editor.CodeGen return null; } - m_Diagnostics.Clear(); // read @@ -95,27 +93,23 @@ namespace Unity.Netcode.Editor.CodeGen } - private TypeReference m_FastBufferReader_TypeRef; - private TypeReference m_NetworkContext_TypeRef; + private MethodReference m_MessagingSystem_ReceiveMessage_MethodRef; private TypeReference m_MessagingSystem_MessageWithHandler_TypeRef; private MethodReference m_MessagingSystem_MessageHandler_Constructor_TypeRef; private FieldReference m_ILPPMessageProvider___network_message_types_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_MessageType_FieldRef; private FieldReference m_MessagingSystem_MessageWithHandler_Handler_FieldRef; private MethodReference m_Type_GetTypeFromHandle_MethodRef; - private MethodReference m_List_Add_MethodRef; + private const string k_ReceiveMessageName = nameof(MessagingSystem.ReceiveMessage); + private bool ImportReferences(ModuleDefinition moduleDefinition) { - m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(typeof(FastBufferReader)); - m_NetworkContext_TypeRef = moduleDefinition.ImportReference(typeof(NetworkContext)); - m_MessagingSystem_MessageHandler_Constructor_TypeRef = - moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]); + m_MessagingSystem_MessageHandler_Constructor_TypeRef = moduleDefinition.ImportReference(typeof(MessagingSystem.MessageHandler).GetConstructors()[0]); var messageWithHandlerType = typeof(MessagingSystem.MessageWithHandler); - m_MessagingSystem_MessageWithHandler_TypeRef = - moduleDefinition.ImportReference(messageWithHandlerType); + m_MessagingSystem_MessageWithHandler_TypeRef = moduleDefinition.ImportReference(messageWithHandlerType); foreach (var fieldInfo in messageWithHandlerType.GetFields()) { switch (fieldInfo.Name) @@ -162,38 +156,18 @@ namespace Unity.Netcode.Editor.CodeGen } } - - return true; - } - - private MethodReference GetNetworkMessageRecieveHandler(TypeDefinition typeDefinition) - { - SequencePoint typeSequence = null; - foreach (var method in typeDefinition.Methods) + var messagingSystemType = typeof(MessagingSystem); + foreach (var methodInfo in messagingSystemType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)) { - var resolved = method.Resolve(); - var methodSequence = resolved.DebugInformation.SequencePoints.FirstOrDefault(); - if (typeSequence == null || methodSequence.StartLine < typeSequence.StartLine) + switch (methodInfo.Name) { - typeSequence = methodSequence; - } - - if (resolved.IsStatic && resolved.IsPublic && resolved.Name == "Receive" && resolved.Parameters.Count == 2 - && !resolved.Parameters[0].IsIn - && !resolved.Parameters[0].ParameterType.IsByReference - && resolved.Parameters[0].ParameterType.Resolve() == - m_FastBufferReader_TypeRef.Resolve() - && resolved.Parameters[1].IsIn - && resolved.Parameters[1].ParameterType.IsByReference - && resolved.Parameters[1].ParameterType.GetElementType().Resolve() == m_NetworkContext_TypeRef.Resolve() - && resolved.ReturnType == resolved.Module.TypeSystem.Void) - { - return method; + case k_ReceiveMessageName: + m_MessagingSystem_ReceiveMessage_MethodRef = moduleDefinition.ImportReference(methodInfo); + break; } } - m_Diagnostics.AddError(typeSequence, $"Class {typeDefinition.FullName} does not implement required method: `public static void Receive(FastBufferReader, in NetworkContext)`"); - return null; + return true; } private MethodDefinition GetOrCreateStaticConstructor(TypeDefinition typeDefinition) @@ -264,11 +238,8 @@ namespace Unity.Netcode.Editor.CodeGen foreach (var type in networkMessageTypes) { - var receiveMethod = GetNetworkMessageRecieveHandler(type); - if (receiveMethod == null) - { - continue; - } + var receiveMethod = new GenericInstanceMethod(m_MessagingSystem_ReceiveMessage_MethodRef); + receiveMethod.GenericArguments.Add(type); CreateInstructionsToRegisterType(processor, instructions, type, receiveMethod); } diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs index f6de9f1..0162d5b 100644 --- a/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -7,7 +7,6 @@ using System.Runtime.CompilerServices; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Cecil.Rocks; -using Unity.Collections; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; using UnityEngine; @@ -17,7 +16,6 @@ using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostPr namespace Unity.Netcode.Editor.CodeGen { - internal sealed class NetworkBehaviourILPP : ILPPInterface { private const string k_ReadValueMethodName = nameof(FastBufferReader.ReadValueSafe); @@ -25,7 +23,8 @@ namespace Unity.Netcode.Editor.CodeGen public override ILPPInterface GetInstance() => this; - public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); + public override bool WillProcess(ICompiledAssembly compiledAssembly) => + compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName); private readonly List m_Diagnostics = new List(); @@ -109,8 +108,10 @@ namespace Unity.Netcode.Editor.CodeGen private FieldReference m_NetworkManager_rpc_name_table_FieldRef; private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef; private TypeReference m_NetworkBehaviour_TypeRef; - private MethodReference m_NetworkBehaviour_SendServerRpc_MethodRef; - private MethodReference m_NetworkBehaviour_SendClientRpc_MethodRef; + private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef; + private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef; + private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef; + private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef; private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef; private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef; private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef; @@ -124,8 +125,6 @@ namespace Unity.Netcode.Editor.CodeGen private TypeReference m_ClientRpcParams_TypeRef; private TypeReference m_FastBufferWriter_TypeRef; - private MethodReference m_FastBufferWriter_Constructor; - private MethodReference m_FastBufferWriter_Dispose; private Dictionary m_FastBufferWriter_WriteValue_MethodRefs = new Dictionary(); private List m_FastBufferWriter_ExtensionMethodRefs = new List(); @@ -144,8 +143,10 @@ namespace Unity.Netcode.Editor.CodeGen private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table); private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage); - private const string k_NetworkBehaviour_SendServerRpc = nameof(NetworkBehaviour.__sendServerRpc); - private const string k_NetworkBehaviour_SendClientRpc = nameof(NetworkBehaviour.__sendClientRpc); + private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc); + private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc); + private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc); + private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc); private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager); private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId); @@ -234,11 +235,17 @@ namespace Unity.Netcode.Editor.CodeGen { switch (methodInfo.Name) { - case k_NetworkBehaviour_SendServerRpc: - m_NetworkBehaviour_SendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + case k_NetworkBehaviour_beginSendServerRpc: + m_NetworkBehaviour_beginSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); break; - case k_NetworkBehaviour_SendClientRpc: - m_NetworkBehaviour_SendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + case k_NetworkBehaviour_endSendServerRpc: + m_NetworkBehaviour_endSendServerRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + break; + case k_NetworkBehaviour_beginSendClientRpc: + m_NetworkBehaviour_beginSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); + break; + case k_NetworkBehaviour_endSendClientRpc: + m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodInfo); break; } } @@ -299,17 +306,12 @@ namespace Unity.Netcode.Editor.CodeGen var fastBufferWriterType = typeof(FastBufferWriter); m_FastBufferWriter_TypeRef = moduleDefinition.ImportReference(fastBufferWriterType); - m_FastBufferWriter_Constructor = moduleDefinition.ImportReference( - fastBufferWriterType.GetConstructor(new[] { typeof(int), typeof(Allocator), typeof(int) })); - m_FastBufferWriter_Dispose = moduleDefinition.ImportReference(fastBufferWriterType.GetMethod("Dispose")); - var fastBufferReaderType = typeof(FastBufferReader); m_FastBufferReader_TypeRef = moduleDefinition.ImportReference(fastBufferReaderType); // Find all extension methods for FastBufferReader and FastBufferWriter to enable user-implemented // methods to be called. - var assemblies = new List(); - assemblies.Add(m_MainModule.Assembly); + var assemblies = new List { m_MainModule.Assembly }; foreach (var reference in m_MainModule.AssemblyReferences) { var assembly = m_AssemblyResolver.Resolve(reference); @@ -319,8 +321,7 @@ namespace Unity.Netcode.Editor.CodeGen } } - var extensionConstructor = - moduleDefinition.ImportReference(typeof(ExtensionAttribute).GetConstructor(new Type[] { })); + var extensionConstructor = moduleDefinition.ImportReference(typeof(ExtensionAttribute).GetConstructor(new Type[] { })); foreach (var assembly in assemblies) { foreach (var module in assembly.Modules) @@ -332,6 +333,7 @@ namespace Unity.Netcode.Editor.CodeGen { continue; } + foreach (var method in type.Methods) { if (!method.IsStatic) @@ -356,13 +358,11 @@ namespace Unity.Netcode.Editor.CodeGen var parameters = method.Parameters; - if (parameters.Count == 2 - && parameters[0].ParameterType.Resolve() == m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve()) + if (parameters.Count == 2 && parameters[0].ParameterType.Resolve() == m_FastBufferWriter_TypeRef.MakeByReferenceType().Resolve()) { m_FastBufferWriter_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method)); } - else if (parameters.Count == 2 - && parameters[0].ParameterType.Resolve() == m_FastBufferReader_TypeRef.MakeByReferenceType().Resolve()) + else if (parameters.Count == 2 && parameters[0].ParameterType.Resolve() == m_FastBufferReader_TypeRef.MakeByReferenceType().Resolve()) { m_FastBufferReader_ExtensionMethodRefs.Add(m_MainModule.ImportReference(method)); } @@ -395,9 +395,20 @@ namespace Unity.Netcode.Editor.CodeGen continue; } + if (methodDefinition.HasCustomAttributes) + { + foreach (var attribute in methodDefinition.CustomAttributes) + { + if (attribute.AttributeType.Name == nameof(AsyncStateMachineAttribute)) + { + m_Diagnostics.AddError(methodDefinition, $"{methodDefinition.FullName}: RPCs cannot be 'async'"); + } + } + } + InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId); - rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute))); + rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId))); if (isEditorOrDevelopment) { @@ -477,7 +488,6 @@ namespace Unity.Netcode.Editor.CodeGen private CustomAttribute CheckAndGetRpcAttribute(MethodDefinition methodDefinition) { CustomAttribute rpcAttribute = null; - bool isServerRpc = false; foreach (var customAttribute in methodDefinition.CustomAttributes) { var customAttributeType_FullName = customAttribute.AttributeType.FullName; @@ -521,7 +531,6 @@ namespace Unity.Netcode.Editor.CodeGen if (isValid) { - isServerRpc = customAttributeType_FullName == CodeGenHelpers.ServerRpcAttribute_FullName; rpcAttribute = customAttribute; } } @@ -569,12 +578,12 @@ namespace Unity.Netcode.Editor.CodeGen checkType = paramType.GetElementType().Resolve(); } - if ( - (parameters[0].ParameterType.Resolve() == checkType - || (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn))) + if ((parameters[0].ParameterType.Resolve() == checkType || + (parameters[0].ParameterType.Resolve() == checkType.MakeByReferenceType().Resolve() && parameters[0].IsIn))) { return method; } + if (method.HasGenericParameters && method.GenericParameters.Count == 1) { if (method.GenericParameters[0].HasConstraints) @@ -584,17 +593,15 @@ namespace Unity.Netcode.Editor.CodeGen { var resolvedConstraint = constraint.Resolve(); - if ( - (resolvedConstraint.IsInterface && - !checkType.HasInterface(resolvedConstraint.FullName)) - || (resolvedConstraint.IsClass && - !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)) - || (resolvedConstraint.Name == "ValueType" && !checkType.IsValueType)) + if ((resolvedConstraint.IsInterface && !checkType.HasInterface(resolvedConstraint.FullName)) || + (resolvedConstraint.IsClass && !checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName)) || + (resolvedConstraint.Name == "ValueType" && !checkType.IsValueType)) { meetsConstraints = false; break; } } + if (meetsConstraints) { var instanceMethod = new GenericInstanceMethod(method); @@ -624,8 +631,8 @@ namespace Unity.Netcode.Editor.CodeGen { if (parameters[1].IsIn) { - if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() - && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) + if (parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && + ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) { methodRef = method; m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef; @@ -635,8 +642,8 @@ namespace Unity.Netcode.Editor.CodeGen else { - if (parameters[1].ParameterType.Resolve() == paramType.Resolve() - && parameters[1].ParameterType.IsArray == paramType.IsArray) + if (parameters[1].ParameterType.Resolve() == paramType.Resolve() && + parameters[1].ParameterType.IsArray == paramType.IsArray) { methodRef = method; m_FastBufferWriter_WriteValue_MethodRefs[assemblyQualifiedName] = methodRef; @@ -707,11 +714,8 @@ namespace Unity.Netcode.Editor.CodeGen { var resolvedConstraint = constraint.Resolve(); - if ( - (resolvedConstraint.IsInterface && - checkType.HasInterface(resolvedConstraint.FullName)) - || (resolvedConstraint.IsClass && - checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))) + if ((resolvedConstraint.IsInterface && checkType.HasInterface(resolvedConstraint.FullName)) || + (resolvedConstraint.IsClass && checkType.Resolve().IsSubclassOf(resolvedConstraint.FullName))) { var instanceMethod = new GenericInstanceMethod(method); instanceMethod.GenericArguments.Add(checkType); @@ -736,11 +740,10 @@ namespace Unity.Netcode.Editor.CodeGen foreach (var method in m_FastBufferReader_ExtensionMethodRefs) { var parameters = method.Resolve().Parameters; - if ( - method.Name == k_ReadValueMethodName - && parameters[1].IsOut - && parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() - && ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) + if (method.Name == k_ReadValueMethodName && + parameters[1].IsOut && + parameters[1].ParameterType.Resolve() == paramType.MakeByReferenceType().Resolve() && + ((ByReferenceType)parameters[1].ParameterType).ElementType.IsArray == paramType.IsArray) { methodRef = method; m_FastBufferReader_ReadValue_MethodRefs[assemblyQualifiedName] = methodRef; @@ -772,8 +775,8 @@ namespace Unity.Netcode.Editor.CodeGen var instructions = new List(); var processor = methodDefinition.Body.GetILProcessor(); var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; - var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` - var rpcDelivery = RpcDelivery.Reliable; // default value MUST be = `RpcAttribute.Delivery` + var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` + var rpcDelivery = RpcDelivery.Reliable; // default value MUST be == `RpcAttribute.Delivery` foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) @@ -797,9 +800,9 @@ namespace Unity.Netcode.Editor.CodeGen // NetworkManager networkManager; methodDefinition.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); int netManLocIdx = methodDefinition.Body.Variables.Count - 1; - // NetworkSerializer serializer; + // FastBufferWriter bufferWriter; methodDefinition.Body.Variables.Add(new VariableDefinition(m_FastBufferWriter_TypeRef)); - int serializerLocIdx = methodDefinition.Body.Variables.Count - 1; + int bufWriterLocIdx = methodDefinition.Body.Variables.Count - 1; // XXXRpcParams if (!hasRpcParams) @@ -854,6 +857,8 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(beginInstr); + // var bufferWriter = __beginSendServerRpc(rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc + // var bufferWriter = __beginSendClientRpc(rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc if (isServerRpc) { // ServerRpc @@ -867,8 +872,7 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_getOwnerClientId_MethodRef)); instructions.Add(processor.Create(OpCodes.Ldloc, netManLocIdx)); - instructions.Add( - processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef)); + instructions.Add(processor.Create(OpCodes.Callvirt, m_NetworkManager_getLocalClientId_MethodRef)); instructions.Add(processor.Create(OpCodes.Ceq)); instructions.Add(processor.Create(OpCodes.Ldc_I4, 0)); instructions.Add(processor.Create(OpCodes.Ceq)); @@ -886,8 +890,7 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(processor.Create(OpCodes.Brfalse, logNextInstr)); // Debug.LogError(...); - instructions.Add(processor.Create(OpCodes.Ldstr, - "Only the owner can invoke a ServerRpc that requires ownership!")); + instructions.Add(processor.Create(OpCodes.Ldstr, "Only the owner can invoke a ServerRpc that requires ownership!")); instructions.Add(processor.Create(OpCodes.Call, m_Debug_LogError_MethodRef)); instructions.Add(logNextInstr); @@ -895,31 +898,86 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(roReturnInstr); instructions.Add(roLastInstr); } + + // var bufferWriter = __beginSendServerRpc(rpcMethodId, serverRpcParams, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); + + // rpcParams + instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); + + // __beginSendServerRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendServerRpc_MethodRef)); + instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx)); } + else + { + // ClientRpc - // var writer = new FastBufferWriter(1285, Allocator.Temp, 63985); - instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, 1300 - sizeof(byte) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))); - instructions.Add(processor.Create(OpCodes.Ldc_I4_2)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, 64000 - sizeof(byte) - sizeof(ulong) - sizeof(uint) - sizeof(ushort))); - instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Constructor)); + // var bufferWriter = __beginSendClientRpc(rpcMethodId, clientRpcParams, rpcDelivery); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); - var firstInstruction = processor.Create(OpCodes.Nop); - instructions.Add(firstInstruction); + // rpcMethodId + instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); + + // rpcParams + instructions.Add(hasRpcParams ? processor.Create(OpCodes.Ldarg, paramCount) : processor.Create(OpCodes.Ldloc, rpcParamsIdx)); + + // rpcDelivery + instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); + + // __beginSendClientRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_beginSendClientRpc_MethodRef)); + instructions.Add(processor.Create(OpCodes.Stloc, bufWriterLocIdx)); + } // write method parameters into stream for (int paramIndex = 0; paramIndex < paramCount; ++paramIndex) { var paramDef = methodDefinition.Parameters[paramIndex]; var paramType = paramDef.ParameterType; - // ServerRpcParams - if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName && isServerRpc && paramIndex == paramCount - 1) + if (paramType.FullName == CodeGenHelpers.ClientRpcSendParams_FullName || + paramType.FullName == CodeGenHelpers.ClientRpcReceiveParams_FullName) { + m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ClientRpcParams)} instead."); + continue; + } + + if (paramType.FullName == CodeGenHelpers.ServerRpcSendParams_FullName || + paramType.FullName == CodeGenHelpers.ServerRpcReceiveParams_FullName) + { + m_Diagnostics.AddError($"Rpcs may not accept {paramType.FullName} as a parameter. Use {nameof(ServerRpcParams)} instead."); + continue; + } + // ServerRpcParams + if (paramType.FullName == CodeGenHelpers.ServerRpcParams_FullName) + { + if (paramIndex != paramCount - 1) + { + m_Diagnostics.AddError(methodDefinition, $"{nameof(ServerRpcParams)} must be the last parameter in a ServerRpc."); + } + if (!isServerRpc) + { + m_Diagnostics.AddError($"ClientRpcs may not accept {nameof(ServerRpcParams)} as a parameter."); + } continue; } // ClientRpcParams - if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName && !isServerRpc && paramIndex == paramCount - 1) + if (paramType.FullName == CodeGenHelpers.ClientRpcParams_FullName) { + if (paramIndex != paramCount - 1) + { + m_Diagnostics.AddError(methodDefinition, $"{nameof(ClientRpcParams)} must be the last parameter in a ClientRpc."); + } + if (isServerRpc) + { + m_Diagnostics.AddError($"ServerRpcs may not accept {nameof(ClientRpcParams)} as a parameter."); + } continue; } @@ -942,8 +1000,8 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(processor.Create(OpCodes.Cgt_Un)); instructions.Add(processor.Create(OpCodes.Stloc, isSetLocalIndex)); - // writer.WriteValueSafe(isSet); - instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + // bufferWriter.WriteValueSafe(isSet); + instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx)); instructions.Add(processor.Create(OpCodes.Ldloca, isSetLocalIndex)); instructions.Add(processor.Create(OpCodes.Call, boolMethodRef)); @@ -956,11 +1014,11 @@ namespace Unity.Netcode.Editor.CodeGen var foundMethodRef = GetWriteMethodForParameter(paramType, out var methodRef); if (foundMethodRef) { - // writer.WriteNetworkSerializable(param) for INetworkSerializable, OR - // writer.WriteNetworkSerializable(param, -1, 0) for INetworkSerializable arrays, OR - // writer.WriteValueSafe(param) for value types, OR - // writer.WriteValueSafe(param, -1, 0) for arrays of value types, OR - // writer.WriteValueSafe(param, false) for strings + // bufferWriter.WriteNetworkSerializable(param) for INetworkSerializable, OR + // bufferWriter.WriteNetworkSerializable(param, -1, 0) for INetworkSerializable arrays, OR + // bufferWriter.WriteValueSafe(param) for value types, OR + // bufferWriter.WriteValueSafe(param, -1, 0) for arrays of value types, OR + // bufferWriter.WriteValueSafe(param, false) for strings var method = methodRef.Resolve(); var checkParameter = method.Parameters[0]; var isExtensionMethod = false; @@ -971,11 +1029,11 @@ namespace Unity.Netcode.Editor.CodeGen } if (!isExtensionMethod || method.Parameters[0].ParameterType.IsByReference) { - instructions.Add(processor.Create(OpCodes.Ldloca, serializerLocIdx)); + instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx)); } else { - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); + instructions.Add(processor.Create(OpCodes.Ldloc, bufWriterLocIdx)); } if (checkParameter.IsIn || checkParameter.IsOut || checkParameter.ParameterType.IsByReference) { @@ -986,16 +1044,14 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(processor.Create(OpCodes.Ldarg, paramIndex + 1)); } // Special handling for WriteValue() on arrays and strings since they have additional arguments. - if (paramType.IsArray - && ((!isExtensionMethod && methodRef.Parameters.Count == 3) - || (isExtensionMethod && methodRef.Parameters.Count == 4))) + if (paramType.IsArray && ((!isExtensionMethod && methodRef.Parameters.Count == 3) || + (isExtensionMethod && methodRef.Parameters.Count == 4))) { instructions.Add(processor.Create(OpCodes.Ldc_I4_M1)); instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); } - else if (paramType == typeSystem.String - && ((!isExtensionMethod && methodRef.Parameters.Count == 2) - || (isExtensionMethod && methodRef.Parameters.Count == 3))) + else if (paramType == typeSystem.String && ((!isExtensionMethod && methodRef.Parameters.Count == 2) || + (isExtensionMethod && methodRef.Parameters.Count == 3))) { instructions.Add(processor.Create(OpCodes.Ldc_I4_0)); } @@ -1015,20 +1071,20 @@ namespace Unity.Netcode.Editor.CodeGen instructions.Add(endInstr); - // __sendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc - // __sendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc + // __endSendServerRpc(ref bufferWriter, rpcMethodId, serverRpcParams, rpcDelivery) -> ServerRpc + // __endSendClientRpc(ref bufferWriter, rpcMethodId, clientRpcParams, rpcDelivery) -> ClientRpc if (isServerRpc) { // ServerRpc - // __sendServerRpc(ref serializer, rpcMethodId, serverRpcParams, rpcDelivery); + + // __endSendServerRpc(ref bufferWriter, rpcMethodId, serverRpcParams, rpcDelivery); instructions.Add(processor.Create(OpCodes.Ldarg_0)); - // serializer - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); + // bufferWriter + instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx)); // rpcMethodId instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - if (hasRpcParams) { // rpcParams @@ -1039,25 +1095,24 @@ namespace Unity.Netcode.Editor.CodeGen // default instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); } - // rpcDelivery instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - // EndSendServerRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendServerRpc_MethodRef)); + // __endSendServerRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendServerRpc_MethodRef)); } else { // ClientRpc - // __sendClientRpc(ref serializer, rpcMethodId, clientRpcParams, rpcDelivery); + + // __endSendClientRpc(ref bufferWriter, rpcMethodId, clientRpcParams, rpcDelivery); instructions.Add(processor.Create(OpCodes.Ldarg_0)); - // serializer - instructions.Add(processor.Create(OpCodes.Ldloc, serializerLocIdx)); + // bufferWriter + instructions.Add(processor.Create(OpCodes.Ldloca, bufWriterLocIdx)); // rpcMethodId instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - if (hasRpcParams) { // rpcParams @@ -1068,36 +1123,11 @@ namespace Unity.Netcode.Editor.CodeGen // default instructions.Add(processor.Create(OpCodes.Ldloc, rpcParamsIdx)); } - // rpcDelivery instructions.Add(processor.Create(OpCodes.Ldc_I4, (int)rpcDelivery)); - // EndSendClientRpc - instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_SendClientRpc_MethodRef)); - } - - { - // TODO: Figure out why try/catch here cause the try block not to execute at all. - // End try block - //instructions.Add(processor.Create(OpCodes.Leave, lastInstr)); - - // writer.Dispose(); - var handlerFirst = processor.Create(OpCodes.Ldloca, serializerLocIdx); - instructions.Add(handlerFirst); - instructions.Add(processor.Create(OpCodes.Call, m_FastBufferWriter_Dispose)); - - // End finally block - //instructions.Add(processor.Create(OpCodes.Endfinally)); - - // try { ... serialization code ... } finally { writer.Dispose(); } - /*var handler = new ExceptionHandler(ExceptionHandlerType.Finally) - { - TryStart = firstInstruction, - TryEnd = handlerFirst, - HandlerStart = handlerFirst, - HandlerEnd = lastInstr - }; - processor.Body.ExceptionHandlers.Add(handler);*/ + // __endSendClientRpc + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour_endSendClientRpc_MethodRef)); } instructions.Add(lastInstr); @@ -1132,25 +1162,21 @@ namespace Unity.Netcode.Editor.CodeGen instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction)); } - private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute) + private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId) { var typeSystem = methodDefinition.Module.TypeSystem; - var nhandler = new MethodDefinition( - $"{methodDefinition.Name}__nhandler", + var rpcHandler = new MethodDefinition( + $"__rpc_handler_{rpcMethodId}", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, methodDefinition.Module.TypeSystem.Void); - nhandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef)); - nhandler.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, m_FastBufferReader_TypeRef)); - nhandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); + rpcHandler.Parameters.Add(new ParameterDefinition("target", ParameterAttributes.None, m_NetworkBehaviour_TypeRef)); + rpcHandler.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, m_FastBufferReader_TypeRef)); + rpcHandler.Parameters.Add(new ParameterDefinition("rpcParams", ParameterAttributes.None, m_RpcParams_TypeRef)); - var processor = nhandler.Body.GetILProcessor(); - - // begin Try/Catch - var tryStart = processor.Create(OpCodes.Nop); - processor.Append(tryStart); + var processor = rpcHandler.Body.GetILProcessor(); var isServerRpc = rpcAttribute.AttributeType.FullName == CodeGenHelpers.ServerRpcAttribute_FullName; - var requireOwnership = true; // default value MUST be = `ServerRpcAttribute.RequireOwnership` + var requireOwnership = true; // default value MUST be == `ServerRpcAttribute.RequireOwnership` foreach (var attrField in rpcAttribute.Fields) { switch (attrField.Name) @@ -1161,10 +1187,10 @@ namespace Unity.Netcode.Editor.CodeGen } } - nhandler.Body.InitLocals = true; + rpcHandler.Body.InitLocals = true; // NetworkManager networkManager; - nhandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); - int netManLocIdx = nhandler.Body.Variables.Count - 1; + rpcHandler.Body.Variables.Add(new VariableDefinition(m_NetworkManager_TypeRef)); + int netManLocIdx = rpcHandler.Body.Variables.Count - 1; { var returnInstr = processor.Create(OpCodes.Ret); @@ -1233,8 +1259,8 @@ namespace Unity.Netcode.Editor.CodeGen var paramType = paramDef.ParameterType; // local variable - nhandler.Body.Variables.Add(new VariableDefinition(paramType)); - int localIndex = nhandler.Body.Variables.Count - 1; + rpcHandler.Body.Variables.Add(new VariableDefinition(paramType)); + int localIndex = rpcHandler.Body.Variables.Count - 1; paramLocalMap[paramIndex] = localIndex; // ServerRpcParams, ClientRpcParams @@ -1268,8 +1294,8 @@ namespace Unity.Netcode.Editor.CodeGen } // reader.ReadValueSafe(out bool isSet) - nhandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); - int isSetLocalIndex = nhandler.Body.Variables.Count - 1; + rpcHandler.Body.Variables.Add(new VariableDefinition(typeSystem.Boolean)); + int isSetLocalIndex = rpcHandler.Body.Variables.Count - 1; processor.Emit(OpCodes.Ldarga, 1); processor.Emit(OpCodes.Ldloca, isSetLocalIndex); processor.Emit(OpCodes.Call, boolMethodRef); @@ -1336,55 +1362,8 @@ namespace Unity.Netcode.Editor.CodeGen processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); - // pull in the Exception Module - var exception = m_MainModule.ImportReference(typeof(Exception)); - - // Get Exception.ToString() - var exp = m_MainModule.ImportReference(typeof(Exception).GetMethod("ToString", new Type[] { })); - - // Get String.Format (This is equivalent to an interpolated string) - var stringFormat = m_MainModule.ImportReference(typeof(string).GetMethod("Format", new Type[] { typeof(string), typeof(object) })); - - nhandler.Body.Variables.Add(new VariableDefinition(exception)); - int exceptionVariableIndex = nhandler.Body.Variables.Count - 1; - - //try ends/catch begins - var catchEnds = processor.Create(OpCodes.Nop); - processor.Emit(OpCodes.Leave, catchEnds); - - // Load the Exception onto the stack - var catchStarts = processor.Create(OpCodes.Stloc, exceptionVariableIndex); - processor.Append(catchStarts); - - // Load string for the error log that will be shown - processor.Emit(OpCodes.Ldstr, $"Unhandled RPC Exception:\n {{0}}"); - processor.Emit(OpCodes.Ldloc, exceptionVariableIndex); - processor.Emit(OpCodes.Callvirt, exp); - processor.Emit(OpCodes.Call, stringFormat); - - // Call Debug.LogError - processor.Emit(OpCodes.Call, m_Debug_LogError_MethodRef); - - // reset NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None; - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldc_I4, (int)NetworkBehaviour.__RpcExecStage.None); - processor.Emit(OpCodes.Stfld, m_NetworkBehaviour_rpc_exec_stage_FieldRef); - - // catch ends - processor.Append(catchEnds); - - processor.Body.ExceptionHandlers.Add(new ExceptionHandler(ExceptionHandlerType.Catch) - { - CatchType = exception, - TryStart = tryStart, - TryEnd = catchStarts, - HandlerStart = catchStarts, - HandlerEnd = catchEnds - }); - processor.Emit(OpCodes.Ret); - - return nhandler; + return rpcHandler; } } } diff --git a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 17f9b50..ae4b24e 100644 --- a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -134,8 +134,10 @@ namespace Unity.Netcode.Editor.CodeGen foreach (var methodDefinition in typeDefinition.Methods) { - if (methodDefinition.Name == nameof(NetworkBehaviour.__sendServerRpc) - || methodDefinition.Name == nameof(NetworkBehaviour.__sendClientRpc)) + if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) || + methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) || + methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) || + methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc)) { methodDefinition.IsFamily = true; } diff --git a/Editor/DontShowInTransportDropdownAttribute.cs b/Editor/DontShowInTransportDropdownAttribute.cs deleted file mode 100644 index 465ea27..0000000 --- a/Editor/DontShowInTransportDropdownAttribute.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System; - -namespace Unity.Netcode.Editor -{ - public class DontShowInTransportDropdownAttribute : Attribute - { - } -} diff --git a/Editor/DontShowInTransportDropdownAttribute.cs.meta b/Editor/DontShowInTransportDropdownAttribute.cs.meta deleted file mode 100644 index da7b0cf..0000000 --- a/Editor/DontShowInTransportDropdownAttribute.cs.meta +++ /dev/null @@ -1,3 +0,0 @@ -fileFormatVersion: 2 -guid: 5f097067d4254dc7ad018d7ad90df7c3 -timeCreated: 1620386886 \ No newline at end of file diff --git a/Editor/NetworkAnimatorEditor.cs b/Editor/NetworkAnimatorEditor.cs index faae024..e9d3b62 100644 --- a/Editor/NetworkAnimatorEditor.cs +++ b/Editor/NetworkAnimatorEditor.cs @@ -1,103 +1,23 @@ -using System; using Unity.Netcode.Components; using UnityEditor; -using UnityEditor.Animations; using UnityEngine; namespace Unity.Netcode.Editor { - public static class TextUtility - { - public static GUIContent TextContent(string name, string tooltip) - { - var newContent = new GUIContent(name); - newContent.tooltip = tooltip; - return newContent; - } - - public static GUIContent TextContent(string name) - { - return new GUIContent(name); - } - } - [CustomEditor(typeof(NetworkAnimator), true)] [CanEditMultipleObjects] public class NetworkAnimatorEditor : UnityEditor.Editor { - private NetworkAnimator m_AnimSync; - [NonSerialized] private bool m_Initialized; - private SerializedProperty m_AnimatorProperty; - private GUIContent m_AnimatorLabel; - - private void Init() - { - if (m_Initialized) - { - return; - } - - m_Initialized = true; - m_AnimSync = target as NetworkAnimator; - - m_AnimatorProperty = serializedObject.FindProperty("m_Animator"); - m_AnimatorLabel = TextUtility.TextContent("Animator", "The Animator component to synchronize."); - } - public override void OnInspectorGUI() { - Init(); serializedObject.Update(); - DrawControls(); - serializedObject.ApplyModifiedProperties(); - } - private void DrawControls() - { EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(m_AnimatorProperty, m_AnimatorLabel); - if (EditorGUI.EndChangeCheck()) - { - m_AnimSync.ResetParameterOptions(); - } + var label = new GUIContent("Animator", "The Animator component to synchronize"); + EditorGUILayout.PropertyField(serializedObject.FindProperty("m_Animator"), label); + EditorGUI.EndChangeCheck(); - if (m_AnimSync.Animator == null) - { - return; - } - - var controller = m_AnimSync.Animator.runtimeAnimatorController as AnimatorController; - if (controller != null) - { - var showWarning = false; - EditorGUI.indentLevel += 1; - int i = 0; - - foreach (var p in controller.parameters) - { - if (i >= NetworkAnimator.K_MaxAnimationParams) - { - showWarning = true; - break; - } - - bool oldSend = m_AnimSync.GetParameterAutoSend(i); - bool send = EditorGUILayout.Toggle(p.name, oldSend); - if (send != oldSend) - { - m_AnimSync.SetParameterAutoSend(i, send); - EditorUtility.SetDirty(target); - } - i += 1; - } - - if (showWarning) - { - EditorGUILayout.HelpBox($"NetworkAnimator can only select between the first {NetworkAnimator.K_MaxAnimationParams} parameters in a mecanim controller", MessageType.Warning); - } - - EditorGUI.indentLevel -= 1; - } + serializedObject.ApplyModifiedProperties(); } } } diff --git a/Editor/NetworkManagerEditor.cs b/Editor/NetworkManagerEditor.cs index f343402..f8d54f7 100644 --- a/Editor/NetworkManagerEditor.cs +++ b/Editor/NetworkManagerEditor.cs @@ -57,7 +57,7 @@ namespace Unity.Netcode.Editor foreach (var type in types) { - if (type.IsSubclassOf(typeof(NetworkTransport)) && type.GetCustomAttributes(typeof(DontShowInTransportDropdownAttribute), true).Length == 0) + if (type.IsSubclassOf(typeof(NetworkTransport)) && !type.IsSubclassOf(typeof(TestingNetworkTransport)) && type != typeof(TestingNetworkTransport)) { m_TransportTypes.Add(type); } diff --git a/Editor/NetworkManagerHelper.cs b/Editor/NetworkManagerHelper.cs index d30dd86..cc34a6e 100644 --- a/Editor/NetworkManagerHelper.cs +++ b/Editor/NetworkManagerHelper.cs @@ -12,7 +12,7 @@ namespace Unity.Netcode.Editor { internal static NetworkManagerHelper Singleton; - // This is primarily to handle multiInstance scenarios where more than 1 NetworkManager could exist + // This is primarily to handle IntegrationTest scenarios where more than 1 NetworkManager could exist private static Dictionary s_LastKnownNetworkManagerParents = new Dictionary(); /// diff --git a/Editor/com.unity.netcode.editor.asmdef b/Editor/com.unity.netcode.editor.asmdef index 5a9e666..ab1d271 100644 --- a/Editor/com.unity.netcode.editor.asmdef +++ b/Editor/com.unity.netcode.editor.asmdef @@ -8,18 +8,11 @@ "includePlatforms": [ "Editor" ], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], "versionDefines": [ { "name": "com.unity.multiplayer.tools", "expression": "", "define": "MULTIPLAYER_TOOLS" } - ], - "noEngineReferences": false + ] } \ No newline at end of file diff --git a/README.md b/README.md index 91b440f..6ce7816 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Netcode for GameObjects + [![Forums](https://img.shields.io/badge/unity--forums-multiplayer-blue)](https://forum.unity.com/forums/multiplayer.26/) [![Discord](https://img.shields.io/discord/449263083769036810.svg?label=discord&logo=discord&color=informational)](https://discord.gg/FM8SE9E) [![Website](https://img.shields.io/badge/docs-website-informational.svg)](https://docs-multiplayer.unity3d.com/) [![Api](https://img.shields.io/badge/docs-api-informational.svg)](https://docs-multiplayer.unity3d.com/docs/mlapi-api/introduction) diff --git a/Runtime/AssemblyInfo.cs b/Runtime/AssemblyInfo.cs index fc40c7e..74b906e 100644 --- a/Runtime/AssemblyInfo.cs +++ b/Runtime/AssemblyInfo.cs @@ -5,8 +5,10 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")] [assembly: InternalsVisibleTo("Unity.Netcode.Editor")] [assembly: InternalsVisibleTo("TestProject.EditorTests")] -[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] -[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")] #endif +[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")] +[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] [assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] +[assembly: InternalsVisibleTo("Unity.Netcode.TestHelpers.Runtime")] +[assembly: InternalsVisibleTo("Unity.Netcode.Adapter.UTP")] diff --git a/Runtime/Configuration/NetworkConfig.cs b/Runtime/Configuration/NetworkConfig.cs index ccd628f..83cc18c 100644 --- a/Runtime/Configuration/NetworkConfig.cs +++ b/Runtime/Configuration/NetworkConfig.cs @@ -239,6 +239,8 @@ namespace Unity.Netcode writer.WriteValueSafe(sortedEntry.Key); } } + + writer.WriteValueSafe(TickRate); writer.WriteValueSafe(ConnectionApproval); writer.WriteValueSafe(ForceSamePrefabs); writer.WriteValueSafe(EnableSceneManagement); diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs index f271c4c..20de22a 100644 --- a/Runtime/Core/NetworkBehaviour.cs +++ b/Runtime/Core/NetworkBehaviour.cs @@ -15,41 +15,56 @@ namespace Unity.Netcode #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` internal enum __RpcExecStage -#pragma warning restore IDE1006 // restore naming rule violation check { None = 0, Server = 1, Client = 2 } -#pragma warning disable IDE1006 // disable naming rule violation check // NetworkBehaviourILPP will override this in derived classes to return the name of the concrete type internal virtual string __getTypeName() => nameof(NetworkBehaviour); -#pragma warning restore IDE1006 // restore naming rule violation check -#pragma warning disable 414 // disable assigned but its value is never used -#pragma warning disable IDE1006 // disable naming rule violation check [NonSerialized] // RuntimeAccessModifiersILPP will make this `protected` internal __RpcExecStage __rpc_exec_stage = __RpcExecStage.None; -#pragma warning restore 414 // restore assigned but its value is never used #pragma warning restore IDE1006 // restore naming rule violation check -#pragma warning disable 414 // disable assigned but its value is never used + private const int k_RpcMessageDefaultSize = 1024; // 1k + private const int k_RpcMessageMaximumSize = 1024 * 64; // 64k + #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` - internal void __sendServerRpc(FastBufferWriter writer, uint rpcMethodId, ServerRpcParams rpcParams, RpcDelivery delivery) -#pragma warning restore 414 // restore assigned but its value is never used + internal FastBufferWriter __beginSendServerRpc(uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) #pragma warning restore IDE1006 // restore naming rule violation check { - NetworkDelivery networkDelivery = NetworkDelivery.Reliable; - switch (delivery) + return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); + } + +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ServerRpcParams serverRpcParams, RpcDelivery rpcDelivery) +#pragma warning restore IDE1006 // restore naming rule violation check + { + var serverRpcMessage = new ServerRpcMessage { + Metadata = new RpcMetadata + { + NetworkObjectId = NetworkObjectId, + NetworkBehaviourId = NetworkBehaviourId, + NetworkRpcMethodId = rpcMethodId, + }, + WriteBuffer = bufferWriter + }; + + NetworkDelivery networkDelivery; + switch (rpcDelivery) + { + default: case RpcDelivery.Reliable: networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; break; case RpcDelivery.Unreliable: - if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort)) + if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE) { throw new OverflowException("RPC parameters are too large for unreliable delivery."); } @@ -57,42 +72,33 @@ namespace Unity.Netcode break; } - var message = new RpcMessage - { - Header = new RpcMessage.HeaderData - { - Type = RpcMessage.RpcType.Server, - NetworkObjectId = NetworkObjectId, - NetworkBehaviourId = NetworkBehaviourId, - NetworkMethodId = rpcMethodId - }, - RpcData = writer - }; - - var rpcMessageSize = 0; + var rpcWriteSize = 0; // If we are a server/host then we just no op and send to ourself if (IsHost || IsServer) { - using var tempBuffer = new FastBufferReader(writer, Allocator.Temp); + using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp); var context = new NetworkContext { SenderId = NetworkManager.ServerClientId, Timestamp = Time.realtimeSinceStartup, SystemOwner = NetworkManager, // header information isn't valid since it's not a real message. - // Passing false to canDefer prevents it being accessed. + // RpcMessage doesn't access this stuff so it's just left empty. Header = new MessageHeader(), SerializedHeaderSize = 0, + MessageSize = 0 }; - message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false); - rpcMessageSize = tempBuffer.Length; + serverRpcMessage.ReadBuffer = tempBuffer; + serverRpcMessage.Handle(ref context); + rpcWriteSize = tempBuffer.Length; } else { - rpcMessageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ServerClientId); + rpcWriteSize = NetworkManager.SendMessage(ref serverRpcMessage, networkDelivery, NetworkManager.ServerClientId); } + bufferWriter.Dispose(); #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) @@ -102,26 +108,44 @@ namespace Unity.Netcode NetworkObject, rpcMethodName, __getTypeName(), - rpcMessageSize); + rpcWriteSize); } #endif } -#pragma warning disable 414 // disable assigned but its value is never used #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` - internal unsafe void __sendClientRpc(FastBufferWriter writer, uint rpcMethodId, ClientRpcParams rpcParams, RpcDelivery delivery) -#pragma warning disable 414 // disable assigned but its value is never used -#pragma warning disable IDE1006 // disable naming rule violation check + internal FastBufferWriter __beginSendClientRpc(uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) +#pragma warning restore IDE1006 // restore naming rule violation check { - NetworkDelivery networkDelivery = NetworkDelivery.Reliable; - switch (delivery) + return new FastBufferWriter(k_RpcMessageDefaultSize, Allocator.Temp, k_RpcMessageMaximumSize); + } + +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMethodId, ClientRpcParams clientRpcParams, RpcDelivery rpcDelivery) +#pragma warning restore IDE1006 // restore naming rule violation check + { + var clientRpcMessage = new ClientRpcMessage { + Metadata = new RpcMetadata + { + NetworkObjectId = NetworkObjectId, + NetworkBehaviourId = NetworkBehaviourId, + NetworkRpcMethodId = rpcMethodId, + }, + WriteBuffer = bufferWriter + }; + + NetworkDelivery networkDelivery; + switch (rpcDelivery) + { + default: case RpcDelivery.Reliable: networkDelivery = NetworkDelivery.ReliableFragmentedSequenced; break; case RpcDelivery.Unreliable: - if (writer.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE - sizeof(RpcMessage.RpcType) - sizeof(ulong) - sizeof(uint) - sizeof(ushort)) + if (bufferWriter.Length > MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE) { throw new OverflowException("RPC parameters are too large for unreliable delivery."); } @@ -129,26 +153,15 @@ namespace Unity.Netcode break; } - var message = new RpcMessage - { - Header = new RpcMessage.HeaderData - { - Type = RpcMessage.RpcType.Client, - NetworkObjectId = NetworkObjectId, - NetworkBehaviourId = NetworkBehaviourId, - NetworkMethodId = rpcMethodId - }, - RpcData = writer - }; - int messageSize; + var rpcWriteSize = 0; // We check to see if we need to shortcut for the case where we are the host/server and we can send a clientRPC // to ourself. Sadly we have to figure that out from the list of clientIds :( bool shouldSendToHost = false; - if (rpcParams.Send.TargetClientIds != null) + if (clientRpcParams.Send.TargetClientIds != null) { - foreach (var clientId in rpcParams.Send.TargetClientIds) + foreach (var clientId in clientRpcParams.Send.TargetClientIds) { if (clientId == NetworkManager.ServerClientId) { @@ -157,11 +170,11 @@ namespace Unity.Netcode } } - messageSize = NetworkManager.SendMessage(message, networkDelivery, in rpcParams.Send.TargetClientIds); + rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, in clientRpcParams.Send.TargetClientIds); } - else if (rpcParams.Send.TargetClientIdsNativeArray != null) + else if (clientRpcParams.Send.TargetClientIdsNativeArray != null) { - foreach (var clientId in rpcParams.Send.TargetClientIdsNativeArray) + foreach (var clientId in clientRpcParams.Send.TargetClientIdsNativeArray) { if (clientId == NetworkManager.ServerClientId) { @@ -170,32 +183,35 @@ namespace Unity.Netcode } } - messageSize = NetworkManager.SendMessage(message, networkDelivery, rpcParams.Send.TargetClientIdsNativeArray.Value); + rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, clientRpcParams.Send.TargetClientIdsNativeArray.Value); } else { shouldSendToHost = IsHost; - messageSize = NetworkManager.SendMessage(message, networkDelivery, NetworkManager.ConnectedClientsIds); + rpcWriteSize = NetworkManager.SendMessage(ref clientRpcMessage, networkDelivery, NetworkManager.ConnectedClientsIds); } // If we are a server/host then we just no op and send to ourself if (shouldSendToHost) { - using var tempBuffer = new FastBufferReader(writer, Allocator.Temp); + using var tempBuffer = new FastBufferReader(bufferWriter, Allocator.Temp); var context = new NetworkContext { SenderId = NetworkManager.ServerClientId, Timestamp = Time.realtimeSinceStartup, SystemOwner = NetworkManager, // header information isn't valid since it's not a real message. - // Passing false to canDefer prevents it being accessed. + // RpcMessage doesn't access this stuff so it's just left empty. Header = new MessageHeader(), SerializedHeaderSize = 0, + MessageSize = 0 }; - message.Handle(tempBuffer, context, NetworkManager, NetworkManager.ServerClientId, false); - messageSize = tempBuffer.Length; + clientRpcMessage.ReadBuffer = tempBuffer; + clientRpcMessage.Handle(ref context); } + bufferWriter.Dispose(); + #if DEVELOPMENT_BUILD || UNITY_EDITOR if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) { @@ -206,7 +222,7 @@ namespace Unity.Netcode NetworkObject, rpcMethodName, __getTypeName(), - messageSize); + rpcWriteSize); } } #endif @@ -260,9 +276,9 @@ namespace Unity.Netcode { // Only server can MODIFY. So allow modification if network is either not running or we are server return !m_NetworkObject || - (m_NetworkObject.NetworkManager == null || - !m_NetworkObject.NetworkManager.IsListening || - m_NetworkObject.NetworkManager.IsServer); + m_NetworkObject.NetworkManager == null || + m_NetworkObject.NetworkManager.IsListening == false || + m_NetworkObject.NetworkManager.IsServer; } /// @@ -284,10 +300,14 @@ namespace Unity.Netcode m_NetworkObject = GetComponentInParent(); } - if (m_NetworkObject == null || NetworkManager.Singleton == null || - (NetworkManager.Singleton != null && !NetworkManager.Singleton.ShutdownInProgress)) + // ShutdownInProgress check: + // This prevents an edge case scenario where the NetworkManager is shutting down but user code + // in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?) + // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages + // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. + if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress)) { - if (NetworkLog.CurrentLogLevel < LogLevel.Normal) + if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { NetworkLog.LogWarning($"Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); } @@ -349,10 +369,7 @@ namespace Unity.Netcode InitializeVariables(); } - internal void InternalOnNetworkDespawn() - { - - } + internal void InternalOnNetworkDespawn() { } /// /// Gets called when the local client gains ownership of this object @@ -428,8 +445,7 @@ namespace Unity.Netcode if (instance == null) { - instance = (NetworkVariableBase)Activator.CreateInstance(fieldType, true); - sortedFields[i].SetValue(this, instance); + throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized."); } instance.Initialize(this); @@ -542,7 +558,7 @@ namespace Unity.Netcode // so we don't have to do this serialization work if we're not going to use the result. if (IsServer && clientId == NetworkManager.ServerClientId) { - var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp); + var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, MessagingSystem.FRAGMENTED_MESSAGE_MAX_SIZE); using (tmpWriter) { message.Serialize(tmpWriter); @@ -550,7 +566,7 @@ namespace Unity.Netcode } else { - NetworkManager.SendMessage(message, m_DeliveryTypesForNetworkVariableGroups[j], clientId); + NetworkManager.SendMessage(ref message, m_DeliveryTypesForNetworkVariableGroups[j], clientId); } } } diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs index 65a54fc..4ecb254 100644 --- a/Runtime/Core/NetworkManager.cs +++ b/Runtime/Core/NetworkManager.cs @@ -85,11 +85,11 @@ namespace Unity.Netcode m_NetworkManager = manager; } - public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage { } - public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage { } @@ -139,6 +139,14 @@ namespace Unity.Netcode return !m_NetworkManager.m_StopProcessingMessages; } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } } private class NetworkManagerMessageSender : IMessageSender @@ -555,6 +563,8 @@ namespace Unity.Netcode return; } + NetworkConfig.NetworkTransport.NetworkMetrics = NetworkMetrics; + //This 'if' should never enter if (SnapshotSystem != null) { @@ -583,6 +593,7 @@ namespace Unity.Netcode // Always clear our prefab override links before building NetworkConfig.NetworkPrefabOverrideLinks.Clear(); + NetworkConfig.OverrideToNetworkPrefab.Clear(); // Build the NetworkPrefabOverrideLinks dictionary for (int i = 0; i < NetworkConfig.NetworkPrefabs.Count; i++) @@ -779,7 +790,7 @@ namespace Unity.Netcode NetworkConfig.NetworkTransport.OnTransportEvent += HandleRawTransportPoll; - NetworkConfig.NetworkTransport.Initialize(); + NetworkConfig.NetworkTransport.Initialize(this); } /// @@ -1299,7 +1310,7 @@ namespace Unity.Netcode ShouldSendConnectionData = NetworkConfig.ConnectionApproval, ConnectionData = NetworkConfig.ConnectionData }; - SendMessage(message, NetworkDelivery.ReliableSequenced, ServerClientId); + SendMessage(ref message, NetworkDelivery.ReliableSequenced, ServerClientId); } private IEnumerator ApprovalTimeout(ulong clientId) @@ -1324,12 +1335,12 @@ namespace Unity.Netcode } } - private ulong TransportIdToClientId(ulong transportId) + internal ulong TransportIdToClientId(ulong transportId) { return transportId == m_ServerTransportId ? ServerClientId : m_TransportIdToClientIdMap[transportId]; } - private ulong ClientIdToTransportId(ulong clientId) + internal ulong ClientIdToTransportId(ulong clientId) { return clientId == ServerClientId ? m_ServerTransportId : m_ClientIdToTransportIdMap[clientId]; } @@ -1344,7 +1355,20 @@ namespace Unity.Netcode s_TransportConnect.Begin(); #endif - clientId = m_NextClientId++; + // Assumptions: + // - When server receives a connection, it *must be* a client + // - When client receives one, it *must be* the server + // Client's can't connect to or talk to other clients. + // Server is a sentinel so only one exists, if we are server, we can't be + // connecting to it. + if (IsServer) + { + clientId = m_NextClientId++; + } + else + { + clientId = ServerClientId; + } m_ClientIdToTransportIdMap[clientId] = transportId; m_TransportIdToClientIdMap[transportId] = clientId; @@ -1422,7 +1446,7 @@ namespace Unity.Netcode } } - internal unsafe int SendMessage(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds) + internal unsafe int SendMessage(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds) where TMessageType : INetworkMessage where TClientIdListType : IReadOnlyList { @@ -1445,12 +1469,18 @@ namespace Unity.Netcode { return 0; } - return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx); + return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx); } - return MessagingSystem.SendMessage(message, delivery, clientIds); + // else + if (clientIds.Count != 1 || clientIds[0] != ServerClientId) + { + throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}"); + } + + return MessagingSystem.SendMessage(ref message, delivery, clientIds); } - internal unsafe int SendMessage(in T message, NetworkDelivery delivery, + internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, ulong* clientIds, int numClientIds) where T : INetworkMessage { @@ -1473,19 +1503,24 @@ namespace Unity.Netcode { return 0; } - return MessagingSystem.SendMessage(message, delivery, nonServerIds, newIdx); + return MessagingSystem.SendMessage(ref message, delivery, nonServerIds, newIdx); + } + // else + if (numClientIds != 1 || clientIds[0] != ServerClientId) + { + throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}"); } - return MessagingSystem.SendMessage(message, delivery, clientIds, numClientIds); + return MessagingSystem.SendMessage(ref message, delivery, clientIds, numClientIds); } - internal unsafe int SendMessage(in T message, NetworkDelivery delivery, in NativeArray clientIds) + internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeArray clientIds) where T : INetworkMessage { - return SendMessage(message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length); + return SendMessage(ref message, delivery, (ulong*)clientIds.GetUnsafePtr(), clientIds.Length); } - internal int SendMessage(in T message, NetworkDelivery delivery, ulong clientId) + internal int SendMessage(ref T message, NetworkDelivery delivery, ulong clientId) where T : INetworkMessage { // Prevent server sending to itself @@ -1493,7 +1528,18 @@ namespace Unity.Netcode { return 0; } - return MessagingSystem.SendMessage(message, delivery, clientId); + + if (!IsServer && clientId != ServerClientId) + { + throw new ArgumentException($"Clients may only send messages to {nameof(ServerClientId)}"); + } + return MessagingSystem.SendMessage(ref message, delivery, clientId); + } + + internal int SendPreSerializedMessage(in FastBufferWriter writer, int maxSize, ref T message, NetworkDelivery delivery, ulong clientId) + where T : INetworkMessage + { + return MessagingSystem.SendPreSerializedMessage(writer, maxSize, ref message, delivery, clientId); } internal void HandleIncomingData(ulong clientId, ArraySegment payload, float receiveTime) @@ -1517,7 +1563,7 @@ namespace Unity.Netcode { if (!IsServer) { - throw new NotServerException("Only server can disconnect remote clients. Use StopClient instead."); + throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead."); } OnClientDisconnectFromServer(clientId); @@ -1614,7 +1660,7 @@ namespace Unity.Netcode { Tick = NetworkTickSystem.ServerTime.Tick }; - SendMessage(message, NetworkDelivery.Unreliable, ConnectedClientsIds); + SendMessage(ref message, NetworkDelivery.Unreliable, ConnectedClientsIds); #if DEVELOPMENT_BUILD || UNITY_EDITOR s_SyncTime.End(); #endif @@ -1661,12 +1707,11 @@ namespace Unity.Netcode { if (SpawnManager.SpawnedObjectsList.Count != 0) { - message.SceneObjectCount = SpawnManager.SpawnedObjectsList.Count; message.SpawnedObjectsList = SpawnManager.SpawnedObjectsList; } } - SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); + SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization if (!NetworkConfig.EnableSceneManagement) @@ -1726,7 +1771,7 @@ namespace Unity.Netcode message.ObjectInfo.Header.HasParent = false; message.ObjectInfo.Header.IsPlayerObject = true; message.ObjectInfo.Header.OwnerClientId = clientId; - var size = SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); + var size = SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientPair.Key); NetworkMetrics.TrackObjectSpawnSent(clientPair.Key, ConnectedClients[clientId].PlayerObject, size); } } diff --git a/Runtime/Core/NetworkObject.cs b/Runtime/Core/NetworkObject.cs index ea7e99a..a5d08ca 100644 --- a/Runtime/Core/NetworkObject.cs +++ b/Runtime/Core/NetworkObject.cs @@ -39,7 +39,7 @@ namespace Unity.Netcode var globalObjectIdString = UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(this).ToString(); GlobalObjectIdHash = XXHash.Hash32(globalObjectIdString); } -#endif +#endif // UNITY_EDITOR /// /// Gets the NetworkManager that owns this NetworkObject instance @@ -328,7 +328,7 @@ namespace Unity.Netcode NetworkObjectId = NetworkObjectId }; // Send destroy call - var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientId); + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectDestroySent(clientId, this, size); } } @@ -714,7 +714,7 @@ namespace Unity.Netcode } } - NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, clientIds, idx); + NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientIds, idx); } } @@ -1142,8 +1142,7 @@ namespace Unity.Netcode var globalObjectIdHash = NetworkManager.PrefabHandler.GetSourceGlobalObjectIdHash(GlobalObjectIdHash); return globalObjectIdHash == 0 ? GlobalObjectIdHash : globalObjectIdHash; } - else - if (NetworkManager.NetworkConfig.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) + else if (NetworkManager.NetworkConfig.OverrideToNetworkPrefab.ContainsKey(GlobalObjectIdHash)) { return NetworkManager.NetworkConfig.OverrideToNetworkPrefab[GlobalObjectIdHash]; } diff --git a/Runtime/Core/SnapshotSystem.cs b/Runtime/Core/SnapshotSystem.cs index 8d9a9c3..f6c29f4 100644 --- a/Runtime/Core/SnapshotSystem.cs +++ b/Runtime/Core/SnapshotSystem.cs @@ -87,7 +87,7 @@ namespace Unity.Netcode internal List SentSpawns = new List(); } - internal delegate int MockSendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId); + internal delegate int MockSendMessage(ref SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId); internal delegate int MockSpawnObject(SnapshotSpawnCommand spawnCommand); internal delegate int MockDespawnObject(SnapshotDespawnCommand despawnCommand); @@ -813,13 +813,22 @@ namespace Unity.Netcode WriteIndex(ref message); WriteSpawns(ref message, clientId); - if (m_NetworkManager) + try { - m_NetworkManager.SendMessage(message, NetworkDelivery.Unreliable, clientId); + if (m_NetworkManager) + { + m_NetworkManager.SendMessage(ref message, NetworkDelivery.Unreliable, clientId); + } + else + { + MockSendMessage(ref message, NetworkDelivery.Unreliable, clientId); + } } - else + finally { - MockSendMessage(message, NetworkDelivery.Unreliable, clientId); + message.Entries.Dispose(); + message.Spawns.Dispose(); + message.Despawns.Dispose(); } m_ClientData[clientId].LastReceivedSequence = 0; diff --git a/Runtime/Logging/NetworkLog.cs b/Runtime/Logging/NetworkLog.cs index 78de043..bf98928 100644 --- a/Runtime/Logging/NetworkLog.cs +++ b/Runtime/Logging/NetworkLog.cs @@ -62,8 +62,7 @@ namespace Unity.Netcode LogType = logType, Message = message }; - var size = NetworkManager.Singleton.SendMessage(networkMessage, NetworkDelivery.ReliableFragmentedSequenced, - NetworkManager.Singleton.ServerClientId); + var size = NetworkManager.Singleton.SendMessage(ref networkMessage, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.Singleton.ServerClientId); NetworkManager.Singleton.NetworkMetrics.TrackServerLogSent(NetworkManager.Singleton.ServerClientId, (uint)logType, size); } diff --git a/Runtime/Messaging/CustomMessageManager.cs b/Runtime/Messaging/CustomMessageManager.cs index 7a7938f..e42cf07 100644 --- a/Runtime/Messaging/CustomMessageManager.cs +++ b/Runtime/Messaging/CustomMessageManager.cs @@ -73,9 +73,9 @@ namespace Unity.Netcode var message = new UnnamedMessage { - Data = messageBuffer + SendData = messageBuffer }; - var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds); + var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) @@ -94,9 +94,9 @@ namespace Unity.Netcode { var message = new UnnamedMessage { - Data = messageBuffer + SendData = messageBuffer }; - var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId); + var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) { @@ -223,9 +223,9 @@ namespace Unity.Netcode var message = new NamedMessage { Hash = hash, - Data = messageStream + SendData = messageStream, }; - var size = m_NetworkManager.SendMessage(message, networkDelivery, clientId); + var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientId); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) @@ -266,9 +266,9 @@ namespace Unity.Netcode var message = new NamedMessage { Hash = hash, - Data = messageStream + SendData = messageStream }; - var size = m_NetworkManager.SendMessage(message, networkDelivery, clientIds); + var size = m_NetworkManager.SendMessage(ref message, networkDelivery, clientIds); // Size is zero if we were only sending the message to ourself in which case it isn't sent. if (size != 0) diff --git a/Runtime/Messaging/INetworkHooks.cs b/Runtime/Messaging/INetworkHooks.cs index ce64fc5..0818046 100644 --- a/Runtime/Messaging/INetworkHooks.cs +++ b/Runtime/Messaging/INetworkHooks.cs @@ -13,18 +13,18 @@ namespace Unity.Netcode /// Called before an individual message is sent. /// /// The destination clientId - /// The type of the message being sent + /// The message being sent /// - void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery); + void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage; /// /// Called after an individual message is sent. /// /// The destination clientId - /// The type of the message being sent + /// The message being sent /// /// Number of bytes in the message, not including the message header - void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes); + void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage; /// /// Called before an individual message is received. @@ -93,5 +93,23 @@ namespace Unity.Netcode /// The type of the message /// bool OnVerifyCanReceive(ulong senderId, Type messageType); + + /// + /// Called after a message is serialized, but before it's handled. + /// Differs from OnBeforeReceiveMessage in that the actual message object is passed and can be inspected. + /// + /// The message object + /// The network context the message is being ahandled in + /// + void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage; + + /// + /// Called after a message is serialized and handled. + /// Differs from OnAfterReceiveMessage in that the actual message object is passed and can be inspected. + /// + /// The message object + /// The network context the message is being ahandled in + /// + void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage; } } diff --git a/Runtime/Messaging/INetworkMessage.cs b/Runtime/Messaging/INetworkMessage.cs index 2f7a1f9..1249081 100644 --- a/Runtime/Messaging/INetworkMessage.cs +++ b/Runtime/Messaging/INetworkMessage.cs @@ -15,7 +15,7 @@ namespace Unity.Netcode /// static message handler for receiving messages of the following name and signature: /// /// - /// public static void Receive(FastBufferReader reader, in NetworkContext context) + /// public static void Receive(FastBufferReader reader, ref NetworkContext context) /// /// /// It is the responsibility of the Serialize and Receive methods to ensure there is enough buffer space @@ -40,10 +40,8 @@ namespace Unity.Netcode /// internal interface INetworkMessage { - /// - /// Used to serialize the message. - /// - /// void Serialize(FastBufferWriter writer); + bool Deserialize(FastBufferReader reader, ref NetworkContext context); + void Handle(ref NetworkContext context); } } diff --git a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index c4d3aef..e4dde7b 100644 --- a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -10,24 +10,28 @@ namespace Unity.Netcode writer.WriteValueSafe(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } - reader.ReadValueSafe(out ChangeOwnershipMessage message); - message.Handle(reader, context, context.SenderId, networkManager, reader.Length); + reader.ReadValueSafe(out this); + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + { + networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context); + return false; + } + + return true; } - public void Handle(FastBufferReader reader, in NetworkContext context, ulong senderId, NetworkManager networkManager, int messageSize) + public void Handle(ref NetworkContext context) { - if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) - { - networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context); - return; - } + + var networkManager = (NetworkManager)context.SystemOwner; + var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; if (networkObject.OwnerClientId == networkManager.LocalClientId) { @@ -43,7 +47,7 @@ namespace Unity.Netcode networkObject.InvokeBehaviourOnGainedOwnership(); } - networkManager.NetworkMetrics.TrackOwnershipChangeReceived(senderId, networkObject, messageSize); + networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); } } } diff --git a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index bf3ecac..14c4ac3 100644 --- a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -7,24 +7,27 @@ namespace Unity.Netcode { public ulong OwnerClientId; public int NetworkTick; - public int SceneObjectCount; // Not serialized, held as references to serialize NetworkVariable data public HashSet SpawnedObjectsList; + private FastBufferReader m_ReceivedSceneObjectData; + public void Serialize(FastBufferWriter writer) { if (!writer.TryBeginWrite(sizeof(ulong) + sizeof(int) + sizeof(int))) { - throw new OverflowException( - $"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); + throw new OverflowException($"Not enough space in the write buffer to serialize {nameof(ConnectionApprovedMessage)}"); } writer.WriteValue(OwnerClientId); writer.WriteValue(NetworkTick); - writer.WriteValue(SceneObjectCount); - if (SceneObjectCount != 0) + uint sceneObjectCount = 0; + if (SpawnedObjectsList != null) { + var pos = writer.Position; + writer.Seek(writer.Position + FastBufferWriter.GetWriteSize(sceneObjectCount)); + // Serialize NetworkVariable data foreach (var sobj in SpawnedObjectsList) { @@ -33,34 +36,41 @@ namespace Unity.Netcode sobj.Observers.Add(OwnerClientId); var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); sceneObject.Serialize(writer); + ++sceneObjectCount; } } + writer.Seek(pos); + writer.WriteValue(sceneObjectCount); + writer.Seek(writer.Length); + } + else + { + writer.WriteValue(sceneObjectCount); } } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } if (!reader.TryBeginRead(sizeof(ulong) + sizeof(int) + sizeof(int))) { - throw new OverflowException( - $"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}"); + throw new OverflowException($"Not enough space in the buffer to read {nameof(ConnectionApprovedMessage)}"); } - var message = new ConnectionApprovedMessage(); - reader.ReadValue(out message.OwnerClientId); - reader.ReadValue(out message.NetworkTick); - reader.ReadValue(out message.SceneObjectCount); - message.Handle(reader, context.SenderId, networkManager); + reader.ReadValue(out OwnerClientId); + reader.ReadValue(out NetworkTick); + m_ReceivedSceneObjectData = reader; + return true; } - public void Handle(FastBufferReader reader, ulong clientId, NetworkManager networkManager) + public void Handle(ref NetworkContext context) { + var networkManager = (NetworkManager)context.SystemOwner; networkManager.LocalClientId = OwnerClientId; networkManager.NetworkMetrics.SetConnectionId(networkManager.LocalClientId); @@ -74,20 +84,21 @@ namespace Unity.Netcode if (!networkManager.NetworkConfig.EnableSceneManagement) { networkManager.SpawnManager.DestroySceneObjects(); + m_ReceivedSceneObjectData.ReadValue(out uint sceneObjectCount); // Deserializing NetworkVariable data is deferred from Receive() to Handle to avoid needing // to create a list to hold the data. This is a breach of convention for performance reasons. - for (ushort i = 0; i < SceneObjectCount; i++) + for (ushort i = 0; i < sceneObjectCount; i++) { var sceneObject = new NetworkObject.SceneObject(); - sceneObject.Deserialize(reader); - NetworkObject.AddSceneObject(sceneObject, reader, networkManager); + sceneObject.Deserialize(m_ReceivedSceneObjectData); + NetworkObject.AddSceneObject(sceneObject, m_ReceivedSceneObjectData, networkManager); } // Mark the client being connected networkManager.IsConnectedClient = true; // When scene management is disabled we notify after everything is synchronized - networkManager.InvokeOnClientConnectedCallback(clientId); + networkManager.InvokeOnClientConnectedCallback(context.SenderId); } } } diff --git a/Runtime/Messaging/Messages/ConnectionRequestMessage.cs b/Runtime/Messaging/Messages/ConnectionRequestMessage.cs index 874a9c6..5297eed 100644 --- a/Runtime/Messaging/Messages/ConnectionRequestMessage.cs +++ b/Runtime/Messaging/Messages/ConnectionRequestMessage.cs @@ -21,19 +21,17 @@ namespace Unity.Netcode } } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsServer) { - return; + return false; } - var message = new ConnectionRequestMessage(); if (networkManager.NetworkConfig.ConnectionApproval) { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash) + - FastBufferWriter.GetWriteSize())) + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash) + FastBufferWriter.GetWriteSize())) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -41,11 +39,12 @@ namespace Unity.Netcode } networkManager.DisconnectClient(context.SenderId); - return; + return false; } - reader.ReadValue(out message.ConfigHash); - if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash)) + reader.ReadValue(out ConfigHash); + + if (!networkManager.NetworkConfig.CompareConfig(ConfigHash)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -53,14 +52,14 @@ namespace Unity.Netcode } networkManager.DisconnectClient(context.SenderId); - return; + return false; } - reader.ReadValueSafe(out message.ConnectionData); + reader.ReadValueSafe(out ConnectionData); } else { - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.ConfigHash))) + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(ConfigHash))) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -68,11 +67,11 @@ namespace Unity.Netcode } networkManager.DisconnectClient(context.SenderId); - return; + return false; } - reader.ReadValue(out message.ConfigHash); + reader.ReadValue(out ConfigHash); - if (!networkManager.NetworkConfig.CompareConfig(message.ConfigHash)) + if (!networkManager.NetworkConfig.CompareConfig(ConfigHash)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { @@ -80,14 +79,18 @@ namespace Unity.Netcode } networkManager.DisconnectClient(context.SenderId); - return; + return false; } } - message.Handle(networkManager, context.SenderId); + + return true; } - public void Handle(NetworkManager networkManager, ulong senderId) + public void Handle(ref NetworkContext context) { + var networkManager = (NetworkManager)context.SystemOwner; + var senderId = context.SenderId; + if (networkManager.PendingClients.TryGetValue(senderId, out PendingClient client)) { // Set to pending approval to prevent future connection requests from being approved @@ -102,8 +105,7 @@ namespace Unity.Netcode (createPlayerObject, playerPrefabHash, approved, position, rotation) => { var localCreatePlayerObject = createPlayerObject; - networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, - position, rotation); + networkManager.HandleApproval(senderId, localCreatePlayerObject, playerPrefabHash, approved, position, rotation); }); } else diff --git a/Runtime/Messaging/Messages/CreateObjectMessage.cs b/Runtime/Messaging/Messages/CreateObjectMessage.cs index 3cea33d..4b753a0 100644 --- a/Runtime/Messaging/Messages/CreateObjectMessage.cs +++ b/Runtime/Messaging/Messages/CreateObjectMessage.cs @@ -3,28 +3,33 @@ namespace Unity.Netcode internal struct CreateObjectMessage : INetworkMessage { public NetworkObject.SceneObject ObjectInfo; + private FastBufferReader m_ReceivedNetworkVariableData; public void Serialize(FastBufferWriter writer) { ObjectInfo.Serialize(writer); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } - var message = new CreateObjectMessage(); - message.ObjectInfo.Deserialize(reader); - message.Handle(context.SenderId, reader, networkManager); + + ObjectInfo.Deserialize(reader); + m_ReceivedNetworkVariableData = reader; + + return true; } - public void Handle(ulong senderId, FastBufferReader reader, NetworkManager networkManager) + public void Handle(ref NetworkContext context) { - var networkObject = NetworkObject.AddSceneObject(ObjectInfo, reader, networkManager); - networkManager.NetworkMetrics.TrackObjectSpawnReceived(senderId, networkObject, reader.Length); + var networkManager = (NetworkManager)context.SystemOwner; + var networkObject = NetworkObject.AddSceneObject(ObjectInfo, m_ReceivedNetworkVariableData, networkManager); + + networkManager.NetworkMetrics.TrackObjectSpawnReceived(context.SenderId, networkObject, context.MessageSize); } } } diff --git a/Runtime/Messaging/Messages/DestroyObjectMessage.cs b/Runtime/Messaging/Messages/DestroyObjectMessage.cs index d19904b..a36f4a0 100644 --- a/Runtime/Messaging/Messages/DestroyObjectMessage.cs +++ b/Runtime/Messaging/Messages/DestroyObjectMessage.cs @@ -9,32 +9,27 @@ namespace Unity.Netcode writer.WriteValueSafe(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } - reader.ReadValueSafe(out DestroyObjectMessage message); - message.Handle(context.SenderId, networkManager, reader.Length); + reader.ReadValueSafe(out this); + return true; } - public void Handle(ulong senderId, NetworkManager networkManager, int messageSize) + public void Handle(ref NetworkContext context) { + var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject)) { // This is the same check and log message that happens inside OnDespawnObject, but we have to do it here - // while we still have access to the network ID, otherwise the log message will be less useful. - if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) - { - NetworkLog.LogWarning($"Trying to destroy {nameof(NetworkObject)} #{NetworkObjectId} but it does not exist in {nameof(NetworkSpawnManager.SpawnedObjects)} anymore!"); - } - return; } - networkManager.NetworkMetrics.TrackObjectDestroyReceived(senderId, networkObject, messageSize); + networkManager.NetworkMetrics.TrackObjectDestroyReceived(context.SenderId, networkObject, context.MessageSize); networkManager.SpawnManager.OnDespawnObject(networkObject, true); } } diff --git a/Runtime/Messaging/Messages/NamedMessage.cs b/Runtime/Messaging/Messages/NamedMessage.cs index 2583d4f..246f5aa 100644 --- a/Runtime/Messaging/Messages/NamedMessage.cs +++ b/Runtime/Messaging/Messages/NamedMessage.cs @@ -3,20 +3,26 @@ namespace Unity.Netcode internal struct NamedMessage : INetworkMessage { public ulong Hash; - public FastBufferWriter Data; + public FastBufferWriter SendData; + + private FastBufferReader m_ReceiveData; public unsafe void Serialize(FastBufferWriter writer) { writer.WriteValueSafe(Hash); - writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length); + writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - var message = new NamedMessage(); - reader.ReadValueSafe(out message.Hash); + reader.ReadValueSafe(out Hash); + m_ReceiveData = reader; + return true; + } - ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(message.Hash, context.SenderId, reader, context.SerializedHeaderSize); + public void Handle(ref NetworkContext context) + { + ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeNamedMessage(Hash, context.SenderId, m_ReceiveData, context.SerializedHeaderSize); } } } diff --git a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs index f791a6b..27fcb51 100644 --- a/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs +++ b/Runtime/Messaging/Messages/NetworkVariableDeltaMessage.cs @@ -19,16 +19,18 @@ namespace Unity.Netcode public ulong ClientId; public NetworkBehaviour NetworkBehaviour; + private FastBufferReader m_ReceivedNetworkVariableData; + public void Serialize(FastBufferWriter writer) { - if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + - FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) { - throw new OverflowException( - $"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); + throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); } + writer.WriteValue(NetworkObjectId); writer.WriteValue(NetworkBehaviourIndex); + for (int k = 0; k < NetworkBehaviour.NetworkVariableFields.Count; k++) { if (!DeliveryMappedNetworkVariableIndex.Contains(k)) @@ -36,7 +38,7 @@ namespace Unity.Netcode // This var does not belong to the currently iterating delivery group. if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - writer.WriteValueSafe((short)0); + writer.WriteValueSafe((ushort)0); } else { @@ -69,7 +71,12 @@ namespace Unity.Netcode var tmpWriter = new FastBufferWriter(MessagingSystem.NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.Temp, short.MaxValue); NetworkBehaviour.NetworkVariableFields[k].WriteDelta(tmpWriter); - writer.WriteValueSafe((ushort)tmpWriter.Length); + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize() + tmpWriter.Length)) + { + throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}"); + } + + writer.WriteValue((ushort)tmpWriter.Length); tmpWriter.CopyTo(writer); } else @@ -93,24 +100,25 @@ namespace Unity.Netcode } } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex))) + { + throw new OverflowException($"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); + } + + reader.ReadValue(out NetworkObjectId); + reader.ReadValue(out NetworkBehaviourIndex); + + m_ReceivedNetworkVariableData = reader; + + return true; + } + + public void Handle(ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; - var message = new NetworkVariableDeltaMessage(); - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.NetworkObjectId) + - FastBufferWriter.GetWriteSize(message.NetworkBehaviourIndex))) - { - throw new OverflowException( - $"Not enough data in the buffer to read {nameof(NetworkVariableDeltaMessage)}"); - } - reader.ReadValue(out message.NetworkObjectId); - reader.ReadValue(out message.NetworkBehaviourIndex); - message.Handle(context.SenderId, reader, context, networkManager); - } - - public void Handle(ulong senderId, FastBufferReader reader, in NetworkContext context, NetworkManager networkManager) - { if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject)) { NetworkBehaviour behaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex); @@ -130,7 +138,7 @@ namespace Unity.Netcode if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - reader.ReadValueSafe(out varSize); + m_ReceivedNetworkVariableData.ReadValueSafe(out varSize); if (varSize == 0) { @@ -139,7 +147,7 @@ namespace Unity.Netcode } else { - reader.ReadValueSafe(out bool deltaExists); + m_ReceivedNetworkVariableData.ReadValueSafe(out bool deltaExists); if (!deltaExists) { continue; @@ -157,7 +165,7 @@ namespace Unity.Netcode NetworkLog.LogError($"[{behaviour.NetworkVariableFields[i].GetType().Name}]"); } - reader.Seek(reader.Position + varSize); + m_ReceivedNetworkVariableData.Seek(m_ReceivedNetworkVariableData.Position + varSize); continue; } @@ -176,39 +184,37 @@ namespace Unity.Netcode return; } - int readStartPos = reader.Position; + int readStartPos = m_ReceivedNetworkVariableData.Position; - behaviour.NetworkVariableFields[i].ReadDelta(reader, networkManager.IsServer); + behaviour.NetworkVariableFields[i].ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer); networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived( - senderId, + context.SenderId, networkObject, behaviour.NetworkVariableFields[i].Name, behaviour.__getTypeName(), - reader.Length); + context.MessageSize); if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety) { - if (reader.Position > (readStartPos + varSize)) + if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning( - $"Var delta read too far. {reader.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + NetworkLog.LogWarning($"Var delta read too far. {m_ReceivedNetworkVariableData.Position - (readStartPos + varSize)} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); } - reader.Seek(readStartPos + varSize); + m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); } - else if (reader.Position < (readStartPos + varSize)) + else if (m_ReceivedNetworkVariableData.Position < (readStartPos + varSize)) { if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) { - NetworkLog.LogWarning( - $"Var delta read too little. {(readStartPos + varSize) - reader.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); + NetworkLog.LogWarning($"Var delta read too little. {(readStartPos + varSize) - m_ReceivedNetworkVariableData.Position} bytes. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(behaviour)} - VariableIndex: {i}"); } - reader.Seek(readStartPos + varSize); + m_ReceivedNetworkVariableData.Seek(readStartPos + varSize); } } } @@ -216,7 +222,7 @@ namespace Unity.Netcode } else { - networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context); + networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, m_ReceivedNetworkVariableData, ref context); } } } diff --git a/Runtime/Messaging/Messages/ParentSyncMessage.cs b/Runtime/Messaging/Messages/ParentSyncMessage.cs index 20ba96f..af4ca11 100644 --- a/Runtime/Messaging/Messages/ParentSyncMessage.cs +++ b/Runtime/Messaging/Messages/ParentSyncMessage.cs @@ -26,42 +26,41 @@ namespace Unity.Netcode } } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } - var message = new ParentSyncMessage(); - reader.ReadValueSafe(out message.NetworkObjectId); - reader.ReadValueSafe(out message.IsReparented); - if (message.IsReparented) + reader.ReadValueSafe(out NetworkObjectId); + reader.ReadValueSafe(out IsReparented); + if (IsReparented) { - reader.ReadValueSafe(out message.IsLatestParentSet); - if (message.IsLatestParentSet) + reader.ReadValueSafe(out IsLatestParentSet); + if (IsLatestParentSet) { reader.ReadValueSafe(out ulong latestParent); - message.LatestParent = latestParent; + LatestParent = latestParent; } } - message.Handle(reader, context, networkManager); + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) + { + networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, ref context); + return false; + } + + return true; } - public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager) + public void Handle(ref NetworkContext context) { - if (networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId)) - { - var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; - networkObject.SetNetworkParenting(IsReparented, LatestParent); - networkObject.ApplyNetworkParenting(); - } - else - { - networkManager.SpawnManager.TriggerOnSpawn(NetworkObjectId, reader, context); - } + var networkManager = (NetworkManager)context.SystemOwner; + var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId]; + networkObject.SetNetworkParenting(IsReparented, LatestParent); + networkObject.ApplyNetworkParenting(); } } } diff --git a/Runtime/Messaging/Messages/RpcMessage.cs b/Runtime/Messaging/Messages/RpcMessage.cs deleted file mode 100644 index 723fe20..0000000 --- a/Runtime/Messaging/Messages/RpcMessage.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; - -namespace Unity.Netcode -{ - internal struct RpcMessage : INetworkMessage - { - public enum RpcType : byte - { - Server, - Client - } - - public struct HeaderData - { - public RpcType Type; - public ulong NetworkObjectId; - public ushort NetworkBehaviourId; - public uint NetworkMethodId; - } - - public HeaderData Header; - public FastBufferWriter RpcData; - - - public unsafe void Serialize(FastBufferWriter writer) - { - if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Header) + RpcData.Length)) - { - throw new OverflowException("Not enough space in the buffer to store RPC data."); - } - writer.WriteValue(Header); - writer.WriteBytes(RpcData.GetUnsafePtr(), RpcData.Length); - } - - public static void Receive(FastBufferReader reader, in NetworkContext context) - { - var message = new RpcMessage(); - if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(message.Header))) - { - throw new OverflowException("Not enough space in the buffer to read RPC data."); - } - reader.ReadValue(out message.Header); - message.Handle(reader, context, (NetworkManager)context.SystemOwner, context.SenderId, true); - } - - public void Handle(FastBufferReader reader, in NetworkContext context, NetworkManager networkManager, ulong senderId, bool canDefer) - { - if (NetworkManager.__rpc_func_table.ContainsKey(Header.NetworkMethodId)) - { - if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(Header.NetworkObjectId)) - { - if (canDefer) - { - networkManager.SpawnManager.TriggerOnSpawn(Header.NetworkObjectId, reader, context); - } - else - { - NetworkLog.LogError($"Tried to invoke an RPC on a non-existent {nameof(NetworkObject)} with {nameof(canDefer)}=false"); - } - return; - } - - var networkObject = networkManager.SpawnManager.SpawnedObjects[Header.NetworkObjectId]; - - var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(Header.NetworkBehaviourId); - if (networkBehaviour == null) - { - return; - } - - var rpcParams = new __RpcParams(); - switch (Header.Type) - { - case RpcType.Server: - rpcParams.Server = new ServerRpcParams - { - Receive = new ServerRpcReceiveParams - { - SenderClientId = senderId - } - }; - break; - case RpcType.Client: - rpcParams.Client = new ClientRpcParams - { - Receive = new ClientRpcReceiveParams - { - } - }; - break; - } - - NetworkManager.__rpc_func_table[Header.NetworkMethodId](networkBehaviour, reader, rpcParams); - -#if DEVELOPMENT_BUILD || UNITY_EDITOR - if (NetworkManager.__rpc_name_table.TryGetValue(Header.NetworkMethodId, out var rpcMethodName)) - { - networkManager.NetworkMetrics.TrackRpcReceived( - senderId, - networkObject, - rpcMethodName, - networkBehaviour.__getTypeName(), - reader.Length); - } -#endif - } - } - } -} diff --git a/Runtime/Messaging/Messages/RpcMessages.cs b/Runtime/Messaging/Messages/RpcMessages.cs new file mode 100644 index 0000000..1645f71 --- /dev/null +++ b/Runtime/Messaging/Messages/RpcMessages.cs @@ -0,0 +1,157 @@ +using System; +using UnityEngine; +using Unity.Collections; + +namespace Unity.Netcode +{ + internal static class RpcMessageHelpers + { + public static unsafe void Serialize(ref FastBufferWriter writer, ref RpcMetadata metadata, ref FastBufferWriter payload) + { + if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize() + payload.Length)) + { + throw new OverflowException("Not enough space in the buffer to store RPC data."); + } + + writer.WriteValue(metadata); + writer.WriteBytes(payload.GetUnsafePtr(), payload.Length); + } + + public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkContext context, ref RpcMetadata metadata, ref FastBufferReader payload) + { + int metadataSize = FastBufferWriter.GetWriteSize(); + if (!reader.TryBeginRead(metadataSize)) + { + throw new InvalidOperationException("Not enough data in the buffer to read RPC meta."); + } + + reader.ReadValue(out metadata); + + var networkManager = (NetworkManager)context.SystemOwner; + if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(metadata.NetworkObjectId)) + { + networkManager.SpawnManager.TriggerOnSpawn(metadata.NetworkObjectId, reader, ref context); + 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 (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId)) + { + return false; + } + + payload = new FastBufferReader(reader.GetUnsafePtr() + metadataSize, Allocator.None, reader.Length - metadataSize); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) + { + networkManager.NetworkMetrics.TrackRpcReceived( + context.SenderId, + networkObject, + rpcMethodName, + networkBehaviour.__getTypeName(), + 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)) + { + throw new InvalidOperationException($"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."); + } + var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId); + + try + { + NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); + } + catch (Exception ex) + { + Debug.LogException(new Exception("Unhandled RPC exception!", ex)); + } + } + } + + internal struct RpcMetadata + { + public ulong NetworkObjectId; + public ushort NetworkBehaviourId; + public uint NetworkRpcMethodId; + } + + internal struct ServerRpcMessage : INetworkMessage + { + public RpcMetadata Metadata; + + public FastBufferWriter WriteBuffer; + public FastBufferReader ReadBuffer; + + public unsafe void Serialize(FastBufferWriter writer) + { + RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); + } + + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); + } + + public void Handle(ref NetworkContext context) + { + var rpcParams = new __RpcParams + { + Server = new ServerRpcParams + { + Receive = new ServerRpcReceiveParams + { + SenderClientId = context.SenderId + } + } + }; + RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams); + } + } + + internal struct ClientRpcMessage : INetworkMessage + { + public RpcMetadata Metadata; + + public FastBufferWriter WriteBuffer; + public FastBufferReader ReadBuffer; + + public void Serialize(FastBufferWriter writer) + { + RpcMessageHelpers.Serialize(ref writer, ref Metadata, ref WriteBuffer); + } + + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + return RpcMessageHelpers.Deserialize(ref reader, ref context, ref Metadata, ref ReadBuffer); + } + + public void Handle(ref NetworkContext context) + { + var rpcParams = new __RpcParams + { + Client = new ClientRpcParams + { + Receive = new ClientRpcReceiveParams + { + } + } + }; + RpcMessageHelpers.Handle(ref context, ref Metadata, ref ReadBuffer, ref rpcParams); + } + } +} diff --git a/Runtime/Messaging/Messages/RpcMessage.cs.meta b/Runtime/Messaging/Messages/RpcMessages.cs.meta similarity index 100% rename from Runtime/Messaging/Messages/RpcMessage.cs.meta rename to Runtime/Messaging/Messages/RpcMessages.cs.meta diff --git a/Runtime/Messaging/Messages/SceneEventMessage.cs b/Runtime/Messaging/Messages/SceneEventMessage.cs index 2104211..48be77d 100644 --- a/Runtime/Messaging/Messages/SceneEventMessage.cs +++ b/Runtime/Messaging/Messages/SceneEventMessage.cs @@ -6,14 +6,22 @@ namespace Unity.Netcode { public SceneEventData EventData; + private FastBufferReader m_ReceivedData; + public void Serialize(FastBufferWriter writer) { EventData.Serialize(writer); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - ((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, reader); + m_ReceivedData = reader; + return true; + } + + public void Handle(ref NetworkContext context) + { + ((NetworkManager)context.SystemOwner).SceneManager.HandleSceneEvent(context.SenderId, m_ReceivedData); } } } diff --git a/Runtime/Messaging/Messages/ServerLogMessage.cs b/Runtime/Messaging/Messages/ServerLogMessage.cs index 41e17bf..8f0cde8 100644 --- a/Runtime/Messaging/Messages/ServerLogMessage.cs +++ b/Runtime/Messaging/Messages/ServerLogMessage.cs @@ -17,21 +17,25 @@ namespace Unity.Netcode BytePacker.WriteValuePacked(writer, Message); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (networkManager.IsServer && networkManager.NetworkConfig.EnableNetworkLogs) { - var message = new ServerLogMessage(); - reader.ReadValueSafe(out message.LogType); - ByteUnpacker.ReadValuePacked(reader, out message.Message); - message.Handle(context.SenderId, networkManager, reader.Length); + reader.ReadValueSafe(out LogType); + ByteUnpacker.ReadValuePacked(reader, out Message); + return true; } + + return false; } - public void Handle(ulong senderId, NetworkManager networkManager, int messageSize) + public void Handle(ref NetworkContext context) { - networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, messageSize); + var networkManager = (NetworkManager)context.SystemOwner; + var senderId = context.SenderId; + + networkManager.NetworkMetrics.TrackServerLogReceived(senderId, (uint)LogType, context.MessageSize); switch (LogType) { diff --git a/Runtime/Messaging/Messages/SnapshotDataMessage.cs b/Runtime/Messaging/Messages/SnapshotDataMessage.cs index bc0aa94..ae887ea 100644 --- a/Runtime/Messaging/Messages/SnapshotDataMessage.cs +++ b/Runtime/Messaging/Messages/SnapshotDataMessage.cs @@ -76,9 +76,6 @@ namespace Unity.Netcode Despawns.Length * sizeof(DespawnData) )) { - Entries.Dispose(); - Spawns.Dispose(); - Despawns.Dispose(); throw new OverflowException($"Not enough space to serialize {nameof(SnapshotDataMessage)}"); } writer.WriteValue(CurrentTick); @@ -96,76 +93,67 @@ namespace Unity.Netcode writer.WriteValue((ushort)Despawns.Length); writer.WriteBytes((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData)); - - Entries.Dispose(); - Spawns.Dispose(); - Despawns.Dispose(); } - public static unsafe void Receive(FastBufferReader reader, in NetworkContext context) + public unsafe bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - var message = new SnapshotDataMessage(); if (!reader.TryBeginRead( - FastBufferWriter.GetWriteSize(message.CurrentTick) + - FastBufferWriter.GetWriteSize(message.Sequence) + - FastBufferWriter.GetWriteSize(message.Range) + FastBufferWriter.GetWriteSize(CurrentTick) + + FastBufferWriter.GetWriteSize(Sequence) + + FastBufferWriter.GetWriteSize(Range) )) { throw new OverflowException($"Not enough space to deserialize {nameof(SnapshotDataMessage)}"); } - reader.ReadValue(out message.CurrentTick); - reader.ReadValue(out message.Sequence); + reader.ReadValue(out CurrentTick); + reader.ReadValue(out Sequence); - reader.ReadValue(out message.Range); - message.ReceiveMainBuffer = new NativeArray(message.Range, Allocator.Temp); - reader.ReadBytesSafe((byte*)message.ReceiveMainBuffer.GetUnsafePtr(), message.Range); - reader.ReadValueSafe(out message.Ack); + reader.ReadValue(out Range); + ReceiveMainBuffer = new NativeArray(Range, Allocator.Temp); + reader.ReadBytesSafe((byte*)ReceiveMainBuffer.GetUnsafePtr(), Range); + reader.ReadValueSafe(out Ack); reader.ReadValueSafe(out ushort length); - message.Entries = new NativeList(length, Allocator.Temp); - message.Entries.Length = length; - reader.ReadBytesSafe((byte*)message.Entries.GetUnsafePtr(), message.Entries.Length * sizeof(EntryData)); + Entries = new NativeList(length, Allocator.Temp) { Length = length }; + reader.ReadBytesSafe((byte*)Entries.GetUnsafePtr(), Entries.Length * sizeof(EntryData)); reader.ReadValueSafe(out length); - message.Spawns = new NativeList(length, Allocator.Temp); - message.Spawns.Length = length; - reader.ReadBytesSafe((byte*)message.Spawns.GetUnsafePtr(), message.Spawns.Length * sizeof(SpawnData)); + Spawns = new NativeList(length, Allocator.Temp) { Length = length }; + reader.ReadBytesSafe((byte*)Spawns.GetUnsafePtr(), Spawns.Length * sizeof(SpawnData)); reader.ReadValueSafe(out length); - message.Despawns = new NativeList(length, Allocator.Temp); - message.Despawns.Length = length; - reader.ReadBytesSafe((byte*)message.Despawns.GetUnsafePtr(), message.Despawns.Length * sizeof(DespawnData)); + Despawns = new NativeList(length, Allocator.Temp) { Length = length }; + reader.ReadBytesSafe((byte*)Despawns.GetUnsafePtr(), Despawns.Length * sizeof(DespawnData)); - using (message.ReceiveMainBuffer) - using (message.Entries) - using (message.Spawns) - using (message.Despawns) - { - message.Handle(context.SenderId, context.SystemOwner); - } + return true; } - public void Handle(ulong senderId, object systemOwner) + public void Handle(ref NetworkContext context) { - if (systemOwner is NetworkManager) + using (ReceiveMainBuffer) + using (Entries) + using (Spawns) + using (Despawns) { - var networkManager = (NetworkManager)systemOwner; - - // todo: temporary hack around bug - if (!networkManager.IsServer) + var systemOwner = context.SystemOwner; + var senderId = context.SenderId; + if (systemOwner is NetworkManager networkManager) { - senderId = networkManager.ServerClientId; - } + // todo: temporary hack around bug + if (!networkManager.IsServer) + { + senderId = networkManager.ServerClientId; + } - var snapshotSystem = networkManager.SnapshotSystem; - snapshotSystem.HandleSnapshot(senderId, this); - } - else - { - var ownerData = (Tuple)systemOwner; - var snapshotSystem = ownerData.Item1; - snapshotSystem.HandleSnapshot(ownerData.Item2, this); - return; + var snapshotSystem = networkManager.SnapshotSystem; + snapshotSystem.HandleSnapshot(senderId, this); + } + else + { + var ownerData = (Tuple)systemOwner; + var snapshotSystem = ownerData.Item1; + snapshotSystem.HandleSnapshot(ownerData.Item2, this); + } } } } diff --git a/Runtime/Messaging/Messages/TimeSyncMessage.cs b/Runtime/Messaging/Messages/TimeSyncMessage.cs index 6cbdfdc..5430acb 100644 --- a/Runtime/Messaging/Messages/TimeSyncMessage.cs +++ b/Runtime/Messaging/Messages/TimeSyncMessage.cs @@ -9,21 +9,22 @@ namespace Unity.Netcode writer.WriteValueSafe(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { var networkManager = (NetworkManager)context.SystemOwner; if (!networkManager.IsClient) { - return; + return false; } - reader.ReadValueSafe(out TimeSyncMessage message); - message.Handle(context.SenderId, networkManager); + reader.ReadValueSafe(out this); + return true; } - public void Handle(ulong senderId, NetworkManager networkManager) + public void Handle(ref NetworkContext context) { + var networkManager = (NetworkManager)context.SystemOwner; var time = new NetworkTime(networkManager.NetworkTickSystem.TickRate, Tick); - networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(senderId) / 1000d); + networkManager.NetworkTimeSystem.Sync(time.Time, networkManager.NetworkConfig.NetworkTransport.GetCurrentRtt(context.SenderId) / 1000d); } } } diff --git a/Runtime/Messaging/Messages/UnnamedMessage.cs b/Runtime/Messaging/Messages/UnnamedMessage.cs index 7969e88..b34d200 100644 --- a/Runtime/Messaging/Messages/UnnamedMessage.cs +++ b/Runtime/Messaging/Messages/UnnamedMessage.cs @@ -2,16 +2,23 @@ namespace Unity.Netcode { internal struct UnnamedMessage : INetworkMessage { - public FastBufferWriter Data; + public FastBufferWriter SendData; + private FastBufferReader m_ReceivedData; public unsafe void Serialize(FastBufferWriter writer) { - writer.WriteBytesSafe(Data.GetUnsafePtr(), Data.Length); + writer.WriteBytesSafe(SendData.GetUnsafePtr(), SendData.Length); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { - ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, reader, context.SerializedHeaderSize); + m_ReceivedData = reader; + return true; + } + + public void Handle(ref NetworkContext context) + { + ((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize); } } } diff --git a/Runtime/Messaging/MessagingSystem.cs b/Runtime/Messaging/MessagingSystem.cs index a74a856..07f1524 100644 --- a/Runtime/Messaging/MessagingSystem.cs +++ b/Runtime/Messaging/MessagingSystem.cs @@ -40,7 +40,7 @@ namespace Unity.Netcode } } - internal delegate void MessageHandler(FastBufferReader reader, in NetworkContext context); + internal delegate void MessageHandler(FastBufferReader reader, ref NetworkContext context, MessagingSystem system); private NativeList m_IncomingMessageQueue = new NativeList(16, Allocator.Persistent); @@ -118,7 +118,7 @@ namespace Unity.Netcode for (var queueIndex = 0; queueIndex < m_IncomingMessageQueue.Length; ++queueIndex) { // Avoid copies... - ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(queueIndex); + ref var item = ref m_IncomingMessageQueue.ElementAt(queueIndex); item.Reader.Dispose(); } @@ -236,6 +236,7 @@ namespace Unity.Netcode Timestamp = timestamp, Header = header, SerializedHeaderSize = serializedHeaderSize, + MessageSize = header.MessageSize, }; var type = m_ReverseTypeMap[header.MessageType]; @@ -260,7 +261,7 @@ namespace Unity.Netcode // for some dynamic-length value. try { - handler.Invoke(reader, context); + handler.Invoke(reader, ref context, this); } catch (Exception e) { @@ -278,7 +279,7 @@ namespace Unity.Netcode for (var index = 0; index < m_IncomingMessageQueue.Length; ++index) { // Avoid copies... - ref var item = ref m_IncomingMessageQueue.GetUnsafeList()->ElementAt(index); + ref var item = ref m_IncomingMessageQueue.ElementAt(index); HandleMessage(item.Header, item.Reader, item.SenderId, item.Timestamp, item.MessageHeaderSerializedSize); if (m_Disposed) { @@ -313,12 +314,31 @@ namespace Unity.Netcode var queue = m_SendQueues[clientId]; for (var i = 0; i < queue.Length; ++i) { - queue.GetUnsafeList()->ElementAt(i).Writer.Dispose(); + queue.ElementAt(i).Writer.Dispose(); } queue.Dispose(); } + public static void ReceiveMessage(FastBufferReader reader, ref NetworkContext context, MessagingSystem system) where T : INetworkMessage, new() + { + var message = new T(); + if (message.Deserialize(reader, ref context)) + { + for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx) + { + system.m_Hooks[hookIdx].OnBeforeHandleMessage(ref message, ref context); + } + + message.Handle(ref context); + + for (var hookIdx = 0; hookIdx < system.m_Hooks.Count; ++hookIdx) + { + system.m_Hooks[hookIdx].OnAfterHandleMessage(ref message, ref context); + } + } + } + private bool CanSend(ulong clientId, Type messageType, NetworkDelivery delivery) { for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) @@ -332,7 +352,7 @@ namespace Unity.Netcode return true; } - internal unsafe int SendMessage(in TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds) + internal int SendMessage(ref TMessageType message, NetworkDelivery delivery, in TClientIdListType clientIds) where TMessageType : INetworkMessage where TClientIdListType : IReadOnlyList { @@ -347,11 +367,17 @@ namespace Unity.Netcode message.Serialize(tmpSerializer); + return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, clientIds); + } + + internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, in IReadOnlyList clientIds) + where TMessageType : INetworkMessage + { using var headerSerializer = new FastBufferWriter(FastBufferWriter.GetWriteSize(), Allocator.Temp); var header = new MessageHeader { - MessageSize = (ushort)tmpSerializer.Length, + MessageSize = (uint)tmpSerializer.Length, MessageType = m_MessageTypes[typeof(TMessageType)], }; BytePacker.WriteValueBitPacked(headerSerializer, header.MessageType); @@ -368,7 +394,7 @@ namespace Unity.Netcode for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { - m_Hooks[hookIdx].OnBeforeSendMessage(clientId, typeof(TMessageType), delivery); + m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery); } var sendQueueItem = m_SendQueues[clientId]; @@ -376,22 +402,22 @@ namespace Unity.Netcode { sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob, maxSize)); - sendQueueItem.GetUnsafeList()->ElementAt(0).Writer.Seek(sizeof(BatchHeader)); + sendQueueItem.ElementAt(0).Writer.Seek(sizeof(BatchHeader)); } else { - ref var lastQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); + ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1); if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length) { sendQueueItem.Add(new SendQueueItem(delivery, NON_FRAGMENTED_MESSAGE_MAX_SIZE, Allocator.TempJob, maxSize)); - sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader)); + sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(BatchHeader)); } } - ref var writeQueueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(sendQueueItem.Length - 1); + ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1); writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length); writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length); @@ -399,13 +425,20 @@ namespace Unity.Netcode writeQueueItem.BatchHeader.BatchSize++; for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { - m_Hooks[hookIdx].OnAfterSendMessage(clientId, typeof(TMessageType), delivery, tmpSerializer.Length + headerSerializer.Length); + m_Hooks[hookIdx].OnAfterSendMessage(clientId, ref message, delivery, tmpSerializer.Length + headerSerializer.Length); } } return tmpSerializer.Length + headerSerializer.Length; } + internal unsafe int SendPreSerializedMessage(in FastBufferWriter tmpSerializer, int maxSize, ref TMessageType message, NetworkDelivery delivery, ulong clientId) + where TMessageType : INetworkMessage + { + ulong* clientIds = stackalloc ulong[] { clientId }; + return SendPreSerializedMessage(tmpSerializer, maxSize, ref message, delivery, new PointerListWrapper(clientIds, 1)); + } + private struct PointerListWrapper : IReadOnlyList where T : unmanaged { @@ -441,24 +474,24 @@ namespace Unity.Netcode } } - internal unsafe int SendMessage(in T message, NetworkDelivery delivery, + internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, ulong* clientIds, int numClientIds) where T : INetworkMessage { - return SendMessage(message, delivery, new PointerListWrapper(clientIds, numClientIds)); + return SendMessage(ref message, delivery, new PointerListWrapper(clientIds, numClientIds)); } - internal unsafe int SendMessage(in T message, NetworkDelivery delivery, ulong clientId) + internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, ulong clientId) where T : INetworkMessage { ulong* clientIds = stackalloc ulong[] { clientId }; - return SendMessage(message, delivery, new PointerListWrapper(clientIds, 1)); + return SendMessage(ref message, delivery, new PointerListWrapper(clientIds, 1)); } - internal unsafe int SendMessage(in T message, NetworkDelivery delivery, in NativeArray clientIds) + internal unsafe int SendMessage(ref T message, NetworkDelivery delivery, in NativeArray clientIds) where T : INetworkMessage { - return SendMessage(message, delivery, new PointerListWrapper((ulong*)clientIds.GetUnsafePtr(), clientIds.Length)); + return SendMessage(ref message, delivery, new PointerListWrapper((ulong*)clientIds.GetUnsafePtr(), clientIds.Length)); } internal unsafe void ProcessSendQueues() @@ -469,7 +502,7 @@ namespace Unity.Netcode var sendQueueItem = kvp.Value; for (var i = 0; i < sendQueueItem.Length; ++i) { - ref var queueItem = ref sendQueueItem.GetUnsafeList()->ElementAt(i); + ref var queueItem = ref sendQueueItem.ElementAt(i); if (queueItem.BatchHeader.BatchSize == 0) { queueItem.Writer.Dispose(); diff --git a/Runtime/Messaging/NetworkContext.cs b/Runtime/Messaging/NetworkContext.cs index f588b5c..8ddc3cb 100644 --- a/Runtime/Messaging/NetworkContext.cs +++ b/Runtime/Messaging/NetworkContext.cs @@ -30,5 +30,10 @@ namespace Unity.Netcode /// The actual serialized size of the header when packed into the buffer /// public int SerializedHeaderSize; + + /// + /// The size of the message in the buffer, header excluded + /// + public uint MessageSize; } } diff --git a/Runtime/Metrics/INetworkMetrics.cs b/Runtime/Metrics/INetworkMetrics.cs index f187deb..a49693a 100644 --- a/Runtime/Metrics/INetworkMetrics.cs +++ b/Runtime/Metrics/INetworkMetrics.cs @@ -83,6 +83,12 @@ namespace Unity.Netcode void TrackSceneEventReceived(ulong senderClientId, uint sceneEventType, string sceneName, long bytesCount); + void TrackPacketSent(uint packetCount); + + void TrackPacketReceived(uint packetCount); + + void TrackRttToServer(int rtt); + void DispatchFrame(); } } diff --git a/Runtime/Metrics/MetricHooks.cs b/Runtime/Metrics/MetricHooks.cs index 741aac5..9e77376 100644 --- a/Runtime/Metrics/MetricHooks.cs +++ b/Runtime/Metrics/MetricHooks.cs @@ -11,14 +11,13 @@ namespace Unity.Netcode m_NetworkManager = networkManager; } - - public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage { } - public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage { - m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, messageType.Name, messageSizeBytes); + m_NetworkManager.NetworkMetrics.TrackNetworkMessageSent(clientId, typeof(T).Name, messageSizeBytes); } public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) @@ -57,5 +56,15 @@ namespace Unity.Netcode { return true; } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + // TODO: Per-message metrics recording moved here + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + // TODO: Per-message metrics recording moved here + } } } diff --git a/Runtime/Metrics/NetworkMetrics.cs b/Runtime/Metrics/NetworkMetrics.cs index 4c00b1e..7df6d53 100644 --- a/Runtime/Metrics/NetworkMetrics.cs +++ b/Runtime/Metrics/NetworkMetrics.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using Unity.Multiplayer.Tools; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.NetStats; +using Unity.Profiling; using UnityEngine; namespace Unity.Netcode @@ -14,6 +15,8 @@ namespace Unity.Netcode static Dictionary s_SceneEventTypeNames; + static ProfilerMarker s_FrameDispatch = new ProfilerMarker($"{nameof(NetworkMetrics)}.DispatchFrame"); + static NetworkMetrics() { s_SceneEventTypeNames = new Dictionary(); @@ -63,6 +66,21 @@ namespace Unity.Netcode private readonly EventMetric m_SceneEventSentEvent = new EventMetric(NetworkMetricTypes.SceneEventSent.Id); private readonly EventMetric m_SceneEventReceivedEvent = new EventMetric(NetworkMetricTypes.SceneEventReceived.Id); +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + private readonly Counter m_PacketSentCounter = new Counter(NetworkMetricTypes.PacketsSent.Id) + { + ShouldResetOnDispatch = true, + }; + private readonly Counter m_PacketReceivedCounter = new Counter(NetworkMetricTypes.PacketsReceived.Id) + { + ShouldResetOnDispatch = true, + }; + private readonly Gauge m_RttToServerGauge = new Gauge(NetworkMetricTypes.RttToServer.Id) + { + ShouldResetOnDispatch = true, + }; +#endif + private ulong m_NumberOfMetricsThisFrame; public NetworkMetrics() @@ -79,6 +97,10 @@ namespace Unity.Netcode .WithMetricEvents(m_RpcSentEvent, m_RpcReceivedEvent) .WithMetricEvents(m_ServerLogSentEvent, m_ServerLogReceivedEvent) .WithMetricEvents(m_SceneEventSentEvent, m_SceneEventReceivedEvent) +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + .WithCounters(m_PacketSentCounter, m_PacketReceivedCounter) + .WithGauges(m_RttToServerGauge) +#endif .Build(); Dispatcher.RegisterObserver(NetcodeObserver.Observer); @@ -404,9 +426,49 @@ namespace Unity.Netcode IncrementMetricCount(); } + public void TrackPacketSent(uint packetCount) + { +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + if (!CanSendMetrics) + { + return; + } + + m_PacketSentCounter.Increment(packetCount); + IncrementMetricCount(); +#endif + } + + public void TrackPacketReceived(uint packetCount) + { +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + if (!CanSendMetrics) + { + return; + } + + m_PacketReceivedCounter.Increment(packetCount); + IncrementMetricCount(); +#endif + } + + public void TrackRttToServer(int rtt) + { +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + if (!CanSendMetrics) + { + return; + } + + m_RttToServerGauge.Set(rtt); +#endif + } + public void DispatchFrame() { + s_FrameDispatch.Begin(); Dispatcher.Dispatch(); + s_FrameDispatch.End(); m_NumberOfMetricsThisFrame = 0; } @@ -421,7 +483,7 @@ namespace Unity.Netcode } } - internal class NetcodeObserver + internal class NetcodeObserver { public static IMetricObserver Observer { get; } = MetricObserverFactory.Construct(); } diff --git a/Runtime/Metrics/NullNetworkMetrics.cs b/Runtime/Metrics/NullNetworkMetrics.cs index 514af8f..b925110 100644 --- a/Runtime/Metrics/NullNetworkMetrics.cs +++ b/Runtime/Metrics/NullNetworkMetrics.cs @@ -137,6 +137,18 @@ namespace Unity.Netcode { } + public void TrackPacketSent(uint packetCount) + { + } + + public void TrackPacketReceived(uint packetCount) + { + } + + public void TrackRttToServer(int rtt) + { + } + public void DispatchFrame() { } diff --git a/Runtime/NetworkVariable/Collections/NetworkList.cs b/Runtime/NetworkVariable/Collections/NetworkList.cs index fb480d4..53a325f 100644 --- a/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -90,18 +90,18 @@ namespace Unity.Netcode { case NetworkListEvent.EventType.Add: { - writer.WriteValueSafe(m_DirtyEvents[i].Value); + NetworkVariable.Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Insert: { writer.WriteValueSafe(m_DirtyEvents[i].Index); - writer.WriteValueSafe(m_DirtyEvents[i].Value); + NetworkVariable.Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Remove: { - writer.WriteValueSafe(m_DirtyEvents[i].Value); + NetworkVariable.Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.RemoveAt: @@ -112,7 +112,7 @@ namespace Unity.Netcode case NetworkListEvent.EventType.Value: { writer.WriteValueSafe(m_DirtyEvents[i].Index); - writer.WriteValueSafe(m_DirtyEvents[i].Value); + NetworkVariable.Write(writer, m_DirtyEvents[i].Value); } break; case NetworkListEvent.EventType.Clear: @@ -130,7 +130,7 @@ namespace Unity.Netcode writer.WriteValueSafe((ushort)m_List.Length); for (int i = 0; i < m_List.Length; i++) { - writer.WriteValueSafe(m_List[i]); + NetworkVariable.Write(writer, m_List[i]); } } @@ -141,7 +141,7 @@ namespace Unity.Netcode reader.ReadValueSafe(out ushort count); for (int i = 0; i < count; i++) { - reader.ReadValueSafe(out T value); + NetworkVariable.Read(reader, out T value); m_List.Add(value); } } @@ -157,7 +157,7 @@ namespace Unity.Netcode { case NetworkListEvent.EventType.Add: { - reader.ReadValueSafe(out T value); + NetworkVariable.Read(reader, out T value); m_List.Add(value); if (OnListChanged != null) @@ -184,7 +184,7 @@ namespace Unity.Netcode case NetworkListEvent.EventType.Insert: { reader.ReadValueSafe(out int index); - reader.ReadValueSafe(out T value); + NetworkVariable.Read(reader, out T value); m_List.InsertRangeWithBeginEnd(index, index + 1); m_List[index] = value; @@ -211,7 +211,7 @@ namespace Unity.Netcode break; case NetworkListEvent.EventType.Remove: { - reader.ReadValueSafe(out T value); + NetworkVariable.Read(reader, out T value); int index = m_List.IndexOf(value); if (index == -1) { @@ -271,7 +271,7 @@ namespace Unity.Netcode case NetworkListEvent.EventType.Value: { reader.ReadValueSafe(out int index); - reader.ReadValueSafe(out T value); + NetworkVariable.Read(reader, out T value); if (index >= m_List.Length) { throw new Exception("Shouldn't be here, index is higher than list length"); diff --git a/Runtime/NetworkVariable/NetworkVariable.cs b/Runtime/NetworkVariable/NetworkVariable.cs index f247bac..ec8cb50 100644 --- a/Runtime/NetworkVariable/NetworkVariable.cs +++ b/Runtime/NetworkVariable/NetworkVariable.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode public class NetworkVariable : NetworkVariableBase where T : unmanaged { // Functions that know how to serialize INetworkSerializable - internal static void WriteNetworkSerializable(FastBufferWriter writer, ref TForMethod value) + internal static void WriteNetworkSerializable(FastBufferWriter writer, in TForMethod value) where TForMethod : INetworkSerializable, new() { writer.WriteNetworkSerializable(value); @@ -22,7 +22,7 @@ namespace Unity.Netcode } // Functions that serialize other types - private static void WriteValue(FastBufferWriter writer, ref TForMethod value) where TForMethod : unmanaged + private static void WriteValue(FastBufferWriter writer, in TForMethod value) where TForMethod : unmanaged { writer.WriteValueSafe(value); } @@ -33,7 +33,7 @@ namespace Unity.Netcode reader.ReadValueSafe(out value); } - internal delegate void WriteDelegate(FastBufferWriter writer, ref TForMethod value); + internal delegate void WriteDelegate(FastBufferWriter writer, in TForMethod value); internal delegate void ReadDelegate(FastBufferReader reader, out TForMethod value); @@ -174,7 +174,7 @@ namespace Unity.Netcode /// public override void WriteField(FastBufferWriter writer) { - Write(writer, ref m_InternalValue); + Write(writer, m_InternalValue); } } } diff --git a/Runtime/NetworkVariable/NetworkVariableBase.cs b/Runtime/NetworkVariable/NetworkVariableBase.cs index 60d61eb..f3db645 100644 --- a/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -10,7 +10,7 @@ namespace Unity.Netcode /// /// The delivery type (QoS) to send data with /// - internal const NetworkDelivery Delivery = NetworkDelivery.ReliableSequenced; + internal const NetworkDelivery Delivery = NetworkDelivery.ReliableFragmentedSequenced; private protected NetworkBehaviour m_NetworkBehaviour; diff --git a/Runtime/Profiling/ProfilingHooks.cs b/Runtime/Profiling/ProfilingHooks.cs index a328be4..94c0910 100644 --- a/Runtime/Profiling/ProfilingHooks.cs +++ b/Runtime/Profiling/ProfilingHooks.cs @@ -37,14 +37,14 @@ namespace Unity.Netcode return marker; } - public void OnBeforeSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery) + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage { - GetSenderProfilerMarker(messageType).Begin(); + GetSenderProfilerMarker(typeof(T)).Begin(); } - public void OnAfterSendMessage(ulong clientId, Type messageType, NetworkDelivery delivery, int messageSizeBytes) + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage { - GetSenderProfilerMarker(messageType).End(); + GetSenderProfilerMarker(typeof(T)).End(); } public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) @@ -86,5 +86,15 @@ namespace Unity.Netcode { return true; } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + // nop + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + // nop + } } } diff --git a/Runtime/SceneManagement/ISceneManagerHandler.cs b/Runtime/SceneManagement/ISceneManagerHandler.cs new file mode 100644 index 0000000..ceab01c --- /dev/null +++ b/Runtime/SceneManagement/ISceneManagerHandler.cs @@ -0,0 +1,28 @@ +using System; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Unity.Netcode +{ + /// + /// Used to override the LoadSceneAsync and UnloadSceneAsync methods called + /// within the NetworkSceneManager. + /// + internal interface ISceneManagerHandler + { + // Generic action to call when a scene is finished loading/unloading + struct SceneEventAction + { + internal uint SceneEventId; + internal Action EventAction; + internal void Invoke() + { + EventAction.Invoke(SceneEventId); + } + } + + AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventAction sceneEventAction); + + AsyncOperation UnloadSceneAsync(Scene scene, SceneEventAction sceneEventAction); + } +} diff --git a/Runtime/SceneManagement/ISceneManagerHandler.cs.meta b/Runtime/SceneManagement/ISceneManagerHandler.cs.meta new file mode 100644 index 0000000..8afc3d0 --- /dev/null +++ b/Runtime/SceneManagement/ISceneManagerHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de907a9fb8151e240800dbcc97f8e745 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/SceneManagement/NetworkSceneManager.cs b/Runtime/SceneManagement/NetworkSceneManager.cs index c40b7b5..9d692a8 100644 --- a/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/Runtime/SceneManagement/NetworkSceneManager.cs @@ -139,13 +139,7 @@ namespace Unity.Netcode /// Used to detect if a scene event is underway /// Only 1 scene event can occur on the server at a time for now. /// - private static bool s_IsSceneEventActive = false; - - // TODO: Remove `m_IsRunningUnitTest` entirely after we switch to multi-process testing - // In MultiInstance tests, we cannot allow clients to load additional scenes as they're sharing the same scene space / Unity instance. -#if UNITY_INCLUDE_TESTS - private readonly bool m_IsRunningUnitTest = SceneManager.GetActiveScene().name.StartsWith("InitTestScene"); -#endif + private bool m_IsSceneEventActive = false; /// /// The delegate callback definition for scene event notifications.
@@ -324,6 +318,31 @@ namespace Unity.Netcode ///
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading; + /// + /// Proof of concept: Interface version that allows for the decoupling from + /// the SceneManager's Load and Unload methods for testing purposes (potentially other future usage) + /// + private class DefaultSceneManagerHandler : ISceneManagerHandler + { + public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) + { + var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); + operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + return operation; + } + + public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) + { + var operation = SceneManager.UnloadSceneAsync(scene); + operation.completed += new Action(asyncOp2 => { sceneEventAction.Invoke(); }); + return operation; + } + } + + internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler(); + /// End of Proof of Concept + + internal readonly Dictionary SceneEventProgressTracking = new Dictionary(); /// @@ -723,10 +742,9 @@ namespace Unity.Netcode { EventData = SceneEventDataStore[sceneEventId] }; - var size = m_NetworkManager.SendMessage(message, k_DeliveryType, targetClientIds); + var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, targetClientIds); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent( - targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); + m_NetworkManager.NetworkMetrics.TrackSceneEventSent(targetClientIds, (uint)SceneEventDataStore[sceneEventId].SceneEventType, SceneNameFromHash(SceneEventDataStore[sceneEventId].SceneHash), size); } /// @@ -789,7 +807,7 @@ namespace Unity.Netcode private SceneEventProgress ValidateSceneEvent(string sceneName, bool isUnloading = false) { // Return scene event already in progress if one is already in progress - if (s_IsSceneEventActive) + if (m_IsSceneEventActive) { return new SceneEventProgress(null, SceneEventProgressStatus.SceneEventInProgress); } @@ -818,7 +836,7 @@ namespace Unity.Netcode IsSpawnedObjectsPendingInDontDestroyOnLoad = true; } - s_IsSceneEventActive = true; + m_IsSceneEventActive = true; // Set our callback delegate handler for completion sceneEventProgress.OnComplete = OnSceneEventProgressCompleted; @@ -845,12 +863,12 @@ namespace Unity.Netcode { EventData = sceneEventData }; - var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds); + var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ConnectedClientsIds); m_NetworkManager.NetworkMetrics.TrackSceneEventSent( m_NetworkManager.ConnectedClientsIds, (uint)sceneEventProgress.SceneEventType, - SceneNameFromHash(sceneEventProgress.SceneHash), + SceneNameFromHash(sceneEventProgress.SceneHash), size); // Send a local notification to the server that all clients are done loading or unloading @@ -917,8 +935,9 @@ namespace Unity.Netcode ScenesLoaded.Remove(scene.handle); - AsyncOperation sceneUnload = SceneManager.UnloadSceneAsync(scene); - sceneUnload.completed += (AsyncOperation asyncOp2) => { OnSceneUnloaded(sceneEventData.SceneEventId); }; + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }); + sceneEventProgress.SetSceneLoadOperation(sceneUnload); // Notify local server that a scene is going to be unloaded @@ -948,8 +967,10 @@ namespace Unity.Netcode if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.SceneHandle)) { - throw new Exception($"Client failed to unload scene {sceneName} " + - $"because we are missing the client scene handle due to the server scene handle {sceneEventData.SceneHandle} not being found!"); + Debug.Log($"Client failed to unload scene {sceneName} " + + $"because we are missing the client scene handle due to the server scene handle {sceneEventData.SceneHandle} not being found."); + EndSceneEvent(sceneEventId); + return; } var sceneHandle = ServerSceneHandleToClientSceneHandle[sceneEventData.SceneHandle]; @@ -960,22 +981,11 @@ namespace Unity.Netcode throw new Exception($"Client failed to unload scene {sceneName} " + $"because the client scene handle {sceneHandle} was not found in ScenesLoaded!"); } - s_IsSceneEventActive = true; - var sceneUnload = (AsyncOperation)null; -#if UNITY_INCLUDE_TESTS - if (m_IsRunningUnitTest) - { - sceneUnload = new AsyncOperation(); - } - else - { - sceneUnload = SceneManager.UnloadSceneAsync(ScenesLoaded[sceneHandle]); - sceneUnload.completed += asyncOp2 => OnSceneUnloaded(sceneEventId); - } -#else - sceneUnload = SceneManager.UnloadSceneAsync(ScenesLoaded[sceneHandle]); - sceneUnload.completed += asyncOp2 => OnSceneUnloaded(sceneEventId); -#endif + m_IsSceneEventActive = true; + + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneUnloaded }); + ScenesLoaded.Remove(sceneHandle); // Remove our server to scene handle lookup @@ -992,13 +1002,6 @@ namespace Unity.Netcode }); OnUnload?.Invoke(m_NetworkManager.LocalClientId, sceneName, sceneUnload); - -#if UNITY_INCLUDE_TESTS - if (m_IsRunningUnitTest) - { - OnSceneUnloaded(sceneEventId); - } -#endif } /// @@ -1045,7 +1048,12 @@ namespace Unity.Netcode EndSceneEvent(sceneEventId); // This scene event is now considered "complete" - s_IsSceneEventActive = false; + m_IsSceneEventActive = false; + } + + private void EmptySceneUnloadedOperation(uint sceneEventId) + { + // Do nothing (this is a stub call since it is only used to flush all currently loaded scenes) } /// @@ -1053,17 +1061,21 @@ namespace Unity.Netcode /// Since we assume a single mode loaded scene will be considered the "currently active scene", /// we only unload any additively loaded scenes. /// - internal void UnloadAdditivelyLoadedScenes() + internal void UnloadAdditivelyLoadedScenes(uint sceneEventId) { // Unload all additive scenes while making sure we don't try to unload the base scene ( loaded in single mode ). var currentActiveScene = SceneManager.GetActiveScene(); foreach (var keyHandleEntry in ScenesLoaded) { - if (currentActiveScene.name != keyHandleEntry.Value.name) + // Validate the scene as well as ignore the DDOL (which will have a negative buildIndex) + if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0) { + var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = EmptySceneUnloadedOperation }); + OnSceneEvent?.Invoke(new SceneEvent() { - AsyncOperation = SceneManager.UnloadSceneAsync(keyHandleEntry.Value), + AsyncOperation = sceneUnload, SceneEventType = SceneEventType.Unload, SceneName = keyHandleEntry.Value.name, LoadSceneMode = LoadSceneMode.Additive, // The only scenes unloaded are scenes that were additively loaded @@ -1103,8 +1115,8 @@ namespace Unity.Netcode sceneEventData.LoadSceneMode = loadSceneMode; // This both checks to make sure the scene is valid and if not resets the active scene event - s_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode); - if (!s_IsSceneEventActive) + m_IsSceneEventActive = ValidateSceneBeforeLoading(sceneEventData.SceneHash, loadSceneMode); + if (!m_IsSceneEventActive) { EndSceneEvent(sceneEventData.SceneEventId); return SceneEventProgressStatus.SceneFailedVerification; @@ -1119,12 +1131,13 @@ namespace Unity.Netcode MoveObjectsToDontDestroyOnLoad(); // Now Unload all currently additively loaded scenes - UnloadAdditivelyLoadedScenes(); + UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId); } // Now start loading the scene - AsyncOperation sceneLoad = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); - sceneLoad.completed += (AsyncOperation asyncOp2) => { OnSceneLoaded(sceneEventData.SceneEventId, sceneName); }; + var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventData.SceneEventId, EventAction = OnSceneLoaded }); + sceneEventProgress.SetSceneLoadOperation(sceneLoad); // Notify the local server that a scene loading event has begun @@ -1160,44 +1173,13 @@ namespace Unity.Netcode return; } -#if UNITY_INCLUDE_TESTS - if (m_IsRunningUnitTest) - { - // Send the loading message - OnSceneEvent?.Invoke(new SceneEvent() - { - AsyncOperation = new AsyncOperation(), - SceneEventType = sceneEventData.SceneEventType, - LoadSceneMode = sceneEventData.LoadSceneMode, - SceneName = sceneName, - ClientId = m_NetworkManager.LocalClientId - }); - - // Only for testing - OnLoad?.Invoke(m_NetworkManager.ServerClientId, sceneName, sceneEventData.LoadSceneMode, new AsyncOperation()); - - // Unit tests must mirror the server's scenes loaded dictionary, otherwise this portion will fail - if (ScenesLoaded.ContainsKey(sceneEventData.SceneHandle)) - { - OnClientLoadedScene(sceneEventId, ScenesLoaded[sceneEventData.SceneHandle]); - } - else - { - EndSceneEvent(sceneEventId); - throw new Exception($"Could not find the scene handle {sceneEventData.SceneHandle} for scene {sceneName} " + - $"during unit test. Did you forget to register this in the unit test?"); - } - return; - } -#endif - if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) { // Move ALL NetworkObjects to the temp scene MoveObjectsToDontDestroyOnLoad(); // Now Unload all currently additively loaded scenes - UnloadAdditivelyLoadedScenes(); + UnloadAdditivelyLoadedScenes(sceneEventData.SceneEventId); } // The Condition: While a scene is asynchronously loaded in single loading scene mode, if any new NetworkObjects are spawned @@ -1205,13 +1187,14 @@ namespace Unity.Netcode // When it is set: Just before starting the asynchronous loading call // When it is unset: After the scene has loaded, the PopulateScenePlacedObjects is called, and all NetworkObjects in the do // not destroy temporary scene are moved into the active scene + // TODO: When Snapshot scene spawning is enabled this needs to be removed. if (sceneEventData.LoadSceneMode == LoadSceneMode.Single) { IsSpawnedObjectsPendingInDontDestroyOnLoad = true; } - var sceneLoad = SceneManager.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode); - sceneLoad.completed += asyncOp2 => OnSceneLoaded(sceneEventId, sceneName); + var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = OnSceneLoaded }); OnSceneEvent?.Invoke(new SceneEvent() { @@ -1230,10 +1213,10 @@ namespace Unity.Netcode /// Client and Server: /// Generic on scene loaded callback method to be called upon a scene loading /// - private void OnSceneLoaded(uint sceneEventId, string sceneName) + private void OnSceneLoaded(uint sceneEventId) { var sceneEventData = SceneEventDataStore[sceneEventId]; - var nextScene = GetAndAddNewlyLoadedSceneByName(sceneName); + var nextScene = GetAndAddNewlyLoadedSceneByName(SceneNameFromHash(sceneEventData.SceneHash)); if (!nextScene.isLoaded || !nextScene.IsValid()) { throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!"); @@ -1314,12 +1297,12 @@ namespace Unity.Netcode { EventData = sceneEventData }; - var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId); + var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, clientId); m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, scene.name, size); } } - s_IsSceneEventActive = false; + m_IsSceneEventActive = false; //First, notify local server that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() { @@ -1351,7 +1334,7 @@ namespace Unity.Netcode sceneEventData.SceneEventType = SceneEventType.LoadComplete; SendSceneEventData(sceneEventId, new ulong[] { m_NetworkManager.ServerClientId }); - s_IsSceneEventActive = false; + m_IsSceneEventActive = false; // Notify local client that the scene was loaded OnSceneEvent?.Invoke(new SceneEvent() @@ -1421,9 +1404,8 @@ namespace Unity.Netcode { EventData = sceneEventData }; - var size = m_NetworkManager.SendMessage(message, k_DeliveryType, clientId); - m_NetworkManager.NetworkMetrics.TrackSceneEventSent( - clientId, (uint)sceneEventData.SceneEventType, "", size); + var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, clientId); + m_NetworkManager.NetworkMetrics.TrackSceneEventSent(clientId, (uint)sceneEventData.SceneEventType, "", size); // Notify the local server that the client has been sent the synchronize event OnSceneEvent?.Invoke(new SceneEvent() @@ -1474,6 +1456,10 @@ namespace Unity.Netcode ScenePlacedObjects.Clear(); } + // Store the sceneHandle and hash + sceneEventData.ClientSceneHandle = sceneHandle; + sceneEventData.ClientSceneHash = sceneHash; + var shouldPassThrough = false; var sceneLoad = (AsyncOperation)null; @@ -1485,38 +1471,28 @@ namespace Unity.Netcode shouldPassThrough = true; } -#if UNITY_INCLUDE_TESTS - if (m_IsRunningUnitTest) - { - // In unit tests, we don't allow clients to load additional scenes since - // MultiInstance unit tests share the same scene space. - shouldPassThrough = true; - sceneLoad = new AsyncOperation(); - } -#endif if (!shouldPassThrough) { // If not, then load the scene - sceneLoad = SceneManager.LoadSceneAsync(sceneName, loadSceneMode); - sceneLoad.completed += asyncOp2 => ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle); + sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, + new ISceneManagerHandler.SceneEventAction() { SceneEventId = sceneEventId, EventAction = ClientLoadedSynchronization }); + + // Notify local client that a scene load has begun + OnSceneEvent?.Invoke(new SceneEvent() + { + AsyncOperation = sceneLoad, + SceneEventType = SceneEventType.Load, + LoadSceneMode = loadSceneMode, + SceneName = sceneName, + ClientId = m_NetworkManager.LocalClientId, + }); + + OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad); } - - // Notify local client that a scene load has begun - OnSceneEvent?.Invoke(new SceneEvent() - { - AsyncOperation = sceneLoad, - SceneEventType = SceneEventType.Load, - LoadSceneMode = loadSceneMode, - SceneName = sceneName, - ClientId = m_NetworkManager.LocalClientId, - }); - - OnLoad?.Invoke(m_NetworkManager.LocalClientId, sceneName, loadSceneMode, sceneLoad); - - if (shouldPassThrough) + else { // If so, then pass through - ClientLoadedSynchronization(sceneEventId, sceneHash, sceneHandle); + ClientLoadedSynchronization(sceneEventId); } } @@ -1525,10 +1501,10 @@ namespace Unity.Netcode /// This handles all of the in-scene and dynamically spawned NetworkObject synchronization /// /// Netcode scene index that was loaded - private void ClientLoadedSynchronization(uint sceneEventId, uint sceneHash, int sceneHandle) + private void ClientLoadedSynchronization(uint sceneEventId) { var sceneEventData = SceneEventDataStore[sceneEventId]; - var sceneName = SceneNameFromHash(sceneHash); + var sceneName = SceneNameFromHash(sceneEventData.ClientSceneHash); var nextScene = GetAndAddNewlyLoadedSceneByName(sceneName); if (!nextScene.isLoaded || !nextScene.IsValid()) @@ -1536,7 +1512,7 @@ namespace Unity.Netcode throw new Exception($"Failed to find valid scene internal Unity.Netcode for {nameof(GameObject)}s error!"); } - var loadSceneMode = (sceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive); + var loadSceneMode = (sceneEventData.ClientSceneHash == sceneEventData.SceneHash ? sceneEventData.LoadSceneMode : LoadSceneMode.Additive); // For now, during a synchronization event, we will make the first scene the "base/master" scene that denotes a "complete scene switch" if (loadSceneMode == LoadSceneMode.Single) @@ -1544,9 +1520,9 @@ namespace Unity.Netcode SceneManager.SetActiveScene(nextScene); } - if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneHandle)) + if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.ClientSceneHandle)) { - ServerSceneHandleToClientSceneHandle.Add(sceneHandle, nextScene.handle); + ServerSceneHandleToClientSceneHandle.Add(sceneEventData.ClientSceneHandle, nextScene.handle); } else { @@ -1561,14 +1537,14 @@ namespace Unity.Netcode var responseSceneEventData = BeginSceneEvent(); responseSceneEventData.LoadSceneMode = loadSceneMode; responseSceneEventData.SceneEventType = SceneEventType.LoadComplete; - responseSceneEventData.SceneHash = sceneHash; + responseSceneEventData.SceneHash = sceneEventData.ClientSceneHash; var message = new SceneEventMessage { EventData = responseSceneEventData }; - var size = m_NetworkManager.SendMessage(message, k_DeliveryType, m_NetworkManager.ServerClientId); + var size = m_NetworkManager.SendMessage(ref message, k_DeliveryType, m_NetworkManager.ServerClientId); m_NetworkManager.NetworkMetrics.TrackSceneEventSent(m_NetworkManager.ServerClientId, (uint)responseSceneEventData.SceneEventType, sceneName, size); @@ -1824,6 +1800,11 @@ namespace Unity.Netcode var objectsToKeep = new HashSet(m_NetworkManager.SpawnManager.SpawnedObjectsList); foreach (var sobj in objectsToKeep) { + if (sobj == null) + { + continue; + } + if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene) { // Only move dynamically spawned network objects with no parent as child objects will follow @@ -1864,7 +1845,7 @@ namespace Unity.Netcode // at the end of scene loading we use this list to soft synchronize all in-scene placed NetworkObjects foreach (var networkObjectInstance in networkObjects) { - // We check to make sure the NetworkManager instance is the same one to be "MultiInstanceHelpers" compatible and filter the list on a per scene basis (additive scenes) + // 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 (additive scenes) if (networkObjectInstance.IsSceneObject == null && networkObjectInstance.NetworkManager == m_NetworkManager && networkObjectInstance.gameObject.scene == sceneToFilterBy && networkObjectInstance.gameObject.scene.handle == sceneToFilterBy.handle) { @@ -1899,6 +1880,10 @@ namespace Unity.Netcode foreach (var sobj in objectsToKeep) { + if (sobj == null) + { + continue; + } // If it is in the DDOL then if (sobj.gameObject.scene == DontDestroyOnLoadScene) { diff --git a/Runtime/SceneManagement/SceneEventData.cs b/Runtime/SceneManagement/SceneEventData.cs index 1d80d2f..0fe9142 100644 --- a/Runtime/SceneManagement/SceneEventData.cs +++ b/Runtime/SceneManagement/SceneEventData.cs @@ -4,7 +4,6 @@ using System.Linq; using Unity.Collections; using UnityEngine.SceneManagement; - namespace Unity.Netcode { /// @@ -100,6 +99,10 @@ namespace Unity.Netcode internal uint SceneHash; internal int SceneHandle; + // Used by the client during synchronization + internal uint ClientSceneHash; + internal int ClientSceneHandle; + /// Only used for scene events, this assures permissions when writing /// NetworkVariable information. If that process changes, then we need to update this internal ulong TargetClientId; @@ -230,7 +233,14 @@ namespace Unity.Netcode internal void AddSpawnedNetworkObjects() { - m_NetworkObjectsSync = m_NetworkManager.SpawnManager.SpawnedObjectsList.ToList(); + m_NetworkObjectsSync.Clear(); + foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList) + { + if (sobj.Observers.Contains(TargetClientId)) + { + m_NetworkObjectsSync.Add(sobj); + } + } m_NetworkObjectsSync.Sort(SortNetworkObjects); } diff --git a/Runtime/Serialization/FastBufferReader.cs b/Runtime/Serialization/FastBufferReader.cs index 0d8dac7..443941d 100644 --- a/Runtime/Serialization/FastBufferReader.cs +++ b/Runtime/Serialization/FastBufferReader.cs @@ -76,7 +76,7 @@ namespace Unity.Netcode /// /// Create a FastBufferReader from a NativeArray. - /// + /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// @@ -93,12 +93,12 @@ namespace Unity.Netcode /// public unsafe FastBufferReader(NativeArray buffer, Allocator allocator, int length = -1, int offset = 0) { - Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator); + Handle = CreateHandle((byte*)buffer.GetUnsafePtr(), length == -1 ? buffer.Length : length, offset, allocator); } /// /// Create a FastBufferReader from an ArraySegment. - /// + /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// @@ -117,13 +117,13 @@ namespace Unity.Netcode } fixed (byte* data = buffer.Array) { - Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Count : length), offset, allocator); + Handle = CreateHandle(data, length == -1 ? buffer.Count : length, offset, allocator); } } /// /// Create a FastBufferReader from an existing byte array. - /// + /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// @@ -142,13 +142,13 @@ namespace Unity.Netcode } fixed (byte* data = buffer) { - Handle = CreateHandle(data, Math.Max(1, length == -1 ? buffer.Length : length), offset, allocator); + Handle = CreateHandle(data, length == -1 ? buffer.Length : length, offset, allocator); } } /// /// Create a FastBufferReader from an existing byte buffer. - /// + /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// @@ -165,12 +165,12 @@ namespace Unity.Netcode /// The offset of the buffer to start copying from public unsafe FastBufferReader(byte* buffer, Allocator allocator, int length, int offset = 0) { - Handle = CreateHandle(buffer, Math.Max(1, length), offset, allocator); + Handle = CreateHandle(buffer, length, offset, allocator); } /// /// Create a FastBufferReader from a FastBufferWriter. - /// + /// /// A new buffer will be created using the given allocator and the value will be copied in. /// FastBufferReader will then own the data. /// @@ -187,7 +187,7 @@ namespace Unity.Netcode /// The offset of the buffer to start copying from public unsafe FastBufferReader(FastBufferWriter writer, Allocator allocator, int length = -1, int offset = 0) { - Handle = CreateHandle(writer.GetUnsafePtr(), Math.Max(1, length == -1 ? writer.Length : length), offset, allocator); + Handle = CreateHandle(writer.GetUnsafePtr(), length == -1 ? writer.Length : length, offset, allocator); } /// @@ -250,7 +250,7 @@ namespace Unity.Netcode /// When you know you will be reading multiple fields back-to-back and you know the total size, /// you can call TryBeginRead() once on the total size, and then follow it with calls to /// ReadValue() instead of ReadValueSafe() for faster serialization. - /// + /// /// Unsafe read operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following @@ -284,7 +284,7 @@ namespace Unity.Netcode /// When you know you will be reading multiple fields back-to-back and you know the total size, /// you can call TryBeginRead() once on the total size, and then follow it with calls to /// ReadValue() instead of ReadValueSafe() for faster serialization. - /// + /// /// Unsafe read operations will throw OverflowException in editor and development builds if you /// go past the point you've marked using TryBeginRead(). In release builds, OverflowException will not be thrown /// for performance reasons, since the point of using TryBeginRead is to avoid bounds checking in the following diff --git a/Runtime/Spawning/NetworkSpawnManager.cs b/Runtime/Spawning/NetworkSpawnManager.cs index e111656..2f0028f 100644 --- a/Runtime/Spawning/NetworkSpawnManager.cs +++ b/Runtime/Spawning/NetworkSpawnManager.cs @@ -102,7 +102,7 @@ namespace Unity.Netcode /// There is a one second maximum lifetime of triggers to avoid memory leaks. After one second has passed /// without the requested object ID being spawned, the triggers for it are automatically deleted. /// - internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, in NetworkContext context) + internal unsafe void TriggerOnSpawn(ulong networkObjectId, FastBufferReader reader, ref NetworkContext context) { if (!m_Triggers.ContainsKey(networkObjectId)) { @@ -212,7 +212,7 @@ namespace Unity.Netcode NetworkObjectId = networkObject.NetworkObjectId, OwnerClientId = networkObject.OwnerClientId }; - var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); foreach (var client in NetworkManager.ConnectedClients) { @@ -280,13 +280,17 @@ namespace Unity.Netcode networkObject.OwnerClientId = clientId; + if (TryGetNetworkClient(clientId, out NetworkClient newNetworkClient)) + { + newNetworkClient.OwnedObjects.Add(networkObject); + } var message = new ChangeOwnershipMessage { NetworkObjectId = networkObject.NetworkObjectId, OwnerClientId = networkObject.OwnerClientId }; - var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, NetworkManager.ConnectedClientsIds); foreach (var client in NetworkManager.ConnectedClients) { @@ -422,6 +426,15 @@ namespace Unity.Netcode throw new SpawnStateException("Object is already spawned"); } + if (!sceneObject) + { + var networkObjectChildren = networkObject.GetComponentsInChildren(); + if (networkObjectChildren.Length > 1) + { + Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!"); + } + } + SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene); } @@ -525,6 +538,13 @@ namespace Unity.Netcode triggerInfo.TriggerData.Dispose(); m_Triggers.Remove(networkId); } + + // propagate the IsSceneObject setting to child NetworkObjects + var children = networkObject.GetComponentsInChildren(); + foreach (var childObject in children) + { + childObject.IsSceneObject = sceneObject; + } } internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject) @@ -543,7 +563,7 @@ namespace Unity.Netcode { ObjectInfo = networkObject.GetMessageSceneObject(clientId) }; - var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, clientId); + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); networkObject.MarkVariablesDirty(); @@ -678,6 +698,7 @@ namespace Unity.Netcode internal void ServerSpawnSceneObjectsOnStartSweep() { var networkObjects = UnityEngine.Object.FindObjectsOfType(); + var networkObjectsToSpawn = new List(); for (int i = 0; i < networkObjects.Length; i++) { @@ -685,10 +706,15 @@ namespace Unity.Netcode { if (networkObjects[i].IsSceneObject == null) { - SpawnNetworkObjectLocally(networkObjects[i], GetNetworkObjectId(), true, false, null, true); + networkObjectsToSpawn.Add(networkObjects[i]); } } } + + foreach (var networkObject in networkObjectsToSpawn) + { + SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, null, true); + } } internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject) @@ -783,7 +809,7 @@ namespace Unity.Netcode { NetworkObjectId = networkObject.NetworkObjectId }; - var size = NetworkManager.SendMessage(message, NetworkDelivery.ReliableSequenced, m_TargetClientIds); + var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, m_TargetClientIds); foreach (var targetClientId in m_TargetClientIds) { NetworkManager.NetworkMetrics.TrackObjectDestroySent(targetClientId, networkObject, size); @@ -822,7 +848,7 @@ namespace Unity.Netcode { foreach (var sobj in SpawnedObjectsList) { - if (sobj.CheckObjectVisibility == null || NetworkManager.IsServer) + if (sobj.CheckObjectVisibility == null) { if (!sobj.Observers.Contains(clientId)) { diff --git a/Runtime/Transports/NetworkTransport.cs b/Runtime/Transports/NetworkTransport.cs index 57e7cfc..08783e3 100644 --- a/Runtime/Transports/NetworkTransport.cs +++ b/Runtime/Transports/NetworkTransport.cs @@ -18,6 +18,8 @@ namespace Unity.Netcode /// true if is supported; otherwise, false. public virtual bool IsSupported => true; + internal INetworkMetrics NetworkMetrics; + /// /// Delegate for transport network events /// @@ -95,6 +97,14 @@ namespace Unity.Netcode /// /// Initializes the transport /// - public abstract void Initialize(); + /// /// optionally pass in NetworkManager + public abstract void Initialize(NetworkManager networkManager = null); } + +#if UNITY_INCLUDE_TESTS + public abstract class TestingNetworkTransport : NetworkTransport + { + + } +#endif } diff --git a/Runtime/Transports/UNET/UNetChannel.cs b/Runtime/Transports/UNET/UNetChannel.cs index 1e1afc0..358ac51 100644 --- a/Runtime/Transports/UNET/UNetChannel.cs +++ b/Runtime/Transports/UNET/UNetChannel.cs @@ -1,3 +1,4 @@ +#if UNITY_UNET_PRESENT using System; #if UNITY_EDITOR using UnityEditor; @@ -50,3 +51,4 @@ namespace Unity.Netcode.Transports.UNET #endif } } +#endif diff --git a/Runtime/Transports/UNET/UNetTransport.cs b/Runtime/Transports/UNET/UNetTransport.cs index 1a102b3..2d6051e 100644 --- a/Runtime/Transports/UNET/UNetTransport.cs +++ b/Runtime/Transports/UNET/UNetTransport.cs @@ -1,3 +1,4 @@ +#if UNITY_UNET_PRESENT #pragma warning disable 618 // disable is obsolete #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member using System; @@ -41,6 +42,8 @@ namespace Unity.Netcode.Transports.UNET public override ulong ServerClientId => GetNetcodeClientId(0, 0, true); + internal NetworkManager NetworkManager; + protected void LateUpdate() { if (UnityEngine.Networking.NetworkTransport.IsStarted && MessageSendMode == SendMode.Queued) @@ -48,7 +51,7 @@ namespace Unity.Netcode.Transports.UNET #if UNITY_WEBGL Debug.LogError("Cannot use queued sending mode for WebGL"); #else - if (NetworkManager.Singleton.IsServer) + if (NetworkManager != null && NetworkManager.IsServer) { foreach (var targetClient in NetworkManager.Singleton.ConnectedClientsList) { @@ -230,8 +233,10 @@ namespace Unity.Netcode.Transports.UNET UnityEngine.Networking.NetworkTransport.Shutdown(); } - public override void Initialize() + public override void Initialize(NetworkManager networkManager = null) { + NetworkManager = networkManager; + m_MessageBuffer = new byte[MessageBufferSize]; UnityEngine.Networking.NetworkTransport.Init(); @@ -279,3 +284,4 @@ namespace Unity.Netcode.Transports.UNET } #pragma warning restore CS1591 // Missing XML comment for publicly visible type or member #pragma warning restore 618 // restore is obsolete +#endif diff --git a/Runtime/com.unity.netcode.runtime.asmdef b/Runtime/com.unity.netcode.runtime.asmdef index 0efd561..956c39d 100644 --- a/Runtime/com.unity.netcode.runtime.asmdef +++ b/Runtime/com.unity.netcode.runtime.asmdef @@ -6,21 +6,28 @@ "Unity.Multiplayer.NetStats", "Unity.Multiplayer.NetStatsReporting", "Unity.Multiplayer.NetworkSolutionInterface", + "Unity.Multiplayer.Tools.MetricTypes", + "Unity.Multiplayer.Tools.NetStats", + "Unity.Multiplayer.Tools.NetStatsReporting", + "Unity.Multiplayer.Tools.NetworkSolutionInterface", "Unity.Collections" ], - "includePlatforms": [], - "excludePlatforms": [], "allowUnsafeCode": true, - "overrideReferences": false, - "precompiledReferences": [], - "autoReferenced": true, - "defineConstraints": [], "versionDefines": [ { "name": "com.unity.multiplayer.tools", "expression": "", "define": "MULTIPLAYER_TOOLS" + }, + { + "name": "Unity", + "expression": "(0,2022.2.0a5)", + "define": "UNITY_UNET_PRESENT" + }, + { + "name": "com.unity.multiplayer.tools", + "expression": "1.0.0-pre.4", + "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4" } - ], - "noEngineReferences": false + ] } \ No newline at end of file diff --git a/Tests/Runtime/Helpers.meta b/TestHelpers.meta similarity index 77% rename from Tests/Runtime/Helpers.meta rename to TestHelpers.meta index 0602aaa..52cc961 100644 --- a/Tests/Runtime/Helpers.meta +++ b/TestHelpers.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: fb1b6e801936c7f4a9af28dbed5ea2ff +guid: d627e2fb516d92242a4930e5cd9291e3 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Tests/Runtime/Metrics/Utility.meta b/TestHelpers/Runtime.meta similarity index 77% rename from Tests/Runtime/Metrics/Utility.meta rename to TestHelpers/Runtime.meta index 8aa9943..6ffc9af 100644 --- a/Tests/Runtime/Metrics/Utility.meta +++ b/TestHelpers/Runtime.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 4e60372130aba464f9f9ae4a24bb9fe0 +guid: e9af0202c9057c944b67aad6e4cdac96 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/TestHelpers/Runtime/AssemblyInfo.cs b/TestHelpers/Runtime/AssemblyInfo.cs new file mode 100644 index 0000000..f9d973f --- /dev/null +++ b/TestHelpers/Runtime/AssemblyInfo.cs @@ -0,0 +1,6 @@ +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("TestProject.EditorTests")] +[assembly: InternalsVisibleTo("TestProject.RuntimeTests")] +[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")] +[assembly: InternalsVisibleTo("Unity.Netcode.RuntimeTests")] diff --git a/TestHelpers/Runtime/AssemblyInfo.cs.meta b/TestHelpers/Runtime/AssemblyInfo.cs.meta new file mode 100644 index 0000000..a3b1d55 --- /dev/null +++ b/TestHelpers/Runtime/AssemblyInfo.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10ca1ce26995e754599c9eedc2c228d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Components.meta b/TestHelpers/Runtime/Components.meta new file mode 100644 index 0000000..ecb3e98 --- /dev/null +++ b/TestHelpers/Runtime/Components.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 19fbc3f43e13a9144a9c66c68a1c43c1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs b/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs new file mode 100644 index 0000000..c3b4e46 --- /dev/null +++ b/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs @@ -0,0 +1,93 @@ + +namespace Unity.Netcode.TestHelpers.Runtime +{ + public class ObjectNameIdentifier : NetworkBehaviour + { + private ulong m_CurrentOwner; + private ulong m_CurrentNetworkObjectId; + private bool m_IsRegistered; + + /// + /// Keep a reference to the assigned NetworkObject + /// + /// + private NetworkObject m_NetworkObject; + + public override void OnNetworkSpawn() + { + RegisterAndLabelNetworkObject(); + } + + protected void RegisterAndLabelNetworkObject() + { + if (!m_IsRegistered) + { + // This is required otherwise it will try to continue to update the NetworkBehaviour even if + // it has been destroyed. + m_NetworkObject = NetworkObject; + m_CurrentOwner = OwnerClientId; + m_CurrentNetworkObjectId = NetworkObjectId; + var objectOriginalName = gameObject.name.Replace("(Clone)", ""); + var serverOrClient = IsServer ? "Server" : "Client"; + if (NetworkObject.IsPlayerObject) + { + gameObject.name = NetworkManager.LocalClientId == OwnerClientId ? $"{objectOriginalName}({OwnerClientId})-Local{objectOriginalName}" : + $"{objectOriginalName}({OwnerClientId})-On{serverOrClient}({NetworkManager.LocalClientId})"; + } + else + { + gameObject.name = $"{objectOriginalName}({NetworkObjectId})-On{serverOrClient}({NetworkManager.LocalClientId})"; + } + + // Don't add the player objects to the global list of NetworkObjects + if (!NetworkObject.IsPlayerObject) + { + NetcodeIntegrationTest.RegisterNetworkObject(NetworkObject); + } + m_IsRegistered = true; + } + } + + protected void DeRegisterNetworkObject() + { + if (m_IsRegistered) + { + NetcodeIntegrationTest.DeregisterNetworkObject(m_CurrentOwner, m_CurrentNetworkObjectId); + m_IsRegistered = false; + } + } + + public override void OnLostOwnership() + { + DeRegisterNetworkObject(); + RegisterAndLabelNetworkObject(); + } + + public override void OnGainedOwnership() + { + DeRegisterNetworkObject(); + RegisterAndLabelNetworkObject(); + } + + public override void OnNetworkDespawn() + { + DeRegisterNetworkObject(); + } + + public override void OnDestroy() + { + if (m_NetworkObject != null) + { + DeRegisterNetworkObject(); + // This is required otherwise it will try to continue to update the NetworkBehaviour even if + // it has been destroyed (most likely integration test specific) + if (m_NetworkObject.ChildNetworkBehaviours != null && m_NetworkObject.ChildNetworkBehaviours.Contains(this)) + { + NetworkObject.ChildNetworkBehaviours.Remove(this); + } + m_NetworkObject = null; + } + base.OnDestroy(); + } + } +} diff --git a/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta b/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta new file mode 100644 index 0000000..35f1ebb --- /dev/null +++ b/TestHelpers/Runtime/Components/ObjectNameIdentifier.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a915cfb2e4f748e4f9526a8bf5ee84f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/ConditionalPredicate.cs b/TestHelpers/Runtime/ConditionalPredicate.cs new file mode 100644 index 0000000..6745ae7 --- /dev/null +++ b/TestHelpers/Runtime/ConditionalPredicate.cs @@ -0,0 +1,60 @@ + +namespace Unity.Netcode.TestHelpers.Runtime +{ + /// + /// Derive from this class to create your own conditional handling for your + /// integration tests when dealing with more complicated scenarios where initializing values, storing state to be + /// used across several integration tests. + /// + public class ConditionalPredicateBase : IConditionalPredicate + { + private bool m_TimedOut; + + public bool TimedOut { get { return m_TimedOut; } } + + protected virtual bool OnHasConditionBeenReached() + { + return true; + } + + public bool HasConditionBeenReached() + { + return OnHasConditionBeenReached(); + } + + protected virtual void OnStarted() { } + + public void Started() + { + OnStarted(); + } + + protected virtual void OnFinished() { } + + public void Finished(bool timedOut) + { + m_TimedOut = timedOut; + OnFinished(); + } + } + + public interface IConditionalPredicate + { + /// + /// Test the conditions of the test to be reached + /// + bool HasConditionBeenReached(); + + /// + /// Wait for condition has started + /// + void Started(); + + /// + /// Wait for condition has finished: + /// Condition(s) met or timed out + /// + void Finished(bool timedOut); + + } +} diff --git a/TestHelpers/Runtime/ConditionalPredicate.cs.meta b/TestHelpers/Runtime/ConditionalPredicate.cs.meta new file mode 100644 index 0000000..b8f40eb --- /dev/null +++ b/TestHelpers/Runtime/ConditionalPredicate.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dada6cae693646a4095924917e5e707a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs new file mode 100644 index 0000000..fcc6fee --- /dev/null +++ b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.SceneManagement; +using Object = UnityEngine.Object; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + /// + /// The default SceneManagerHandler used for all NetcodeIntegrationTest derived children. + /// + internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable + { + internal CoroutineRunner CoroutineRunner; + + // Default client simulated delay time + protected const float k_ClientLoadingSimulatedDelay = 0.02f; + + // Controls the client simulated delay time + protected float m_ClientLoadingSimulatedDelay = k_ClientLoadingSimulatedDelay; + + public delegate bool CanClientsLoadUnloadDelegateHandler(); + public event CanClientsLoadUnloadDelegateHandler CanClientsLoad; + public event CanClientsLoadUnloadDelegateHandler CanClientsUnload; + + internal List CoroutinesRunning = new List(); + + /// + /// Used to control when clients should attempt to fake-load a scene + /// Note: Unit/Integration tests that only use + /// need to subscribe to the CanClientsLoad and CanClientsUnload events + /// in order to control when clients can fake-load. + /// Tests that derive from already have integrated + /// support and you can override and + /// . + /// + protected bool OnCanClientsLoad() + { + if (CanClientsLoad != null) + { + return CanClientsLoad.Invoke(); + } + return true; + } + + /// + /// Fake-Loads a scene for a client + /// + internal IEnumerator ClientLoadSceneCoroutine(string sceneName, ISceneManagerHandler.SceneEventAction sceneEventAction) + { + yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay); + while (!OnCanClientsLoad()) + { + yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay); + } + sceneEventAction.Invoke(); + } + + protected bool OnCanClientsUnload() + { + if (CanClientsUnload != null) + { + return CanClientsUnload.Invoke(); + } + return true; + } + + /// + /// Fake-Unloads a scene for a client + /// + internal IEnumerator ClientUnloadSceneCoroutine(ISceneManagerHandler.SceneEventAction sceneEventAction) + { + yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay); + while (!OnCanClientsUnload()) + { + yield return new WaitForSeconds(m_ClientLoadingSimulatedDelay); + } + sceneEventAction.Invoke(); + } + + public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, ISceneManagerHandler.SceneEventAction sceneEventAction) + { + CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientLoadSceneCoroutine(sceneName, sceneEventAction))); + // This is OK to return a "nothing" AsyncOperation since we are simulating client loading + return new AsyncOperation(); + } + + public AsyncOperation UnloadSceneAsync(Scene scene, ISceneManagerHandler.SceneEventAction sceneEventAction) + { + CoroutinesRunning.Add(CoroutineRunner.StartCoroutine(ClientUnloadSceneCoroutine(sceneEventAction))); + // This is OK to return a "nothing" AsyncOperation since we are simulating client loading + return new AsyncOperation(); + } + + public IntegrationTestSceneHandler() + { + if (CoroutineRunner == null) + { + CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent(); + } + } + + public void Dispose() + { + foreach (var coroutine in CoroutinesRunning) + { + CoroutineRunner.StopCoroutine(coroutine); + } + CoroutineRunner.StopAllCoroutines(); + + Object.Destroy(CoroutineRunner.gameObject); + } + } +} diff --git a/TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta new file mode 100644 index 0000000..72081c4 --- /dev/null +++ b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 384935cc0ae40d641910e4c3924038c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Metrics.meta b/TestHelpers/Runtime/Metrics.meta new file mode 100644 index 0000000..1e3c818 --- /dev/null +++ b/TestHelpers/Runtime/Metrics.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ebacdb7d8cb876a43b4a908dd6d83aa9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Metrics/Utility/MetricTestBase.cs b/TestHelpers/Runtime/Metrics/MetricTestBase.cs similarity index 57% rename from Tests/Runtime/Metrics/Utility/MetricTestBase.cs rename to TestHelpers/Runtime/Metrics/MetricTestBase.cs index 14a42d3..773a79b 100644 --- a/Tests/Runtime/Metrics/Utility/MetricTestBase.cs +++ b/TestHelpers/Runtime/Metrics/MetricTestBase.cs @@ -1,20 +1,11 @@ #if MULTIPLAYER_TOOLS -using System; using System.Collections; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Unity.Multiplayer.Tools.MetricTypes; -using UnityEngine; -using UnityEngine.TestTools; -namespace Unity.Netcode.RuntimeTests.Metrics.Utility +namespace Unity.Netcode.TestHelpers.Runtime.Metrics { - internal abstract class SingleClientMetricTestBase : BaseMultiInstanceTest + internal abstract class SingleClientMetricTestBase : NetcodeIntegrationTest { - protected override int NbClients => 1; - - protected virtual Action UpdatePlayerPrefab => _ => { }; + protected override int NumberOfClients => 1; internal NetworkManager Server { get; private set; } @@ -24,23 +15,24 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility internal NetworkMetrics ClientMetrics { get; private set; } - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnServerAndClientsCreated() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab); - Server = m_ServerNetworkManager; - ServerMetrics = Server.NetworkMetrics as NetworkMetrics; Client = m_ClientNetworkManagers[0]; + base.OnServerAndClientsCreated(); + } + + protected override IEnumerator OnStartedServerAndClients() + { + ServerMetrics = Server.NetworkMetrics as NetworkMetrics; ClientMetrics = Client.NetworkMetrics as NetworkMetrics; + yield return base.OnStartedServerAndClients(); } } - public abstract class DualClientMetricTestBase : BaseMultiInstanceTest + public abstract class DualClientMetricTestBase : NetcodeIntegrationTest { - protected override int NbClients => 2; - - protected virtual Action UpdatePlayerPrefab => _ => { }; + protected override int NumberOfClients => 2; internal NetworkManager Server { get; private set; } @@ -54,17 +46,20 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility internal NetworkMetrics SecondClientMetrics { get; private set; } - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnServerAndClientsCreated() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab); - Server = m_ServerNetworkManager; - ServerMetrics = Server.NetworkMetrics as NetworkMetrics; FirstClient = m_ClientNetworkManagers[0]; + SecondClient = m_ClientNetworkManagers[1]; + base.OnServerAndClientsCreated(); + } + + protected override IEnumerator OnStartedServerAndClients() + { + ServerMetrics = Server.NetworkMetrics as NetworkMetrics; FirstClientMetrics = FirstClient.NetworkMetrics as NetworkMetrics; - SecondClient = m_ClientNetworkManagers[0]; SecondClientMetrics = SecondClient.NetworkMetrics as NetworkMetrics; + yield return base.OnStartedServerAndClients(); } } } diff --git a/Tests/Runtime/Metrics/Utility/MetricTestBase.cs.meta b/TestHelpers/Runtime/Metrics/MetricTestBase.cs.meta similarity index 100% rename from Tests/Runtime/Metrics/Utility/MetricTestBase.cs.meta rename to TestHelpers/Runtime/Metrics/MetricTestBase.cs.meta diff --git a/Tests/Runtime/Metrics/Utility/NetworkVariableComponent.cs b/TestHelpers/Runtime/Metrics/NetworkVariableComponent.cs similarity index 88% rename from Tests/Runtime/Metrics/Utility/NetworkVariableComponent.cs rename to TestHelpers/Runtime/Metrics/NetworkVariableComponent.cs index ba0a8ce..d21b8b1 100644 --- a/Tests/Runtime/Metrics/Utility/NetworkVariableComponent.cs +++ b/TestHelpers/Runtime/Metrics/NetworkVariableComponent.cs @@ -1,7 +1,7 @@ #if MULTIPLAYER_TOOLS using UnityEngine; -namespace Unity.Netcode.RuntimeTests.Metrics.Utility +namespace Unity.Netcode.TestHelpers.Runtime.Metrics { public class NetworkVariableComponent : NetworkBehaviour { diff --git a/Tests/Runtime/Metrics/Utility/NetworkVariableComponent.cs.meta b/TestHelpers/Runtime/Metrics/NetworkVariableComponent.cs.meta similarity index 100% rename from Tests/Runtime/Metrics/Utility/NetworkVariableComponent.cs.meta rename to TestHelpers/Runtime/Metrics/NetworkVariableComponent.cs.meta diff --git a/Tests/Runtime/Metrics/Utility/RpcTestComponent.cs b/TestHelpers/Runtime/Metrics/RpcTestComponent.cs similarity index 88% rename from Tests/Runtime/Metrics/Utility/RpcTestComponent.cs rename to TestHelpers/Runtime/Metrics/RpcTestComponent.cs index 36dbb6b..354a801 100644 --- a/Tests/Runtime/Metrics/Utility/RpcTestComponent.cs +++ b/TestHelpers/Runtime/Metrics/RpcTestComponent.cs @@ -1,6 +1,6 @@ using System; -namespace Unity.Netcode.RuntimeTests.Metrics.Utility +namespace Unity.Netcode.TestHelpers.Runtime.Metrics { public class RpcTestComponent : NetworkBehaviour { diff --git a/Tests/Runtime/Metrics/Utility/RpcTestComponent.cs.meta b/TestHelpers/Runtime/Metrics/RpcTestComponent.cs.meta similarity index 100% rename from Tests/Runtime/Metrics/Utility/RpcTestComponent.cs.meta rename to TestHelpers/Runtime/Metrics/RpcTestComponent.cs.meta diff --git a/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs b/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs new file mode 100644 index 0000000..6912e73 --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs @@ -0,0 +1,50 @@ +#if MULTIPLAYER_TOOLS +using Unity.Multiplayer.Tools.MetricTypes; +using Unity.Multiplayer.Tools.NetStats; + +namespace Unity.Netcode.TestHelpers.Runtime.Metrics +{ + internal class WaitForCounterMetricValue : WaitForMetricValues + { + private long m_Value; + + public delegate bool CounterFilter(long metric); + private CounterFilter m_CounterFilterDelegate; + + public WaitForCounterMetricValue(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName) + : base(dispatcher, directionalMetricName) + { + } + + public WaitForCounterMetricValue(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, CounterFilter counterFilter) + : this(dispatcher, directionalMetricName) + { + m_CounterFilterDelegate = counterFilter; + } + + public long AssertMetricValueHaveBeenFound() + { + AssertHasError(); + AssertIsFound(); + + return m_Value; + } + + public override void Observe(MetricCollection collection) + { + if (FindMetric(collection, out var metric)) + { + var typedMetric = metric as Counter; + if (typedMetric == default) + { + SetError(metric); + return; + } + + m_Value = typedMetric.Value; + m_Found = m_CounterFilterDelegate != null ? m_CounterFilterDelegate(m_Value) : true; + } + } + } +} +#endif diff --git a/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs.meta b/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs.meta new file mode 100644 index 0000000..73b1537 --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForCounterMetricValue.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aa1d3026d48b43bfa4c76e253b08b3ae +timeCreated: 1644269156 \ No newline at end of file diff --git a/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs b/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs new file mode 100644 index 0000000..8378f16 --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs @@ -0,0 +1,60 @@ +#if MULTIPLAYER_TOOLS +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using NUnit.Framework; +using Unity.Multiplayer.Tools.MetricTypes; +using Unity.Multiplayer.Tools.NetStats; + +namespace Unity.Netcode.TestHelpers.Runtime.Metrics +{ + internal class WaitForEventMetricValues : WaitForMetricValues + { + IReadOnlyCollection m_EventValues; + + public delegate bool EventFilter(TMetric metric); + EventFilter m_EventFilterDelegate; + + public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName) + : base(dispatcher, directionalMetricName) + { + } + + public WaitForEventMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, EventFilter eventFilter) + : this(dispatcher, directionalMetricName) + { + m_EventFilterDelegate = eventFilter; + } + + public IReadOnlyCollection AssertMetricValuesHaveBeenFound() + { + AssertHasError(); + AssertIsFound(); + + return m_EventValues; + } + + public override void Observe(MetricCollection collection) + { + if (FindMetric(collection, out var metric)) + { + var typedMetric = metric as IEventMetric; + if (typedMetric == default) + { + SetError(metric); + return; + } + + if (typedMetric.Values.Any()) + { + // Apply filter if one was provided + m_EventValues = m_EventFilterDelegate != null ? typedMetric.Values.Where(x => m_EventFilterDelegate(x)).ToList() : typedMetric.Values.ToList(); + m_Found = m_EventValues.Count > 0; + } + } + } + } +} +#endif diff --git a/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs.meta b/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs.meta new file mode 100644 index 0000000..93efe71 --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForEventMetricValues.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 319c55f92728431283c9e888d8f9d70e +timeCreated: 1644269156 \ No newline at end of file diff --git a/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs b/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs new file mode 100644 index 0000000..240639f --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs @@ -0,0 +1,55 @@ +#if MULTIPLAYER_TOOLS +using Unity.Multiplayer.Tools.MetricTypes; +using Unity.Multiplayer.Tools.NetStats; + +namespace Unity.Netcode.TestHelpers.Runtime.Metrics +{ + internal class WaitForGaugeMetricValues : WaitForMetricValues + { + private double m_Value; + + public delegate bool GaugeFilter(double metric); + private GaugeFilter m_GaugeFilterDelegate; + + public WaitForGaugeMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName) + : base(dispatcher, directionalMetricName) + { + } + + public WaitForGaugeMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, GaugeFilter counterFilter) + : this(dispatcher, directionalMetricName) + { + m_GaugeFilterDelegate = counterFilter; + } + + public bool MetricFound() + { + return m_Found; + } + + public double AssertMetricValueHaveBeenFound() + { + AssertHasError(); + AssertIsFound(); + + return m_Value; + } + + public override void Observe(MetricCollection collection) + { + if (FindMetric(collection, out var metric)) + { + var typedMetric = metric as Gauge; + if (typedMetric == default) + { + SetError(metric); + return; + } + + m_Value = typedMetric.Value; + m_Found = m_GaugeFilterDelegate != null ? m_GaugeFilterDelegate(m_Value) : true; + } + } + } +} +#endif diff --git a/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs.meta b/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs.meta new file mode 100644 index 0000000..7ad4b45 --- /dev/null +++ b/TestHelpers/Runtime/Metrics/WaitForGaugeMetricValues.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1d76c4e546c546a3b9d63b2c74fcbbca +timeCreated: 1644269156 \ No newline at end of file diff --git a/Tests/Runtime/Metrics/Utility/WaitForMetricValues.cs b/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs similarity index 51% rename from Tests/Runtime/Metrics/Utility/WaitForMetricValues.cs rename to TestHelpers/Runtime/Metrics/WaitForMetricValues.cs index 62fa831..0101385 100644 --- a/Tests/Runtime/Metrics/Utility/WaitForMetricValues.cs +++ b/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs @@ -1,59 +1,27 @@ #if MULTIPLAYER_TOOLS using System.Collections; -using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.NetStats; -namespace Unity.Netcode.RuntimeTests.Metrics.Utility +namespace Unity.Netcode.TestHelpers.Runtime.Metrics { - internal class WaitForMetricValues : IMetricObserver + internal abstract class WaitForMetricValues : IMetricObserver { - readonly string m_MetricName; - bool m_Found; - bool m_HasError; - string m_Error; - uint m_NbFrames = 0; - IReadOnlyCollection m_Values; - - public delegate bool Filter(TMetric metric); - - Filter m_FilterDelegate; - + protected readonly string m_MetricName; + protected bool m_Found; + protected bool m_HasError; + protected string m_Error; + protected uint m_NbFrames = 0; public WaitForMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName) { m_MetricName = directionalMetricName.Id; - dispatcher.RegisterObserver(this); } - public WaitForMetricValues(IMetricDispatcher dispatcher, DirectionalMetricInfo directionalMetricName, Filter filter) - : this(dispatcher, directionalMetricName) - { - m_FilterDelegate = filter; - } - - public IEnumerator WaitForMetricsReceived() - { - yield return WaitForFrames(60); - } - - public IReadOnlyCollection AssertMetricValuesHaveBeenFound() - { - if (m_HasError) - { - Assert.Fail(m_Error); - } - - if (!m_Found) - { - Assert.Fail($"Found no matching values for metric of type '{typeof(TMetric).Name}', with name '{m_MetricName}' during '{m_NbFrames}' frames."); - } - - return m_Values; - } + abstract public void Observe(MetricCollection collection); public void AssertMetricValuesHaveNotBeenFound() { @@ -72,37 +40,51 @@ namespace Unity.Netcode.RuntimeTests.Metrics.Utility } } - public void Observe(MetricCollection collection) + public IEnumerator WaitForMetricsReceived() + { + yield return WaitForFrames(60); + } + + protected void AssertHasError() + { + if (m_HasError) + { + Assert.Fail(m_Error); + } + } + + protected void AssertIsFound() + { + if (!m_Found) + { + Assert.Fail($"Found no matching values for metric of type '{typeof(TMetric).Name}', with name '{m_MetricName}' during '{m_NbFrames}' frames."); + } + } + + protected bool FindMetric(MetricCollection collection, out IMetric metric) { if (m_Found || m_HasError) { - return; + metric = null; + return false; } - var metric = collection.Metrics.SingleOrDefault(x => x.Name == m_MetricName); + metric = collection.Metrics.SingleOrDefault(x => x.Name == m_MetricName); if (metric == default) { m_HasError = true; m_Error = $"Metric collection does not contain metric named '{m_MetricName}'."; - return; + return false; } - var typedMetric = metric as IEventMetric; - if (typedMetric == default) - { - m_HasError = true; - m_Error = $"Metric collection contains a metric of type '{metric.GetType().Name}' for name '{m_MetricName}', but was expecting '{typeof(TMetric).Name}'."; + return true; + } - return; - } - - if (typedMetric.Values.Any()) - { - // Apply filter if one was provided - m_Values = m_FilterDelegate != null ? typedMetric.Values.Where(x => m_FilterDelegate(x)).ToList() : typedMetric.Values.ToList(); - m_Found = m_Values.Count > 0; - } + protected void SetError(IMetric metric) + { + m_HasError = true; + m_Error = $"Metric collection contains a metric of type '{metric.GetType().Name}' for name '{m_MetricName}', but was expecting '{typeof(TMetric).Name}'."; } private IEnumerator WaitForFrames(uint maxNbFrames) diff --git a/Tests/Runtime/Metrics/Utility/WaitForMetricValues.cs.meta b/TestHelpers/Runtime/Metrics/WaitForMetricValues.cs.meta similarity index 100% rename from Tests/Runtime/Metrics/Utility/WaitForMetricValues.cs.meta rename to TestHelpers/Runtime/Metrics/WaitForMetricValues.cs.meta diff --git a/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/TestHelpers/Runtime/NetcodeIntegrationTest.cs new file mode 100644 index 0000000..17e4eff --- /dev/null +++ b/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -0,0 +1,727 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +using Object = UnityEngine.Object; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + /// + /// The default Netcode for GameObjects integration test helper class + /// + public abstract class NetcodeIntegrationTest + { + protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(4.0f); + protected static WaitForSeconds s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate); + + /// + /// Registered list of all NetworkObjects spawned. + /// Format is as follows: + /// [ClientId-side where this NetworkObject instance resides][NetworkObjectId][NetworkObject] + /// Where finding the NetworkObject with a NetworkObjectId of 10 on ClientId of 2 would be: + /// s_GlobalNetworkObjects[2][10] + /// To find the client or server player objects please see: + /// + /// + protected static Dictionary> s_GlobalNetworkObjects = new Dictionary>(); + + public static void RegisterNetworkObject(NetworkObject networkObject) + { + if (!s_GlobalNetworkObjects.ContainsKey(networkObject.NetworkManager.LocalClientId)) + { + s_GlobalNetworkObjects.Add(networkObject.NetworkManager.LocalClientId, new Dictionary()); + } + if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId].ContainsKey(networkObject.NetworkObjectId)) + { + if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId] == null) + { + Assert.False(s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId][networkObject.NetworkObjectId] != null, + $"Duplicate NetworkObjectId {networkObject.NetworkObjectId} found in {nameof(s_GlobalNetworkObjects)} for client id {networkObject.NetworkManager.LocalClientId}!"); + } + else + { + s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId][networkObject.NetworkObjectId] = networkObject; + } + } + else + { + s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId].Add(networkObject.NetworkObjectId, networkObject); + } + } + + public static void DeregisterNetworkObject(NetworkObject networkObject) + { + if (networkObject.IsSpawned && networkObject.NetworkManager != null) + { + DeregisterNetworkObject(networkObject.NetworkManager.LocalClientId, networkObject.NetworkObjectId); + } + } + + public static void DeregisterNetworkObject(ulong localClientId, ulong networkObjectId) + { + if (s_GlobalNetworkObjects.ContainsKey(localClientId) && s_GlobalNetworkObjects[localClientId].ContainsKey(networkObjectId)) + { + s_GlobalNetworkObjects[localClientId].Remove(networkObjectId); + if (s_GlobalNetworkObjects[localClientId].Count == 0) + { + s_GlobalNetworkObjects.Remove(localClientId); + } + } + } + + protected int TotalClients => m_UseHost ? NumberOfClients + 1 : NumberOfClients; + + protected const uint k_DefaultTickRate = 30; + protected abstract int NumberOfClients { get; } + + public enum NetworkManagerInstatiationMode + { + PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class + AllTests, // This will create one set of NetworkManagers used for all tests within a child derived class (destroyed once all tests are finished) + DoNotCreate // This will not create any NetworkManagers, it is up to the derived class to manage. + } + + public enum HostOrServer + { + Host, + Server + } + + protected GameObject m_PlayerPrefab; + protected NetworkManager m_ServerNetworkManager; + protected NetworkManager[] m_ClientNetworkManagers; + + /// + /// Contains each client relative set of player NetworkObject instances + /// [Client Relative set of player instances][The player instance ClientId][The player instance's NetworkObject] + /// Example: + /// To get the player instance with a ClientId of 3 that was instantiated (relative) on the player instance with a ClientId of 2 + /// m_PlayerNetworkObjects[2][3] + /// + protected Dictionary> m_PlayerNetworkObjects = new Dictionary>(); + + protected bool m_UseHost = true; + protected int m_TargetFrameRate = 60; + + protected NetcodeIntegrationTestHelpers.InstanceTransport m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.SIP; + + private NetworkManagerInstatiationMode m_NetworkManagerInstatiationMode; + + /// + /// The very first thing invoked during the that + /// determines how this integration test handles NetworkManager instantiation + /// and destruction. + /// Override this method to change the default mode: + /// + /// + protected virtual NetworkManagerInstatiationMode OnSetIntegrationTestMode() + { + return NetworkManagerInstatiationMode.PerTest; + } + + protected virtual void OnOneTimeSetup() + { + } + + [OneTimeSetUp] + public void OneTimeSetup() + { + m_NetworkManagerInstatiationMode = OnSetIntegrationTestMode(); + + // Enable NetcodeIntegrationTest auto-label feature + NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(true); + OnOneTimeSetup(); + } + + /// + /// Called before creating and starting the server and clients + /// Note: For and + /// mode integration tests. + /// For those two modes, if you want to have access to the server or client + /// s then override . + /// and + /// + protected virtual IEnumerator OnSetup() + { + yield return null; + } + + [UnitySetUp] + public IEnumerator SetUp() + { + yield return OnSetup(); + if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null || + m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest) + { + CreateServerAndClients(); + + yield return StartServerAndClients(); + } + } + + /// + /// Override this to add components or adjustments to the default player prefab + /// + /// + protected virtual void OnCreatePlayerPrefab() + { + } + + private void CreatePlayerPrefab() + { + // Create playerPrefab + m_PlayerPrefab = new GameObject("Player"); + NetworkObject networkObject = m_PlayerPrefab.AddComponent(); + + // Make it a prefab + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + + OnCreatePlayerPrefab(); + } + + /// + /// This is invoked before the server and client(s) are started. + /// Override this method if you want to make any adjustments to their + /// NetworkManager instances. + /// + protected virtual void OnServerAndClientsCreated() + { + } + + /// + /// Will create number of clients. + /// To create a specific number of clients + /// + protected void CreateServerAndClients() + { + CreateServerAndClients(NumberOfClients); + } + + /// + /// Creates the server and clients + /// + /// + protected void CreateServerAndClients(int numberOfClients) + { + CreatePlayerPrefab(); + + // Create multiple NetworkManager instances + if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_NetworkTransport)) + { + Debug.LogError("Failed to create instances"); + Assert.Fail("Failed to create instances"); + } + + m_ClientNetworkManagers = clients; + m_ServerNetworkManager = server; + + if (m_ServerNetworkManager != null) + { + s_DefaultWaitForTick = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate); + } + + // Set the player prefab for the server and clients + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + } + + // Provides opportunity to allow child derived classes to + // modify the NetworkManager's configuration before starting. + OnServerAndClientsCreated(); + } + + /// + /// Override this method and return false in order to be able + /// to manually control when the server and clients are started. + /// + protected virtual bool CanStartServerAndClients() + { + return true; + } + + /// + /// Invoked after the server and clients have started. + /// Note: No connection verification has been done at this point + /// + protected virtual IEnumerator OnStartedServerAndClients() + { + yield return null; + } + + /// + /// Invoked after the server and clients have started and verified + /// their connections with each other. + /// + protected virtual IEnumerator OnServerAndClientsConnected() + { + yield return null; + } + + /// + /// This starts the server and clients as long as + /// returns true. + /// + protected IEnumerator StartServerAndClients() + { + if (CanStartServerAndClients()) + { + // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server + // is started and after each client is started. + if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers)) + { + Debug.LogError("Failed to start instances"); + Assert.Fail("Failed to start instances"); + } + + RegisterSceneManagerHandler(); + + // Notification that the server and clients have been started + yield return OnStartedServerAndClients(); + + // Wait for all clients to connect + yield return WaitForClientsConnectedOrTimeOut(); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!"); + + if (s_GlobalTimeoutHelper.TimedOut) + { + yield return null; + } + + if (m_UseHost || m_ServerNetworkManager.IsHost) + { + // Add the server player instance to all m_ClientSidePlayerNetworkObjects entries + var serverPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId); + foreach (var playerNetworkObject in serverPlayerClones) + { + if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId)) + { + m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary()); + } + m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject); + } + } + + // Creates a dictionary for all player instances client and server relative + // This provides a simpler way to get a specific player instance relative to a client instance + foreach (var networkManager in m_ClientNetworkManagers) + { + Assert.NotNull(networkManager.LocalClient.PlayerObject, $"{nameof(StartServerAndClients)} detected that client {networkManager.LocalClientId} does not have an assigned player NetworkObject!"); + + // Get all player instances for the current client NetworkManager instance + var clientPlayerClones = Object.FindObjectsOfType().Where((c) => c.IsPlayerObject && c.OwnerClientId == networkManager.LocalClientId); + // Add this player instance to each client player entry + foreach (var playerNetworkObject in clientPlayerClones) + { + // When the server is not the host this needs to be done + if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId)) + { + m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary()); + } + m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject); + } + } + + // Notification that at this time the server and client(s) are instantiated, + // started, and connected on both sides. + yield return OnServerAndClientsConnected(); + } + } + + /// + /// Override this method to control when clients + /// can fake-load a scene. + /// + protected virtual bool CanClientsLoad() + { + return true; + } + + /// + /// Override this method to control when clients + /// can fake-unload a scene. + /// + protected virtual bool CanClientsUnload() + { + return true; + } + + /// + /// De-Registers from the CanClientsLoad and CanClientsUnload events of the + /// ClientSceneHandler (default is IntegrationTestSceneHandler). + /// + protected void DeRegisterSceneManagerHandler() + { + if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null) + { + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad; + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload; + } + } + + /// + /// Registers the CanClientsLoad and CanClientsUnload events of the + /// ClientSceneHandler. + /// The default is: . + /// + protected void RegisterSceneManagerHandler() + { + if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null) + { + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad; + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload; + } + } + + private bool ClientSceneHandler_CanClientsUnload() + { + return CanClientsUnload(); + } + + private bool ClientSceneHandler_CanClientsLoad() + { + return CanClientsLoad(); + } + + /// + /// This shuts down all NetworkManager instances registered via the + /// class and cleans up + /// the test runner scene of any left over NetworkObjects. + /// + /// + protected void ShutdownAndCleanUp() + { + // Shutdown and clean up both of our NetworkManager instances + try + { + if (NetcodeIntegrationTestHelpers.ClientSceneHandler != null) + { + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsLoad -= ClientSceneHandler_CanClientsLoad; + NetcodeIntegrationTestHelpers.ClientSceneHandler.CanClientsUnload -= ClientSceneHandler_CanClientsUnload; + } + + NetcodeIntegrationTestHelpers.Destroy(); + + m_PlayerNetworkObjects.Clear(); + s_GlobalNetworkObjects.Clear(); + } + catch (Exception e) { throw e; } + finally + { + if (m_PlayerPrefab != null) + { + Object.Destroy(m_PlayerPrefab); + m_PlayerPrefab = null; + } + } + + // Cleanup any remaining NetworkObjects + DestroySceneNetworkObjects(); + + // reset the m_ServerWaitForTick for the next test to initialize + s_DefaultWaitForTick = new WaitForSeconds(1.0f / k_DefaultTickRate); + } + + /// + /// Note: For mode + /// this is called before ShutdownAndCleanUp. + /// + protected virtual IEnumerator OnTearDown() + { + yield return null; + } + + [UnityTearDown] + public IEnumerator TearDown() + { + yield return OnTearDown(); + + if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest) + { + ShutdownAndCleanUp(); + } + } + + /// + /// Override this method to do handle cleaning up once the test(s) + /// within the child derived class have completed + /// Note: For mode + /// this is called before ShutdownAndCleanUp. + /// + protected virtual void OnOneTimeTearDown() + { + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + OnOneTimeTearDown(); + + if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests) + { + ShutdownAndCleanUp(); + } + + // Disable NetcodeIntegrationTest auto-label feature + NetcodeIntegrationTestHelpers.RegisterNetcodeIntegrationTest(false); + } + + /// + /// Override this to filter out the s that you + /// want to allow to persist between integration tests. + /// + /// + /// + /// the network object in question to be destroyed + protected virtual bool CanDestroyNetworkObject(NetworkObject networkObject) + { + return true; + } + + /// + /// Destroys all NetworkObjects at the end of a test cycle. + /// + protected void DestroySceneNetworkObjects() + { + var networkObjects = Object.FindObjectsOfType(); + foreach (var networkObject in networkObjects) + { + // This can sometimes be null depending upon order of operations + // when dealing with parented NetworkObjects. If NetworkObjectB + // is a child of NetworkObjectA and NetworkObjectA comes before + // NetworkObjectB in the list of NeworkObjects found, then when + // NetworkObjectA's GameObject is destroyed it will also destroy + // NetworkObjectB's GameObject which will destroy NetworkObjectB. + // If there is a null entry in the list, this is the most likely + // scenario and so we just skip over it. + if (networkObject == null) + { + continue; + } + if (CanDestroyNetworkObject(networkObject)) + { + // Destroy the GameObject that holds the NetworkObject component + Object.DestroyImmediate(networkObject.gameObject); + } + } + } + + /// + /// Waits for the function condition to return true or it will time out. + /// This will operate at the current m_ServerNetworkManager.NetworkConfig.TickRate + /// and allow for a unique TimeoutHelper handler (if none then it uses the default) + /// Notes: This provides more stability when running integration tests that could be + /// impacted by: + /// -how the integration test is being executed (i.e. in editor or in a stand alone build) + /// -potential platform performance issues (i.e. VM is throttled or maxed) + /// Note: For more complex tests, and the overloaded + /// version of this method + /// + public static IEnumerator WaitForConditionOrTimeOut(Func checkForCondition, TimeoutHelper timeOutHelper = null) + { + if (checkForCondition == null) + { + throw new ArgumentNullException($"checkForCondition cannot be null!"); + } + + // If none is provided we use the default global time out helper + if (timeOutHelper == null) + { + timeOutHelper = s_GlobalTimeoutHelper; + } + + // Start checking for a timeout + timeOutHelper.Start(); + while (!timeOutHelper.HasTimedOut()) + { + // Update and check to see if the condition has been met + if (checkForCondition.Invoke()) + { + break; + } + + // Otherwise wait for 1 tick interval + yield return s_DefaultWaitForTick; + } + // Stop checking for a timeout + timeOutHelper.Stop(); + } + + /// + /// This version accepts an IConditionalPredicate implementation to provide + /// more flexibility for checking complex conditional cases. + /// + public static IEnumerator WaitForConditionOrTimeOut(IConditionalPredicate conditionalPredicate, TimeoutHelper timeOutHelper = null) + { + if (conditionalPredicate == null) + { + throw new ArgumentNullException($"checkForCondition cannot be null!"); + } + + // If none is provided we use the default global time out helper + if (timeOutHelper == null) + { + timeOutHelper = s_GlobalTimeoutHelper; + } + + conditionalPredicate.Started(); + yield return WaitForConditionOrTimeOut(conditionalPredicate.HasConditionBeenReached, timeOutHelper); + conditionalPredicate.Finished(timeOutHelper.TimedOut); + } + + /// + /// Validates that all remote clients (i.e. non-server) detect they are connected + /// to the server and that the server reflects the appropriate number of clients + /// have connected or it will time out. + /// + /// An array of clients to be checked + protected IEnumerator WaitForClientsConnectedOrTimeOut(NetworkManager[] clientsToCheck) + { + var remoteClientCount = clientsToCheck.Length; + var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount; + + yield return WaitForConditionOrTimeOut(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount && + m_ServerNetworkManager.ConnectedClients.Count == serverClientCount); + } + + /// + /// Overloaded method that just passes in all clients to + /// + /// + protected IEnumerator WaitForClientsConnectedOrTimeOut() + { + yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers); + } + + /// + /// Creates a basic NetworkObject test prefab, assigns it to a new + /// NetworkPrefab entry, and then adds it to the server and client(s) + /// NetworkManagers' NetworkConfig.NetworkPrefab lists. + /// + /// the basic name to be used for each instance + /// NetworkObject of the GameObject assigned to the new NetworkPrefab entry + protected GameObject CreateNetworkObjectPrefab(string baseName) + { + var prefabCreateAssertError = $"You can only invoke this method during {nameof(OnServerAndClientsCreated)} " + + $"but before {nameof(OnStartedServerAndClients)}!"; + Assert.IsNotNull(m_ServerNetworkManager, prefabCreateAssertError); + Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError); + + var gameObject = new GameObject(); + gameObject.name = baseName; + var networkObject = gameObject.AddComponent(); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + var networkPrefab = new NetworkPrefab() { Prefab = gameObject }; + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); + } + return gameObject; + } + + /// + /// Overloaded method + /// + protected GameObject SpawnObject(GameObject prefabGameObject, NetworkManager owner, bool destroyWithScene = false) + { + var prefabNetworkObject = prefabGameObject.GetComponent(); + Assert.IsNotNull(prefabNetworkObject, $"{nameof(GameObject)} {prefabGameObject.name} does not have a {nameof(NetworkObject)} component!"); + return SpawnObject(prefabNetworkObject, owner, destroyWithScene); + } + + /// + /// Spawn a NetworkObject prefab instance + /// + /// the prefab NetworkObject to spawn + /// the owner of the instance + /// default is false + /// GameObject instance spawned + private GameObject SpawnObject(NetworkObject prefabNetworkObject, NetworkManager owner, bool destroyWithScene = false) + { + Assert.IsTrue(prefabNetworkObject.GlobalObjectIdHash > 0, $"{nameof(GameObject)} {prefabNetworkObject.name} has a {nameof(NetworkObject.GlobalObjectIdHash)} value of 0! Make sure to make it a valid prefab before trying to spawn!"); + var newInstance = Object.Instantiate(prefabNetworkObject.gameObject); + var networkObjectToSpawn = newInstance.GetComponent(); + networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning + if (owner == m_ServerNetworkManager) + { + if (m_UseHost) + { + networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); + } + else + { + networkObjectToSpawn.Spawn(destroyWithScene); + } + } + else + { + networkObjectToSpawn.SpawnWithOwnership(owner.LocalClientId, destroyWithScene); + } + + return newInstance; + } + + /// + /// Overloaded method + /// + protected List SpawnObjects(GameObject prefabGameObject, NetworkManager owner, int count, bool destroyWithScene = false) + { + var prefabNetworkObject = prefabGameObject.GetComponent(); + Assert.IsNotNull(prefabNetworkObject, $"{nameof(GameObject)} {prefabGameObject.name} does not have a {nameof(NetworkObject)} component!"); + return SpawnObjects(prefabNetworkObject, owner, count, destroyWithScene); + } + + /// + /// Will spawn (x) number of prefab NetworkObjects + /// + /// + /// the prefab NetworkObject to spawn + /// the owner of the instance + /// number of instances to create and spawn + /// default is false + private List SpawnObjects(NetworkObject prefabNetworkObject, NetworkManager owner, int count, bool destroyWithScene = false) + { + var gameObjectsSpawned = new List(); + for (int i = 0; i < count; i++) + { + gameObjectsSpawned.Add(SpawnObject(prefabNetworkObject, owner, destroyWithScene)); + } + return gameObjectsSpawned; + } + + /// + /// Default constructor + /// + public NetcodeIntegrationTest() + { + + } + + /// + /// Optional Host or Server integration tests + /// Constructor that allows you To break tests up as a host + /// and a server. + /// Example: Decorate your child derived class with TestFixture + /// and then create a constructor at the child level + /// [TestFixture(HostOrServer.Host)] + /// [TestFixture(HostOrServer.Server)] + /// public class MyChildClass : NetcodeIntegrationTest + /// { + /// MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { } + /// } + /// + /// + public NetcodeIntegrationTest(HostOrServer hostOrServer) + { + m_UseHost = hostOrServer == HostOrServer.Host ? true : false; + } + } +} diff --git a/Tests/Runtime/BaseMultiInstanceTest.cs.meta b/TestHelpers/Runtime/NetcodeIntegrationTest.cs.meta similarity index 100% rename from Tests/Runtime/BaseMultiInstanceTest.cs.meta rename to TestHelpers/Runtime/NetcodeIntegrationTest.cs.meta diff --git a/Tests/Runtime/MultiInstanceHelpers.cs b/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs similarity index 50% rename from Tests/Runtime/MultiInstanceHelpers.cs rename to TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index 70abe74..7135030 100644 --- a/Tests/Runtime/MultiInstanceHelpers.cs +++ b/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -7,22 +7,180 @@ using UnityEngine; using UnityEngine.SceneManagement; using Object = UnityEngine.Object; -namespace Unity.Netcode.RuntimeTests +namespace Unity.Netcode.TestHelpers.Runtime { /// /// Provides helpers for running multi instance tests. /// - public static class MultiInstanceHelpers + public static class NetcodeIntegrationTestHelpers { public const int DefaultMinFrames = 1; - public const int DefaultMaxFrames = 64; + public const float DefaultTimeout = 1f; private static List s_NetworkManagerInstances = new List(); + private static Dictionary s_Hooks = new Dictionary(); private static bool s_IsStarted; private static int s_ClientCount; private static int s_OriginalTargetFrameRate = -1; + public delegate bool MessageHandleCheck(object receivedMessage); + + internal class MessageHandleCheckWithResult + { + public MessageHandleCheck Check; + public bool Result; + } + + private class MultiInstanceHooks : INetworkHooks + { + public Dictionary> HandleChecks = new Dictionary>(); + + public static bool CheckForMessageOfType(object receivedMessage) where T : INetworkMessage + { + return receivedMessage.GetType() == typeof(T); + } + + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType) + { + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + if (HandleChecks.ContainsKey(typeof(T))) + { + foreach (var check in HandleChecks[typeof(T)]) + { + if (check.Check(message)) + { + check.Result = true; + HandleChecks[typeof(T)].Remove(check); + break; + } + } + } + } + } + + private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene"; + public static List NetworkManagerInstances => s_NetworkManagerInstances; + public enum InstanceTransport + { + SIP, +#if UTP_ADAPTER + UTP +#endif + } + + internal static IntegrationTestSceneHandler ClientSceneHandler = null; + + /// + /// Registers the IntegrationTestSceneHandler for integration tests. + /// The default client behavior is to not load scenes on the client side. + /// + private static void RegisterSceneManagerHandler(NetworkManager networkManager) + { + if (!networkManager.IsServer) + { + if (ClientSceneHandler == null) + { + ClientSceneHandler = new IntegrationTestSceneHandler(); + } + networkManager.SceneManager.SceneManagerHandler = ClientSceneHandler; + } + } + + /// + /// Call this to clean up the IntegrationTestSceneHandler and destroy the s_CoroutineRunner. + /// Note: + /// If deriving from or using then you + /// typically won't need to do this. + /// + public static void CleanUpHandlers() + { + if (ClientSceneHandler != null) + { + ClientSceneHandler.Dispose(); + ClientSceneHandler = null; + } + } + + /// + /// Call this to register scene validation and the IntegrationTestSceneHandler + /// Note: + /// If deriving from or using then you + /// typically won't need to call this. + /// + public static void RegisterHandlers(NetworkManager networkManager, bool serverSideSceneManager = false) + { + SceneManagerValidationAndTestRunnerInitialization(networkManager); + + if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager) + { + RegisterSceneManagerHandler(networkManager); + } + } + + /// + /// Create the correct NetworkTransport, attach it to the game object and return it. + /// Default value is SIPTransport. + /// + internal static NetworkTransport CreateInstanceTransport(InstanceTransport instanceTransport, GameObject go) + { + switch (instanceTransport) + { + case InstanceTransport.SIP: + default: + return go.AddComponent(); +#if UTP_ADAPTER + case InstanceTransport.UTP: + return go.AddComponent(); +#endif + } + } + /// /// Creates NetworkingManagers and configures them for use in a multi instance setting. /// @@ -30,10 +188,10 @@ namespace Unity.Netcode.RuntimeTests /// The server NetworkManager /// The clients NetworkManagers /// The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown. - public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60) + public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, InstanceTransport instanceTransport = InstanceTransport.SIP) { s_NetworkManagerInstances = new List(); - CreateNewClients(clientCount, out clients); + CreateNewClients(clientCount, out clients, instanceTransport); // Create gameObject var go = new GameObject("NetworkManager - Server"); @@ -46,7 +204,7 @@ namespace Unity.Netcode.RuntimeTests server.NetworkConfig = new NetworkConfig() { // Set transport - NetworkTransport = go.AddComponent() + NetworkTransport = CreateInstanceTransport(instanceTransport, go) }; s_OriginalTargetFrameRate = Application.targetFrameRate; @@ -60,8 +218,7 @@ namespace Unity.Netcode.RuntimeTests /// /// The amount of clients /// - /// - public static bool CreateNewClients(int clientCount, out NetworkManager[] clients) + public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, InstanceTransport instanceTransport = InstanceTransport.SIP) { clients = new NetworkManager[clientCount]; var activeSceneName = SceneManager.GetActiveScene().name; @@ -76,7 +233,7 @@ namespace Unity.Netcode.RuntimeTests clients[i].NetworkConfig = new NetworkConfig() { // Set transport - NetworkTransport = go.AddComponent() + NetworkTransport = CreateInstanceTransport(instanceTransport, go) }; } @@ -91,6 +248,7 @@ namespace Unity.Netcode.RuntimeTests public static void StopOneClient(NetworkManager clientToStop) { clientToStop.Shutdown(); + s_Hooks.Remove(clientToStop); Object.Destroy(clientToStop.gameObject); NetworkManagerInstances.Remove(clientToStop); } @@ -112,6 +270,7 @@ namespace Unity.Netcode.RuntimeTests foreach (var networkManager in NetworkManagerInstances) { networkManager.Shutdown(); + s_Hooks.Remove(networkManager); } // Destroy the network manager instances @@ -122,29 +281,71 @@ namespace Unity.Netcode.RuntimeTests NetworkManagerInstances.Clear(); - // Destroy the temporary GameObject used to run co-routines - if (s_CoroutineRunner != null) - { - s_CoroutineRunner.StopAllCoroutines(); - Object.DestroyImmediate(s_CoroutineRunner); - } + CleanUpHandlers(); Application.targetFrameRate = s_OriginalTargetFrameRate; } + /// + /// We want to exclude the TestRunner scene on the host-server side so it won't try to tell clients to + /// synchronize to this scene when they connect + /// + private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) + { + // exclude test runner scene + if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName)) + { + return false; + } + return true; + } + + /// + /// This registers scene validation callback for the server to prevent it from telling connecting + /// clients to synchronize (i.e. load) the test runner scene. This will also register the test runner + /// scene and its handle for both client(s) and server-host. + /// + private static void SceneManagerValidationAndTestRunnerInitialization(NetworkManager networkManager) + { + // If VerifySceneBeforeLoading is not already set, then go ahead and set it so the host/server + // will not try to synchronize clients to the TestRunner scene. We only need to do this for the server. + if (networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null) + { + networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad; + // If a unit/integration test does not handle this on their own, then Ignore the validation warning + networkManager.SceneManager.DisableValidationWarnings(true); + } + + // Register the test runner scene so it will be able to synchronize NetworkObjects without logging a + // warning about using the currently active scene + var scene = SceneManager.GetActiveScene(); + // As long as this is a test runner scene (or most likely a test runner scene) + if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName)) + { + // Register the test runner scene just so we avoid another warning about not being able to find the + // scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes + // loaded and register the server to client scene handle since host-server shares the test runner scene + // with the clients. + networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name); + networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); + } + } + + public delegate void BeforeClientStartCallback(); + /// /// Starts NetworkManager instances created by the Create method. /// /// Whether or not to create a Host instead of Server /// The Server NetworkManager /// The Clients NetworkManager - /// called immediately after server and client(s) are started + /// called immediately after server is started and before client(s) are started /// - public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, Action startInitializationCallback = null) + public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, BeforeClientStartCallback callback = null) { if (s_IsStarted) { - throw new InvalidOperationException("MultiInstanceHelper already started. Did you forget to Destroy?"); + throw new InvalidOperationException($"{nameof(NetcodeIntegrationTestHelpers)} already thinks it is started. Did you forget to Destroy?"); } s_IsStarted = true; @@ -159,48 +360,52 @@ namespace Unity.Netcode.RuntimeTests server.StartServer(); } + var hooks = new MultiInstanceHooks(); + server.MessagingSystem.Hook(hooks); + s_Hooks[server] = hooks; + // if set, then invoke this for the server - startInitializationCallback?.Invoke(server); + RegisterHandlers(server); + + callback?.Invoke(); for (int i = 0; i < clients.Length; i++) { clients[i].StartClient(); + hooks = new MultiInstanceHooks(); + clients[i].MessagingSystem.Hook(hooks); + s_Hooks[clients[i]] = hooks; // if set, then invoke this for the client - startInitializationCallback?.Invoke(clients[i]); + RegisterHandlers(clients[i]); } return true; } - // Empty MonoBehaviour that is a holder of coroutine - private class CoroutineRunner : MonoBehaviour - { - } - - private static CoroutineRunner s_CoroutineRunner; - /// - /// Runs a IEnumerator as a Coroutine on a dummy GameObject. Used to get exceptions coming from the coroutine + /// Used to return a value of type T from a wait condition /// - /// The IEnumerator to run - public static Coroutine Run(IEnumerator enumerator) - { - if (s_CoroutineRunner == null) - { - s_CoroutineRunner = new GameObject(nameof(CoroutineRunner)).AddComponent(); - } - - return s_CoroutineRunner.StartCoroutine(enumerator); - } - - public class CoroutineResultWrapper + public class ResultWrapper { public T Result; } private static uint s_AutoIncrementGlobalObjectIdHashCounter = 111111; + public static uint GetNextGlobalIdHashValue() + { + return ++s_AutoIncrementGlobalObjectIdHashCounter; + } + + + public static bool IsNetcodeIntegrationTestRunning { get; internal set; } + public static void RegisterNetcodeIntegrationTest(bool registered) + { + IsNetcodeIntegrationTestRunning = registered; + } + + /// /// Normally we would only allow player prefabs to be set to a prefab. Not runtime created objects. /// In order to prevent having a Resource folder full of a TON of prefabs that we have to maintain, @@ -226,6 +431,14 @@ namespace Unity.Netcode.RuntimeTests // 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) + { + // Add the object identifier component + networkObject.gameObject.AddComponent(); + } } // We use GameObject instead of SceneObject to be able to keep hierarchy @@ -254,15 +467,24 @@ namespace Unity.Netcode.RuntimeTests } } + /// + /// Waits (yields) until specified amount of network ticks has been passed. + /// + public static IEnumerator WaitForTicks(NetworkManager networkManager, int count) + { + var targetTick = networkManager.NetworkTickSystem.LocalTime.Tick + count; + yield return new WaitUntil(() => networkManager.NetworkTickSystem.LocalTime.Tick >= targetTick); + } + /// /// Waits on the client side to be connected. /// /// The client /// The result. If null, it will automatically assert /// The max frames to wait for - public static IEnumerator WaitForClientConnected(NetworkManager client, CoroutineResultWrapper result = null, int maxFrames = DefaultMaxFrames) + public static IEnumerator WaitForClientConnected(NetworkManager client, ResultWrapper result = null, float timeout = DefaultTimeout) { - yield return WaitForClientsConnected(new NetworkManager[] { client }, result, maxFrames); + yield return WaitForClientsConnected(new NetworkManager[] { client }, result, timeout); } /// @@ -272,7 +494,7 @@ namespace Unity.Netcode.RuntimeTests /// The result. If null, it will automatically assert< /// The max frames to wait for /// - public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, CoroutineResultWrapper result = null, int maxFrames = DefaultMaxFrames) + public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, ResultWrapper result = null, float timeout = DefaultTimeout) { // Make sure none are the host client foreach (var client in clients) @@ -283,9 +505,10 @@ namespace Unity.Netcode.RuntimeTests } } - var startFrameNumber = Time.frameCount; var allConnected = true; - while (Time.frameCount - startFrameNumber <= maxFrames) + var startTime = Time.realtimeSinceStartup; + + while (Time.realtimeSinceStartup - startTime < timeout) { allConnected = true; foreach (var client in clients) @@ -326,9 +549,9 @@ namespace Unity.Netcode.RuntimeTests /// The server /// The result. If null, it will automatically assert /// The max frames to wait for - public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, CoroutineResultWrapper result = null, int maxFrames = DefaultMaxFrames) + public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, ResultWrapper result = null, float timeout = DefaultTimeout) { - yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, maxFrames); + yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, timeout); } /// @@ -337,16 +560,16 @@ namespace Unity.Netcode.RuntimeTests /// The server /// The result. If null, it will automatically assert /// The max frames to wait for - public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, CoroutineResultWrapper result = null, int maxFrames = DefaultMaxFrames) + public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, ResultWrapper result = null, float timeout = DefaultTimeout) { if (!server.IsServer) { throw new InvalidOperationException("Cannot wait for connected as client"); } - var startFrameNumber = Time.frameCount; + var startTime = Time.realtimeSinceStartup; - while (Time.frameCount - startFrameNumber <= maxFrames && server.ConnectedClients.Count != clientCount) + while (Time.realtimeSinceStartup - startTime < timeout && server.ConnectedClients.Count != clientCount) { var nextFrameNumber = Time.frameCount + 1; yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); @@ -372,16 +595,16 @@ namespace Unity.Netcode.RuntimeTests /// The result /// Whether or not to fail if no object is found and result is null /// The max frames to wait for - public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, CoroutineResultWrapper result, bool failIfNull = true, int maxFrames = DefaultMaxFrames) + public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, ResultWrapper result, bool failIfNull = true, float timeout = DefaultTimeout) { if (result == null) { throw new ArgumentNullException("Result cannot be null"); } - var startFrameNumber = Time.frameCount; + var startTime = Time.realtimeSinceStartup; - while (Time.frameCount - startFrameNumber <= maxFrames && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId)) + while (Time.realtimeSinceStartup - startTime < timeout && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId)) { var nextFrameNumber = Time.frameCount + 1; yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); @@ -403,7 +626,7 @@ namespace Unity.Netcode.RuntimeTests /// The result /// Whether or not to fail if no object is found and result is null /// The max frames to wait for - public static IEnumerator GetNetworkObjectByRepresentation(Func predicate, NetworkManager representation, CoroutineResultWrapper result, bool failIfNull = true, int maxFrames = DefaultMaxFrames) + public static IEnumerator GetNetworkObjectByRepresentation(Func predicate, NetworkManager representation, ResultWrapper result, bool failIfNull = true, float timeout = DefaultTimeout) { if (result == null) { @@ -415,9 +638,9 @@ namespace Unity.Netcode.RuntimeTests throw new ArgumentNullException("Predicate cannot be null"); } - var startFrame = Time.frameCount; + var startTime = Time.realtimeSinceStartup; - while (Time.frameCount - startFrame <= maxFrames && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value))) + while (Time.realtimeSinceStartup - startTime < timeout && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value))) { var nextFrameNumber = Time.frameCount + 1; yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); @@ -431,29 +654,6 @@ namespace Unity.Netcode.RuntimeTests } } - /// - /// Runs some code, then verifies the condition (combines 'Run' and 'WaitForCondition') - /// - /// Action / code to run - /// The predicate to wait for - /// The max frames to wait for - public static IEnumerator RunAndWaitForCondition(Action workload, Func predicate, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames) - { - var waitResult = new CoroutineResultWrapper(); - workload(); - - yield return Run(WaitForCondition( - predicate, - waitResult, - maxFrames: maxFrames, - minFrames: minFrames)); - - if (!waitResult.Result) - { - Assert.Fail("Predicate condition failed"); - } - } - /// /// Waits for a predicate condition to be met /// @@ -461,33 +661,26 @@ namespace Unity.Netcode.RuntimeTests /// The result. If null, it will fail if the predicate is not met /// The min frames to wait for /// The max frames to wait for - public static IEnumerator WaitForCondition(Func predicate, CoroutineResultWrapper result = null, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames) + public static IEnumerator WaitForCondition(Func predicate, ResultWrapper result = null, float timeout = DefaultTimeout, int minFrames = DefaultMinFrames) { if (predicate == null) { throw new ArgumentNullException("Predicate cannot be null"); } - var startFrameNumber = Time.frameCount; + var startTime = Time.realtimeSinceStartup; if (minFrames > 0) { - yield return new WaitUntil(() => - { - return Time.frameCount >= minFrames; - }); + yield return new WaitUntil(() => Time.frameCount >= minFrames); } - while (Time.frameCount - startFrameNumber <= maxFrames && - !predicate()) + while (Time.realtimeSinceStartup - startTime < timeout && !predicate()) { // Changed to 2 frames to avoid the scenario where it would take 1+ frames to // see a value change (i.e. discovered in the NetworkTransformTests) var nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => - { - return Time.frameCount >= nextFrameNumber; - }); + yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); } var res = predicate(); @@ -501,5 +694,70 @@ namespace Unity.Netcode.RuntimeTests Assert.True(res, "PREDICATE CONDITION"); } } + + /// + /// Waits for a message of the given type to be received + /// + /// The result. If null, it will fail if the predicate is not met + /// The max time in seconds to wait for + internal static IEnumerator WaitForMessageOfType(NetworkManager toBeReceivedBy, ResultWrapper result = null, float timeout = 0.5f) where T : INetworkMessage + { + var hooks = s_Hooks[toBeReceivedBy]; + if (!hooks.HandleChecks.ContainsKey(typeof(T))) + { + hooks.HandleChecks.Add(typeof(T), new List()); + } + var check = new MessageHandleCheckWithResult { Check = MultiInstanceHooks.CheckForMessageOfType }; + hooks.HandleChecks[typeof(T)].Add(check); + if (result == null) + { + result = new ResultWrapper(); + } + yield return ExecuteWaitForHook(check, result, timeout); + + Assert.True(result.Result, $"Expected message {typeof(T).Name} was not received within {timeout}s."); + } + + /// + /// Waits for a specific message, defined by a user callback, to be received + /// + /// Called for each received message to check if it's the right one + /// The result. If null, it will fail if the predicate is not met + /// The max time in seconds to wait for + internal static IEnumerator WaitForMessageMeetingRequirement(NetworkManager toBeReceivedBy, MessageHandleCheck requirement, ResultWrapper result = null, float timeout = DefaultTimeout) + { + var hooks = s_Hooks[toBeReceivedBy]; + if (!hooks.HandleChecks.ContainsKey(typeof(T))) + { + hooks.HandleChecks.Add(typeof(T), new List()); + } + var check = new MessageHandleCheckWithResult { Check = requirement }; + hooks.HandleChecks[typeof(T)].Add(check); + if (result == null) + { + result = new ResultWrapper(); + } + yield return ExecuteWaitForHook(check, result, timeout); + + Assert.True(result.Result, $"Expected message meeting user requirements was not received within {timeout}s."); + } + + private static IEnumerator ExecuteWaitForHook(MessageHandleCheckWithResult check, ResultWrapper result, float timeout) + { + var startTime = Time.realtimeSinceStartup; + + while (!check.Result && Time.realtimeSinceStartup - startTime < timeout) + { + yield return null; + } + + var res = check.Result; + result.Result = res; + } + } + + // Empty MonoBehaviour that is a holder of coroutine + internal class CoroutineRunner : MonoBehaviour + { } } diff --git a/Tests/Runtime/MultiInstanceHelpers.cs.meta b/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs.meta similarity index 100% rename from Tests/Runtime/MultiInstanceHelpers.cs.meta rename to TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs.meta diff --git a/Tests/Runtime/Helpers/NetworkManagerHelper.cs b/TestHelpers/Runtime/NetworkManagerHelper.cs similarity index 92% rename from Tests/Runtime/Helpers/NetworkManagerHelper.cs rename to TestHelpers/Runtime/NetworkManagerHelper.cs index 0b40a2b..7ac99ac 100644 --- a/Tests/Runtime/Helpers/NetworkManagerHelper.cs +++ b/TestHelpers/Runtime/NetworkManagerHelper.cs @@ -2,9 +2,8 @@ using System; using System.Collections.Generic; using UnityEngine; using NUnit.Framework; -using Unity.Netcode.Transports.UNET; -namespace Unity.Netcode.RuntimeTests +namespace Unity.Netcode.TestHelpers.Runtime { /// /// Helper class to instantiate a NetworkManager @@ -22,9 +21,9 @@ namespace Unity.Netcode.RuntimeTests public static NetworkManager NetworkManagerObject { get; internal set; } public static GameObject NetworkManagerGameObject { get; internal set; } - internal static Dictionary InstantiatedGameObjects = new Dictionary(); - internal static Dictionary InstantiatedNetworkObjects = new Dictionary(); - internal static NetworkManagerOperatingMode CurrentNetworkManagerMode; + public static Dictionary InstantiatedGameObjects = new Dictionary(); + public static Dictionary InstantiatedNetworkObjects = new Dictionary(); + public static NetworkManagerOperatingMode CurrentNetworkManagerMode; /// /// This provides the ability to start NetworkManager in various modes @@ -68,7 +67,11 @@ namespace Unity.Netcode.RuntimeTests Debug.Log($"{nameof(NetworkManager)} Instantiated."); - var unetTransport = NetworkManagerGameObject.AddComponent(); + // NOTE: For now we only use SIPTransport for tests until UnityTransport + // has been verified working in nightly builds + // TODO-MTT-2486: Provide support for other transports once tested and verified + // working on consoles. + var sipTransport = NetworkManagerGameObject.AddComponent(); if (networkConfig == null) { networkConfig = new NetworkConfig @@ -78,14 +81,7 @@ namespace Unity.Netcode.RuntimeTests } NetworkManagerObject.NetworkConfig = networkConfig; - - unetTransport.ConnectAddress = "127.0.0.1"; - unetTransport.ConnectPort = 7777; - unetTransport.ServerListenPort = 7777; - unetTransport.MessageBufferSize = 65535; - unetTransport.MaxConnections = 100; - unetTransport.MessageSendMode = UNetTransport.SendMode.Immediately; - NetworkManagerObject.NetworkConfig.NetworkTransport = unetTransport; + NetworkManagerObject.NetworkConfig.NetworkTransport = sipTransport; // Starts the network manager in the mode specified StartNetworkManagerMode(managerMode); diff --git a/Tests/Runtime/Helpers/NetworkManagerHelper.cs.meta b/TestHelpers/Runtime/NetworkManagerHelper.cs.meta similarity index 100% rename from Tests/Runtime/Helpers/NetworkManagerHelper.cs.meta rename to TestHelpers/Runtime/NetworkManagerHelper.cs.meta diff --git a/Tests/Runtime/Helpers/NetworkVariableHelper.cs b/TestHelpers/Runtime/NetworkVariableHelper.cs similarity index 96% rename from Tests/Runtime/Helpers/NetworkVariableHelper.cs rename to TestHelpers/Runtime/NetworkVariableHelper.cs index 2b3a16a..880c14f 100644 --- a/Tests/Runtime/Helpers/NetworkVariableHelper.cs +++ b/TestHelpers/Runtime/NetworkVariableHelper.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; -namespace Unity.Netcode.RuntimeTests +namespace Unity.Netcode.TestHelpers.Runtime { /// /// Will automatically register for the NetworkVariable OnValueChanged @@ -13,7 +13,7 @@ namespace Unity.Netcode.RuntimeTests /// From both we can then at least determine if the value indeed changed /// /// - internal class NetworkVariableHelper : NetworkVariableBaseHelper where T : unmanaged + public class NetworkVariableHelper : NetworkVariableBaseHelper where T : unmanaged { private readonly NetworkVariable m_NetworkVariable; public delegate void OnMyValueChangedDelegateHandler(T previous, T next); @@ -73,7 +73,7 @@ namespace Unity.Netcode.RuntimeTests /// The number of times a specific NetworkVariable instance had its value changed (i.e. !Equal) /// Note: This could be expanded for future tests focuses around NetworkVariables /// - internal class NetworkVariableBaseHelper + public class NetworkVariableBaseHelper { private static Dictionary s_Instances; private static Dictionary s_InstanceChangedCount; diff --git a/Tests/Runtime/Helpers/NetworkVariableHelper.cs.meta b/TestHelpers/Runtime/NetworkVariableHelper.cs.meta similarity index 100% rename from Tests/Runtime/Helpers/NetworkVariableHelper.cs.meta rename to TestHelpers/Runtime/NetworkVariableHelper.cs.meta diff --git a/TestHelpers/Runtime/TimeoutHelper.cs b/TestHelpers/Runtime/TimeoutHelper.cs new file mode 100644 index 0000000..f5d2f70 --- /dev/null +++ b/TestHelpers/Runtime/TimeoutHelper.cs @@ -0,0 +1,42 @@ +using UnityEngine; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + /// + /// Can be used independently or assigned to in the + /// event the default timeout period needs to be adjusted + /// + public class TimeoutHelper + { + private const float k_DefaultTimeOutWaitPeriod = 2.0f; + + private float m_MaximumTimeBeforeTimeOut; + private float m_TimeOutPeriod; + + private bool m_IsStarted; + public bool TimedOut { get; internal set; } + + public void Start() + { + m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod; + m_IsStarted = true; + TimedOut = false; + } + + public void Stop() + { + TimedOut = HasTimedOut(); + m_IsStarted = false; + } + + public bool HasTimedOut() + { + return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut; + } + + public TimeoutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod) + { + m_TimeOutPeriod = timeOutPeriod; + } + } +} diff --git a/TestHelpers/Runtime/TimeoutHelper.cs.meta b/TestHelpers/Runtime/TimeoutHelper.cs.meta new file mode 100644 index 0000000..252b9e2 --- /dev/null +++ b/TestHelpers/Runtime/TimeoutHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dcb37435a7d719d4795b6916d4ad12e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Transport.meta b/TestHelpers/Runtime/Transport.meta new file mode 100644 index 0000000..b909388 --- /dev/null +++ b/TestHelpers/Runtime/Transport.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d764f651f0e54e8281952933cc49be97 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Transport/MessageHooks.cs b/TestHelpers/Runtime/Transport/MessageHooks.cs new file mode 100644 index 0000000..5d71a0b --- /dev/null +++ b/TestHelpers/Runtime/Transport/MessageHooks.cs @@ -0,0 +1,70 @@ +using System; + +namespace Unity.Netcode.TestHelpers.Runtime +{ + internal class MessageHooks : INetworkHooks + { + public bool IsWaiting; + public delegate bool MessageReceiptCheck(object receivedMessage); + public MessageReceiptCheck ReceiptCheck; + + public static bool CheckForMessageOfType(object receivedMessage) where T : INetworkMessage + { + return receivedMessage is T; + } + + public void OnBeforeSendMessage(ulong clientId, ref T message, NetworkDelivery delivery) where T : INetworkMessage + { + } + + public void OnAfterSendMessage(ulong clientId, ref T message, NetworkDelivery delivery, int messageSizeBytes) where T : INetworkMessage + { + } + + public void OnBeforeReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnAfterReceiveMessage(ulong senderId, Type messageType, int messageSizeBytes) + { + } + + public void OnBeforeSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnAfterSendBatch(ulong clientId, int messageCount, int batchSizeInBytes, NetworkDelivery delivery) + { + } + + public void OnBeforeReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public void OnAfterReceiveBatch(ulong senderId, int messageCount, int batchSizeInBytes) + { + } + + public bool OnVerifyCanSend(ulong destinationId, Type messageType, NetworkDelivery delivery) + { + return true; + } + + public bool OnVerifyCanReceive(ulong senderId, Type messageType) + { + return true; + } + + public void OnBeforeHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + } + + public void OnAfterHandleMessage(ref T message, ref NetworkContext context) where T : INetworkMessage + { + if (IsWaiting && (ReceiptCheck == null || ReceiptCheck.Invoke(message))) + { + IsWaiting = false; + } + } + } +} diff --git a/TestHelpers/Runtime/Transport/MessageHooks.cs.meta b/TestHelpers/Runtime/Transport/MessageHooks.cs.meta new file mode 100644 index 0000000..38bd5d1 --- /dev/null +++ b/TestHelpers/Runtime/Transport/MessageHooks.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e860701e80ca3be4d8cb89d340faebd1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/TestHelpers/Runtime/Transport/MessageHooksConditional.cs b/TestHelpers/Runtime/Transport/MessageHooksConditional.cs new file mode 100644 index 0000000..71791d2 --- /dev/null +++ b/TestHelpers/Runtime/Transport/MessageHooksConditional.cs @@ -0,0 +1,95 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + + +namespace Unity.Netcode.TestHelpers.Runtime +{ + public class MessageHooksConditional : ConditionalPredicateBase + { + private List m_MessageHookEntries; + + public bool AllMessagesReceived { get; internal set; } + public int NumberOfMessagesReceived + { + get + { + return m_MessageHookEntries.Where((c) => !c.MessageHooks.IsWaiting).Count(); + } + } + + public string GetHooksStillWaiting() + { + var retMessageTypes = string.Empty; + var waitingMessages = m_MessageHookEntries.Where((c) => c.MessageHooks.IsWaiting); + + foreach (var waitingMessage in waitingMessages) + { + retMessageTypes += $":{waitingMessage.MessageType}:"; + } + + return retMessageTypes; + } + + + protected override bool OnHasConditionBeenReached() + { + AllMessagesReceived = NumberOfMessagesReceived == m_MessageHookEntries.Count; + + if (AllMessagesReceived) + { + return AllMessagesReceived; + } + + return AllMessagesReceived; + } + + protected override void OnFinished() + { + base.OnFinished(); + } + + public void Reset() + { + foreach (var entry in m_MessageHookEntries) + { + entry.Initialize(); + } + } + + public MessageHooksConditional(List messageHookEntries) + { + m_MessageHookEntries = messageHookEntries; + } + } + + public class MessageHookEntry + { + internal MessageHooks MessageHooks; + protected NetworkManager m_NetworkManager; + private MessageHooks.MessageReceiptCheck m_MessageReceiptCheck; + internal string MessageType; + + public void Initialize() + { + Assert.IsNotNull(m_MessageReceiptCheck, $"{nameof(m_MessageReceiptCheck)} is null, did you forget to initialize?"); + MessageHooks = new MessageHooks(); + MessageHooks.ReceiptCheck = m_MessageReceiptCheck; + Assert.IsNotNull(m_NetworkManager.MessagingSystem, $"{nameof(NetworkManager.MessagingSystem)} is null! Did you forget to start first?"); + m_NetworkManager.MessagingSystem.Hook(MessageHooks); + } + + internal void AssignMessageType() where T : INetworkMessage + { + MessageType = typeof(T).Name; + m_MessageReceiptCheck = MessageHooks.CheckForMessageOfType; + Initialize(); + } + + public MessageHookEntry(NetworkManager networkManager) + { + m_NetworkManager = networkManager; + } + } +} + diff --git a/TestHelpers/Runtime/Transport/MessageHooksConditional.cs.meta b/TestHelpers/Runtime/Transport/MessageHooksConditional.cs.meta new file mode 100644 index 0000000..aafbd81 --- /dev/null +++ b/TestHelpers/Runtime/Transport/MessageHooksConditional.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a97b3c56f3b2ba42a0669716e81674f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Transport/SIPTransport.cs b/TestHelpers/Runtime/Transport/SIPTransport.cs similarity index 97% rename from Tests/Runtime/Transport/SIPTransport.cs rename to TestHelpers/Runtime/Transport/SIPTransport.cs index 6dfa181..654afb3 100644 --- a/Tests/Runtime/Transport/SIPTransport.cs +++ b/TestHelpers/Runtime/Transport/SIPTransport.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -#if UNITY_EDITOR -using Unity.Netcode.Editor; -#endif using UnityEngine; -namespace Unity.Netcode.RuntimeTests +namespace Unity.Netcode.TestHelpers.Runtime { /// /// SIPTransport (SIngleProcessTransport) @@ -13,10 +10,7 @@ namespace Unity.Netcode.RuntimeTests /// it's designed for the netcode in a way where no networking stack has to be available /// it's designed for testing purposes and it's not designed with speed in mind /// -#if UNITY_EDITOR - [DontShowInTransportDropdown] -#endif - public class SIPTransport : NetworkTransport + public class SIPTransport : TestingNetworkTransport { private struct Event { @@ -104,7 +98,7 @@ namespace Unity.Netcode.RuntimeTests return 50; } - public override void Initialize() + public override void Initialize(NetworkManager networkManager = null) { } diff --git a/Tests/Runtime/Transport/SIPTransport.cs.meta b/TestHelpers/Runtime/Transport/SIPTransport.cs.meta similarity index 100% rename from Tests/Runtime/Transport/SIPTransport.cs.meta rename to TestHelpers/Runtime/Transport/SIPTransport.cs.meta diff --git a/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef b/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef new file mode 100644 index 0000000..5390755 --- /dev/null +++ b/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef @@ -0,0 +1,32 @@ +{ + "name": "Unity.Netcode.TestHelpers.Runtime", + "rootNamespace": "Unity.Netcode.TestHelpers.Runtime", + "references": [ + "Unity.Netcode.Runtime", + "Unity.Netcode.Adapter.UTP", + "Unity.Multiplayer.MetricTypes", + "Unity.Multiplayer.NetStats", + "Unity.Multiplayer.Tools.MetricTypes", + "Unity.Multiplayer.Tools.NetStats" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "versionDefines": [ + { + "name": "com.unity.multiplayer.tools", + "expression": "", + "define": "MULTIPLAYER_TOOLS" + }, + { + "name": "com.unity.netcode.adapter.utp", + "expression": "", + "define": "UTP_ADAPTER" + }, + { + "name": "com.unity.multiplayer.tools", + "expression": "1.0.0-pre.4", + "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4" + } + ] +} \ No newline at end of file diff --git a/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef.meta b/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef.meta new file mode 100644 index 0000000..92fe6f2 --- /dev/null +++ b/TestHelpers/Runtime/com.unity.netcode.testhelpers.runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 101012598ee9f064da897a34e72947f9 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/Messaging/MessageReceivingTests.cs b/Tests/Editor/Messaging/MessageReceivingTests.cs index 90dc911..862a236 100644 --- a/Tests/Editor/Messaging/MessageReceivingTests.cs +++ b/Tests/Editor/Messaging/MessageReceivingTests.cs @@ -15,6 +15,7 @@ namespace Unity.Netcode.EditorTests public int B; public int C; public static bool Deserialized; + public static bool Handled; public static List DeserializedValues = new List(); public void Serialize(FastBufferWriter writer) @@ -22,11 +23,17 @@ namespace Unity.Netcode.EditorTests writer.WriteValueSafe(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { Deserialized = true; - reader.ReadValueSafe(out TestMessage value); - DeserializedValues.Add(value); + reader.ReadValueSafe(out this); + return true; + } + + public void Handle(ref NetworkContext context) + { + Handled = true; + DeserializedValues.Add(this); } } @@ -39,7 +46,7 @@ namespace Unity.Netcode.EditorTests new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessage), - Handler = TestMessage.Receive + Handler = MessagingSystem.ReceiveMessage } }; } @@ -51,6 +58,7 @@ namespace Unity.Netcode.EditorTests public void SetUp() { TestMessage.Deserialized = false; + TestMessage.Handled = false; TestMessage.DeserializedValues.Clear(); m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestMessageProvider()); @@ -94,6 +102,7 @@ namespace Unity.Netcode.EditorTests { m_MessagingSystem.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]); } @@ -129,7 +138,8 @@ namespace Unity.Netcode.EditorTests { m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); Assert.IsFalse(TestMessage.Deserialized); - Assert.IsEmpty(TestMessage.DeserializedValues); ; + Assert.IsFalse(TestMessage.Handled); + Assert.IsEmpty(TestMessage.DeserializedValues); } } } @@ -162,6 +172,7 @@ namespace Unity.Netcode.EditorTests m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); m_MessagingSystem.ProcessIncomingMessageQueue(); Assert.IsTrue(TestMessage.Deserialized); + Assert.IsTrue(TestMessage.Handled); Assert.AreEqual(1, TestMessage.DeserializedValues.Count); Assert.AreEqual(message, TestMessage.DeserializedValues[0]); } @@ -199,10 +210,12 @@ namespace Unity.Netcode.EditorTests { m_MessagingSystem.HandleIncomingData(0, new ArraySegment(writer.ToArray()), 0); Assert.IsFalse(TestMessage.Deserialized); + Assert.IsFalse(TestMessage.Handled); Assert.IsEmpty(TestMessage.DeserializedValues); m_MessagingSystem.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]); diff --git a/Tests/Editor/Messaging/MessageRegistrationTests.cs b/Tests/Editor/Messaging/MessageRegistrationTests.cs index 5f25cbb..3a6cd31 100644 --- a/Tests/Editor/Messaging/MessageRegistrationTests.cs +++ b/Tests/Editor/Messaging/MessageRegistrationTests.cs @@ -5,21 +5,23 @@ namespace Unity.Netcode.EditorTests { public class MessageRegistrationTests { - private struct TestMessageOne : INetworkMessage { public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) { writer.WriteValue(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { + return true; + } + public void Handle(ref NetworkContext context) + { } } @@ -28,15 +30,18 @@ namespace Unity.Netcode.EditorTests public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) { writer.WriteValue(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { + return true; + } + public void Handle(ref NetworkContext context) + { } } private class TestMessageProviderOne : IMessageProvider @@ -48,12 +53,12 @@ namespace Unity.Netcode.EditorTests new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageOne), - Handler = TestMessageOne.Receive + Handler = MessagingSystem.ReceiveMessage }, new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageTwo), - Handler = TestMessageTwo.Receive + Handler = MessagingSystem.ReceiveMessage } }; } @@ -64,15 +69,18 @@ namespace Unity.Netcode.EditorTests public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) { writer.WriteValue(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { + return true; + } + public void Handle(ref NetworkContext context) + { } } private class TestMessageProviderTwo : IMessageProvider @@ -84,26 +92,28 @@ namespace Unity.Netcode.EditorTests new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageThree), - Handler = TestMessageThree.Receive + Handler = MessagingSystem.ReceiveMessage } }; } } - private struct TestMessageFour : INetworkMessage { public int A; public int B; public int C; - public void Serialize(FastBufferWriter writer) { writer.WriteValue(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) { + return true; + } + public void Handle(ref NetworkContext context) + { } } private class TestMessageProviderThree : IMessageProvider @@ -115,7 +125,7 @@ namespace Unity.Netcode.EditorTests new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessageFour), - Handler = TestMessageFour.Receive + Handler = MessagingSystem.ReceiveMessage } }; } @@ -158,21 +168,17 @@ namespace Unity.Netcode.EditorTests using (systemTwo) using (systemThree) { - MessagingSystem.MessageHandler handlerOne = TestMessageOne.Receive; - MessagingSystem.MessageHandler handlerTwo = TestMessageTwo.Receive; - MessagingSystem.MessageHandler handlerThree = TestMessageThree.Receive; - MessagingSystem.MessageHandler handlerFour = TestMessageFour.Receive; + MessagingSystem.MessageHandler handlerOne = MessagingSystem.ReceiveMessage; + MessagingSystem.MessageHandler handlerTwo = MessagingSystem.ReceiveMessage; + MessagingSystem.MessageHandler handlerThree = MessagingSystem.ReceiveMessage; + MessagingSystem.MessageHandler handlerFour = MessagingSystem.ReceiveMessage; var foundHandlerOne = systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]; - Assert.AreEqual(handlerOne, - systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]); - Assert.AreEqual(handlerTwo, - systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageTwo))]); - Assert.AreEqual(handlerThree, - systemTwo.MessageHandlers[systemTwo.GetMessageType(typeof(TestMessageThree))]); - Assert.AreEqual(handlerFour, - systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); + Assert.AreEqual(handlerOne, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageOne))]); + Assert.AreEqual(handlerTwo, systemOne.MessageHandlers[systemOne.GetMessageType(typeof(TestMessageTwo))]); + Assert.AreEqual(handlerThree, systemTwo.MessageHandlers[systemTwo.GetMessageType(typeof(TestMessageThree))]); + Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); } } } diff --git a/Tests/Editor/Messaging/MessageSendingTests.cs b/Tests/Editor/Messaging/MessageSendingTests.cs index ebf6b21..79c5629 100644 --- a/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/Tests/Editor/Messaging/MessageSendingTests.cs @@ -14,14 +14,18 @@ namespace Unity.Netcode.EditorTests public int B; public int C; public static bool Serialized; - public void Serialize(FastBufferWriter writer) { Serialized = true; writer.WriteValueSafe(this); } - public static void Receive(FastBufferReader reader, in NetworkContext context) + public bool Deserialize(FastBufferReader reader, ref NetworkContext context) + { + return true; + } + + public void Handle(ref NetworkContext context) { } } @@ -45,7 +49,7 @@ namespace Unity.Netcode.EditorTests new MessagingSystem.MessageWithHandler { MessageType = typeof(TestMessage), - Handler = TestMessage.Receive + Handler = MessagingSystem.ReceiveMessage } }; } @@ -86,7 +90,7 @@ namespace Unity.Netcode.EditorTests public void WhenSendingMessage_SerializeIsCalled() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); Assert.IsTrue(TestMessage.Serialized); } @@ -94,7 +98,7 @@ namespace Unity.Netcode.EditorTests public void WhenSendingMessage_NothingIsSentBeforeProcessingSendQueue() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); Assert.IsEmpty(m_MessageSender.MessageQueue); } @@ -102,7 +106,7 @@ namespace Unity.Netcode.EditorTests public void WhenProcessingSendQueue_MessageIsSent() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.ProcessSendQueues(); Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); @@ -112,9 +116,9 @@ namespace Unity.Netcode.EditorTests public void WhenSendingMultipleMessages_MessagesAreBatched() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.ProcessSendQueues(); Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); @@ -127,7 +131,7 @@ namespace Unity.Netcode.EditorTests var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes for (var i = 0; i < 1300 / size; ++i) { - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); } m_MessagingSystem.ProcessSendQueues(); @@ -141,7 +145,7 @@ namespace Unity.Netcode.EditorTests var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes for (var i = 0; i < (1300 / size) + 1; ++i) { - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); } m_MessagingSystem.ProcessSendQueues(); @@ -155,7 +159,7 @@ namespace Unity.Netcode.EditorTests var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes for (var i = 0; i < (1300 / size) + 1; ++i) { - m_MessagingSystem.SendMessage(message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, m_Clients); } m_MessagingSystem.ProcessSendQueues(); @@ -166,9 +170,9 @@ namespace Unity.Netcode.EditorTests public void WhenSwitchingDelivery_NewBatchesAreCreated() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Unreliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Unreliable, m_Clients); m_MessagingSystem.ProcessSendQueues(); Assert.AreEqual(2, m_MessageSender.MessageQueue.Count); @@ -178,9 +182,9 @@ namespace Unity.Netcode.EditorTests public void WhenSwitchingChannel_NewBatchesAreNotCreated() { var message = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.ProcessSendQueues(); Assert.AreEqual(1, m_MessageSender.MessageQueue.Count); @@ -191,8 +195,8 @@ namespace Unity.Netcode.EditorTests { var message = GetMessage(); var message2 = GetMessage(); - m_MessagingSystem.SendMessage(message, NetworkDelivery.Reliable, m_Clients); - m_MessagingSystem.SendMessage(message2, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message, NetworkDelivery.Reliable, m_Clients); + m_MessagingSystem.SendMessage(ref message2, NetworkDelivery.Reliable, m_Clients); m_MessagingSystem.ProcessSendQueues(); var reader = new FastBufferReader(m_MessageSender.MessageQueue[0], Allocator.Temp); diff --git a/Tests/Editor/Serialization.meta b/Tests/Editor/Serialization.meta index b3c7bee..8a7aaf9 100644 --- a/Tests/Editor/Serialization.meta +++ b/Tests/Editor/Serialization.meta @@ -1,3 +1,8 @@ -fileFormatVersion: 2 +fileFormatVersion: 2 guid: e12c4be6e89f459aa2826abba8c8d301 -timeCreated: 1628799671 \ No newline at end of file +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs b/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs index e08d038..acc4f5f 100644 --- a/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs +++ b/Tests/Editor/Serialization/BaseFastBufferReaderWriterTest.cs @@ -3,7 +3,7 @@ using NUnit.Framework; using UnityEngine; using Random = System.Random; -namespace Unity.Netcode +namespace Unity.Netcode.EditorTests { public abstract class BaseFastBufferReaderWriterTest { diff --git a/Tests/Editor/Serialization/FastBufferReaderTests.cs b/Tests/Editor/Serialization/FastBufferReaderTests.cs index 6156ffa..1286dc1 100644 --- a/Tests/Editor/Serialization/FastBufferReaderTests.cs +++ b/Tests/Editor/Serialization/FastBufferReaderTests.cs @@ -346,6 +346,52 @@ namespace Unity.Netcode.EditorTests } } + [Test] + public void WhenCreatingAReaderFromAnEmptyArraySegment_LengthIsZero() + { + var bytes = new byte[] { }; + var input = new ArraySegment(bytes, 0, 0); + using var reader = new FastBufferReader(input, Allocator.Temp); + Assert.AreEqual(0, reader.Length); + } + + [Test] + public void WhenCreatingAReaderFromAnEmptyArray_LengthIsZero() + { + var input = new byte[] { }; + using var reader = new FastBufferReader(input, Allocator.Temp); + Assert.AreEqual(0, reader.Length); + } + + [Test] + public void WhenCreatingAReaderFromAnEmptyNativeArray_LengthIsZero() + { + var input = new NativeArray(0, Allocator.Temp); + using var reader = new FastBufferReader(input, Allocator.Temp); + Assert.AreEqual(0, reader.Length); + } + + [Test] + public void WhenCreatingAReaderFromAnEmptyFastBufferWriter_LengthIsZero() + { + var input = new FastBufferWriter(0, Allocator.Temp); + using var reader = new FastBufferReader(input, Allocator.Temp); + Assert.AreEqual(0, reader.Length); + } + + [Test] + public void WhenCreatingAReaderFromAnEmptyBuffer_LengthIsZero() + { + var input = new byte[] { }; + unsafe + { + fixed (byte* ptr = input) + { + using var reader = new FastBufferReader(ptr, Allocator.Temp, 0); + Assert.AreEqual(0, reader.Length); + } + } + } [Test] public void WhenCallingReadByteWithoutCallingTryBeingReadFirst_OverflowExceptionIsThrown() diff --git a/Tests/Editor/SnapshotTests.cs b/Tests/Editor/SnapshotTests.cs index 2ab3354..7fc465e 100644 --- a/Tests/Editor/SnapshotTests.cs +++ b/Tests/Editor/SnapshotTests.cs @@ -67,7 +67,7 @@ namespace Unity.Netcode.EditorTests return 0; } - internal int SendMessage(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId) + internal int SendMessage(ref SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId) { if (!m_PassBackResponses) { @@ -106,19 +106,15 @@ namespace Unity.Netcode.EditorTests message.Serialize(writer); using var reader = new FastBufferReader(writer, Allocator.Temp); var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple(m_RecvSnapshot, 0) }; - SnapshotDataMessage.Receive(reader, context); - } - else - { - message.Spawns.Dispose(); - message.Despawns.Dispose(); - message.Entries.Dispose(); + var newMessage = new SnapshotDataMessage(); + newMessage.Deserialize(reader, ref context); + newMessage.Handle(ref context); } return 0; } - internal int SendMessageRecvSide(in SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId) + internal int SendMessageRecvSide(ref SnapshotDataMessage message, NetworkDelivery delivery, ulong clientId) { if (m_PassBackResponses) { @@ -126,13 +122,9 @@ namespace Unity.Netcode.EditorTests message.Serialize(writer); using var reader = new FastBufferReader(writer, Allocator.Temp); var context = new NetworkContext { SenderId = 0, Timestamp = 0.0f, SystemOwner = new Tuple(m_SendSnapshot, 1) }; - SnapshotDataMessage.Receive(reader, context); - } - else - { - message.Spawns.Dispose(); - message.Despawns.Dispose(); - message.Entries.Dispose(); + var newMessage = new SnapshotDataMessage(); + newMessage.Deserialize(reader, ref context); + newMessage.Handle(ref context); } return 0; @@ -369,4 +361,3 @@ namespace Unity.Netcode.EditorTests } } } - diff --git a/Tests/Editor/com.unity.netcode.editortests.asmdef b/Tests/Editor/com.unity.netcode.editortests.asmdef index 25626d1..f0e437d 100644 --- a/Tests/Editor/com.unity.netcode.editortests.asmdef +++ b/Tests/Editor/com.unity.netcode.editortests.asmdef @@ -7,7 +7,9 @@ "Unity.Netcode.Editor", "Unity.Netcode.Components", "Unity.Multiplayer.MetricTypes", - "Unity.Multiplayer.NetStats" + "Unity.Multiplayer.NetStats", + "Unity.Multiplayer.Tools.MetricTypes", + "Unity.Multiplayer.Tools.NetStats" ], "optionalUnityReferences": [ "TestAssemblies" @@ -15,18 +17,7 @@ "includePlatforms": [ "Editor" ], - "excludePlatforms": [], "allowUnsafeCode": true, - "overrideReferences": true, - "precompiledReferences": [ - "nunit.framework.dll" - ], - "autoReferenced": false, - "defineConstraints": [ - "UNITY_INCLUDE_TESTS" - ], - "versionDefines": [], - "noEngineReferences": false, "versionDefines": [ { "name": "com.unity.multiplayer.tools", diff --git a/Tests/Runtime/BaseMultiInstanceTest.cs b/Tests/Runtime/BaseMultiInstanceTest.cs deleted file mode 100644 index feed6e8..0000000 --- a/Tests/Runtime/BaseMultiInstanceTest.cs +++ /dev/null @@ -1,178 +0,0 @@ -using System; -using System.Collections; -using System.Linq; -using NUnit.Framework; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.TestTools; -using Object = UnityEngine.Object; - -namespace Unity.Netcode.RuntimeTests -{ - public abstract class BaseMultiInstanceTest - { - private const string k_FirstPartOfTestRunnerSceneName = "InitTestScene"; - - protected GameObject m_PlayerPrefab; - protected NetworkManager m_ServerNetworkManager; - protected NetworkManager[] m_ClientNetworkManagers; - - protected abstract int NbClients { get; } - - protected bool m_BypassStartAndWaitForClients = false; - - [UnitySetUp] - public virtual IEnumerator Setup() - { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, _ => { }); - } - - [UnityTearDown] - public virtual IEnumerator Teardown() - { - // Shutdown and clean up both of our NetworkManager instances - try - { - MultiInstanceHelpers.Destroy(); - } - catch (Exception e) { throw e; } - finally - { - if (m_PlayerPrefab != null) - { - Object.Destroy(m_PlayerPrefab); - m_PlayerPrefab = null; - } - } - - // Make sure any NetworkObject with a GlobalObjectIdHash value of 0 is destroyed - // If we are tearing down, we don't want to leave NetworkObjects hanging around - var networkObjects = Object.FindObjectsOfType().ToList(); - foreach (var networkObject in networkObjects) - { - Object.DestroyImmediate(networkObject); - } - - // wait for next frame so everything is destroyed, so following tests can execute from clean environment - int nextFrameNumber = Time.frameCount + 1; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); - } - - /// - /// We want to exclude the TestRunner scene on the host-server side so it won't try to tell clients to - /// synchronize to this scene when they connect - /// - private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode) - { - // exclude test runner scene - if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName)) - { - return false; - } - return true; - } - - /// - /// This registers scene validation callback for the server to prevent it from telling connecting - /// clients to synchronize (i.e. load) the test runner scene. This will also register the test runner - /// scene and its handle for both client(s) and server-host. - /// - public static void SceneManagerValidationAndTestRunnerInitialization(NetworkManager networkManager) - { - // If VerifySceneBeforeLoading is not already set, then go ahead and set it so the host/server - // will not try to synchronize clients to the TestRunner scene. We only need to do this for the server. - if (networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null) - { - networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad; - // If a unit/integration test does not handle this on their own, then Ignore the validation warning - networkManager.SceneManager.DisableValidationWarnings(true); - } - - // Register the test runner scene so it will be able to synchronize NetworkObjects without logging a - // warning about using the currently active scene - var scene = SceneManager.GetActiveScene(); - // As long as this is a test runner scene (or most likely a test runner scene) - if (scene.name.StartsWith(k_FirstPartOfTestRunnerSceneName)) - { - // Register the test runner scene just so we avoid another warning about not being able to find the - // scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes - // loaded and register the server to client scene handle since host-server shares the test runner scene - // with the clients. - networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name); - networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); - } - } - - /// - /// Utility to spawn some clients and a server and set them up - /// - /// - /// Update the prefab with whatever is needed before players spawn - /// The targetFrameRate of the Unity engine to use while this multi instance test is running. Will be reset on teardown. - /// - public IEnumerator StartSomeClientsAndServerWithPlayers(bool useHost, int nbClients, Action updatePlayerPrefab = null, int targetFrameRate = 60) - { - // Make sure any NetworkObject with a GlobalObjectIdHash value of 0 is destroyed - // If we are tearing down, we don't want to leave NetworkObjects hanging around - var networkObjects = Object.FindObjectsOfType().ToList(); - var networkObjectsList = networkObjects.Where(c => c.GlobalObjectIdHash == 0); - foreach (var netObject in networkObjects) - { - Object.DestroyImmediate(netObject); - } - - // Create multiple NetworkManager instances - if (!MultiInstanceHelpers.Create(nbClients, out NetworkManager server, out NetworkManager[] clients, targetFrameRate)) - { - Debug.LogError("Failed to create instances"); - Assert.Fail("Failed to create instances"); - } - - m_ClientNetworkManagers = clients; - m_ServerNetworkManager = server; - - // Create playerPrefab - m_PlayerPrefab = new GameObject("Player"); - NetworkObject networkObject = m_PlayerPrefab.AddComponent(); - /* - * Normally we would only allow player prefabs to be set to a prefab. Not runtime created objects. - * In order to prevent having a Resource folder full of a TON of prefabs that we have to maintain, - * MultiInstanceHelper has a helper function that lets you mark a runtime created object to be - * treated as a prefab by the Netcode. That's how we can get away with creating the player prefab - * at runtime without it being treated as a SceneObject or causing other conflicts with the Netcode. - */ - // Make it a prefab - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); - - if (updatePlayerPrefab != null) - { - updatePlayerPrefab(m_PlayerPrefab); // update player prefab with whatever is needed before players are spawned - } - - // Set the player prefab - server.NetworkConfig.PlayerPrefab = m_PlayerPrefab; - - for (int i = 0; i < clients.Length; i++) - { - clients[i].NetworkConfig.PlayerPrefab = m_PlayerPrefab; - } - - if (!m_BypassStartAndWaitForClients) - { - // Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server - // is started and after each client is started. - if (!MultiInstanceHelpers.Start(useHost, server, clients, SceneManagerValidationAndTestRunnerInitialization)) - { - Debug.LogError("Failed to start instances"); - Assert.Fail("Failed to start instances"); - } - - // Wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); - - // Wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, useHost ? nbClients + 1 : nbClients)); - } - } - } -} diff --git a/Tests/Runtime/Components/BufferDataValidationComponent.cs b/Tests/Runtime/Components/BufferDataValidationComponent.cs index 759cce1..b9e1ac8 100644 --- a/Tests/Runtime/Components/BufferDataValidationComponent.cs +++ b/Tests/Runtime/Components/BufferDataValidationComponent.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using UnityEngine; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { diff --git a/Tests/Runtime/Components/NetworkVariableTestComponent.cs b/Tests/Runtime/Components/NetworkVariableTestComponent.cs index 96d11b3..78045c3 100644 --- a/Tests/Runtime/Components/NetworkVariableTestComponent.cs +++ b/Tests/Runtime/Components/NetworkVariableTestComponent.cs @@ -1,4 +1,5 @@ using UnityEngine; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -11,24 +12,24 @@ namespace Unity.Netcode.RuntimeTests /// internal class NetworkVariableTestComponent : NetworkBehaviour { - private NetworkVariable m_NetworkVariableBool; - private NetworkVariable m_NetworkVariableByte; - private NetworkVariable m_NetworkVariableColor; - private NetworkVariable m_NetworkVariableColor32; - private NetworkVariable m_NetworkVariableDouble; - private NetworkVariable m_NetworkVariableFloat; - private NetworkVariable m_NetworkVariableInt; - private NetworkVariable m_NetworkVariableLong; - private NetworkVariable m_NetworkVariableSByte; - private NetworkVariable m_NetworkVariableQuaternion; - private NetworkVariable m_NetworkVariableShort; - private NetworkVariable m_NetworkVariableVector4; - private NetworkVariable m_NetworkVariableVector3; - private NetworkVariable m_NetworkVariableVector2; - private NetworkVariable m_NetworkVariableRay; - private NetworkVariable m_NetworkVariableULong; - private NetworkVariable m_NetworkVariableUInt; - private NetworkVariable m_NetworkVariableUShort; + private NetworkVariable m_NetworkVariableBool = new NetworkVariable(); + private NetworkVariable m_NetworkVariableByte = new NetworkVariable(); + private NetworkVariable m_NetworkVariableColor = new NetworkVariable(); + private NetworkVariable m_NetworkVariableColor32 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableDouble = new NetworkVariable(); + private NetworkVariable m_NetworkVariableFloat = new NetworkVariable(); + private NetworkVariable m_NetworkVariableInt = new NetworkVariable(); + private NetworkVariable m_NetworkVariableLong = new NetworkVariable(); + private NetworkVariable m_NetworkVariableSByte = new NetworkVariable(); + private NetworkVariable m_NetworkVariableQuaternion = new NetworkVariable(); + private NetworkVariable m_NetworkVariableShort = new NetworkVariable(); + private NetworkVariable m_NetworkVariableVector4 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableVector3 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableVector2 = new NetworkVariable(); + private NetworkVariable m_NetworkVariableRay = new NetworkVariable(); + private NetworkVariable m_NetworkVariableULong = new NetworkVariable(); + private NetworkVariable m_NetworkVariableUInt = new NetworkVariable(); + private NetworkVariable m_NetworkVariableUShort = new NetworkVariable(); public NetworkVariableHelper Bool_Var; diff --git a/Tests/Runtime/Components/NetworkVisibilityComponent.cs b/Tests/Runtime/Components/NetworkVisibilityComponent.cs new file mode 100644 index 0000000..2abf2c8 --- /dev/null +++ b/Tests/Runtime/Components/NetworkVisibilityComponent.cs @@ -0,0 +1,13 @@ +namespace Unity.Netcode.RuntimeTests +{ + public class NetworkVisibilityComponent : NetworkBehaviour + { + public void Hide() + { + GetComponent().CheckObjectVisibility += HandleCheckObjectVisibility; + } + + protected virtual bool HandleCheckObjectVisibility(ulong clientId) => false; + + } +} diff --git a/Tests/Runtime/Components/NetworkVisibilityComponent.cs.meta b/Tests/Runtime/Components/NetworkVisibilityComponent.cs.meta new file mode 100644 index 0000000..b148896 --- /dev/null +++ b/Tests/Runtime/Components/NetworkVisibilityComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c8284d1c5a9f4c3783a16e314376ea3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/ConnectionApproval.cs b/Tests/Runtime/ConnectionApproval.cs index 3b48766..d9b4b39 100644 --- a/Tests/Runtime/ConnectionApproval.cs +++ b/Tests/Runtime/ConnectionApproval.cs @@ -4,6 +4,7 @@ using System.Collections; using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { diff --git a/Tests/Runtime/DisconnectTests.cs b/Tests/Runtime/DisconnectTests.cs index 45fb045..2fb7541 100644 --- a/Tests/Runtime/DisconnectTests.cs +++ b/Tests/Runtime/DisconnectTests.cs @@ -3,6 +3,7 @@ using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -12,13 +13,13 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator RemoteDisconnectPlayerObjectCleanup() { // create server and client instances - MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); + NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); // create prefab var gameObject = new GameObject("PlayerObject"); var networkObject = gameObject.AddComponent(); networkObject.DontDestroyWithOwner = true; - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); server.NetworkConfig.PlayerPrefab = gameObject; @@ -28,13 +29,13 @@ namespace Unity.Netcode.RuntimeTests } // start server and connect clients - MultiInstanceHelpers.Start(false, server, clients); + NetcodeIntegrationTestHelpers.Start(false, server, clients); // wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); // wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server)); + yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(server); // disconnect the remote client server.DisconnectClient(clients[0].LocalClientId); @@ -47,7 +48,7 @@ namespace Unity.Netcode.RuntimeTests Assert.False(server.SpawnManager.SpawnedObjects.Any(x => x.Value.IsPlayerObject && x.Value.OwnerClientId == clients[0].LocalClientId)); // cleanup - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); } } } diff --git a/Tests/Runtime/HiddenVariableTests.cs b/Tests/Runtime/HiddenVariableTests.cs index 96e60f5..7701169 100644 --- a/Tests/Runtime/HiddenVariableTests.cs +++ b/Tests/Runtime/HiddenVariableTests.cs @@ -2,6 +2,7 @@ using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -41,47 +42,23 @@ namespace Unity.Netcode.RuntimeTests } } - public class HiddenVariableTests : BaseMultiInstanceTest + public class HiddenVariableTests : NetcodeIntegrationTest { - protected override int NbClients => 4; + protected override int NumberOfClients => 4; private NetworkObject m_NetSpawnedObject; private List m_NetSpawnedObjectOnClient = new List(); private GameObject m_TestNetworkPrefab; - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients, - updatePlayerPrefab: playerPrefab => - { - var networkTransform = playerPrefab.AddComponent(); - m_TestNetworkPrefab = PreparePrefab(); - }); + m_PlayerPrefab.AddComponent(); } - public GameObject PreparePrefab() + protected override void OnServerAndClientsCreated() { - var prefabToSpawn = new GameObject("MyTestObject"); - var networkObjectPrefab = prefabToSpawn.AddComponent(); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab); - prefabToSpawn.AddComponent(); - - m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); - } - return prefabToSpawn; - } - - public IEnumerator WaitForConnectedCount(int targetCount) - { - var endTime = Time.realtimeSinceStartup + 1.0; - while (m_ServerNetworkManager.ConnectedClientsList.Count < targetCount && Time.realtimeSinceStartup < endTime) - { - yield return new WaitForSeconds(0.01f); - } + m_TestNetworkPrefab = CreateNetworkObjectPrefab("MyTestObject"); + m_TestNetworkPrefab.AddComponent(); } public IEnumerator WaitForSpawnCount(int targetCount) @@ -131,12 +108,11 @@ namespace Unity.Netcode.RuntimeTests foreach (var netMan in m_ClientNetworkManagers) { - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run( - MultiInstanceHelpers.GetNetworkObjectByRepresentation( + var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.NetworkObjectId == m_NetSpawnedObject.NetworkObjectId, netMan, - serverClientPlayerResult)); + serverClientPlayerResult); m_NetSpawnedObjectOnClient.Add(serverClientPlayerResult.Result); } } @@ -151,22 +127,17 @@ namespace Unity.Netcode.RuntimeTests Debug.Log("Running test"); - var spawnedObject = Object.Instantiate(m_TestNetworkPrefab); - m_NetSpawnedObject = spawnedObject.GetComponent(); - m_NetSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager; - yield return WaitForConnectedCount(NbClients); - Debug.Log("Clients connected"); // ==== Spawn object with ownership on one client var client = m_ServerNetworkManager.ConnectedClientsList[1]; var otherClient = m_ServerNetworkManager.ConnectedClientsList[2]; - m_NetSpawnedObject.SpawnWithOwnership(client.ClientId); + m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent(); yield return RefreshGameObects(); // === Check spawn occured - yield return WaitForSpawnCount(NbClients + 1); - Debug.Assert(HiddenVariableObject.SpawnCount == NbClients + 1); + yield return WaitForSpawnCount(NumberOfClients + 1); + Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1); Debug.Log("Objects spawned"); // ==== Set the NetworkVariable value to 2 diff --git a/Tests/Runtime/IntegrationTestExamples.cs b/Tests/Runtime/IntegrationTestExamples.cs new file mode 100644 index 0000000..d67030b --- /dev/null +++ b/Tests/Runtime/IntegrationTestExamples.cs @@ -0,0 +1,184 @@ +using System.Collections; +using System.Linq; +using UnityEngine; +using UnityEngine.TestTools; +using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class IntegrationTestUpdated : NetcodeIntegrationTest + { + private GameObject m_MyNetworkPrefab; + protected override int NumberOfClients => 1; + + protected override void OnServerAndClientsCreated() + { + m_MyNetworkPrefab = CreateNetworkObjectPrefab("Object"); + m_MyNetworkPrefab.AddComponent(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + SpawnObject(m_MyNetworkPrefab, m_ServerNetworkManager); + yield return base.OnServerAndClientsConnected(); + } + + [UnityTest] + public IEnumerator MyFirstIntegationTest() + { + // Check the condition for this test and automatically handle varying processing + // environments and conditions + yield return WaitForConditionOrTimeOut(() => + Object.FindObjectsOfType().Where( + (c) => c.IsSpawned).Count() == 2); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for instances " + + "to be detected!"); + } + } + + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + public class IntegrationTestExtended : NetcodeIntegrationTest + { + private GameObject m_MyNetworkPrefab; + protected override int NumberOfClients => 1; + + protected override void OnServerAndClientsCreated() + { + m_MyNetworkPrefab = CreateNetworkObjectPrefab("Object"); + m_MyNetworkPrefab.AddComponent(); + } + + public IntegrationTestExtended(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override IEnumerator OnServerAndClientsConnected() + { + SpawnObject(m_MyNetworkPrefab, m_ServerNetworkManager); + yield return base.OnServerAndClientsConnected(); + } + + [UnityTest] + public IEnumerator MyFirstIntegationTest() + { + // Check the condition for this test and automatically handle varying processing + // environments and conditions + yield return WaitForConditionOrTimeOut(() => + Object.FindObjectsOfType().Where( + (c) => c.IsSpawned).Count() == 2); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for instances " + + "to be detected!"); + } + } + + public class ExampleTestComponent : NetworkBehaviour + { + } + + public class IntegrationTestPlayers : NetcodeIntegrationTest + { + protected override int NumberOfClients => 5; + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + return base.OnServerAndClientsConnected(); + } + + [Test] + public void TestClientRelativePlayers() + { + // Check that all instances have the ExampleTestComponent + foreach (var clientRelativePlayers in m_PlayerNetworkObjects) + { + foreach (var playerInstance in clientRelativePlayers.Value) + { + var player = playerInstance.Value; + Assert.NotNull(player.GetComponent()); + } + } + + // Confirm Player ID 1 on Client ID 4 is not the local player + Assert.IsFalse(m_PlayerNetworkObjects[4][1].IsLocalPlayer); + // Confirm Player ID 4 on Client ID 4 is the local player + Assert.IsTrue(m_PlayerNetworkObjects[4][4].IsLocalPlayer); + // Confirm Player ID 0 on Client ID 0 (host) NetworkManager is the server + Assert.IsTrue(m_PlayerNetworkObjects[0][0].NetworkManager.IsServer); + // Confirm Player ID 0 on Client ID 4 (client) NetworkManager is not the server + Assert.IsFalse(m_PlayerNetworkObjects[4][0].NetworkManager.IsServer); + } + } + + public class SpawnTest : NetworkBehaviour + { + public static int TotalSpawned; + public override void OnNetworkSpawn() { TotalSpawned++; } + public override void OnNetworkDespawn() { TotalSpawned--; } + } + public class IntegrationTestSpawning : NetcodeIntegrationTest + { + protected override int NumberOfClients => 2; + private GameObject m_NetworkPrefabToSpawn; + private int m_NumberToSpawn = 5; + + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() + { + return NetworkManagerInstatiationMode.AllTests; + } + + protected override void OnServerAndClientsCreated() + { + m_NetworkPrefabToSpawn = CreateNetworkObjectPrefab("TrackingTest"); + m_NetworkPrefabToSpawn.gameObject.AddComponent(); + } + + protected override IEnumerator OnServerAndClientsConnected() + { + SpawnObjects(m_NetworkPrefabToSpawn, m_ServerNetworkManager, m_NumberToSpawn); + return base.OnServerAndClientsConnected(); + } + + [UnityTest] + [Order(1)] + public IEnumerator TestRelativeNetworkObjects() + { + var expected = m_NumberToSpawn * TotalClients; + // Wait for all clients to have spawned all instances + yield return WaitForConditionOrTimeOut(() => SpawnTest.TotalSpawned == expected); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all to " + + $"spawn! Total Spawned: {SpawnTest.TotalSpawned}"); + + var client1Relative = s_GlobalNetworkObjects[1].Values.Where((c) => + c.gameObject.GetComponent() != null); + foreach (var networkObject in client1Relative) + { + var testComp = networkObject.GetComponent(); + // Confirm each one is owned by the server + Assert.IsTrue(testComp.IsOwnedByServer, $"{testComp.name} is not owned" + + $" by the server!"); + } + } + + [UnityTest] + [Order(2)] + public IEnumerator TestDespawnNetworkObjects() + { + var serverRelative = s_GlobalNetworkObjects[0].Values.Where((c) => + c.gameObject.GetComponent() != null).ToList(); + foreach (var networkObject in serverRelative) + { + networkObject.Despawn(); + } + // Wait for all clients to have spawned all instances + yield return WaitForConditionOrTimeOut(() => SpawnTest.TotalSpawned == 0); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all to " + + $"despawn! Total Spawned: {SpawnTest.TotalSpawned}"); + } + } +} diff --git a/Tests/Runtime/IntegrationTestExamples.cs.meta b/Tests/Runtime/IntegrationTestExamples.cs.meta new file mode 100644 index 0000000..d0c1c99 --- /dev/null +++ b/Tests/Runtime/IntegrationTestExamples.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5a64e8d7b2082a9409b53f83d0c4e537 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Messaging/NamedMessageTests.cs b/Tests/Runtime/Messaging/NamedMessageTests.cs index 9c246e3..63dd7d3 100644 --- a/Tests/Runtime/Messaging/NamedMessageTests.cs +++ b/Tests/Runtime/Messaging/NamedMessageTests.cs @@ -5,16 +5,24 @@ using NUnit.Framework; using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { - public class NamedMessageTests : BaseMultiInstanceTest + public class NamedMessageTests : NetcodeIntegrationTest { - protected override int NbClients => 2; + protected override int NumberOfClients => 2; private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() + { + // Don't spin up and shutdown NetworkManager instances for each test + // within this set of integration tests. + return NetworkManagerInstatiationMode.AllTests; + } + [UnityTest] public IEnumerator NamedMessageIsReceivedOnClientWithContent() { diff --git a/Tests/Runtime/Messaging/UnnamedMessageTests.cs b/Tests/Runtime/Messaging/UnnamedMessageTests.cs index 45721a0..569c5ff 100644 --- a/Tests/Runtime/Messaging/UnnamedMessageTests.cs +++ b/Tests/Runtime/Messaging/UnnamedMessageTests.cs @@ -5,12 +5,13 @@ using NUnit.Framework; using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { - public class UnnamedMessageTests : BaseMultiInstanceTest + public class UnnamedMessageTests : NetcodeIntegrationTest { - protected override int NbClients => 2; + protected override int NumberOfClients => 2; private NetworkManager FirstClient => m_ClientNetworkManagers[0]; private NetworkManager SecondClient => m_ClientNetworkManagers[1]; diff --git a/Tests/Runtime/Metrics/MessagingMetricsTests.cs b/Tests/Runtime/Metrics/MessagingMetricsTests.cs index ad3cec2..31c3e00 100644 --- a/Tests/Runtime/Metrics/MessagingMetricsTests.cs +++ b/Tests/Runtime/Metrics/MessagingMetricsTests.cs @@ -6,7 +6,7 @@ using System.Linq; using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; +using Unity.Netcode.TestHelpers.Runtime.Metrics; using UnityEngine; using UnityEngine.TestTools; @@ -20,12 +20,12 @@ namespace Unity.Netcode.RuntimeTests.Metrics private static readonly int k_NamedMessageOverhead = (int)k_MessageNameHashSize + k_MessageHeaderSize; private static readonly int k_UnnamedMessageOverhead = k_MessageHeaderSize; - protected override int NbClients => 2; + protected override int NumberOfClients => 2; [UnityTest] public IEnumerator TrackNetworkMessageSentMetric() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) @@ -38,17 +38,15 @@ namespace Unity.Netcode.RuntimeTests.Metrics yield return waitForMetricValues.WaitForMetricsReceived(); var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); - Assert.AreEqual(1, networkMessageSentMetricValues.Count); - var networkMessageEvent = networkMessageSentMetricValues.First(); - Assert.AreEqual(nameof(NamedMessage), networkMessageEvent.Name); - Assert.AreEqual(FirstClient.LocalClientId, networkMessageEvent.Connection.Id); + // We should have 1 NamedMessage and some potential SnapshotMessage + Assert.That(networkMessageSentMetricValues, Has.Exactly(1).Matches(x => x.Name == nameof(NamedMessage))); } [UnityTest] public IEnumerator TrackNetworkMessageSentMetricToMultipleClients() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { @@ -74,7 +72,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics { Debug.Log($"Received from {sender}"); }); - var waitForMetricValues = new WaitForMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageReceived); + var waitForMetricValues = new WaitForEventMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageReceived, + metric => metric.Name == nameof(NamedMessage)); + using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { writer.WriteValueSafe(messageName); @@ -85,16 +85,14 @@ namespace Unity.Netcode.RuntimeTests.Metrics yield return waitForMetricValues.WaitForMetricsReceived(); var networkMessageReceivedValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); - Assert.AreEqual(1, networkMessageReceivedValues.Count(x => x.Name.Equals(nameof(NamedMessage)))); - - var namedMessageReceived = networkMessageReceivedValues.First(); - Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id); + // We should have 1 NamedMessage and some potential SnapshotMessage + Assert.That(networkMessageReceivedValues, Has.Exactly(1).Matches(x => x.Name == nameof(NamedMessage))); } [UnityTest] public IEnumerator TrackNamedMessageSentMetric() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) @@ -119,7 +117,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackNamedMessageSentMetricToMultipleClients() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { @@ -140,7 +138,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackNamedMessageSentMetricToSelf() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { @@ -157,7 +155,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackNamedMessageReceivedMetric() { - var waitForMetricValues = new WaitForMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NamedMessageReceived); + var waitForMetricValues = new WaitForEventMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NamedMessageReceived); var messageName = Guid.NewGuid(); @@ -198,7 +196,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics } - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); yield return waitForMetricValues.WaitForMetricsReceived(); @@ -214,7 +212,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics public IEnumerator TrackUnnamedMessageSentMetricToMultipleClients() { var message = Guid.NewGuid(); - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { writer.WriteValueSafe(message); @@ -237,7 +235,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackUnnamedMessageSentMetricToSelf() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent); var messageName = Guid.NewGuid(); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { @@ -255,7 +253,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics public IEnumerator TrackUnnamedMessageReceivedMetric() { var message = Guid.NewGuid(); - var waitForMetricValues = new WaitForMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageReceived); + var waitForMetricValues = new WaitForEventMetricValues(FirstClientMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageReceived); using (var writer = new FastBufferWriter(1300, Allocator.Temp)) { writer.WriteValueSafe(message); diff --git a/Tests/Runtime/Metrics/MetricsDispatchTests.cs b/Tests/Runtime/Metrics/MetricsDispatchTests.cs index 0c8cc09..12adec0 100644 --- a/Tests/Runtime/Metrics/MetricsDispatchTests.cs +++ b/Tests/Runtime/Metrics/MetricsDispatchTests.cs @@ -4,6 +4,8 @@ using System.Collections; using NUnit.Framework; using Unity.Multiplayer.Tools.NetStats; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { diff --git a/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs b/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs index 9dd0277..436be78 100644 --- a/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs +++ b/Tests/Runtime/Metrics/NetworkObjectMetricsTests.cs @@ -1,52 +1,43 @@ #if MULTIPLAYER_TOOLS -using System; using System.Collections; using System.Collections.Generic; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime.Metrics; + namespace Unity.Netcode.RuntimeTests.Metrics { internal class NetworkObjectMetricsTests : SingleClientMetricTestBase { - private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn"; - private NetworkObject m_NewNetworkPrefab; + // Keep less than 23 chars to avoid issues if compared against a 32-byte fixed string + // since it will have "(Clone)" appended + private const string k_NewNetworkObjectName = "MetricObject"; + private GameObject m_NewNetworkPrefab; - protected override Action UpdatePlayerPrefab => _ => + /// + /// Use OnServerAndClientsCreated to create any additional prefabs that you might need + /// + protected override void OnServerAndClientsCreated() { - var gameObject = new GameObject(k_NewNetworkObjectName); - m_NewNetworkPrefab = gameObject.AddComponent(); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NewNetworkPrefab); - - var networkPrefab = new NetworkPrefab { Prefab = gameObject }; - m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); - foreach (var client in m_ClientNetworkManagers) - { - client.NetworkConfig.NetworkPrefabs.Add(networkPrefab); - } - }; + base.OnServerAndClientsCreated(); + m_NewNetworkPrefab = CreateNetworkObjectPrefab(k_NewNetworkObjectName); + } private NetworkObject SpawnNetworkObject() { - // Spawn another network object so we can hide multiple. - var gameObject = UnityEngine.Object.Instantiate(m_NewNetworkPrefab); // new GameObject(NewNetworkObjectName); - var networkObject = gameObject.GetComponent(); - networkObject.NetworkManagerOwner = Server; - networkObject.Spawn(); - - return networkObject; + return SpawnObject(m_NewNetworkPrefab, m_ServerNetworkManager).GetComponent(); } [UnityTest] public IEnumerator TrackNetworkObjectSpawnSentMetric() { - var waitForMetricEvent = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent); + var waitForMetricEvent = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent); - SpawnNetworkObject(); + var spawnedObject = SpawnNetworkObject(); yield return waitForMetricEvent.WaitForMetricsReceived(); @@ -55,26 +46,27 @@ namespace Unity.Netcode.RuntimeTests.Metrics var objectSpawned = objectSpawnedSentMetricValues.Last(); Assert.AreEqual(Client.LocalClientId, objectSpawned.Connection.Id); - Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectSpawned.NetworkId.Name); + Assert.AreEqual(spawnedObject.name, objectSpawned.NetworkId.Name); Assert.AreNotEqual(0, objectSpawned.BytesCount); } [UnityTest] public IEnumerator TrackNetworkObjectSpawnReceivedMetric() { - var waitForMetricEvent = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedReceived); + var waitForMetricEvent = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedReceived); var networkObject = SpawnNetworkObject(); + yield return s_DefaultWaitForTick; yield return waitForMetricEvent.WaitForMetricsReceived(); var objectSpawnedReceivedMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound(); Assert.AreEqual(1, objectSpawnedReceivedMetricValues.Count); - + var clientSideObject = s_GlobalNetworkObjects[1][networkObject.NetworkObjectId]; var objectSpawned = objectSpawnedReceivedMetricValues.First(); Assert.AreEqual(Server.LocalClientId, objectSpawned.Connection.Id); Assert.AreEqual(networkObject.NetworkObjectId, objectSpawned.NetworkId.NetworkId); - Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectSpawned.NetworkId.Name); + Assert.AreEqual(clientSideObject.name, objectSpawned.NetworkId.Name); Assert.AreNotEqual(0, objectSpawned.BytesCount); } @@ -83,20 +75,22 @@ namespace Unity.Netcode.RuntimeTests.Metrics { var networkObject = SpawnNetworkObject(); - yield return new WaitForSeconds(0.2f); + yield return s_DefaultWaitForTick; - var waitForMetricEvent = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent); + var waitForMetricEvent = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent); + var objectName = networkObject.name; Server.SpawnManager.OnDespawnObject(networkObject, true); + yield return s_DefaultWaitForTick; yield return waitForMetricEvent.WaitForMetricsReceived(); var objectDestroyedSentMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound(); - Assert.AreEqual(2, objectDestroyedSentMetricValues.Count); // As there's a client and server, this event is emitted twice. + Assert.AreEqual(2, objectDestroyedSentMetricValues.Count); var objectDestroyed = objectDestroyedSentMetricValues.Last(); Assert.AreEqual(Client.LocalClientId, objectDestroyed.Connection.Id); - Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectDestroyed.NetworkId.Name); + Assert.AreEqual(objectName, objectDestroyed.NetworkId.Name); Assert.AreNotEqual(0, objectDestroyed.BytesCount); } @@ -105,11 +99,14 @@ namespace Unity.Netcode.RuntimeTests.Metrics { var networkObject = SpawnNetworkObject(); - yield return new WaitForSeconds(0.2f); + yield return s_DefaultWaitForTick; - var waitForMetricEvent = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedReceived); + var waitForMetricEvent = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedReceived); + var objectId = networkObject.NetworkObjectId; + var objectName = s_GlobalNetworkObjects[1][objectId].name; Server.SpawnManager.OnDespawnObject(networkObject, true); + yield return s_DefaultWaitForTick; yield return waitForMetricEvent.WaitForMetricsReceived(); @@ -118,8 +115,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics var objectDestroyed = objectDestroyedReceivedMetricValues.First(); Assert.AreEqual(Server.LocalClientId, objectDestroyed.Connection.Id); - Assert.AreEqual(networkObject.NetworkObjectId, objectDestroyed.NetworkId.NetworkId); - Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectDestroyed.NetworkId.Name); + Assert.AreEqual(objectId, objectDestroyed.NetworkId.NetworkId); + Assert.AreEqual(objectName, objectDestroyed.NetworkId.Name); Assert.AreNotEqual(0, objectDestroyed.BytesCount); } @@ -128,16 +125,16 @@ namespace Unity.Netcode.RuntimeTests.Metrics { var networkObject1 = SpawnNetworkObject(); var networkObject2 = SpawnNetworkObject(); - - yield return new WaitForSeconds(0.2f); + yield return s_DefaultWaitForTick; NetworkObject.NetworkHide(new List { networkObject1, networkObject2 }, Client.LocalClientId); - yield return new WaitForSeconds(0.2f); + yield return s_DefaultWaitForTick; - var waitForMetricEvent = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent); + var waitForMetricEvent = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent); NetworkObject.NetworkShow(new List { networkObject1, networkObject2 }, Client.LocalClientId); + yield return s_DefaultWaitForTick; yield return waitForMetricEvent.WaitForMetricsReceived(); @@ -166,12 +163,12 @@ namespace Unity.Netcode.RuntimeTests.Metrics var networkObject1 = SpawnNetworkObject(); var networkObject2 = SpawnNetworkObject(); - yield return new WaitForSeconds(0.2f); + yield return s_DefaultWaitForTick; - var waitForMetricEvent = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent); + var waitForMetricEvent = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent); NetworkObject.NetworkHide(new List { networkObject1, networkObject2 }, Client.LocalClientId); - + yield return s_DefaultWaitForTick; yield return waitForMetricEvent.WaitForMetricsReceived(); var objectDestroyedSentMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound(); diff --git a/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs b/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs index 29c0f0a..2ab85fa 100644 --- a/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs +++ b/Tests/Runtime/Metrics/NetworkVariableMetricsTests.cs @@ -1,23 +1,25 @@ #if MULTIPLAYER_TOOLS -using System; using System.Collections; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; -using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { internal class NetworkVariableMetricsTests : SingleClientMetricTestBase { - protected override Action UpdatePlayerPrefab => prefab => prefab.AddComponent(); + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } [UnityTest] public IEnumerator TrackNetworkVariableDeltaSentMetric() { - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaSent); yield return waitForMetricValues.WaitForMetricsReceived(); @@ -32,7 +34,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackNetworkVariableDeltaReceivedMetric() { - var waitForMetricValues = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaReceived); + var waitForMetricValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaReceived); yield return waitForMetricValues.WaitForMetricsReceived(); diff --git a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs index 853bd73..f41b6ee 100644 --- a/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs +++ b/Tests/Runtime/Metrics/OwnershipChangeMetricsTests.cs @@ -4,9 +4,10 @@ using System.Collections; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { @@ -17,11 +18,11 @@ namespace Unity.Netcode.RuntimeTests.Metrics // Header is dynamically sized due to packing, will be 2 bytes for all test messages. private const int k_MessageHeaderSize = 2; - protected override Action UpdatePlayerPrefab => _ => + protected override void OnServerAndClientsCreated() { var gameObject = new GameObject(k_NewNetworkObjectName); m_NewNetworkPrefab = gameObject.AddComponent(); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NewNetworkPrefab); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_NewNetworkPrefab); var networkPrefab = new NetworkPrefab { Prefab = gameObject }; m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(networkPrefab); @@ -29,7 +30,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics { client.NetworkConfig.NetworkPrefabs.Add(networkPrefab); } - }; + base.OnServerAndClientsCreated(); + } private NetworkObject SpawnNetworkObject() { @@ -49,7 +51,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics yield return new WaitForSeconds(0.2f); - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeSent); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeSent); networkObject.ChangeOwnership(1); @@ -70,7 +72,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics yield return new WaitForSeconds(0.2f); - var waitForMetricValues = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeReceived); + var waitForMetricValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeReceived); networkObject.ChangeOwnership(1); diff --git a/Tests/Runtime/Metrics/PacketMetricsTests.cs b/Tests/Runtime/Metrics/PacketMetricsTests.cs new file mode 100644 index 0000000..14448e9 --- /dev/null +++ b/Tests/Runtime/Metrics/PacketMetricsTests.cs @@ -0,0 +1,62 @@ +#if MULTIPLAYER_TOOLS +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 +using System.Collections; +using NUnit.Framework; +using Unity.Collections; +using Unity.Multiplayer.Tools.MetricTypes; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.TestHelpers.Runtime.Metrics; + +namespace Unity.Netcode.RuntimeTests.Metrics +{ + internal class PacketMetricsTests : SingleClientMetricTestBase + { + + protected override void OnOneTimeSetup() + { +#if UTP_ADAPTER + m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.UTP; +#else + m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.SIP; +#endif + base.OnOneTimeSetup(); + } + + [UnityTest] + public IEnumerator TrackPacketSentMetric() + { + var waitForMetricValues = new WaitForCounterMetricValue(ServerMetrics.Dispatcher, NetworkMetricTypes.PacketsSent, metric => metric > 0); + + using (var writer = new FastBufferWriter(sizeof(uint), Allocator.Temp)) + { + writer.WriteValueSafe(1337); + Server.CustomMessagingManager.SendUnnamedMessageToAll(writer); + } + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var totalPacketCount = waitForMetricValues.AssertMetricValueHaveBeenFound(); + Assert.That(totalPacketCount, Is.InRange(1, 4)); + } + + [UnityTest] + public IEnumerator TrackPacketReceivedMetric() + { + var waitForMetricValues = new WaitForCounterMetricValue(ClientMetrics.Dispatcher, NetworkMetricTypes.PacketsReceived, metric => metric > 0); + + using (var writer = new FastBufferWriter(sizeof(uint), Allocator.Temp)) + { + writer.WriteValueSafe(1337); + Server.CustomMessagingManager.SendUnnamedMessageToAll(writer); + } + + yield return waitForMetricValues.WaitForMetricsReceived(); + + var totalPacketCount = waitForMetricValues.AssertMetricValueHaveBeenFound(); + Assert.That(totalPacketCount, Is.InRange(1, 4)); + } + } +} +#endif +#endif diff --git a/Tests/Runtime/Metrics/PacketMetricsTests.cs.meta b/Tests/Runtime/Metrics/PacketMetricsTests.cs.meta new file mode 100644 index 0000000..d55ef2f --- /dev/null +++ b/Tests/Runtime/Metrics/PacketMetricsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 84a4884f4ea744a9944284d56b29531b +timeCreated: 1644269306 \ No newline at end of file diff --git a/Tests/Runtime/Metrics/RpcMetricsTests.cs b/Tests/Runtime/Metrics/RpcMetricsTests.cs index e3a1dc0..0d802ce 100644 --- a/Tests/Runtime/Metrics/RpcMetricsTests.cs +++ b/Tests/Runtime/Metrics/RpcMetricsTests.cs @@ -1,28 +1,27 @@ #if MULTIPLAYER_TOOLS -using System; using System.Collections; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; -using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { internal class RpcMetricsTests : SingleClientMetricTestBase { - protected override Action UpdatePlayerPrefab => prefab => prefab.AddComponent(); + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); + base.OnCreatePlayerPrefab(); + } [UnityTest] public IEnumerator TrackRpcSentMetricOnServer() { - var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Server, clientPlayer)); + var waitForMetricValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - var waitForMetricValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - - clientPlayer.Result.GetComponent().MyClientRpc(); + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][Client.LocalClientId].GetComponent().MyClientRpc(); yield return waitForMetricValues.WaitForMetricsReceived(); @@ -39,12 +38,9 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackRpcSentMetricOnClient() { - var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Client, clientPlayer)); + var waitForClientMetricsValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - var waitForClientMetricsValues = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcSent); - - clientPlayer.Result.GetComponent().MyServerRpc(); + m_PlayerNetworkObjects[Client.LocalClientId][Client.LocalClientId].GetComponent().MyServerRpc(); yield return waitForClientMetricsValues.WaitForMetricsReceived(); @@ -61,12 +57,8 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackRpcReceivedMetricOnServer() { - var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Client, clientPlayer)); - - var waitForServerMetricsValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); - - clientPlayer.Result.GetComponent().MyServerRpc(); + var waitForServerMetricsValues = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); + m_PlayerNetworkObjects[Client.LocalClientId][Client.LocalClientId].GetComponent().MyServerRpc(); yield return waitForServerMetricsValues.WaitForMetricsReceived(); @@ -83,16 +75,13 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackRpcReceivedMetricOnClient() { - var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Server, clientPlayer)); + var waitForClientMetricsValues = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); - var waitForServerMetricsValues = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived); + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][Client.LocalClientId].GetComponent().MyClientRpc(); - clientPlayer.Result.GetComponent().MyClientRpc(); + yield return waitForClientMetricsValues.WaitForMetricsReceived(); - yield return waitForServerMetricsValues.WaitForMetricsReceived(); - - var clientRpcReceivedValues = waitForServerMetricsValues.AssertMetricValuesHaveBeenFound(); + var clientRpcReceivedValues = waitForClientMetricsValues.AssertMetricValuesHaveBeenFound(); Assert.AreEqual(1, clientRpcReceivedValues.Count); var rpcReceived = clientRpcReceivedValues.First(); diff --git a/Tests/Runtime/Metrics/RttMetricsTests.cs b/Tests/Runtime/Metrics/RttMetricsTests.cs new file mode 100644 index 0000000..29a5e76 --- /dev/null +++ b/Tests/Runtime/Metrics/RttMetricsTests.cs @@ -0,0 +1,101 @@ +#if MULTIPLAYER_TOOLS +#if MULTIPLAYER_TOOLS_1_0_0_PRE_4 + +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Unity.Collections; +using Unity.Multiplayer.Tools.MetricTypes; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; +using Unity.Netcode.TestHelpers.Runtime.Metrics; + +namespace Unity.Netcode.RuntimeTests.Metrics +{ + /// + /// Note: This is one way to easily identify each specific test. + /// Since the test only tested 1 and then 2 clients, I made this + /// and enum, but you can always remove the enum in the constructor, + /// replace it with an int, and then test from 1 to 9 clients. + /// Just an example of how you can accomplish the same task using + /// the NetcodeIntegrationTest + /// + [TestFixture(ClientCount.OneClient)] + [TestFixture(ClientCount.TwoClients)] + internal class RttMetricsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => m_ClientCount; + + public enum ClientCount + { + OneClient, + TwoClients + } + + private int m_ClientCount; + + public RttMetricsTests(ClientCount numberOfClients) + { + m_ClientCount = numberOfClients == ClientCount.OneClient ? 1 : 2; + } + + /// + /// Note: We are using the OnOneTimeSetup to select the transport to use for + /// this test set. + /// + protected override void OnOneTimeSetup() + { +#if UTP_ADAPTER + m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.UTP; +#else + m_NetworkTransport = NetcodeIntegrationTestHelpers.InstanceTransport.SIP; +#endif + } + + [UnityTest] + public IEnumerator TrackRttMetricServerToClient() + { + var waitForMetricValues = new WaitForGaugeMetricValues((m_ServerNetworkManager.NetworkMetrics as NetworkMetrics).Dispatcher, NetworkMetricTypes.RttToServer); + + using (var writer = new FastBufferWriter(sizeof(uint), Allocator.Temp)) + { + writer.WriteValueSafe(1337); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessageToAll(writer); + } + + yield return WaitForConditionOrTimeOut(() => waitForMetricValues.MetricFound()); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(TrackRttMetricServerToClient)} timed out waiting for metric to be found for {m_ClientCount} clients!"); + + var rttValue = waitForMetricValues.AssertMetricValueHaveBeenFound(); + Assert.AreEqual(0f, rttValue); + } + + [UnityTest] + public IEnumerator TrackRttMetricClientToServer() + { + var clientGaugeMetricValues = new List(); + foreach (var client in m_ClientNetworkManagers) + { + clientGaugeMetricValues.Add(new WaitForGaugeMetricValues((client.NetworkMetrics as NetworkMetrics).Dispatcher, NetworkMetricTypes.RttToServer, metric => metric > 0f)); + } + + using (var writer = new FastBufferWriter(sizeof(uint), Allocator.Temp)) + { + writer.WriteValueSafe(1337); + m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessageToAll(writer); + } + + yield return WaitForConditionOrTimeOut(() => clientGaugeMetricValues.Where((c) => c.MetricFound()).Count() == NumberOfClients); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"{nameof(TrackRttMetricClientToServer)} timed out waiting for metric to be found for {m_ClientCount} clients!"); + + foreach (var clientGaugeMetricValue in clientGaugeMetricValues) + { + var rttValue = clientGaugeMetricValue.AssertMetricValueHaveBeenFound(); + Assert.That(rttValue, Is.GreaterThanOrEqualTo(1f)); + } + } + } +} +#endif +#endif diff --git a/Tests/Runtime/Metrics/RttMetricsTests.cs.meta b/Tests/Runtime/Metrics/RttMetricsTests.cs.meta new file mode 100644 index 0000000..704d1b4 --- /dev/null +++ b/Tests/Runtime/Metrics/RttMetricsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 452fbcb436d24291ba7db3a68e3e50ac +timeCreated: 1644269321 \ No newline at end of file diff --git a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs index 8036dfd..77a5a5f 100644 --- a/Tests/Runtime/Metrics/ServerLogsMetricTests.cs +++ b/Tests/Runtime/Metrics/ServerLogsMetricTests.cs @@ -4,8 +4,8 @@ using System.Collections; using System.Linq; using NUnit.Framework; using Unity.Multiplayer.Tools.MetricTypes; -using Unity.Netcode.RuntimeTests.Metrics.Utility; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime.Metrics; namespace Unity.Netcode.RuntimeTests.Metrics { @@ -19,10 +19,13 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackServerLogSentMetric() { - var waitForSentMetric = new WaitForMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ServerLogSent); + var waitForSentMetric = new WaitForEventMetricValues(ClientMetrics.Dispatcher, NetworkMetricTypes.ServerLogSent); var message = Guid.NewGuid().ToString(); + Client.LogLevel = LogLevel.Developer; + Server.LogLevel = LogLevel.Developer; NetworkLog.LogWarningServer(message); + yield return s_DefaultWaitForTick; yield return waitForSentMetric.WaitForMetricsReceived(); @@ -38,11 +41,15 @@ namespace Unity.Netcode.RuntimeTests.Metrics [UnityTest] public IEnumerator TrackServerLogReceivedMetric() { - var waitForReceivedMetric = new WaitForMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ServerLogReceived); + var waitForReceivedMetric = new WaitForEventMetricValues(ServerMetrics.Dispatcher, NetworkMetricTypes.ServerLogReceived); var message = Guid.NewGuid().ToString(); + Client.LogLevel = LogLevel.Developer; + Server.LogLevel = LogLevel.Developer; NetworkLog.LogWarningServer(message); + yield return s_DefaultWaitForTick; + yield return waitForReceivedMetric.WaitForMetricsReceived(); var receivedMetrics = waitForReceivedMetric.AssertMetricValuesHaveBeenFound(); diff --git a/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs b/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs index 2e8986c..3f649f9 100644 --- a/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs +++ b/Tests/Runtime/Metrics/TransportBytesMetricsTests.cs @@ -6,7 +6,7 @@ using NUnit.Framework; using Unity.Collections; using Unity.Multiplayer.Tools.MetricTypes; using Unity.Multiplayer.Tools.NetStats; -using Unity.Netcode.RuntimeTests.Metrics.Utility; +using Unity.Netcode.TestHelpers.Runtime.Metrics; using UnityEngine.TestTools; namespace Unity.Netcode.RuntimeTests.Metrics @@ -26,7 +26,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics try { writer.WriteValueSafe(messageName); - + Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), Client.LocalClientId, writer); } finally @@ -54,7 +54,7 @@ namespace Unity.Netcode.RuntimeTests.Metrics try { writer.WriteValueSafe(messageName); - + Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), Client.LocalClientId, writer); } finally diff --git a/Tests/Runtime/NestedNetworkManagerTests.cs b/Tests/Runtime/NestedNetworkManagerTests.cs index 9e0ea70..ff99617 100644 --- a/Tests/Runtime/NestedNetworkManagerTests.cs +++ b/Tests/Runtime/NestedNetworkManagerTests.cs @@ -1,37 +1,38 @@ -using System.Collections; using UnityEngine; +using NUnit.Framework; using UnityEngine.TestTools; -using Unity.Netcode; -using Unity.Netcode.RuntimeTests; +using Unity.Netcode.TestHelpers.Runtime; using Object = UnityEngine.Object; -namespace TestProject.RuntimeTests +namespace Unity.Netcode.RuntimeTests { public class NestedNetworkManagerTests { - [UnityTest] - public IEnumerator CheckNestedNetworkManager() + [Test] + public void CheckNestedNetworkManager() { var parent = new GameObject("ParentObject"); var networkManagerObject = new GameObject(nameof(CheckNestedNetworkManager)); - // Make our NetworkManager's GameObject nested - networkManagerObject.transform.parent = parent.transform; - - // Pre-generate the error message we are expecting to see - var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform); var transport = networkManagerObject.AddComponent(); var networkManager = networkManagerObject.AddComponent(); networkManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport }; - // Trap for the nested NetworkManager exception - LogAssert.Expect(LogType.Error, messageToCheck); - yield return new WaitForSeconds(0.02f); + // Make our NetworkManager's GameObject nested + networkManagerObject.transform.parent = parent.transform; + + // Generate the error message we are expecting to see + var messageToCheck = NetworkManager.GenerateNestedNetworkManagerMessage(networkManagerObject.transform); + + // Trap for the nested NetworkManager exception +#if UNITY_EDITOR + LogAssert.Expect(LogType.Error, messageToCheck); +#else + LogAssert.Expect(LogType.Exception, $"Exception: {messageToCheck}"); +#endif // Clean up Object.Destroy(parent); - - yield return null; } } } diff --git a/Tests/Runtime/NetworkAnimator.meta b/Tests/Runtime/NetworkAnimator.meta new file mode 100644 index 0000000..c08562e --- /dev/null +++ b/Tests/Runtime/NetworkAnimator.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 3acde7838205d4b09ae3a035554c51c5 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs b/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs new file mode 100644 index 0000000..eb46c00 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs @@ -0,0 +1,237 @@ +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.Netcode.Components; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(HostOrServer.Host)] + [TestFixture(HostOrServer.Server)] + public class NetworkAnimatorTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private GameObject m_PlayerOnServer; + private GameObject m_PlayerOnClient; + + private Animator m_PlayerOnServerAnimator; + private Animator m_PlayerOnClientAnimator; + + public NetworkAnimatorTests(HostOrServer hostOrServer) : base(hostOrServer) { } + + protected override void OnCreatePlayerPrefab() + { + // ideally, we would build up the AnimatorController entirely in code and not need an asset, + // but after some attempts this doesn't seem readily doable. Instead, we load a controller + var controller = Resources.Load("TestAnimatorController") as RuntimeAnimatorController; + var animator = m_PlayerPrefab.AddComponent(); + animator.runtimeAnimatorController = controller; + + var networkAnimator = m_PlayerPrefab.AddComponent(); + networkAnimator.Animator = animator; + } + + protected override IEnumerator OnServerAndClientsConnected() + { + m_PlayerOnServer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject; + m_PlayerOnServerAnimator = m_PlayerOnServerAnimator = m_PlayerOnServer.GetComponent(); + + m_PlayerOnClient = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject; + m_PlayerOnClientAnimator = m_PlayerOnClient.GetComponent(); + + return base.OnServerAndClientsConnected(); + } + + // helper function to scan an animator and verify a given clip is present + private bool HasClip(Animator animator, string clipName) + { + var clips = new List(); + animator.GetCurrentAnimatorClipInfo(0, clips); + foreach (var clip in clips) + { + if (clip.clip.name == clipName) + { + return true; + } + } + return false; + } + + [UnityTest] + public IEnumerator AnimationTriggerReset([Values(true, false)] bool asHash) + { + // We have "UnboundTrigger" purposely not bound to any animations so we can test resetting. + // If we used a trigger that was bound to a transition, then the trigger would reset as soon as the + // transition happens. This way it will stay stuck on + string triggerString = "UnboundTrigger"; + int triggerHash = Animator.StringToHash(triggerString); + + // Verify trigger is off + Assert.True(m_PlayerOnServerAnimator.GetBool(triggerString) == false); + Assert.True(m_PlayerOnClientAnimator.GetBool(triggerString) == false); + + // trigger. + if (asHash) + { + m_PlayerOnServer.GetComponent().SetTrigger(triggerHash); + } + else + { + m_PlayerOnServer.GetComponent().SetTrigger(triggerString); + } + + // verify trigger is set for client and server + yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) : m_PlayerOnServerAnimator.GetBool(triggerString)); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server trigger set check"); + + yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) : m_PlayerOnClientAnimator.GetBool(triggerString)); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client trigger set check"); + + // reset the trigger + if (asHash) + { + m_PlayerOnServer.GetComponent().ResetTrigger(triggerHash); + } + else + { + m_PlayerOnServer.GetComponent().ResetTrigger(triggerString); + } + + // verify trigger is reset for client and server + yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) == false : m_PlayerOnServerAnimator.GetBool(triggerString) == false); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server reset check"); + + yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) == false : m_PlayerOnClientAnimator.GetBool(triggerString) == false); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client reset check"); + } + + + [UnityTest] + public IEnumerator AnimationStateSyncTest() + { + // check that we have started in the default state + Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); + Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); + + // cause a change to the AlphaState state by setting AlphaParameter, which is + // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) + m_PlayerOnServerAnimator.SetBool("AlphaParameter", true); + + // ...and now we should be in the AlphaState having triggered the AlphaParameter + yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); + + // ...and now the client should also have sync'd and arrived at the correct state + yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server"); + } + + [UnityTest] + public IEnumerator AnimationLayerStateSyncTest() + { + int layer = 1; + // check that we have started in the default state + Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2")); + Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2")); + + // cause a change to the AlphaState state by setting AlphaParameter, which is + // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) + m_PlayerOnServerAnimator.SetBool("Layer2AlphaParameter", true); + + // ...and now we should be in the AlphaState having triggered the AlphaParameter + yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); + + // ...and now the client should also have sync'd and arrived at the correct state + yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server"); + } + + [UnityTest] + public IEnumerator AnimationLayerWeightTest() + { + int layer = 1; + float targetWeight = 0.333f; + + // check that we have started in the default state + Assert.True(Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), 1f)); + Assert.True(Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), 1f)); + + m_PlayerOnServerAnimator.SetLayerWeight(layer, targetWeight); + + // ...and now we should be in the AlphaState having triggered the AlphaParameter + yield return WaitForConditionOrTimeOut(() => + Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), targetWeight) + ); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); + + // ...and now the client should also have sync'd and arrived at the correct state + yield return WaitForConditionOrTimeOut(() => + Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), targetWeight) + ); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state"); + } + + + [UnityTest] + public IEnumerator AnimationStateSyncTriggerTest([Values(true, false)] bool asHash) + { + string triggerString = "TestTrigger"; + int triggerHash = Animator.StringToHash(triggerString); + + // check that we have started in the default state + Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); + Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState")); + + // cause a change to the AlphaState state by setting TestTrigger + // note, we have a special test for triggers because activating triggers via the + // NetworkAnimator is special; for other parameters you set them on the Animator and NetworkAnimator + // listens. But because triggers are super short and transitory, we require users to call + // NetworkAnimator.SetTrigger so we don't miss it + if (asHash) + { + m_PlayerOnServer.GetComponent().SetTrigger(triggerHash); + } + else + { + m_PlayerOnServer.GetComponent().SetTrigger(triggerString); + } + + // ...and now we should be in the AlphaState having triggered the AlphaParameter + yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state via trigger"); + + // ...and now the client should also have sync'd and arrived at the correct state + yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server via trigger"); + } + + [UnityTest] + public IEnumerator AnimationStateSyncTestWithOverride() + { + // set up the animation override controller + var overrideController = Resources.Load("TestAnimatorOverrideController") as AnimatorOverrideController; + m_PlayerOnServer.GetComponent().runtimeAnimatorController = overrideController; + m_PlayerOnClient.GetComponent().runtimeAnimatorController = overrideController; + + // in our default state, we should see the OverrideDefaultAnimation clip + Assert.True(HasClip(m_PlayerOnServerAnimator, "OverrideDefaultAnimation")); + Assert.True(HasClip(m_PlayerOnClientAnimator, "OverrideDefaultAnimation")); + + // cause a change to the AlphaState state by setting AlphaParameter, which is + // the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset) + m_PlayerOnServerAnimator.SetBool("AlphaParameter", true); + + // ...and now we should be in the AlphaState having set the AlphaParameter + yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its overriden animation state"); + + // ...and now the client should also have sync'd and arrived at the correct state + yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation")); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to reach its overriden animation state"); + } + } +} diff --git a/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta b/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta new file mode 100644 index 0000000..19205b5 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/NetworkAnimatorTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c2e5a740c1abd4315801e3f26ecf8adb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources.meta b/Tests/Runtime/NetworkAnimator/Resources.meta new file mode 100644 index 0000000..d3faa16 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c3a8707ef624947a7ae8843ca6c70c0a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim b/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim new file mode 100644 index 0000000..cb964ee --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AlphaAnimation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta b/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta new file mode 100644 index 0000000..46edd4d --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/AlphaAnimation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: db8faf64ca46248abb6624513ac1fb1b +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim b/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim new file mode 100644 index 0000000..f19491b --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: DefaultAnimation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta b/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta new file mode 100644 index 0000000..e79be12 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/DefaultAnimation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 1f6191147839943ab93e2171cc15c5e9 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim b/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim new file mode 100644 index 0000000..e0bbbe7 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Layer2Animation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta b/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta new file mode 100644 index 0000000..2d1fbd7 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/Layer2Animation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d31c84f6372c54d7eb8decb27010d005 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim b/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim new file mode 100644 index 0000000..6123420 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: OverrideAlphaAnimation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta b/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta new file mode 100644 index 0000000..bf0e868 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/OverrideAlphaAnimation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 05a2afc2ff8884d32afc64ed6765880a +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim b/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim new file mode 100644 index 0000000..9ac6917 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim @@ -0,0 +1,53 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!74 &7400000 +AnimationClip: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: OverrideDefaultAnimation + serializedVersion: 6 + m_Legacy: 0 + m_Compressed: 0 + m_UseHighQualityCurve: 1 + m_RotationCurves: [] + m_CompressedRotationCurves: [] + m_EulerCurves: [] + m_PositionCurves: [] + m_ScaleCurves: [] + m_FloatCurves: [] + m_PPtrCurves: [] + m_SampleRate: 60 + m_WrapMode: 0 + m_Bounds: + m_Center: {x: 0, y: 0, z: 0} + m_Extent: {x: 0, y: 0, z: 0} + m_ClipBindingConstant: + genericBindings: [] + pptrCurveMapping: [] + m_AnimationClipSettings: + serializedVersion: 2 + m_AdditiveReferencePoseClip: {fileID: 0} + m_AdditiveReferencePoseTime: 0 + m_StartTime: 0 + m_StopTime: 1 + m_OrientationOffsetY: 0 + m_Level: 0 + m_CycleOffset: 0 + m_HasAdditiveReferencePose: 0 + m_LoopTime: 0 + m_LoopBlend: 0 + m_LoopBlendOrientation: 0 + m_LoopBlendPositionY: 0 + m_LoopBlendPositionXZ: 0 + m_KeepOriginalOrientation: 0 + m_KeepOriginalPositionY: 1 + m_KeepOriginalPositionXZ: 0 + m_HeightFromFeet: 0 + m_Mirror: 0 + m_EditorCurves: [] + m_EulerEditorCurves: [] + m_HasGenericRootTransform: 0 + m_HasMotionFloatCurves: 0 + m_Events: [] diff --git a/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta b/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta new file mode 100644 index 0000000..bcfba8f --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/OverrideDefaultAnimation.anim.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cf503a5569d0b4df4910a26d09ce4530 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 7400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller new file mode 100644 index 0000000..247a8e4 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller @@ -0,0 +1,449 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1102 &-8144973961595650150 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New State + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &-7257898091357968356 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: New State + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1101 &-7235917949335567458 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: Layer2AlphaParameter + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 6016706997111698284} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &-6097014330458455406 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: AlphaParameter + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1198466922477486815} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1107 &-1914299053840757887 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Base Layer + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: -1198466922477486815} + m_Position: {x: 70, y: 290, z: 0} + - serializedVersion: 1 + m_State: {fileID: 320527679719022362} + m_Position: {x: 110, y: 490, z: 0} + - serializedVersion: 1 + m_State: {fileID: 3942933370568001311} + m_Position: {x: 380, y: 280, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 30, y: 180, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: -1198466922477486815} +--- !u!1102 &-1198466922477486815 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: DefaultState + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 232953446134799302} + - {fileID: 8340347106517238820} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: TestAnimatorController + serializedVersion: 5 + m_AnimatorParameters: + - m_Name: AlphaParameter + m_Type: 4 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: TestTrigger + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: UnboundTrigger + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + - m_Name: Layer2AlphaParameter + m_Type: 4 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 9100000} + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: -1914299053840757887} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} + - serializedVersion: 5 + m_Name: Layer2 + m_StateMachine: {fileID: 1433017894673297828} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 1 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1101 &232953446134799302 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: AlphaParameter + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 320527679719022362} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1102 &320527679719022362 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: AlphaState + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &927597079590233140 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Layer2AlphaState + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: -7235917949335567458} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: d31c84f6372c54d7eb8decb27010d005, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &1433017894673297828 +AnimatorStateMachine: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Layer2 + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 6016706997111698284} + m_Position: {x: 160, y: 250, z: 0} + - serializedVersion: 1 + m_State: {fileID: 927597079590233140} + m_Position: {x: 270, y: 370, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: [] + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 50, y: 20, z: 0} + m_EntryPosition: {x: 50, y: 120, z: 0} + m_ExitPosition: {x: 800, y: 120, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 6016706997111698284} +--- !u!1102 &3942933370568001311 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: TriggeredState + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: [] + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1101 &5326371122012901575 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: AlphaParameter + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: -1198466922477486815} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1102 &6016706997111698284 +AnimatorState: + serializedVersion: 6 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: DefaultStateLayer2 + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 6324505406226331058} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1101 &6324505406226331058 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: Layer2AlphaParameter + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 927597079590233140} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 2 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &8340347106517238820 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: TestTrigger + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 3942933370568001311} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 1 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 diff --git a/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta new file mode 100644 index 0000000..9c9d715 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorController.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a0b8ebecb362240989d16159bdfa067c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController new file mode 100644 index 0000000..3d70809 --- /dev/null +++ b/Tests/Runtime/NetworkAnimator/Resources/TestAnimatorOverrideController.overrideController @@ -0,0 +1,15 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!221 &22100000 +AnimatorOverrideController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: TestAnimatorOverrideController + m_Controller: {fileID: 9100000, guid: a0b8ebecb362240989d16159bdfa067c, type: 2} + m_Clips: + - m_OriginalClip: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2} + m_OverrideClip: {fileID: 7400000, guid: cf503a5569d0b4df4910a26d09ce4530, type: 2} + - m_OriginalClip: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2} + m_OverrideClip: {fileID: 7400000, guid: 05a2afc2ff8884d32afc64ed6765880a, type: 2} diff --git a/Tests/Runtime/NetworkBehaviourGenericTests.cs b/Tests/Runtime/NetworkBehaviourGenericTests.cs new file mode 100644 index 0000000..7d66770 --- /dev/null +++ b/Tests/Runtime/NetworkBehaviourGenericTests.cs @@ -0,0 +1,61 @@ +using System.Collections; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + /// + /// This class is for testing general fixes or functionality of NetworkBehaviours + /// + public class NetworkBehaviourGenericTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 0; + + private bool m_AllowServerToStart; + + protected override bool CanStartServerAndClients() + { + return m_AllowServerToStart; + } + + public class SimpleNetworkBehaviour : NetworkBehaviour + { + } + + /// + /// This test validates a fix to NetworkBehaviour.NetworkObject when + /// the NetworkManager.LogLevel is set to Developer + /// Note: This test does not require any clients, but should not impact this + /// particular test if new tests are added to this class that do require clients + /// + [UnityTest] + public IEnumerator ValidateNoSpam() + { + m_AllowServerToStart = true; + var objectToTest = new GameObject(); + var simpleNetworkBehaviour = objectToTest.AddComponent(); + + // Now just start the Host + yield return StartServerAndClients(); + + // set the log level to developer + m_ServerNetworkManager.LogLevel = LogLevel.Developer; + + // 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(); + + // Assure no log messages are logged when they should not be logged + isNull = simpleNetworkBehaviour.NetworkObject != null; + LogAssert.NoUnexpectedReceived(); + + networkObjectToTest.Despawn(); + Object.Destroy(networkObjectToTest); + } + } +} diff --git a/Tests/Runtime/NetworkBehaviourGenericTests.cs.meta b/Tests/Runtime/NetworkBehaviourGenericTests.cs.meta new file mode 100644 index 0000000..38444d1 --- /dev/null +++ b/Tests/Runtime/NetworkBehaviourGenericTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f87989d8d290ed24a9048b6dbddae527 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkBehaviourUpdaterTests.cs b/Tests/Runtime/NetworkBehaviourUpdaterTests.cs index 7ebec09..21bfd10 100644 --- a/Tests/Runtime/NetworkBehaviourUpdaterTests.cs +++ b/Tests/Runtime/NetworkBehaviourUpdaterTests.cs @@ -1,180 +1,344 @@ -using System; using System.Collections; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests { - public class NetworkBehaviourUpdaterTests : BaseMultiInstanceTest + /// + /// This is a refactor of the original test's NetworkBehaviour INetVarInfo derived NetworkBehaviours + /// + public class NetVarContainer : NetworkBehaviour { - protected override int NbClients => throw new NotSupportedException("handled per test"); - - private static Type[] s_TypesToTest = new[] { null, typeof(ZeroNetVar), typeof(OneNetVar), typeof(TwoNetVar) }; - - [UnitySetUp] - public override IEnumerator Setup() + /// + /// Creates a prefab with two instances of this NetworkBehaviour + /// + /// + public static GameObject CreatePrefabGameObject(NetVarCombinationTypes netVarsToCheck) { - yield break; + var gameObject = new GameObject + { + // Always a good idea to name the Prefab for easy identification purposes + name = "NetVarContainerObject" + }; + var networkObject = gameObject.AddComponent(); + + // Create the two instances of the NetVarContainer components and add them to the + // GameObject of this prefab + var netVarContainer = gameObject.AddComponent(); + netVarContainer.NumberOfNetVarsToCheck = netVarsToCheck.FirstType; + netVarContainer.ValueToSetNetVarTo = NetworkBehaviourUpdaterTests.NetVarValueToSet; + netVarContainer = gameObject.AddComponent(); + netVarContainer.NumberOfNetVarsToCheck = netVarsToCheck.SecondType; + netVarContainer.ValueToSetNetVarTo = NetworkBehaviourUpdaterTests.NetVarValueToSet; + + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + + return gameObject; + } + + public enum NetVarsToCheck + { + One, + Two + } + + public NetVarsToCheck NumberOfNetVarsToCheck; + public int ValueToSetNetVarTo = 0; + + /// + /// Only used on the client-side for this test, this + /// is used to see if the network variables have changed. + /// + public bool HaveAllValuesChanged(int valueToCheck) + { + var allValuesChanged = false; + switch (NumberOfNetVarsToCheck) + { + case NetVarsToCheck.Two: + { + allValuesChanged = m_FirstValue.Value == valueToCheck && m_SeconValue.Value == valueToCheck; + break; + } + case NetVarsToCheck.One: + { + allValuesChanged = m_FirstValue.Value == valueToCheck; + break; + } + } + return allValuesChanged; } /// - /// This runs test combinations for the following - /// test with 0, 1, 2 clients - /// test with host and server mode - /// test with 0, 1, 2 spawned objects - /// test with 0, 1, 2 network behaviour per prefab - /// test with 0, 1, 2 network variable per network behaviour - /// for each, update netvar - /// for each check value changed - /// check that all network variables are no longer dirty after update + /// Only used on the server side to check the isDirty flag for the + /// NetworkVariables being used for each test iteration /// - /// - /// - /// - /// - /// - /// - /// - [UnityTest] - public IEnumerator BehaviourUpdaterAllTests([Values(0, 1, 2)] int nbClients, [Values] bool useHost, [Values(0, 1, 2)] int nbSpawnedObjects, - [ValueSource(nameof(s_TypesToTest))] Type firstNetworkBehaviour, [ValueSource(nameof(s_TypesToTest))] Type secondNetworkBehaviour) + public bool AreNetVarsDirty() { - // Create multiple NetworkManager instances - if (!MultiInstanceHelpers.Create(nbClients, out NetworkManager server, out NetworkManager[] clients)) + var areDirty = false; + switch (NumberOfNetVarsToCheck) { - Debug.LogError("Failed to create instances"); - Assert.Fail("Failed to create instances"); - } - m_ClientNetworkManagers = clients; - m_ServerNetworkManager = server; - Assert.That(m_ClientNetworkManagers.Length, Is.EqualTo(nbClients)); - Assert.That(m_ServerNetworkManager, Is.Not.Null); - - // setup prefab to spawn - void AddNetworkBehaviour(Type type, GameObject prefab) - { - if (type != null) - { - var info = prefab.AddComponent(type) as INetVarInfo; - } - } - var prefabToSpawn = new GameObject(); - var networkObjectPrefab = prefabToSpawn.AddComponent(); - AddNetworkBehaviour(firstNetworkBehaviour, prefabToSpawn); - AddNetworkBehaviour(secondNetworkBehaviour, prefabToSpawn); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab); - m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); - foreach (var clientNetworkManager in m_ClientNetworkManagers) - { - clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); - } - - // Start the instances - if (!MultiInstanceHelpers.Start(useHost, server, clients)) - { - Debug.LogError("Failed to start instances"); - Assert.Fail("Failed to start instances"); - } - // Wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); - - // Wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, clientCount: useHost ? nbClients + 1 : nbClients)); - - // gathering netvars to test on - var serverNetVarsToUpdate = new List>(); - for (int i = 0; i < nbSpawnedObjects; i++) - { - var spawnedObject = Object.Instantiate(prefabToSpawn); - var networkSpawnedObject = spawnedObject.GetComponent(); - networkSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager; - networkSpawnedObject.Spawn(); - int nbBehaviours = 0; - foreach (var networkBehaviour in spawnedObject.GetComponents()) - { - serverNetVarsToUpdate.AddRange(((INetVarInfo)networkBehaviour).AllNetVars); - nbBehaviours++; - } - Assert.That(nbBehaviours, Is.EqualTo((firstNetworkBehaviour == null ? 0 : 1) + (secondNetworkBehaviour == null ? 0 : 1))); - } - var serverNetVarCount = serverNetVarsToUpdate.Count; - - yield return new WaitForSeconds(0); // wait a frame to make sure spawn is done - // todo: with Snapshot spawns enabled and the current race condition, the following line is needed: - // yield return new WaitForSeconds(0.2f); // wait a bit to fix the spawn/update race condition - - foreach (var netVar in serverNetVarsToUpdate) - { - Assert.That(netVar.Value, Is.EqualTo(0)); // sanity check - } - - // test updating all netvars - int updatedValue = 1; - foreach (var netVar in serverNetVarsToUpdate) - { - netVar.Value = updatedValue; - Assert.That(netVar.IsDirty, Is.True); - } - - m_ServerNetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(m_ServerNetworkManager); - - // make sure we're not dirty anymore and that clients will receive that new value - foreach (var netVar in serverNetVarsToUpdate) - { - // if we don't have connected clients, netvars remain dirty - Assert.That(netVar.IsDirty, nbClients > 0 || useHost ? Is.Not.True : Is.True); - } - foreach (var client in m_ClientNetworkManagers) - { - var nbVarsCheckedClientSide = 0; - var countSpawnObjectResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.WaitForCondition(() => client.SpawnManager.SpawnedObjects.Count == nbSpawnedObjects, countSpawnObjectResult); - Assert.That(countSpawnObjectResult.Result, Is.True); - - foreach (var spawnedObject in client.SpawnManager.SpawnedObjects) - { - foreach (var behaviour in spawnedObject.Value.GetComponentsInChildren()) + case NetVarsToCheck.Two: { - foreach (var networkVariable in behaviour.NetworkVariableFields) - { - var varInt = networkVariable as NetworkVariable; - var varUpdateResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.WaitForCondition(() => varInt.Value == updatedValue, varUpdateResult); - Assert.That(varUpdateResult.Result, Is.True); - - nbVarsCheckedClientSide++; - Assert.That(varInt.Value, Is.EqualTo(updatedValue)); - } + areDirty = m_FirstValue.IsDirty() && m_SeconValue.IsDirty(); + break; } + case NetVarsToCheck.One: + { + areDirty = m_FirstValue.IsDirty(); + break; + } + } + + return areDirty; + } + + /// + /// The original version of this test only ever had up to 2 NetworkVariables per + /// NetworkBehaviour. As opposed to using a List of NetworkVariables, we just + /// create the maximum number that could be used and then only use what we need + /// for each test iteration. + /// + private NetworkVariable m_FirstValue = new NetworkVariable(); + private NetworkVariable m_SeconValue = new NetworkVariable(); + + public override void OnNetworkSpawn() + { + // Clients will register each NetworkObject when it is spawned + if (!IsServer) + { + NetworkBehaviourUpdaterTests.ClientSideNotifyObjectSpawned(gameObject); + } + } + + /// + /// Server side only, sets the NetworkVariables being used to the ValueToSetNetVarTo + /// that is pre-configured when the Network Prefab is created. + /// + public void SetNetworkVariableValues() + { + if (IsServer) + { + switch (NumberOfNetVarsToCheck) + { + case NetVarsToCheck.Two: + { + m_FirstValue.Value = ValueToSetNetVarTo; + m_SeconValue.Value = ValueToSetNetVarTo; + Assert.True(AreNetVarsDirty(), "Not all NetworkVariables were marked dirty on server after spawned!"); + break; + } + case NetVarsToCheck.One: + { + m_FirstValue.Value = ValueToSetNetVarTo; + Assert.True(AreNetVarsDirty(), "Not all NetworkVariables were marked dirty on server after spawned!"); + break; + } } - Assert.That(nbVarsCheckedClientSide, Is.EqualTo(m_ClientNetworkManagers.Length > 0 ? serverNetVarCount : 0)); } } } - public interface INetVarInfo + /// + /// Used to define how many NetworkVariables to use per NetVarContainer instance. + /// There are always two + /// + public struct NetVarCombinationTypes { - public List> AllNetVars { get; } + public NetVarContainer.NetVarsToCheck FirstType; + public NetVarContainer.NetVarsToCheck SecondType; } - public class ZeroNetVar : NetworkBehaviour, INetVarInfo + public class NetworkBehaviourUpdaterTests : NetcodeIntegrationTest { - public List> AllNetVars => new List>(); // Needed to be independant from NetworkBehaviour's list of fields. This way, if that changes, we can still do this validation in this test - } + // Go ahead and create maximum number of clients (not all tests will use them) + protected override int NumberOfClients => 2; + public const int NetVarValueToSet = 1; + private static List s_ClientSpawnedNetworkObjects = new List(); + private List m_ActiveClientsForCurrentTest; - public class OneNetVar : NetworkBehaviour, INetVarInfo - { - private NetworkVariable m_SomeValue = new NetworkVariable(); - public List> AllNetVars => new List>() { m_SomeValue }; - } + /// + /// Clients will call this when NetworkObjects are spawned on their end + /// + /// the GameObject of the NetworkObject spawned + public static void ClientSideNotifyObjectSpawned(GameObject objectSpaned) + { + if (!s_ClientSpawnedNetworkObjects.Contains(objectSpaned)) + { + s_ClientSpawnedNetworkObjects.Add(objectSpaned); + } + } - public class TwoNetVar : NetworkBehaviour, INetVarInfo - { - private NetworkVariable m_SomeValue = new NetworkVariable(); - private NetworkVariable m_SomeOtherValue = new NetworkVariable(); - public List> AllNetVars => new List>() { m_SomeValue, m_SomeOtherValue }; + protected override bool CanStartServerAndClients() + { + return false; + } + + /// + /// Creates the server and client(s) required for this particular test iteration + /// + private IEnumerator StartClientsAndServer(bool useHost, int numberOfClients, GameObject prefabObject) + { + // Sanity check to make sure we are not trying to create more clients than we have available to use + Assert.True(numberOfClients <= m_ClientNetworkManagers.Length); + m_ActiveClientsForCurrentTest = new List(); + + // Create a list of the clients to be used in this test from the available clients + for (int i = 0; i < numberOfClients; i++) + { + m_ActiveClientsForCurrentTest.Add(m_ClientNetworkManagers[i]); + } + + // Add the prefab to be used for this particular test iteration + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabObject }); + m_ServerNetworkManager.NetworkConfig.TickRate = 30; + foreach (var clientManager in m_ActiveClientsForCurrentTest) + { + m_ServerNetworkManager.NetworkConfig.TickRate = 30; + clientManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabObject }); + } + + // Now spin everything up normally + var clientsAsArry = m_ActiveClientsForCurrentTest.ToArray(); + Assert.True(NetcodeIntegrationTestHelpers.Start(useHost, m_ServerNetworkManager, clientsAsArry), "Failed to start server and client instances"); + + // Only if we have clients (not host) + if (numberOfClients > 0) + { + RegisterSceneManagerHandler(); + } + + // Wait for connection on client and server side + yield return WaitForClientsConnectedOrTimeOut(clientsAsArry); + } + + /// + /// This list replaces the original NetworkVariable types to be checked. + /// Both NetworkVariables are of type int and the original version of this test was testing + /// the NetworkBehaviour Update when there were 1 or more (i.e two) on the same NetworkBehaviour. + /// After reviewing, we really only needed to test a much smaller combination of types and so + /// this pre-generated array represents the reduced set of combinations to test. + /// Note: + /// The original test was also testing for no NetworkVariables of type int, which there ended up + /// being no reason to do that and only added to the length of the execution time for this test. + /// + public static NetVarCombinationTypes[] NetVarCombinationTypeValues = new[]{ + new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.One, SecondType = NetVarContainer.NetVarsToCheck.One }, + new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.One, SecondType = NetVarContainer.NetVarsToCheck.Two }, + new NetVarCombinationTypes() { FirstType = NetVarContainer.NetVarsToCheck.Two, SecondType = NetVarContainer.NetVarsToCheck.Two }}; + + /// + /// The updated BehaviourUpdaterAllTests was re-designed to replicate the same functionality being tested in the + /// original version of this test with additional time out handling and a re-organization in the order of operations. + /// Things like making sure all clients have spawned the NetworkObjects in question prior to testing for the + /// NetworkVariable value changes helped to eliminate the timing issues that were happening when this test was run + /// in a stand alone test runner build (i.e. all consoles run the stand alone version as opposed to the in-editor + /// version like the desktop tests use). + /// This update also updated how the server and clients were being constructed to help reduce the execution time. + /// + /// whether to run the server as a host or not + /// the NetworkVariable combination types + /// number of clients to use for the test + /// number of NetworkObjects to be spawned + [UnityTest] + public IEnumerator BehaviourUpdaterAllTests([Values] bool useHost, + [ValueSource(nameof(NetVarCombinationTypeValues))] NetVarCombinationTypes varCombinationTypes, + [Values(0, 1, 2)] int nbClients, [Values(1, 2)] int numToSpawn) + { + s_ClientSpawnedNetworkObjects.Clear(); + + // The edge case scenario where we can exit early is when we are running + // just the server (i.e. non-host) and there are zero clients. Under this + // edge case scenario of the various combinations we do not need to run + // this test as the IsDirty flag is never cleared when no clients exist at all. + if (nbClients == 0 && !useHost) + { + yield break; + } + + // Create our prefab based on the NetVarCombinationTypes + var prefabToSpawn = NetVarContainer.CreatePrefabGameObject(varCombinationTypes); + + yield return StartClientsAndServer(useHost, nbClients, prefabToSpawn); + + // Tracks the server-side spawned prefab instances + var spawnedPrefabs = new List(); + var tickInterval = 1.0f / m_ServerNetworkManager.NetworkConfig.TickRate; + + // Used to determine if the client-side checks of this test should be + // executed or not as well is used to make sure all clients have spawned + // the appropriate number of NetworkObjects with the NetVarContainer behaviour + var numberOfObjectsToSpawnOnClients = numToSpawn * nbClients; + + // spawn the objects + for (int i = 0; i < numToSpawn; i++) + { + var spawnedObject = Object.Instantiate(prefabToSpawn); + spawnedPrefabs.Add(spawnedObject); + var networkSpawnedObject = spawnedObject.GetComponent(); + networkSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager; + networkSpawnedObject.Spawn(); + } + + // When there are no clients (excluding when server is in host mode), we can skip all of this + // wait until all objects are spawned on the clients + if (numberOfObjectsToSpawnOnClients > 0) + { + // Waits for all clients to spawn the NetworkObjects + yield return WaitForConditionOrTimeOut(() => numberOfObjectsToSpawnOnClients == s_ClientSpawnedNetworkObjects.Count); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for clients to report spawning objects! " + + $"Total reported client-side spawned objects {s_ClientSpawnedNetworkObjects.Count}"); + } + + // Once all clients have spawned the NetworkObjects, set the network variables for + // those NetworkObjects on the server-side. + foreach (var spawnedPrefab in spawnedPrefabs) + { + var netVarContiners = spawnedPrefab.GetComponents(); + foreach (var netVarContiner in netVarContiners) + { + netVarContiner.SetNetworkVariableValues(); + } + } + + // Update the NetworkBehaviours to make sure all network variables are no longer marked as dirty + m_ServerNetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(m_ServerNetworkManager); + + // Verify that all network variables are no longer dirty on server side only if we have clients (including host) + foreach (var serverSpawnedObject in spawnedPrefabs) + { + var netVarContainers = serverSpawnedObject.GetComponents(); + foreach (var netVarContainer in netVarContainers) + { + Assert.False(netVarContainer.AreNetVarsDirty(), "Some NetworkVariables were still marked dirty after NetworkBehaviourUpdate!"); + } + } + + // When there are no clients (excluding when server is in host mode), we can skip all of this + if (numberOfObjectsToSpawnOnClients > 0) + { + // Get a list of all NetVarContainer components on the client-side spawned NetworkObjects + var clientSideNetVarContainers = new List(); + foreach (var clientSpawnedObjects in s_ClientSpawnedNetworkObjects) + { + var netVarContainers = clientSpawnedObjects.GetComponents(); + foreach (var netvarContiner in netVarContainers) + { + clientSideNetVarContainers.Add(netvarContiner); + } + } + + yield return WaitForConditionOrTimeOut(() => + clientSideNetVarContainers.Where(d => + d.HaveAllValuesChanged(NetVarValueToSet)).Count() == clientSideNetVarContainers.Count); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side NetVarContainers to report all NetworkVariables have been updated!"); + } + + Object.DestroyImmediate(prefabToSpawn); + } } } diff --git a/Tests/Editor/NetworkManagerCustomMessageManagerTests.cs b/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs similarity index 92% rename from Tests/Editor/NetworkManagerCustomMessageManagerTests.cs rename to Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs index 2094b1e..eb2e81b 100644 --- a/Tests/Editor/NetworkManagerCustomMessageManagerTests.cs +++ b/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using UnityEngine; -namespace Unity.Netcode.EditorTests +namespace Unity.Netcode.RuntimeTests { public class NetworkManagerCustomMessageManagerTests { @@ -13,6 +13,9 @@ namespace Unity.Netcode.EditorTests var transport = gameObject.AddComponent(); networkManager.NetworkConfig = new NetworkConfig(); + + + // Set dummy transport that does nothing networkManager.NetworkConfig.NetworkTransport = transport; @@ -24,6 +27,8 @@ namespace Unity.Netcode.EditorTests Debug.Assert(preManager == null); Debug.Assert(networkManager.CustomMessagingManager != null); + + networkManager.Shutdown(); Object.DestroyImmediate(gameObject); } } diff --git a/Tests/Editor/NetworkManagerCustomMessageManagerTests.cs.meta b/Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs.meta similarity index 100% rename from Tests/Editor/NetworkManagerCustomMessageManagerTests.cs.meta rename to Tests/Runtime/NetworkManagerCustomMessageManagerTests.cs.meta diff --git a/Tests/Editor/NetworkManagerSceneManagerTests.cs b/Tests/Runtime/NetworkManagerSceneManagerTests.cs similarity index 96% rename from Tests/Editor/NetworkManagerSceneManagerTests.cs rename to Tests/Runtime/NetworkManagerSceneManagerTests.cs index c1f5b30..5bb76c2 100644 --- a/Tests/Editor/NetworkManagerSceneManagerTests.cs +++ b/Tests/Runtime/NetworkManagerSceneManagerTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using UnityEngine; -namespace Unity.Netcode.EditorTests +namespace Unity.Netcode.RuntimeTests { public class NetworkManagerSceneManagerTests { diff --git a/Tests/Editor/NetworkManagerSceneManagerTests.cs.meta b/Tests/Runtime/NetworkManagerSceneManagerTests.cs.meta similarity index 100% rename from Tests/Editor/NetworkManagerSceneManagerTests.cs.meta rename to Tests/Runtime/NetworkManagerSceneManagerTests.cs.meta diff --git a/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs index 2bc2c37..c989d90 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectDestroyTests.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests @@ -12,18 +13,9 @@ namespace Unity.Netcode.RuntimeTests /// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run . Client runs it. /// - Client destroy spawned => throw exception. /// - public class NetworkObjectDestroyTests : BaseMultiInstanceTest + public class NetworkObjectDestroyTests : NetcodeIntegrationTest { - protected override int NbClients => 1; - - [UnitySetUp] - public override IEnumerator Setup() - { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - // playerPrefab.AddComponent(); - }); - } + protected override int NumberOfClients => 1; /// /// Tests that a server can destroy a NetworkObject and that it gets despawned correctly. @@ -33,12 +25,12 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator TestNetworkObjectServerDestroy() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ServerNetworkManager, serverClientPlayerResult); // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ClientNetworkManagers[0], clientClientPlayerResult); Assert.IsNotNull(serverClientPlayerResult.Result.gameObject); Assert.IsNotNull(clientClientPlayerResult.Result.gameObject); @@ -46,12 +38,9 @@ namespace Unity.Netcode.RuntimeTests // destroy the server player Object.Destroy(serverClientPlayerResult.Result.gameObject); - yield return null; + yield return NetcodeIntegrationTestHelpers.WaitForMessageOfType(m_ClientNetworkManagers[0]); Assert.IsTrue(serverClientPlayerResult.Result == null); // Assert.IsNull doesn't work here - - yield return null; // wait one frame more until we receive on client - Assert.IsTrue(clientClientPlayerResult.Result == null); // create an unspawned networkobject and destroy it @@ -71,12 +60,12 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator TestNetworkObjectClientDestroy() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ServerNetworkManager, serverClientPlayerResult); // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + var clientClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ClientNetworkManagers[0], clientClientPlayerResult); // destroy the client player, this is not allowed LogAssert.Expect(LogType.Exception, "NotServerException: Destroy a spawned NetworkObject on a non-host client is not valid. Call Destroy or Despawn on the server/host instead."); diff --git a/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs index 24dba57..59da25f 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectDontDestroyWithOwnerTests.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; using Object = UnityEngine.Object; namespace Unity.Netcode.RuntimeTests @@ -13,13 +14,13 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator DontDestroyWithOwnerTest() { // create server and client instances - MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); + NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); // create prefab var gameObject = new GameObject("ClientOwnedObject"); var networkObject = gameObject.AddComponent(); networkObject.DontDestroyWithOwner = true; - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); server.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { @@ -35,13 +36,13 @@ namespace Unity.Netcode.RuntimeTests } // start server and connect clients - MultiInstanceHelpers.Start(false, server, clients); + NetcodeIntegrationTestHelpers.Start(false, server, clients); // wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); // wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server)); + yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(server); // network objects var networkObjects = new List(); @@ -56,13 +57,13 @@ namespace Unity.Netcode.RuntimeTests } // wait for object spawn on client - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => clients[0].SpawnManager.SpawnedObjects.Count == 32)); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => clients[0].SpawnManager.SpawnedObjects.Count == 32); // disconnect the client that owns all the clients - MultiInstanceHelpers.StopOneClient(clients[0]); + NetcodeIntegrationTestHelpers.StopOneClient(clients[0]); // wait for disconnect - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => server.ConnectedClients.Count == 0)); + yield return NetcodeIntegrationTest.WaitForConditionOrTimeOut(() => server.ConnectedClients.Count == 0); for (int i = 0; i < networkObjects.Count; i++) { @@ -71,7 +72,7 @@ namespace Unity.Netcode.RuntimeTests } // cleanup - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); } } } diff --git a/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs new file mode 100644 index 0000000..ffe624c --- /dev/null +++ b/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs @@ -0,0 +1,58 @@ +using System.Collections; +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class NetworkObjectNetworkClientOwnedObjectsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + private NetworkPrefab m_NetworkPrefab; + protected override void OnServerAndClientsCreated() + { + // create prefab + var gameObject = new GameObject("ClientOwnedObject"); + var networkObject = gameObject.AddComponent(); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + + m_NetworkPrefab = (new NetworkPrefab() + { + Prefab = gameObject + }); + + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(m_NetworkPrefab); + + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.NetworkPrefabs.Add(m_NetworkPrefab); + } + } + + [UnityTest] + public IEnumerator ChangeOwnershipOwnedObjectsAddTest() + { + NetworkObject serverObject = Object.Instantiate(m_NetworkPrefab.Prefab).GetComponent(); + serverObject.NetworkManagerOwner = m_ServerNetworkManager; + serverObject.Spawn(); + + // Provide enough time for the client to receive and process the spawned message. + yield return s_DefaultWaitForTick; + + // The object is owned by server + Assert.False(m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].OwnedObjects.Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); + + // Change the ownership + serverObject.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); + + // Provide enough time for the client to receive and process the change in ownership message. + yield return s_DefaultWaitForTick; + + // Ensure it's now added to the list + Assert.True(m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].OwnedObjects.Any(x => x.NetworkObjectId == serverObject.NetworkObjectId)); + + } + } +} diff --git a/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs.meta b/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs.meta new file mode 100644 index 0000000..8470304 --- /dev/null +++ b/Tests/Runtime/NetworkObject/NetworkObjectNetworkClientOwnedObjectsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 067d53283c8fa4c31b638b830473c5b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs index 8a15e2e..94f1618 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOnNetworkDespawnTests.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests @@ -37,14 +38,14 @@ namespace Unity.Netcode.RuntimeTests [UnitySetUp] public IEnumerator Setup() { - Assert.IsTrue(MultiInstanceHelpers.Create(1, out m_ServerHost, out m_Clients)); + Assert.IsTrue(NetcodeIntegrationTestHelpers.Create(1, out m_ServerHost, out m_Clients)); m_ObjectToSpawn = new GameObject(); m_NetworkObject = m_ObjectToSpawn.AddComponent(); m_ObjectToSpawn.AddComponent(); // Make it a prefab - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_NetworkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_NetworkObject); var networkPrefab = new NetworkPrefab(); networkPrefab.Prefab = m_ObjectToSpawn; @@ -67,7 +68,7 @@ namespace Unity.Netcode.RuntimeTests Object.Destroy(m_ObjectToSpawn); m_ObjectToSpawn = null; } - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); yield return null; } @@ -85,22 +86,22 @@ namespace Unity.Netcode.RuntimeTests [UnityTest] public IEnumerator TestNetworkObjectDespawnOnShutdown([Values(InstanceType.Server, InstanceType.Host, InstanceType.Client)] InstanceType despawnCheck) { - var useHost = despawnCheck == InstanceType.Server ? false : true; + var useHost = despawnCheck != InstanceType.Server; var networkManager = despawnCheck == InstanceType.Host || despawnCheck == InstanceType.Server ? m_ServerHost : m_Clients[0]; // Start the instances - if (!MultiInstanceHelpers.Start(useHost, m_ServerHost, m_Clients)) + if (!NetcodeIntegrationTestHelpers.Start(useHost, m_ServerHost, m_Clients)) { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); } // [Client-Side] Wait for a connection to the server - yield return MultiInstanceHelpers.WaitForClientsConnected(m_Clients, null, 512); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(m_Clients, null, 512); // [Host-Server-Side] Check to make sure all clients are connected var clientCount = useHost ? m_Clients.Length + 1 : m_Clients.Length; - yield return MultiInstanceHelpers.WaitForClientsConnectedToServer(m_ServerHost, clientCount, null, 512); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnectedToServer(m_ServerHost, clientCount, null, 512); // Spawn the test object var spawnedObject = Object.Instantiate(m_NetworkObject); @@ -109,8 +110,8 @@ namespace Unity.Netcode.RuntimeTests spawnedNetworkObject.Spawn(true); // Get the spawned object relative to which NetworkManager instance we are testing. - var relativeSpawnedObject = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent() != null), networkManager, relativeSpawnedObject)); + var relativeSpawnedObject = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation((x => x.GetComponent() != null), networkManager, relativeSpawnedObject); var onNetworkDespawnTestComponent = relativeSpawnedObject.Result.GetComponent(); // Confirm it is not set before shutting down the NetworkManager diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index 597563f..c374ef8 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -3,22 +3,21 @@ using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { - public class NetworkObjectOnSpawnTests : BaseMultiInstanceTest + public class NetworkObjectOnSpawnTests : NetcodeIntegrationTest { private GameObject m_TestNetworkObjectPrefab; private GameObject m_TestNetworkObjectInstance; - protected override int NbClients => 2; - + protected override int NumberOfClients => 2; /// /// Tests that instantiating a and destroying without spawning it /// does not run or . /// - /// [UnityTest] public IEnumerator InstantiateDestroySpawnNotCalled() { @@ -29,6 +28,8 @@ namespace Unity.Netcode.RuntimeTests // instantiate m_TestNetworkObjectInstance = Object.Instantiate(m_TestNetworkObjectPrefab); yield return null; + Object.Destroy(m_TestNetworkObjectInstance); + } private class FailWhenSpawned : NetworkBehaviour @@ -44,20 +45,13 @@ namespace Unity.Netcode.RuntimeTests } } - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - // add test component - playerPrefab.AddComponent(); - }); + m_PlayerPrefab.AddComponent(); } - [UnityTearDown] - public override IEnumerator Teardown() + protected override IEnumerator OnTearDown() { - if (m_TestNetworkObjectPrefab != null) { Object.Destroy(m_TestNetworkObjectPrefab); @@ -67,10 +61,11 @@ namespace Unity.Netcode.RuntimeTests { Object.Destroy(m_TestNetworkObjectInstance); } - yield return base.Teardown(); - + yield return base.OnTearDown(); } + private List m_ClientTrackOnSpawnInstances = new List(); + /// /// Test that callbacks are run for playerobject spawn, despawn, regular spawn, destroy on server. /// @@ -79,19 +74,13 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator TestOnNetworkSpawnCallbacks() { // [Host-Side] Get the Host owned instance - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + var serverInstance = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ServerNetworkManager.LocalClientId].GetComponent(); - var serverInstance = serverClientPlayerResult.Result.GetComponent(); - - var clientInstances = new List(); foreach (var client in m_ClientNetworkManagers) { - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), client, clientClientPlayerResult)); - var clientRpcTests = clientClientPlayerResult.Result.GetComponent(); + var clientRpcTests = m_PlayerNetworkObjects[client.LocalClientId][m_ServerNetworkManager.LocalClientId].gameObject.GetComponent(); Assert.IsNotNull(clientRpcTests); - clientInstances.Add(clientRpcTests); + m_ClientTrackOnSpawnInstances.Add(clientRpcTests); } // -------------- step 1 check player spawn despawn @@ -99,69 +88,84 @@ namespace Unity.Netcode.RuntimeTests // check spawned on server Assert.AreEqual(1, serverInstance.OnNetworkSpawnCalledCount); - // safety check despawned + // safety check server despawned Assert.AreEqual(0, serverInstance.OnNetworkDespawnCalledCount); - // check spawned on client - foreach (var clientInstance in clientInstances) + // Conditional check for clients spawning or despawning + var checkSpawnCondition = false; + var expectedSpawnCount = 1; + var expectedDespawnCount = 0; + bool HasConditionBeenMet() { - Assert.AreEqual(1, clientInstance.OnNetworkSpawnCalledCount); - - // safety check despawned - Assert.AreEqual(0, clientInstance.OnNetworkDespawnCalledCount); + var clientsCompleted = 0; + // check spawned on client + foreach (var clientInstance in m_ClientTrackOnSpawnInstances) + { + if (checkSpawnCondition) + { + if (clientInstance.OnNetworkSpawnCalledCount == expectedSpawnCount) + { + clientsCompleted++; + } + } + else + { + if (clientInstance.OnNetworkDespawnCalledCount == expectedDespawnCount) + { + clientsCompleted++; + } + } + } + return clientsCompleted >= NumberOfClients; } - // despawn on server. However, since we'll be using this object later in the test, don't delete it (false) + // safety check that all clients have not been despawned yet + Assert.True(HasConditionBeenMet(), "Failed condition that all clients not despawned yet!"); + + // now verify that all clients have been spawned + checkSpawnCondition = true; + yield return WaitForConditionOrTimeOut(HasConditionBeenMet); + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side spawns!"); + + // despawn on server. However, since we'll be using this object later in the test, don't delete it serverInstance.GetComponent().Despawn(false); // check despawned on server Assert.AreEqual(1, serverInstance.OnNetworkDespawnCalledCount); + // we now expect the clients to each have despawned once + expectedDespawnCount = 1; - // wait long enough for player object to be despawned - int nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + yield return s_DefaultWaitForTick; + // verify that all client-side instances are despawned + checkSpawnCondition = false; + yield return WaitForConditionOrTimeOut(HasConditionBeenMet); - // check despawned on clients - foreach (var clientInstance in clientInstances) - { - Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount); - } - - //----------- step 2 check spawn again and destroy + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns!"); + //----------- step 2 check spawn and destroy again serverInstance.GetComponent().Spawn(); - - // wait long enough for player object to be spawned - nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); - + // 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. Assert.AreEqual(2, serverInstance.OnNetworkSpawnCalledCount); - // check spawned on client - foreach (var clientInstance in clientInstances) - { - Assert.AreEqual(1, clientInstance.OnNetworkSpawnCalledCount); - } + checkSpawnCondition = true; + yield return WaitForConditionOrTimeOut(HasConditionBeenMet); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side spawns! (2nd pass)"); // destroy the server object Object.Destroy(serverInstance.gameObject); - // wait one frame for destroy to kick in - yield return null; + yield return s_DefaultWaitForTick; // check whether despawned was called again on server instance Assert.AreEqual(2, serverInstance.OnNetworkDespawnCalledCount); - // wait long enough for player object to be despawned on client - nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + checkSpawnCondition = false; + yield return WaitForConditionOrTimeOut(HasConditionBeenMet); - // check despawned on clients - foreach (var clientInstance in clientInstances) - { - Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount); - } + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out while waiting for client side despawns! (2nd pass)"); } private class TrackOnSpawnFunctions : NetworkBehaviour diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs index c5bdc8b..2bf8c02 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOwnershipTests.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -52,7 +53,7 @@ namespace Unity.Netcode.RuntimeTests Assert.That(k_ClientInstanceCount, Is.GreaterThan(0)); // create NetworkManager instances - Assert.That(MultiInstanceHelpers.Create(k_ClientInstanceCount, out m_ServerNetworkManager, out m_ClientNetworkManagers)); + Assert.That(NetcodeIntegrationTestHelpers.Create(k_ClientInstanceCount, out m_ServerNetworkManager, out m_ClientNetworkManagers)); Assert.That(m_ServerNetworkManager, Is.Not.Null); Assert.That(m_ClientNetworkManagers, Is.Not.Null); Assert.That(m_ClientNetworkManagers.Length, Is.EqualTo(k_ClientInstanceCount)); @@ -61,7 +62,7 @@ namespace Unity.Netcode.RuntimeTests m_DummyPrefab = new GameObject("DummyPrefabPrototype"); m_DummyPrefab.AddComponent(); m_DummyPrefab.AddComponent(); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_DummyPrefab.GetComponent()); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(m_DummyPrefab.GetComponent()); m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab { Prefab = m_DummyPrefab }); foreach (var clientNetworkManager in m_ClientNetworkManagers) { @@ -69,19 +70,19 @@ namespace Unity.Netcode.RuntimeTests } // start server and client NetworkManager instances - Assert.That(MultiInstanceHelpers.Start(m_IsHost, m_ServerNetworkManager, m_ClientNetworkManagers)); + Assert.That(NetcodeIntegrationTestHelpers.Start(m_IsHost, m_ServerNetworkManager, m_ClientNetworkManagers)); // wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(m_ClientNetworkManagers)); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(m_ClientNetworkManagers); // wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(m_ServerNetworkManager)); + yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(m_ServerNetworkManager); } [TearDown] public void Teardown() { - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); if (m_DummyGameObject != null) { @@ -106,8 +107,7 @@ namespace Unity.Netcode.RuntimeTests var dummyNetworkObjectId = dummyNetworkObject.NetworkObjectId; Assert.That(dummyNetworkObjectId, Is.GreaterThan(0)); - int nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + yield return NetcodeIntegrationTestHelpers.WaitForMessageOfType(m_ClientNetworkManagers[0]); Assert.That(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId)); foreach (var clientNetworkManager in m_ClientNetworkManagers) @@ -136,17 +136,14 @@ namespace Unity.Netcode.RuntimeTests Assert.That(m_ServerNetworkManager.ConnectedClients.ContainsKey(m_ClientNetworkManagers[0].LocalClientId)); serverObject.ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId); - nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + yield return NetcodeIntegrationTestHelpers.WaitForMessageOfType(m_ClientNetworkManagers[0]); Assert.That(clientComponent.OnGainedOwnershipFired); Assert.That(clientComponent.CachedOwnerIdOnGainedOwnership, Is.EqualTo(m_ClientNetworkManagers[0].LocalClientId)); serverObject.ChangeOwnership(m_ServerNetworkManager.ServerClientId); - nextFrameNumber = Time.frameCount + 2; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); - + yield return NetcodeIntegrationTestHelpers.WaitForMessageOfType(m_ClientNetworkManagers[0]); Assert.That(serverObject.OwnerClientId, Is.EqualTo(m_ServerNetworkManager.LocalClientId)); Assert.That(clientComponent.OnLostOwnershipFired); diff --git a/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs index d092ab1..7d0bf09 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectSceneSerializationTests.cs @@ -3,6 +3,7 @@ using UnityEngine; using UnityEngine.SceneManagement; using NUnit.Framework; using Unity.Collections; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { diff --git a/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs new file mode 100644 index 0000000..9eedf8f --- /dev/null +++ b/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs @@ -0,0 +1,65 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + // "many" in this case means enough to exceed a ushort_max message size written in the header + // 1500 is not a magic number except that it's big enough to trigger a failure + private const int k_SpawnedObjects = 1500; + + private NetworkPrefab m_PrefabToSpawn; + + // Using this component assures we will know precisely how many prefabs were spawned on the client + public class SpawnObjecTrackingComponent : NetworkBehaviour + { + public static int SpawnedObjects; + public override void OnNetworkSpawn() + { + if (!IsServer) + { + SpawnedObjects++; + } + } + } + + protected override void OnServerAndClientsCreated() + { + SpawnObjecTrackingComponent.SpawnedObjects = 0; + // create prefab + var gameObject = new GameObject("TestObject"); + var networkObject = gameObject.AddComponent(); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); + gameObject.AddComponent(); + + m_PrefabToSpawn = new NetworkPrefab() { Prefab = gameObject }; + + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(m_PrefabToSpawn); + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.NetworkPrefabs.Add(m_PrefabToSpawn); + } + } + + [UnityTest] + // When this test fails it does so without an exception and will wait the default ~6 minutes + [Timeout(10000)] + public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived() + { + for (int x = 0; x < k_SpawnedObjects; x++) + { + NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent(); + serverObject.NetworkManagerOwner = m_ServerNetworkManager; + serverObject.Spawn(); + } + // ensure all objects are replicated before spawning more + yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects < k_SpawnedObjects); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for the client to spawn {k_SpawnedObjects} objects!"); + } + } +} diff --git a/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs.meta b/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs.meta new file mode 100644 index 0000000..a62cd5a --- /dev/null +++ b/Tests/Runtime/NetworkObject/NetworkObjectSpawnManyObjectsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4e0439aa2d2a72e4aa42b051d26af366 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/NetworkPrefabHandlerTests.cs b/Tests/Runtime/NetworkPrefabHandlerTests.cs index da1b712..600d9b1 100644 --- a/Tests/Runtime/NetworkPrefabHandlerTests.cs +++ b/Tests/Runtime/NetworkPrefabHandlerTests.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Collections.Generic; using UnityEngine; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -22,7 +23,7 @@ namespace Unity.Netcode.RuntimeTests { Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString()); NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID]; - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(validPrefab); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(validPrefab); m_ObjectId++; return validPrefab.gameObject; } diff --git a/Tests/Runtime/NetworkShowHideTests.cs b/Tests/Runtime/NetworkShowHideTests.cs index 2df915b..58a46bc 100644 --- a/Tests/Runtime/NetworkShowHideTests.cs +++ b/Tests/Runtime/NetworkShowHideTests.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -29,9 +30,9 @@ namespace Unity.Netcode.RuntimeTests } - public class NetworkShowHideTests : BaseMultiInstanceTest + public class NetworkShowHideTests : NetcodeIntegrationTest { - protected override int NbClients => 2; + protected override int NumberOfClients => 2; private ulong m_ClientId0; private GameObject m_PrefabToSpawn; @@ -43,15 +44,14 @@ namespace Unity.Netcode.RuntimeTests private NetworkObject m_Object2OnClient0; private NetworkObject m_Object3OnClient0; - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients, - updatePlayerPrefab: playerPrefab => - { - var networkTransform = playerPrefab.AddComponent(); - m_PrefabToSpawn = PreparePrefab(typeof(ShowHideObject)); - }); + var networkTransform = m_PlayerPrefab.AddComponent(); + } + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = PreparePrefab(typeof(ShowHideObject)); } public GameObject PreparePrefab(Type type) @@ -59,7 +59,7 @@ namespace Unity.Netcode.RuntimeTests var prefabToSpawn = new GameObject(); prefabToSpawn.AddComponent(type); var networkObjectPrefab = prefabToSpawn.AddComponent(); - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab); m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); foreach (var clientNetworkManager in m_ClientNetworkManagers) { @@ -138,25 +138,22 @@ namespace Unity.Netcode.RuntimeTests private IEnumerator RefreshNetworkObjects() { - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run( - MultiInstanceHelpers.GetNetworkObjectByRepresentation( + var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.NetworkObjectId == m_NetSpawnedObject1.NetworkObjectId, m_ClientNetworkManagers[0], - serverClientPlayerResult)); + serverClientPlayerResult); m_Object1OnClient0 = serverClientPlayerResult.Result; - yield return MultiInstanceHelpers.Run( - MultiInstanceHelpers.GetNetworkObjectByRepresentation( + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.NetworkObjectId == m_NetSpawnedObject2.NetworkObjectId, m_ClientNetworkManagers[0], - serverClientPlayerResult)); + serverClientPlayerResult); m_Object2OnClient0 = serverClientPlayerResult.Result; - serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run( - MultiInstanceHelpers.GetNetworkObjectByRepresentation( + serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.NetworkObjectId == m_NetSpawnedObject3.NetworkObjectId, m_ClientNetworkManagers[0], - serverClientPlayerResult)); + serverClientPlayerResult); m_Object3OnClient0 = serverClientPlayerResult.Result; // make sure the objects are set with the right network manager diff --git a/Tests/Runtime/NetworkSpawnManagerTests.cs b/Tests/Runtime/NetworkSpawnManagerTests.cs index 621ba68..a3882d3 100644 --- a/Tests/Runtime/NetworkSpawnManagerTests.cs +++ b/Tests/Runtime/NetworkSpawnManagerTests.cs @@ -2,16 +2,17 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { - public class NetworkSpawnManagerTests : BaseMultiInstanceTest + public class NetworkSpawnManagerTests : NetcodeIntegrationTest { private ulong serverSideClientId => m_ServerNetworkManager.ServerClientId; private ulong clientSideClientId => m_ClientNetworkManagers[0].LocalClientId; private ulong otherClientSideClientId => m_ClientNetworkManagers[1].LocalClientId; - protected override int NbClients => 2; + protected override int NumberOfClients => 2; [Test] public void TestServerCanAccessItsOwnPlayer() @@ -96,7 +97,7 @@ namespace Unity.Netcode.RuntimeTests // test when client connects, player object is now available // connect new client - if (!MultiInstanceHelpers.CreateNewClients(1, out NetworkManager[] clients)) + if (!NetcodeIntegrationTestHelpers.CreateNewClients(1, out NetworkManager[] clients)) { Debug.LogError("Failed to create instances"); Assert.Fail("Failed to create instances"); @@ -104,8 +105,8 @@ namespace Unity.Netcode.RuntimeTests var newClientNetworkManager = clients[0]; newClientNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; newClientNetworkManager.StartClient(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnected(newClientNetworkManager)); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => m_ServerNetworkManager.ConnectedClients.ContainsKey(newClientNetworkManager.LocalClientId))); + yield return NetcodeIntegrationTestHelpers.WaitForClientConnected(newClientNetworkManager); + yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.ConnectedClients.ContainsKey(newClientNetworkManager.LocalClientId)); var newClientLocalClientId = newClientNetworkManager.LocalClientId; // test new client can get that itself locally @@ -119,8 +120,8 @@ namespace Unity.Netcode.RuntimeTests // test when client disconnects, player object no longer available. var nbConnectedClients = m_ServerNetworkManager.ConnectedClients.Count; - MultiInstanceHelpers.StopOneClient(newClientNetworkManager); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => m_ServerNetworkManager.ConnectedClients.Count == nbConnectedClients - 1)); + NetcodeIntegrationTestHelpers.StopOneClient(newClientNetworkManager); + yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.ConnectedClients.Count == nbConnectedClients - 1); serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId); Assert.Null(serverSideNewClientPlayer); diff --git a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index dd61209..e2f215f 100644 --- a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -8,90 +8,92 @@ using NUnit.Framework; // using Unity.Netcode.Samples; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { + public class NetworkTransformTestComponent : NetworkTransform + { + public bool ReadyToReceivePositionUpdate = false; + + public override void OnNetworkSpawn() + { + base.OnNetworkSpawn(); + + ReadyToReceivePositionUpdate = true; + } + } + // [TestFixture(true, true)] [TestFixture(true, false)] // [TestFixture(false, true)] [TestFixture(false, false)] - public class NetworkTransformTests : BaseMultiInstanceTest + public class NetworkTransformTests : NetcodeIntegrationTest { private NetworkObject m_ClientSideClientPlayer; private NetworkObject m_ServerSideClientPlayer; private readonly bool m_TestWithClientNetworkTransform; - private readonly bool m_TestWithHost; - public NetworkTransformTests(bool testWithHost, bool testWithClientNetworkTransform) { - m_TestWithHost = testWithHost; // from test fixture + m_UseHost = testWithHost; // from test fixture m_TestWithClientNetworkTransform = testWithClientNetworkTransform; } - protected override int NbClients => 1; + protected override int NumberOfClients => 1; - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients, updatePlayerPrefab: playerPrefab => + if (m_TestWithClientNetworkTransform) { - if (m_TestWithClientNetworkTransform) - { - // playerPrefab.AddComponent(); - } - else - { - playerPrefab.AddComponent(); - } - }); + // m_PlayerPrefab.AddComponent(); + } + else + { + var networkTransform = m_PlayerPrefab.AddComponent(); + networkTransform.Interpolate = false; + } + } + protected override void OnServerAndClientsCreated() + { #if NGO_TRANSFORM_DEBUG // Log assert for writing without authority is a developer log... // TODO: This is why monolithic test base classes and test helpers are an anti-pattern - this is part of an individual test case setup but is separated from the code verifying it! m_ServerNetworkManager.LogLevel = LogLevel.Developer; m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; #endif + } - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ServerNetworkManager, serverClientPlayerResult)); + protected override IEnumerator OnServerAndClientsConnected() + { + // Get the client player representation on both the server and the client side + m_ServerSideClientPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; + m_ClientSideClientPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ClientNetworkManagers[0], clientClientPlayerResult)); + // Get the NetworkTransformTestComponent to make sure the client side is ready before starting test + var otherSideNetworkTransformComponent = m_ClientSideClientPlayer.GetComponent(); - m_ServerSideClientPlayer = serverClientPlayerResult.Result; - m_ClientSideClientPlayer = clientClientPlayerResult.Result; + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransformComponent.ReadyToReceivePositionUpdate == true); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side to notify it is ready!"); + + yield return base.OnServerAndClientsConnected(); } // TODO: rewrite after perms & authority changes [UnityTest] public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool testLocalTransform) { - var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper(); + // Get the client player's NetworkTransform for both instances + var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); + var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - NetworkTransform authoritativeNetworkTransform; - NetworkTransform otherSideNetworkTransform; - // if (m_TestWithClientNetworkTransform) - // { - // // client auth net transform can write from client, not from server - // otherSideNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - // authoritativeNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - // } - // else - { - // server auth net transform can't write from client, not from client - authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - } Assert.That(!otherSideNetworkTransform.CanCommitToTransform); Assert.That(authoritativeNetworkTransform.CanCommitToTransform); - authoritativeNetworkTransform.Interpolate = false; - otherSideNetworkTransform.Interpolate = false; - if (authoritativeNetworkTransform.CanCommitToTransform) { authoritativeNetworkTransform.InLocalSpace = testLocalTransform; @@ -106,23 +108,25 @@ namespace Unity.Netcode.RuntimeTests // test position var authPlayerTransform = authoritativeNetworkTransform.transform; - authPlayerTransform.position = new Vector3(10, 20, 30); + Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "server side pos should be zero at first"); // sanity check - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.position.x > approximation, waitResult, maxFrames: 120)); - if (!waitResult.Result) - { - throw new Exception("timeout while waiting for position change"); - } + + authPlayerTransform.position = new Vector3(10, 20, 30); + + yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.position.x > approximation); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}"); + Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with == // test rotation authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter Assert.AreEqual(Quaternion.identity, otherSideNetworkTransform.transform.rotation, "wrong initial value for rotation"); // sanity check - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation, waitResult, maxFrames: 120)); - if (!waitResult.Result) - { - throw new Exception("timeout while waiting for rotation change"); - } + + yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for rotation change"); + // approximation needed here since eulerAngles isn't super precise. Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}"); Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}"); @@ -133,11 +137,11 @@ namespace Unity.Netcode.RuntimeTests UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check authPlayerTransform.localScale = new Vector3(2, 3, 4); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation, waitResult, maxFrames: 120)); - if (!waitResult.Result) - { - throw new Exception("timeout while waiting for scale change"); - } + + yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for scale change"); + UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost"); UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost"); UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost"); @@ -147,33 +151,17 @@ namespace Unity.Netcode.RuntimeTests } [UnityTest] - // [Ignore("skipping for now, still need to figure weird multiinstance issue with hosts")] public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool testClientAuthority) { - // test server can't change client authoritative transform - NetworkTransform authoritativeNetworkTransform; - NetworkTransform otherSideNetworkTransform; - - // if (m_TestWithClientNetworkTransform) - // { - // // client auth net transform can write from client, not from server - // otherSideNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - // authoritativeNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - // } - // else - { - // server auth net transform can't write from client, not from client - authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); - otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); - } - - authoritativeNetworkTransform.Interpolate = false; - otherSideNetworkTransform.Interpolate = false; + // Get the client player's NetworkTransform for both instances + var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent(); + var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent(); Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "other side pos should be zero at first"); // sanity check + otherSideNetworkTransform.transform.position = new Vector3(4, 5, 6); - yield return null; // one frame + yield return s_DefaultWaitForTick; Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!"); #if NGO_TRANSFORM_DEBUG @@ -190,12 +178,10 @@ namespace Unity.Netcode.RuntimeTests * test teleport without interpolation * test dynamic spawning */ - - [UnityTearDown] - public override IEnumerator Teardown() + protected override IEnumerator OnTearDown() { - yield return base.Teardown(); UnityEngine.Object.DestroyImmediate(m_PlayerPrefab); + yield return base.OnTearDown(); } } } diff --git a/Tests/Runtime/NetworkVarBufferCopyTest.cs b/Tests/Runtime/NetworkVarBufferCopyTest.cs index 8097d66..b927cb1 100644 --- a/Tests/Runtime/NetworkVarBufferCopyTest.cs +++ b/Tests/Runtime/NetworkVarBufferCopyTest.cs @@ -1,10 +1,12 @@ using System.Collections; +using System.Collections.Generic; using NUnit.Framework; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { - public class NetworkVarBufferCopyTest : BaseMultiInstanceTest + public class NetworkVarBufferCopyTest : NetcodeIntegrationTest { public class DummyNetVar : NetworkVariableBase { @@ -13,7 +15,7 @@ namespace Unity.Netcode.RuntimeTests public bool FieldWritten; public bool DeltaRead; public bool FieldRead; - public bool Dirty = true; + public bool Dirty = false; public override void ResetDirty() { @@ -80,56 +82,88 @@ namespace Unity.Netcode.RuntimeTests public class DummyNetBehaviour : NetworkBehaviour { - public DummyNetVar NetVar; - } - protected override int NbClients => 1; + public DummyNetVar NetVar = new DummyNetVar(); - [UnitySetUp] - public override IEnumerator Setup() - { - yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients, - updatePlayerPrefab: playerPrefab => + public override void OnNetworkSpawn() + { + if (!IsServer) { - var dummyNetBehaviour = playerPrefab.AddComponent(); - }); + ClientDummyNetBehaviourSpawned(this); + } + base.OnNetworkSpawn(); + } + } + protected override int NumberOfClients => 1; + + private static List s_ClientDummyNetBehavioursSpawned = new List(); + public static void ClientDummyNetBehaviourSpawned(DummyNetBehaviour dummyNetBehaviour) + { + s_ClientDummyNetBehavioursSpawned.Add(dummyNetBehaviour); + } + + protected override IEnumerator OnSetup() + { + s_ClientDummyNetBehavioursSpawned.Clear(); + return base.OnSetup(); + } + + protected override void OnCreatePlayerPrefab() + { + m_PlayerPrefab.AddComponent(); } [UnityTest] public IEnumerator TestEntireBufferIsCopiedOnNetworkVariableDelta() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation( + var serverClientPlayerResult = new NetcodeIntegrationTestHelpers.ResultWrapper(); + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, - m_ServerNetworkManager, serverClientPlayerResult)); - - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation( - x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, - m_ClientNetworkManagers[0], clientClientPlayerResult)); + m_ServerNetworkManager, serverClientPlayerResult); var serverSideClientPlayer = serverClientPlayerResult.Result; + var serverComponent = serverSideClientPlayer.GetComponent(); + + // 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 clientSideClientPlayer = clientClientPlayerResult.Result; + var clientComponent = clientSideClientPlayer.GetComponent(); - var serverComponent = (serverSideClientPlayer).GetComponent(); - var clientComponent = (clientSideClientPlayer).GetComponent(); + // Wait for the DummyNetBehaviours on the client side to notify they have been initialized and spawned + yield return WaitForConditionOrTimeOut(() => s_ClientDummyNetBehavioursSpawned.Count >= 1); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!"); - var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition( - () => clientComponent.NetVar.DeltaRead == true, - waitResult, - maxFrames: 120)); - - if (!waitResult.Result) - { - Assert.Fail("Failed to send a delta within 120 frames"); - } + // Check that FieldWritten is written when dirty + serverComponent.NetVar.Dirty = true; + yield return s_DefaultWaitForTick; Assert.True(serverComponent.NetVar.FieldWritten); + + // Check that DeltaWritten is written when dirty + serverComponent.NetVar.Dirty = true; + yield return s_DefaultWaitForTick; Assert.True(serverComponent.NetVar.DeltaWritten); - Assert.True(clientComponent.NetVar.FieldRead); - Assert.True(clientComponent.NetVar.DeltaRead); + + // Check that both FieldRead and DeltaRead were invoked on the client side + yield return WaitForConditionOrTimeOut(() => clientComponent.NetVar.FieldRead == true && clientComponent.NetVar.DeltaRead == true); + + var timedOutMessage = "Timed out waiting for client reads: "; + if (s_GlobalTimeoutHelper.TimedOut) + { + if (!clientComponent.NetVar.FieldRead) + { + timedOutMessage += "[FieldRead]"; + } + + if (!clientComponent.NetVar.DeltaRead) + { + timedOutMessage += "[DeltaRead]"; + } + } + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, timedOutMessage); } } } diff --git a/Tests/Runtime/NetworkVariableTests.cs b/Tests/Runtime/NetworkVariableTests.cs index 306ec31..4dfc6e4 100644 --- a/Tests/Runtime/NetworkVariableTests.cs +++ b/Tests/Runtime/NetworkVariableTests.cs @@ -1,13 +1,15 @@ using System; using System.Collections; -using UnityEngine; +using System.Collections.Generic; using UnityEngine.TestTools; using NUnit.Framework; using Unity.Collections; +using Unity.Netcode.TestHelpers.Runtime; +using Random = UnityEngine.Random; namespace Unity.Netcode.RuntimeTests { - public struct TestStruct : INetworkSerializable + public struct TestStruct : INetworkSerializable, IEquatable { public uint SomeInt; public bool SomeBool; @@ -27,12 +29,31 @@ namespace Unity.Netcode.RuntimeTests serializer.SerializeValue(ref SomeInt); serializer.SerializeValue(ref SomeBool); } + + public bool Equals(TestStruct other) + { + return SomeInt == other.SomeInt && SomeBool == other.SomeBool; + } + + public override bool Equals(object obj) + { + return obj is TestStruct other && Equals(other); + } + + public override int GetHashCode() + { + unchecked + { + return ((int)SomeInt * 397) ^ SomeBool.GetHashCode(); + } + } } public class NetworkVariableTest : NetworkBehaviour { public readonly NetworkVariable TheScalar = new NetworkVariable(); public readonly NetworkList TheList = new NetworkList(); + public readonly NetworkList TheLargeList = new NetworkList(); public readonly NetworkVariable FixedString32 = new NetworkVariable(); @@ -47,14 +68,26 @@ namespace Unity.Netcode.RuntimeTests } public readonly NetworkVariable TheStruct = new NetworkVariable(); + public readonly NetworkList TheListOfStructs = new NetworkList(); public bool ListDelegateTriggered; + + public override void OnNetworkSpawn() + { + if (!IsServer) + { + NetworkVariableTests.ClientNetworkVariableTestSpawned(this); + } + base.OnNetworkSpawn(); + } } - public class NetworkVariableTests : BaseMultiInstanceTest + [TestFixture(true)] + [TestFixture(false)] + public class NetworkVariableTests : NetcodeIntegrationTest { private const string k_FixedStringTestValue = "abcdefghijklmnopqrstuvwxyz"; - protected override int NbClients => 2; + protected override int NumberOfClients => 2; private const uint k_TestUInt = 0x12345678; @@ -64,36 +97,72 @@ namespace Unity.Netcode.RuntimeTests private const int k_TestKey1 = 0x0f0f; + private static List s_ClientNetworkVariableTestInstances = new List(); + public static void ClientNetworkVariableTestSpawned(NetworkVariableTest networkVariableTest) + { + s_ClientNetworkVariableTestInstances.Add(networkVariableTest); + } + // Player1 component on the server private NetworkVariableTest m_Player1OnServer; // Player1 component on client1 private NetworkVariableTest m_Player1OnClient1; - private bool m_TestWithHost; + private NetworkListTestPredicate m_NetworkListPredicateHandler; - [UnitySetUp] - public override IEnumerator Setup() + private bool m_EnsureLengthSafety; + + public NetworkVariableTests(bool ensureLengthSafety) { - yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients, - updatePlayerPrefab: playerPrefab => - { - playerPrefab.AddComponent(); - }); + m_EnsureLengthSafety = ensureLengthSafety; + } + + protected override bool CanStartServerAndClients() + { + return false; + } + + /// + /// This is an adjustment to how the server and clients are started in order + /// to avoid timing issues when running in a stand alone test runner build. + /// + private IEnumerator InitializeServerAndClients(bool useHost) + { + s_ClientNetworkVariableTestInstances.Clear(); + m_PlayerPrefab.AddComponent(); + + m_ServerNetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; + m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + foreach (var client in m_ClientNetworkManagers) + { + client.NetworkConfig.EnsureNetworkVariableLengthSafety = m_EnsureLengthSafety; + client.NetworkConfig.PlayerPrefab = m_PlayerPrefab; + } + + Assert.True(NetcodeIntegrationTestHelpers.Start(useHost, m_ServerNetworkManager, m_ClientNetworkManagers), "Failed to start server and client instances"); + + RegisterSceneManagerHandler(); + + // Wait for connection on client and server side + yield return WaitForClientsConnectedOrTimeOut(); // These are the *SERVER VERSIONS* of the *CLIENT PLAYER 1 & 2* - var result = new MultiInstanceHelpers.CoroutineResultWrapper(); + var result = new NetcodeIntegrationTestHelpers.ResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation( + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, - m_ServerNetworkManager, result)); + m_ServerNetworkManager, result); + + // Assign server-side client's player m_Player1OnServer = result.Result.GetComponent(); // This is client1's view of itself - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation( + yield return NetcodeIntegrationTestHelpers.GetNetworkObjectByRepresentation( x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, - m_ClientNetworkManagers[0], result)); + m_ClientNetworkManagers[0], result); + // Assign client-side local player m_Player1OnClient1 = result.Result.GetComponent(); m_Player1OnServer.TheList.Clear(); @@ -106,6 +175,14 @@ namespace Unity.Netcode.RuntimeTests { throw new Exception("at least one client network container not empty at start"); } + + var instanceCount = useHost ? NumberOfClients * 3 : NumberOfClients * 2; + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(() => s_ClientNetworkVariableTestInstances.Count == instanceCount); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for all client NetworkVariableTest instances to register they have spawned!"); + + yield return s_DefaultWaitForTick; } /// @@ -114,13 +191,13 @@ namespace Unity.Netcode.RuntimeTests [UnityTest] public IEnumerator AllNetworkVariableTypes([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - // Create, instantiate, and host // This would normally go in Setup, but since every other test but this one - // uses MultiInstanceHelper, and it does its own NetworkManager setup / teardown, + // 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 _)); + Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out NetworkManager server, useHost ? NetworkManagerHelper.NetworkManagerOperatingMode.Host : NetworkManagerHelper.NetworkManagerOperatingMode.Server)); + + Assert.IsTrue(server.IsHost == useHost, $"{nameof(useHost)} does not match the server.IsHost value!"); Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent"); @@ -131,14 +208,8 @@ namespace Unity.Netcode.RuntimeTests // Start Testing networkVariableTestComponent.EnableTesting = true; - var testsAreComplete = networkVariableTestComponent.IsTestComplete(); - - // Wait for the NetworkVariable tests to complete - while (!testsAreComplete) - { - yield return new WaitForSeconds(0.003f); - testsAreComplete = networkVariableTestComponent.IsTestComplete(); - } + yield return WaitForConditionOrTimeOut(() => true == networkVariableTestComponent.IsTestComplete()); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for the test to complete!"); // Stop Testing networkVariableTestComponent.EnableTesting = false; @@ -148,18 +219,16 @@ namespace Unity.Netcode.RuntimeTests // Disable this once we are done. networkVariableTestComponent.gameObject.SetActive(false); - Assert.IsTrue(testsAreComplete); - // This would normally go in Teardown, but since every other test but this one - // uses MultiInstanceHelper, and it does its own NetworkManager setup / teardown, + // 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(); } - [Test] - public void ClientWritePermissionTest([Values(true, false)] bool useHost) + [UnityTest] + public IEnumerator ClientWritePermissionTest([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; + yield return InitializeServerAndClients(useHost); // client must not be allowed to write to a server auth variable Assert.Throws(() => m_Player1OnClient1.TheScalar.Value = k_TestVal1); @@ -168,222 +237,186 @@ namespace Unity.Netcode.RuntimeTests [UnityTest] public IEnumerator FixedString32Test([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.FixedString32.Value = k_FixedStringTestValue; + yield return InitializeServerAndClients(useHost); + m_Player1OnServer.FixedString32.Value = k_FixedStringTestValue; - // we are writing to the private and public variables on player 1's object... - }, - () => - { - - // ...and we should see the writes to the private var only on the server & the owner, - // but the public variable everywhere - return - m_Player1OnClient1.FixedString32.Value == k_FixedStringTestValue; - } - ); + // Now wait for the client side version to be updated to k_FixedStringTestValue + yield return WaitForConditionOrTimeOut(() => m_Player1OnClient1.FixedString32.Value == k_FixedStringTestValue); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side NetworkVariable to update!"); } [UnityTest] public IEnumerator NetworkListAdd([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - m_Player1OnServer.TheList.Add(k_TestVal2); - }, - () => - { - return m_Player1OnServer.TheList.Count == 2 && - m_Player1OnClient1.TheList.Count == 2 && - m_Player1OnServer.ListDelegateTriggered && - m_Player1OnClient1.ListDelegateTriggered && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1 && - m_Player1OnServer.TheList[1] == k_TestVal2 && - m_Player1OnClient1.TheList[1] == k_TestVal2; - } - ); + yield return InitializeServerAndClients(useHost); + m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.Add, 10); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); + } + + [UnityTest] + public IEnumerator WhenListContainsManyLargeValues_OverflowExceptionIsNotThrown([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.ContainsLarge, 20); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } [UnityTest] public IEnumerator NetworkListContains([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - }, - () => - { - return m_Player1OnServer.TheList.Count == 1 && - m_Player1OnClient1.TheList.Count == 1 && - m_Player1OnServer.TheList.Contains(k_TestVal1) && - m_Player1OnClient1.TheList.Contains(k_TestVal1); - } - ); + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); + + // Now test the NetworkList.Contains method + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.Contains); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } [UnityTest] - public IEnumerator NetworkListRemoveValue([Values(true, false)] bool useHost) + public IEnumerator NetworkListRemove([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - m_Player1OnServer.TheList.Add(k_TestVal2); - m_Player1OnServer.TheList.Add(k_TestVal3); - m_Player1OnServer.TheList.Remove(k_TestVal2); - }, - () => - { - return m_Player1OnServer.TheList.Count == 2 && - m_Player1OnClient1.TheList.Count == 2 && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1 && - m_Player1OnServer.TheList[1] == k_TestVal3 && - m_Player1OnClient1.TheList[1] == k_TestVal3; - } - ); + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); + + // Remove two entries by index + m_Player1OnServer.TheList.Remove(3); + m_Player1OnServer.TheList.Remove(5); + + // Really just verifies the data at this point + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } [UnityTest] public IEnumerator NetworkListInsert([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - m_Player1OnServer.TheList.Add(k_TestVal2); - m_Player1OnServer.TheList.Insert(1, k_TestVal3); - }, - () => - { - return m_Player1OnServer.TheList.Count == 3 && - m_Player1OnClient1.TheList.Count == 3 && - m_Player1OnServer.ListDelegateTriggered && - m_Player1OnClient1.ListDelegateTriggered && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1 && - m_Player1OnServer.TheList[1] == k_TestVal3 && - m_Player1OnClient1.TheList[1] == k_TestVal3 && - m_Player1OnServer.TheList[2] == k_TestVal2 && - m_Player1OnClient1.TheList[2] == k_TestVal2; - } - ); + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); + + // Now randomly insert a random value entry + m_Player1OnServer.TheList.Insert(Random.Range(0, 9), Random.Range(1, 99)); + + // Verify the element count and values on the client matches the server + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } [UnityTest] public IEnumerator NetworkListIndexOf([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - m_Player1OnServer.TheList.Add(k_TestVal2); - m_Player1OnServer.TheList.Add(k_TestVal3); - }, - () => - { - return m_Player1OnServer.TheList.IndexOf(k_TestVal1) == 0 && - m_Player1OnClient1.TheList.IndexOf(k_TestVal1) == 0 && - m_Player1OnServer.TheList.IndexOf(k_TestVal2) == 1 && - m_Player1OnClient1.TheList.IndexOf(k_TestVal2) == 1 && - m_Player1OnServer.TheList.IndexOf(k_TestVal3) == 2 && - m_Player1OnClient1.TheList.IndexOf(k_TestVal3) == 2; - } - ); - } + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); - [UnityTest] - public IEnumerator NetworkListArrayOperator([Values(true, false)] bool useHost) - { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal3); - m_Player1OnServer.TheList.Add(k_TestVal3); - m_Player1OnServer.TheList[0] = k_TestVal1; - m_Player1OnServer.TheList[1] = k_TestVal2; - }, - () => - { - return m_Player1OnServer.TheList.Count == 2 && - m_Player1OnClient1.TheList.Count == 2 && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1 && - m_Player1OnServer.TheList[1] == k_TestVal2 && - m_Player1OnClient1.TheList[1] == k_TestVal2; - } - ); + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.IndexOf); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); } - [UnityTest] public IEnumerator NetworkListValueUpdate([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - }, - () => - { - return m_Player1OnServer.TheList.Count == 1 && - m_Player1OnClient1.TheList.Count == 1 && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1; - } - ); - var testSucceeded = false; + yield return InitializeServerAndClients(useHost); + // Add 1 element value and verify it is the same on the client + m_NetworkListPredicateHandler = new NetworkListTestPredicate(m_Player1OnServer, m_Player1OnClient1, NetworkListTestPredicate.NetworkListTestStates.Add, 1); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); + // Setup our original and + var previousValue = m_Player1OnServer.TheList[0]; + var updatedValue = previousValue + 10; + + // Callback that verifies the changed event occurred and that the original and new values are correct void TestValueUpdatedCallback(NetworkListEvent changedEvent) { - testSucceeded = changedEvent.PreviousValue == k_TestVal1 && - changedEvent.Value == k_TestVal3; + testSucceeded = changedEvent.PreviousValue == previousValue && + changedEvent.Value == updatedValue; } - try - { - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList[0] = k_TestVal3; - m_Player1OnClient1.TheList.OnListChanged += TestValueUpdatedCallback; - }, - () => - { - return m_Player1OnServer.TheList.Count == 1 && - m_Player1OnClient1.TheList.Count == 1 && - m_Player1OnServer.TheList[0] == k_TestVal3 && - m_Player1OnClient1.TheList[0] == k_TestVal3; - } - ); - } - finally - { - m_Player1OnClient1.TheList.OnListChanged -= TestValueUpdatedCallback; - } + // Subscribe to the OnListChanged event on the client side and + m_Player1OnClient1.TheList.OnListChanged += TestValueUpdatedCallback; + m_Player1OnServer.TheList[0] = updatedValue; + + // Wait until we know the client side matches the server side before checking if the callback was a success + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); Assert.That(testSucceeded); + m_Player1OnClient1.TheList.OnListChanged -= TestValueUpdatedCallback; } - [Test] - public void NetworkListIEnumerator([Values(true, false)] bool useHost) + [UnityTest] + public IEnumerator NetworkListRemoveAt([Values(true, false)] bool useHost) { - m_TestWithHost = useHost; + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); + + // Randomly remove a few entries + m_Player1OnServer.TheList.RemoveAt(Random.Range(0, m_Player1OnServer.TheList.Count - 1)); + m_Player1OnServer.TheList.RemoveAt(Random.Range(0, m_Player1OnServer.TheList.Count - 1)); + m_Player1OnServer.TheList.RemoveAt(Random.Range(0, m_Player1OnServer.TheList.Count - 1)); + + // Verify the element count and values on the client matches the server + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); + } + + [UnityTest] + public IEnumerator NetworkListClear([Values(true, false)] bool useHost) + { + // Re-use the NetworkListAdd to initialize the server and client as well as make sure the list is populated + yield return NetworkListAdd(useHost); + m_Player1OnServer.TheList.Clear(); + // Verify the element count and values on the client matches the server + m_NetworkListPredicateHandler.SetNetworkListTestState(NetworkListTestPredicate.NetworkListTestStates.VerifyData); + yield return WaitForConditionOrTimeOut(m_NetworkListPredicateHandler); + } + + [UnityTest] + public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + + bool VerifyStructure() + { + return m_Player1OnClient1.TheStruct.Value.SomeBool == m_Player1OnServer.TheStruct.Value.SomeBool && + m_Player1OnClient1.TheStruct.Value.SomeInt == m_Player1OnServer.TheStruct.Value.SomeInt; + } + + m_Player1OnServer.TheStruct.Value = new TestStruct() { SomeInt = k_TestUInt, SomeBool = false }; + m_Player1OnServer.TheStruct.SetDirty(true); + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyStructure); + } + + [UnityTest] + public IEnumerator TestINetworkSerializableCallsNetworkSerialize([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); + TestStruct.NetworkSerializeCalledOnWrite = false; + TestStruct.NetworkSerializeCalledOnRead = false; + m_Player1OnServer.TheStruct.Value = new TestStruct() { SomeInt = k_TestUInt, SomeBool = false }; + + static bool VerifyCallback() => TestStruct.NetworkSerializeCalledOnWrite && TestStruct.NetworkSerializeCalledOnRead; + + // Wait for the client-side to notify it is finished initializing and spawning. + yield return WaitForConditionOrTimeOut(VerifyCallback); + } + + #region COULD_BE_REMOVED + [UnityTest] + [Ignore("This is used several times already in the NetworkListPredicate")] + // TODO: If we end up using the new suggested pattern, then delete this + public IEnumerator NetworkListArrayOperator([Values(true, false)] bool useHost) + { + yield return NetworkListAdd(useHost); + } + + [UnityTest] + [Ignore("This is used several times already in the NetworkListPredicate")] + // TODO: If we end up using the new suggested pattern, then delete this + public IEnumerator NetworkListIEnumerator([Values(true, false)] bool useHost) + { + yield return InitializeServerAndClients(useHost); var correctVals = new int[3]; correctVals[0] = k_TestVal1; correctVals[1] = k_TestVal2; @@ -404,99 +437,184 @@ namespace Unity.Netcode.RuntimeTests } } } + #endregion - [UnityTest] - public IEnumerator NetworkListRemoveAt([Values(true, false)] bool useHost) + + protected override IEnumerator OnTearDown() { - m_TestWithHost = useHost; + m_NetworkListPredicateHandler = null; + yield return base.OnTearDown(); + } + } - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheList.Add(k_TestVal1); - m_Player1OnServer.TheList.Add(k_TestVal2); - m_Player1OnServer.TheList.Add(k_TestVal3); - m_Player1OnServer.TheList.RemoveAt(1); - }, - () => - { - return m_Player1OnServer.TheList.Count == 2 && - m_Player1OnClient1.TheList.Count == 2 && - m_Player1OnServer.TheList[0] == k_TestVal1 && - m_Player1OnClient1.TheList[0] == k_TestVal1 && - m_Player1OnServer.TheList[1] == k_TestVal3 && - m_Player1OnClient1.TheList[1] == k_TestVal3; - } - ); + /// + /// Handles the more generic conditional logic for NetworkList tests + /// which can be used with the + /// that accepts anything derived from the class + /// as a parameter. + /// + public class NetworkListTestPredicate : ConditionalPredicateBase + { + private const int k_MaxRandomValue = 1000; + + private Dictionary> m_StateFunctions; + + // Player1 component on the Server + private NetworkVariableTest m_Player1OnServer; + + // Player1 component on client1 + private NetworkVariableTest m_Player1OnClient1; + + private string m_TestStageFailedMessage; + + public enum NetworkListTestStates + { + Add, + ContainsLarge, + Contains, + VerifyData, + IndexOf, } - [UnityTest] - public IEnumerator NetworkListClear([Values(true, false)] bool useHost) + private NetworkListTestStates m_NetworkListTestState; + + public void SetNetworkListTestState(NetworkListTestStates networkListTestState) { - m_TestWithHost = useHost; - - // first put some stuff in; re-use the add test - yield return NetworkListAdd(useHost); - - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => m_Player1OnServer.TheList.Clear(), - () => - { - return - m_Player1OnServer.ListDelegateTriggered && - m_Player1OnClient1.ListDelegateTriggered && - m_Player1OnServer.TheList.Count == 0 && - m_Player1OnClient1.TheList.Count == 0; - } - ); + m_NetworkListTestState = networkListTestState; } - [UnityTest] - public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost) + /// + /// Determines if the condition has been reached for the current NetworkListTestState + /// + protected override bool OnHasConditionBeenReached() { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - m_Player1OnServer.TheStruct.Value = - new TestStruct() { SomeInt = k_TestUInt, SomeBool = false }; - m_Player1OnServer.TheStruct.SetDirty(true); - }, - () => - { - return - m_Player1OnClient1.TheStruct.Value.SomeBool == false && - m_Player1OnClient1.TheStruct.Value.SomeInt == k_TestUInt; - } - ); + var isStateRegistered = m_StateFunctions.ContainsKey(m_NetworkListTestState); + Assert.IsTrue(isStateRegistered); + return m_StateFunctions[m_NetworkListTestState].Invoke(); } - [UnityTest] - public IEnumerator TestINetworkSerializableCallsNetworkSerialize([Values(true, false)] bool useHost) + /// + /// Provides all information about the players for both sides for simplicity and informative sake. + /// + /// + private string ConditionFailedInfo() { - m_TestWithHost = useHost; - yield return MultiInstanceHelpers.RunAndWaitForCondition( - () => - { - TestStruct.NetworkSerializeCalledOnWrite = false; - TestStruct.NetworkSerializeCalledOnRead = false; - m_Player1OnServer.TheStruct.Value = - new TestStruct() { SomeInt = k_TestUInt, SomeBool = false }; - m_Player1OnServer.TheStruct.SetDirty(true); - }, - () => - { - return - TestStruct.NetworkSerializeCalledOnWrite && - TestStruct.NetworkSerializeCalledOnRead; - } - ); + return $"{m_NetworkListTestState} condition test failed:\n Server List Count: { m_Player1OnServer.TheList.Count} vs Client List Count: { m_Player1OnClient1.TheList.Count}\n" + + $"Server List Count: { m_Player1OnServer.TheLargeList.Count} vs Client List Count: { m_Player1OnClient1.TheLargeList.Count}\n" + + $"Server Delegate Triggered: {m_Player1OnServer.ListDelegateTriggered} | Client Delegate Triggered: {m_Player1OnClient1.ListDelegateTriggered}\n"; } - [UnityTearDown] - public override IEnumerator Teardown() + /// + /// When finished, check if a time out occurred and if so assert and provide meaningful information to troubleshoot why + /// + protected override void OnFinished() { - yield return base.Teardown(); + Assert.IsFalse(TimedOut, $"{nameof(NetworkListTestPredicate)} timed out waiting for the {m_NetworkListTestState} condition to be reached! \n" + ConditionFailedInfo()); + } + + // Uses the ArrayOperator and validates that on both sides the count and values are the same + private bool OnVerifyData() + { + // Wait until both sides have the same number of elements + if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) + { + return false; + } + + // Check the client values against the server values to make sure they match + for (int i = 0; i < m_Player1OnServer.TheList.Count; i++) + { + if (m_Player1OnServer.TheList[i] != m_Player1OnClient1.TheList[i]) + { + return false; + } + } + return true; + } + + /// + /// Verifies the data count, values, and that the ListDelegate on both sides was triggered + /// + private bool OnAdd() + { + bool wasTriggerred = m_Player1OnServer.ListDelegateTriggered && m_Player1OnClient1.ListDelegateTriggered; + return wasTriggerred && OnVerifyData(); + } + + /// + /// The current version of this test only verified the count of the large list, so that is what this does + /// + private bool OnContainsLarge() + { + return m_Player1OnServer.TheLargeList.Count == m_Player1OnClient1.TheLargeList.Count; + } + + /// + /// Tests NetworkList.Contains which also verifies all values are the same on both sides + /// + private bool OnContains() + { + // Wait until both sides have the same number of elements + if (m_Player1OnServer.TheList.Count != m_Player1OnClient1.TheList.Count) + { + return false; + } + + // Parse through all server values and use the NetworkList.Contains method to check if the value is in the list on the client side + foreach (var serverValue in m_Player1OnServer.TheList) + { + if (!m_Player1OnClient1.TheList.Contains(serverValue)) + { + return false; + } + } + return true; + } + + /// + /// Tests NetworkList.IndexOf and verifies that all values are aligned on both sides + /// + private bool OnIndexOf() + { + foreach (var serverSideValue in m_Player1OnServer.TheList) + { + var indexToTest = m_Player1OnServer.TheList.IndexOf(serverSideValue); + if (indexToTest != m_Player1OnServer.TheList.IndexOf(serverSideValue)) + { + return false; + } + } + return true; + } + + public NetworkListTestPredicate(NetworkVariableTest player1OnServer, NetworkVariableTest player1OnClient1, NetworkListTestStates networkListTestState, int elementCount) + { + m_NetworkListTestState = networkListTestState; + m_Player1OnServer = player1OnServer; + m_Player1OnClient1 = player1OnClient1; + m_StateFunctions = new Dictionary> + { + { NetworkListTestStates.Add, OnAdd }, + { NetworkListTestStates.ContainsLarge, OnContainsLarge }, + { NetworkListTestStates.Contains, OnContains }, + { NetworkListTestStates.VerifyData, OnVerifyData }, + { NetworkListTestStates.IndexOf, OnIndexOf } + }; + + if (networkListTestState == NetworkListTestStates.ContainsLarge) + { + for (var i = 0; i < elementCount; ++i) + { + m_Player1OnServer.TheLargeList.Add(new FixedString128Bytes()); + } + } + else + { + for (int i = 0; i < elementCount; i++) + { + m_Player1OnServer.TheList.Add(Random.Range(0, k_MaxRandomValue)); + } + } } } } diff --git a/Tests/Runtime/NetworkVisibilityTests.cs b/Tests/Runtime/NetworkVisibilityTests.cs new file mode 100644 index 0000000..e9d65b5 --- /dev/null +++ b/Tests/Runtime/NetworkVisibilityTests.cs @@ -0,0 +1,55 @@ +using System.Collections; +using System.Linq; +using UnityEngine.TestTools; +using NUnit.Framework; +using UnityEngine; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + [TestFixture(SceneManagementState.SceneManagementEnabled)] + [TestFixture(SceneManagementState.SceneManagementDisabled)] + public class NetworkVisibilityTests : NetcodeIntegrationTest + { + public enum SceneManagementState + { + SceneManagementEnabled, + SceneManagementDisabled + } + protected override int NumberOfClients => 1; + private GameObject m_TestNetworkPrefab; + private bool m_SceneManagementEnabled; + + public NetworkVisibilityTests(SceneManagementState sceneManagementState) + { + m_SceneManagementEnabled = sceneManagementState == SceneManagementState.SceneManagementEnabled; + } + + protected override void OnServerAndClientsCreated() + { + m_TestNetworkPrefab = CreateNetworkObjectPrefab("Object"); + m_TestNetworkPrefab.AddComponent(); + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled; + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + clientNetworkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled; + } + base.OnServerAndClientsCreated(); + } + + + protected override IEnumerator OnServerAndClientsConnected() + { + SpawnObject(m_TestNetworkPrefab, m_ServerNetworkManager); + + yield return base.OnServerAndClientsConnected(); + } + + [UnityTest] + public IEnumerator HiddenObjectsTest() + { + yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType().Where((c) => c.IsSpawned).Count() == 2); + Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for the visible object count to equal 2!"); + } + } +} diff --git a/Tests/Runtime/NetworkVisibilityTests.cs.meta b/Tests/Runtime/NetworkVisibilityTests.cs.meta new file mode 100644 index 0000000..3747caf --- /dev/null +++ b/Tests/Runtime/NetworkVisibilityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45ff8252bf32540509fde11c19e0f3c2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs b/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs index d053704..443d8df 100644 --- a/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs +++ b/Tests/Runtime/Physics/NetworkRigidbody2DTest.cs @@ -3,8 +3,9 @@ using NUnit.Framework; using Unity.Netcode.Components; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; -namespace Unity.Netcode.RuntimeTests.Physics +namespace Unity.Netcode.RuntimeTests { public class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase { @@ -16,23 +17,19 @@ namespace Unity.Netcode.RuntimeTests.Physics public override bool Kinematic => true; } - public abstract class NetworkRigidbody2DTestBase : BaseMultiInstanceTest + public abstract class NetworkRigidbody2DTestBase : NetcodeIntegrationTest { - protected override int NbClients => 1; + protected override int NumberOfClients => 1; public abstract bool Kinematic { get; } - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - playerPrefab.AddComponent(); - playerPrefab.AddComponent(); - playerPrefab.AddComponent(); - playerPrefab.GetComponent().interpolation = RigidbodyInterpolation2D.Interpolate; - playerPrefab.GetComponent().isKinematic = Kinematic; - }); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation2D.Interpolate; + m_PlayerPrefab.GetComponent().isKinematic = Kinematic; } /// @@ -43,19 +40,19 @@ namespace Unity.Netcode.RuntimeTests.Physics public IEnumerator TestRigidbodyKinematicEnableDisable() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + 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 MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + 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 NetworkRigidbodyTestBase.WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); // server rigidbody has authority and should have a kinematic mode of false Assert.True(serverPlayer.GetComponent().isKinematic == Kinematic); @@ -68,15 +65,14 @@ namespace Unity.Netcode.RuntimeTests.Physics // despawn the server player, (but keep it around on the server) serverPlayer.GetComponent().Despawn(false); - yield return NetworkRigidbodyTestBase.WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); // This should equal Kinematic Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic); - yield return NetworkRigidbodyTestBase.WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5); Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. } - } } diff --git a/Tests/Runtime/Physics/NetworkRigidbodyTest.cs b/Tests/Runtime/Physics/NetworkRigidbodyTest.cs index bb42349..fb7b8e8 100644 --- a/Tests/Runtime/Physics/NetworkRigidbodyTest.cs +++ b/Tests/Runtime/Physics/NetworkRigidbodyTest.cs @@ -3,8 +3,9 @@ using NUnit.Framework; using Unity.Netcode.Components; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; -namespace Unity.Netcode.RuntimeTests.Physics +namespace Unity.Netcode.RuntimeTests { public class NetworkRigidbodyDynamicTest : NetworkRigidbodyTestBase { @@ -16,23 +17,19 @@ namespace Unity.Netcode.RuntimeTests.Physics public override bool Kinematic => true; } - public abstract class NetworkRigidbodyTestBase : BaseMultiInstanceTest + public abstract class NetworkRigidbodyTestBase : NetcodeIntegrationTest { - protected override int NbClients => 1; + protected override int NumberOfClients => 1; public abstract bool Kinematic { get; } - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - playerPrefab.AddComponent(); - playerPrefab.AddComponent(); - playerPrefab.AddComponent(); - playerPrefab.GetComponent().interpolation = RigidbodyInterpolation.Interpolate; - playerPrefab.GetComponent().isKinematic = Kinematic; - }); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.GetComponent().interpolation = RigidbodyInterpolation.Interpolate; + m_PlayerPrefab.GetComponent().isKinematic = Kinematic; } /// @@ -43,44 +40,38 @@ namespace Unity.Netcode.RuntimeTests.Physics public IEnumerator TestRigidbodyKinematicEnableDisable() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult)); + 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 MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + 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); + Assert.IsNotNull(serverPlayer, "serverPlayer is not null"); + Assert.IsNotNull(clientPlayer, "clientPlayer is not null"); - yield return WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); // server rigidbody has authority and should have a kinematic mode of false - Assert.True(serverPlayer.GetComponent().isKinematic == Kinematic); - Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverPlayer.GetComponent().interpolation); + Assert.True(serverPlayer.GetComponent().isKinematic == Kinematic, "serverPlayer kinematic"); + Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverPlayer.GetComponent().interpolation, "server equal interpolate"); // client rigidbody has no authority and should have a kinematic mode of true - Assert.True(clientPlayer.GetComponent().isKinematic); - Assert.AreEqual(RigidbodyInterpolation.None, clientPlayer.GetComponent().interpolation); + Assert.True(clientPlayer.GetComponent().isKinematic, "clientPlayer kinematic"); + Assert.AreEqual(RigidbodyInterpolation.None, clientPlayer.GetComponent().interpolation, "client equal interpolate"); // despawn the server player (but keep it around on the server) serverPlayer.GetComponent().Despawn(false); - yield return WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); - Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic); + Assert.IsTrue(serverPlayer.GetComponent().isKinematic == Kinematic, "serverPlayer second kinematic"); - yield return WaitForFrames(5); + yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 3); - Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned. - } - - public static IEnumerator WaitForFrames(int count) - { - int nextFrameNumber = Time.frameCount + count; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + Assert.IsTrue(clientPlayer == null, "clientPlayer being null"); // safety check that object is actually despawned. } } } diff --git a/Tests/Runtime/Profiling/NetworkVariableNameTests.cs b/Tests/Runtime/Profiling/NetworkVariableNameTests.cs index e9d005a..25d2e3b 100644 --- a/Tests/Runtime/Profiling/NetworkVariableNameTests.cs +++ b/Tests/Runtime/Profiling/NetworkVariableNameTests.cs @@ -1,5 +1,6 @@ using System; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { diff --git a/Tests/Runtime/RpcManyClientsTests.cs b/Tests/Runtime/RpcManyClientsTests.cs new file mode 100644 index 0000000..89dfbe9 --- /dev/null +++ b/Tests/Runtime/RpcManyClientsTests.cs @@ -0,0 +1,153 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + +namespace Unity.Netcode.RuntimeTests +{ + public class RpcManyClientsObject : NetworkBehaviour + { + public int Count = 0; + public List ReceivedFrom = new List(); + [ServerRpc(RequireOwnership = false)] + public void ResponseServerRpc(ServerRpcParams rpcParams = default) + { + ReceivedFrom.Add(rpcParams.Receive.SenderClientId); + Count++; + } + + [ClientRpc] + public void NoParamsClientRpc() + { + ResponseServerRpc(); + } + + [ClientRpc] + public void OneParamClientRpc(int value) + { + ResponseServerRpc(); + } + + [ClientRpc] + public void TwoParamsClientRpc(int value1, int value2) + { + ResponseServerRpc(); + } + + [ClientRpc] + public void WithParamsClientRpc(ClientRpcParams param) + { + ResponseServerRpc(); + } + } + + public class RpcManyClientsTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 10; + + private GameObject m_PrefabToSpawn; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = PreparePrefab(typeof(RpcManyClientsObject)); + } + + public GameObject PreparePrefab(Type type) + { + var prefabToSpawn = new GameObject(); + prefabToSpawn.AddComponent(type); + var networkObjectPrefab = prefabToSpawn.AddComponent(); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab); + m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); + foreach (var clientNetworkManager in m_ClientNetworkManagers) + { + clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = prefabToSpawn }); + } + return prefabToSpawn; + } + + [UnityTest] + public IEnumerator RpcManyClientsTest() + { + var spawnedObject = UnityEngine.Object.Instantiate(m_PrefabToSpawn); + var netSpawnedObject = spawnedObject.GetComponent(); + netSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager; + + netSpawnedObject.Spawn(); + + var messageHookList = new List(); + var serverMessageHookEntry = new MessageHookEntry(m_ServerNetworkManager); + serverMessageHookEntry.AssignMessageType(); + messageHookList.Add(serverMessageHookEntry); + foreach (var client in m_ClientNetworkManagers) + { + var clientMessageHookEntry = new MessageHookEntry(client); + clientMessageHookEntry.AssignMessageType(); + messageHookList.Add(clientMessageHookEntry); + } + var rpcMessageHooks = new MessageHooksConditional(messageHookList); + + var rpcManyClientsObject = netSpawnedObject.GetComponent(); + + rpcManyClientsObject.Count = 0; + rpcManyClientsObject.NoParamsClientRpc(); // RPC with no params + + // Check that all ServerRpcMessages were sent + yield return WaitForConditionOrTimeOut(rpcMessageHooks); + + // Now provide a small window of time to let the server receive and process all messages + yield return WaitForConditionOrTimeOut(() => TotalClients == rpcManyClientsObject.Count); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out wait for {nameof(rpcManyClientsObject.NoParamsClientRpc)}! Only {rpcManyClientsObject.Count} of {TotalClients} was received!"); + + rpcManyClientsObject.Count = 0; + rpcManyClientsObject.OneParamClientRpc(0); // RPC with one param + rpcMessageHooks.Reset(); + yield return WaitForConditionOrTimeOut(rpcMessageHooks); + + // Now provide a small window of time to let the server receive and process all messages + yield return WaitForConditionOrTimeOut(() => TotalClients == rpcManyClientsObject.Count); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out wait for {nameof(rpcManyClientsObject.OneParamClientRpc)}! Only {rpcManyClientsObject.Count} of {TotalClients} was received!"); + + var param = new ClientRpcParams(); + + rpcManyClientsObject.Count = 0; + rpcManyClientsObject.TwoParamsClientRpc(0, 0); // RPC with two params + + rpcMessageHooks.Reset(); + yield return WaitForConditionOrTimeOut(rpcMessageHooks); + // Now provide a small window of time to let the server receive and process all messages + yield return WaitForConditionOrTimeOut(() => TotalClients == rpcManyClientsObject.Count); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out wait for {nameof(rpcManyClientsObject.TwoParamsClientRpc)}! Only {rpcManyClientsObject.Count} of {TotalClients} was received!"); + + rpcManyClientsObject.ReceivedFrom.Clear(); + rpcManyClientsObject.Count = 0; + var target = new List { m_ClientNetworkManagers[1].LocalClientId, m_ClientNetworkManagers[2].LocalClientId }; + param.Send.TargetClientIds = target; + rpcManyClientsObject.WithParamsClientRpc(param); + + messageHookList.Clear(); + var targetedClientMessageHookEntry = new MessageHookEntry(m_ClientNetworkManagers[1]); + targetedClientMessageHookEntry.AssignMessageType(); + messageHookList.Add(targetedClientMessageHookEntry); + targetedClientMessageHookEntry = new MessageHookEntry(m_ClientNetworkManagers[2]); + targetedClientMessageHookEntry.AssignMessageType(); + messageHookList.Add(targetedClientMessageHookEntry); + rpcMessageHooks = new MessageHooksConditional(messageHookList); + + yield return WaitForConditionOrTimeOut(rpcMessageHooks); + + // Now provide a small window of time to let the server receive and process all messages + yield return WaitForConditionOrTimeOut(() => 2 == rpcManyClientsObject.Count); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out wait for {nameof(rpcManyClientsObject.TwoParamsClientRpc)}! Only {rpcManyClientsObject.Count} of 2 was received!"); + + // either of the 2 selected clients can reply to the server first, due to network timing + 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)); + } + } +} diff --git a/Tests/Runtime/RpcManyClientsTests.cs.meta b/Tests/Runtime/RpcManyClientsTests.cs.meta new file mode 100644 index 0000000..a5df53a --- /dev/null +++ b/Tests/Runtime/RpcManyClientsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1fc23d25ed71f46ecadbff65a20522c8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/RpcQueueTests.cs b/Tests/Runtime/RpcQueueTests.cs index ae0e7d4..5a6c659 100644 --- a/Tests/Runtime/RpcQueueTests.cs +++ b/Tests/Runtime/RpcQueueTests.cs @@ -3,6 +3,7 @@ using System.Collections; using UnityEngine; using UnityEngine.TestTools; using NUnit.Framework; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { diff --git a/Tests/Runtime/RpcTests.cs b/Tests/Runtime/RpcTests.cs index 2d6e96b..f68e512 100644 --- a/Tests/Runtime/RpcTests.cs +++ b/Tests/Runtime/RpcTests.cs @@ -1,12 +1,14 @@ using System; using System.Collections; +using System.Collections.Generic; using NUnit.Framework; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; using Debug = UnityEngine.Debug; namespace Unity.Netcode.RuntimeTests { - public class RpcTests : BaseMultiInstanceTest + public class RpcTests : NetcodeIntegrationTest { public class RpcTestNB : NetworkBehaviour { @@ -26,54 +28,47 @@ namespace Unity.Netcode.RuntimeTests } } - protected override int NbClients => 1; + protected override int NumberOfClients => 1; - [UnitySetUp] - public override IEnumerator Setup() + protected override void OnCreatePlayerPrefab() { - yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab => - { - playerPrefab.AddComponent(); - }); + m_PlayerPrefab.AddComponent(); } [UnityTest] public IEnumerator TestRpcs() { - // This is the *SERVER VERSION* of the *CLIENT PLAYER* - var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - var clientId = m_ClientNetworkManagers[0].LocalClientId; - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ServerNetworkManager, serverClientPlayerResult)); + // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component + var serverClientRpcTestNB = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); - // This is the *CLIENT VERSION* of the *CLIENT PLAYER* - var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper(); - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == clientId), m_ClientNetworkManagers[0], clientClientPlayerResult)); + // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component + var localClienRpcTestNB = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); // Setup state bool hasReceivedServerRpc = false; bool hasReceivedClientRpcRemotely = false; bool hasReceivedClientRpcLocally = false; - clientClientPlayerResult.Result.GetComponent().OnClient_Rpc += () => + localClienRpcTestNB.OnClient_Rpc += () => { Debug.Log("ClientRpc received on client object"); hasReceivedClientRpcRemotely = true; }; - clientClientPlayerResult.Result.GetComponent().OnServer_Rpc += (clientId, param) => + localClienRpcTestNB.OnServer_Rpc += (clientId, param) => { // The RPC invoked locally. (Weaver failure?) Assert.Fail("ServerRpc invoked locally. Weaver failure?"); }; - serverClientPlayerResult.Result.GetComponent().OnServer_Rpc += (clientId, param) => + serverClientRpcTestNB.OnServer_Rpc += (clientId, param) => { Debug.Log("ServerRpc received on server object"); Assert.True(param.Receive.SenderClientId == clientId); hasReceivedServerRpc = true; }; - serverClientPlayerResult.Result.GetComponent().OnClient_Rpc += () => + serverClientRpcTestNB.OnClient_Rpc += () => { // The RPC invoked locally. (Weaver failure?) Debug.Log("ClientRpc received on server object"); @@ -81,13 +76,28 @@ namespace Unity.Netcode.RuntimeTests }; // Send ServerRpc - clientClientPlayerResult.Result.GetComponent().MyServerRpc(clientId); + localClienRpcTestNB.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId); // Send ClientRpc - serverClientPlayerResult.Result.GetComponent().MyClientRpc(); + serverClientRpcTestNB.MyClientRpc(); - // Wait for RPCs to be received - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => hasReceivedServerRpc && hasReceivedClientRpcLocally && hasReceivedClientRpcRemotely)); + // Validate each NetworkManager relative MessagingSystem received each respective RPC + var messageHookList = new List(); + var serverMessageHookEntry = new MessageHookEntry(m_ServerNetworkManager); + serverMessageHookEntry.AssignMessageType(); + messageHookList.Add(serverMessageHookEntry); + foreach (var client in m_ClientNetworkManagers) + { + var clientMessageHookEntry = new MessageHookEntry(client); + clientMessageHookEntry.AssignMessageType(); + messageHookList.Add(clientMessageHookEntry); + } + var rpcMessageHooks = new MessageHooksConditional(messageHookList); + yield return WaitForConditionOrTimeOut(rpcMessageHooks); + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for messages: {rpcMessageHooks.GetHooksStillWaiting()}"); + + // Make sure RPCs propagated all the way up and were called on the relative destination class instance + yield return WaitForConditionOrTimeOut(() => hasReceivedServerRpc && hasReceivedClientRpcLocally && hasReceivedClientRpcRemotely); Assert.True(hasReceivedServerRpc, "ServerRpc was not received"); Assert.True(hasReceivedClientRpcLocally, "ClientRpc was not locally received on the server"); diff --git a/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs b/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs index bcc7119..2b0c6a2 100644 --- a/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs +++ b/Tests/Runtime/Serialization/NetworkBehaviourReferenceTests.cs @@ -3,8 +3,9 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; -namespace Unity.Netcode.RuntimeTests.Serialization +namespace Unity.Netcode.RuntimeTests { /// /// Unit tests to test: diff --git a/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs b/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs index 3cd24eb..49c4a1f 100644 --- a/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs +++ b/Tests/Runtime/Serialization/NetworkObjectReferenceTests.cs @@ -4,9 +4,10 @@ using NUnit.Framework; using Unity.Collections; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; using Object = UnityEngine.Object; -namespace Unity.Netcode.RuntimeTests.Serialization +namespace Unity.Netcode.RuntimeTests { /// /// Unit tests to test: diff --git a/Tests/Editor/StartStopTests.cs b/Tests/Runtime/StartStopTests.cs similarity index 98% rename from Tests/Editor/StartStopTests.cs rename to Tests/Runtime/StartStopTests.cs index f232795..403404d 100644 --- a/Tests/Editor/StartStopTests.cs +++ b/Tests/Runtime/StartStopTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using UnityEngine; -namespace Unity.Netcode.EditorTests +namespace Unity.Netcode.RuntimeTests { public class StartStopTests { diff --git a/Tests/Editor/StartStopTests.cs.meta b/Tests/Runtime/StartStopTests.cs.meta similarity index 100% rename from Tests/Editor/StartStopTests.cs.meta rename to Tests/Runtime/StartStopTests.cs.meta diff --git a/Tests/Runtime/StopStartRuntimeTests.cs b/Tests/Runtime/StopStartRuntimeTests.cs index aa94fd9..3326dbb 100644 --- a/Tests/Runtime/StopStartRuntimeTests.cs +++ b/Tests/Runtime/StopStartRuntimeTests.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -10,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests [UnityTest] public IEnumerator WhenShuttingDownAndRestarting_SDKRestartsSuccessfullyAndStaysRunning() { // create server and client instances - MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); + NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients); try { @@ -19,7 +20,7 @@ namespace Unity.Netcode.RuntimeTests var gameObject = new GameObject("PlayerObject"); var networkObject = gameObject.AddComponent(); networkObject.DontDestroyWithOwner = true; - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); server.NetworkConfig.PlayerPrefab = gameObject; @@ -29,13 +30,13 @@ namespace Unity.Netcode.RuntimeTests } // start server and connect clients - MultiInstanceHelpers.Start(false, server, clients); + NetcodeIntegrationTestHelpers.Start(false, server, clients); // wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); // wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server)); + yield return NetcodeIntegrationTestHelpers.WaitForClientConnectedToServer(server); // shutdown the server server.Shutdown(); @@ -66,7 +67,7 @@ namespace Unity.Netcode.RuntimeTests finally { // cleanup - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); } } } diff --git a/Tests/Runtime/Timing/NetworkTimeSystemTests.cs b/Tests/Runtime/Timing/NetworkTimeSystemTests.cs index caee733..c03de7f 100644 --- a/Tests/Runtime/Timing/NetworkTimeSystemTests.cs +++ b/Tests/Runtime/Timing/NetworkTimeSystemTests.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -41,13 +42,35 @@ namespace Unity.Netcode.RuntimeTests { var tickSystem = NetworkManager.Singleton.NetworkTickSystem; var delta = tickSystem.LocalTime.FixedDeltaTime; + var previous_localTickCalculated = 0; + var previous_serverTickCalculated = 0; while (tickSystem.LocalTime.Time < 3f) { yield return null; - Assert.AreEqual(Mathf.FloorToInt((tickSystem.LocalTime.TimeAsFloat / delta)), NetworkManager.Singleton.LocalTime.Tick); - Assert.AreEqual(Mathf.FloorToInt((tickSystem.ServerTime.TimeAsFloat / delta)), NetworkManager.Singleton.ServerTime.Tick); - Assert.True(Mathf.Approximately((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time)); + + var tickCalculated = tickSystem.LocalTime.Time / delta; + previous_localTickCalculated = (int)tickCalculated; + + // This check is needed due to double division imprecision of large numbers + if ((tickCalculated - previous_localTickCalculated) >= 0.999999999999) + { + previous_localTickCalculated++; + } + + + tickCalculated = NetworkManager.Singleton.ServerTime.Time / delta; + previous_serverTickCalculated = (int)tickCalculated; + + // This check is needed due to double division imprecision of large numbers + if ((tickCalculated - previous_serverTickCalculated) >= 0.999999999999) + { + previous_serverTickCalculated++; + } + + Assert.AreEqual(previous_localTickCalculated, NetworkManager.Singleton.LocalTime.Tick, $"Calculated local tick {previous_localTickCalculated} does not match local tick {NetworkManager.Singleton.LocalTime.Tick}!"); + Assert.AreEqual(previous_serverTickCalculated, NetworkManager.Singleton.ServerTime.Tick, $"Calculated server tick {previous_serverTickCalculated} does not match server tick {NetworkManager.Singleton.ServerTime.Tick}!"); + Assert.True(Mathf.Approximately((float)NetworkManager.Singleton.LocalTime.Time, (float)NetworkManager.Singleton.ServerTime.Time), $"Local time {(float)NetworkManager.Singleton.LocalTime.Time} is not approximately server time {(float)NetworkManager.Singleton.ServerTime.Time}!"); } } diff --git a/Tests/Runtime/Timing/TimeInitializationTest.cs b/Tests/Runtime/Timing/TimeInitializationTest.cs index d6cbfc5..be5d1e3 100644 --- a/Tests/Runtime/Timing/TimeInitializationTest.cs +++ b/Tests/Runtime/Timing/TimeInitializationTest.cs @@ -2,6 +2,7 @@ using System.Collections; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { @@ -18,14 +19,14 @@ namespace Unity.Netcode.RuntimeTests public IEnumerator TestClientTimeInitializationOnConnect([Values(0, 1f)] float serverStartDelay, [Values(0, 1f)] float clientStartDelay, [Values(true, false)] bool isHost) { // Create multiple NetworkManager instances - if (!MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients, 30)) + if (!NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients, 30)) { Debug.LogError("Failed to create instances"); Assert.Fail("Failed to create instances"); } yield return new WaitForSeconds(serverStartDelay); - MultiInstanceHelpers.Start(false, server, new NetworkManager[] { }, BaseMultiInstanceTest.SceneManagerValidationAndTestRunnerInitialization); // passing no clients on purpose to start them manually later + NetcodeIntegrationTestHelpers.Start(false, server, new NetworkManager[] { }); // passing no clients on purpose to start them manually later // 0 ticks should have passed var serverTick = server.NetworkTickSystem.ServerTime.Tick; @@ -34,9 +35,11 @@ namespace Unity.Netcode.RuntimeTests // server time should be 0 Assert.AreEqual(0, server.NetworkTickSystem.ServerTime.Time); - // wait 2 frames to ensure network tick is run - yield return null; - yield return null; + // wait until at least more than 2 server ticks have passed + // Note: Waiting for more than 2 ticks on the server is due + // to the time system applying buffering to the received time + // in NetworkTimeSystem.Sync + yield return new WaitUntil(() => server.NetworkTickSystem.ServerTime.Tick > 2); var serverTimePassed = server.NetworkTickSystem.ServerTime.Time; var expectedServerTickCount = Mathf.FloorToInt((float)(serverTimePassed * 30)); @@ -56,16 +59,13 @@ namespace Unity.Netcode.RuntimeTests var clientStartRealTime = Time.time; m_Client.StartClient(); - BaseMultiInstanceTest.SceneManagerValidationAndTestRunnerInitialization(clients[0]); + NetcodeIntegrationTestHelpers.RegisterHandlers(clients[0]); m_Client.NetworkTickSystem.Tick += NetworkTickSystemOnTick; m_ClientTickCounter = 0; - - // don't check for anything here and assume non-async connection. - // Wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); + yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients); var clientStartRealTimeDuration = Time.time - clientStartRealTime; var clientStartRealTickDuration = Mathf.FloorToInt(clientStartRealTimeDuration * 30); @@ -75,7 +75,6 @@ namespace Unity.Netcode.RuntimeTests Assert.True(m_ClientTickCounter <= clientStartRealTickDuration); - MultiInstanceHelpers.Destroy(); yield return null; } @@ -95,7 +94,7 @@ namespace Unity.Netcode.RuntimeTests [UnityTearDown] public virtual IEnumerator Teardown() { - MultiInstanceHelpers.Destroy(); + NetcodeIntegrationTestHelpers.Destroy(); yield return null; } } diff --git a/Tests/Runtime/Timing/TimeMultiInstanceTest.cs b/Tests/Runtime/Timing/TimeMultiInstanceTest.cs index ccba585..c08a16b 100644 --- a/Tests/Runtime/Timing/TimeMultiInstanceTest.cs +++ b/Tests/Runtime/Timing/TimeMultiInstanceTest.cs @@ -4,13 +4,14 @@ using System.Linq; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; namespace Unity.Netcode.RuntimeTests { /// /// Tests the times of two clients connecting to a server using the SIPTransport (returns 50ms RTT but has no latency simulation) /// - public class TimeMultiInstanceTest : BaseMultiInstanceTest + public class TimeIntegrationTest : NetcodeIntegrationTest { private const double k_AdditionalTimeTolerance = 0.3d; // magic number and in theory not needed but without this mac os test fail in Yamato because it looks like we get random framerate drops during unit test. @@ -18,12 +19,11 @@ namespace Unity.Netcode.RuntimeTests private NetworkTimeState m_Client1State; private NetworkTimeState m_Client2State; - protected override int NbClients => 2; + protected override int NumberOfClients => 2; - // we need to change frame rate which is done in startup so removing this from here and moving into the test. - public override IEnumerator Setup() + protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode() { - yield break; + return NetworkManagerInstatiationMode.DoNotCreate; } private void UpdateTimeStates(NetworkManager[] networkManagers) @@ -46,14 +46,14 @@ namespace Unity.Netcode.RuntimeTests [TestCase(10, 30u, ExpectedResult = null)] [TestCase(60, 60u, ExpectedResult = null)] [TestCase(60, 10u, ExpectedResult = null)] - public IEnumerator TestTimeMultiInstance(int targetFrameRate, uint tickRate) + public IEnumerator TestTimeIntegrationTest(int targetFrameRate, uint tickRate) { - yield return StartSomeClientsAndServerWithPlayersCustom(true, NbClients, targetFrameRate, tickRate); + yield return StartSomeClientsAndServerWithPlayersCustom(true, NumberOfClients, targetFrameRate, tickRate); double frameInterval = 1d / targetFrameRate; double tickInterval = 1d / tickRate; - var networkManagers = MultiInstanceHelpers.NetworkManagerInstances.ToArray(); + var networkManagers = NetcodeIntegrationTestHelpers.NetworkManagerInstances.ToArray(); var server = networkManagers.First(t => t.IsServer); var firstClient = networkManagers.First(t => !t.IsServer); @@ -89,13 +89,15 @@ namespace Unity.Netcode.RuntimeTests // compares the two client times, only difference should be based on buffering. m_Client1State.AssertCheckDifference(m_Client2State, 0.2 - tickInterval, (0.1 - tickInterval), tickInterval * 2 + frameInterval * 2 + k_AdditionalTimeTolerance); } + + ShutdownAndCleanUp(); } - // This is from BaseMultiInstanceTest but we need a custom version of this to modifiy the config + // This is from NetcodeIntegrationTest but we need a custom version of this to modifiy the config private IEnumerator StartSomeClientsAndServerWithPlayersCustom(bool useHost, int nbClients, int targetFrameRate, uint tickRate) { // Create multiple NetworkManager instances - if (!MultiInstanceHelpers.Create(nbClients, out NetworkManager server, out NetworkManager[] clients, targetFrameRate)) + if (!NetcodeIntegrationTestHelpers.Create(nbClients, out NetworkManager server, out NetworkManager[] clients, targetFrameRate)) { Debug.LogError("Failed to create instances"); Assert.Fail("Failed to create instances"); @@ -111,12 +113,12 @@ namespace Unity.Netcode.RuntimeTests /* * Normally we would only allow player prefabs to be set to a prefab. Not runtime created objects. * In order to prevent having a Resource folder full of a TON of prefabs that we have to maintain, - * MultiInstanceHelper has a helper function that lets you mark a runtime created object to be + * NetcodeIntegrationTestHelpers has a helper function that lets you mark a runtime created object to be * treated as a prefab by the Netcode. That's how we can get away with creating the player prefab * at runtime without it being treated as a SceneObject or causing other conflicts with the Netcode. */ // Make it a prefab - MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject); + NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(networkObject); // Set the player prefab server.NetworkConfig.PlayerPrefab = m_PlayerPrefab; @@ -130,23 +132,14 @@ namespace Unity.Netcode.RuntimeTests server.NetworkConfig.TickRate = tickRate; // Start the instances - if (!MultiInstanceHelpers.Start(useHost, server, clients)) + if (!NetcodeIntegrationTestHelpers.Start(useHost, server, clients)) { Debug.LogError("Failed to start instances"); Assert.Fail("Failed to start instances"); } - // Wait for connection on client side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients)); - - // Wait for connection on server side - yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnectedToServer(server, useHost ? nbClients + 1 : nbClients)); - } - - private IEnumerator WaitForFrames(int count) - { - int nextFrameNumber = Time.frameCount + count; - yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber); + // Wait for connection on client and server side + yield return WaitForClientsConnectedOrTimeOut(); } private readonly struct NetworkTimeState : IEquatable diff --git a/Tests/Runtime/TransformInterpolationTests.cs b/Tests/Runtime/TransformInterpolationTests.cs new file mode 100644 index 0000000..cedd2f2 --- /dev/null +++ b/Tests/Runtime/TransformInterpolationTests.cs @@ -0,0 +1,116 @@ +using System.Collections; +using Unity.Netcode.Components; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using Unity.Netcode.TestHelpers.Runtime; + + +namespace Unity.Netcode.RuntimeTests +{ + public class TransformInterpolationObject : NetworkBehaviour + { + public bool CheckPosition; + public bool IsMoving; + public bool IsFixed; + + private void Update() + { + // Check the position of the nested object on the client + if (CheckPosition) + { + if (transform.position.y < 0.0f || transform.position.y > 100.0f) + { + Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0"); + } + } + + // Move the nested object on the server + if (IsMoving) + { + var y = Time.realtimeSinceStartup; + while (y > 10.0f) + { + y -= 10.0f; + } + + // change the space between local and global every second + GetComponent().InLocalSpace = ((int)y % 2 == 0); + + transform.position = new Vector3(0.0f, y * 10, 0.0f); + } + + // On the server, make sure to keep the parent object at a fixed position + if (IsFixed) + { + transform.position = new Vector3(1000.0f, 1000.0f, 1000.0f); + } + } + } + + public class TransformInterpolationTests : NetcodeIntegrationTest + { + protected override int NumberOfClients => 1; + + private ulong m_ClientId0; + private GameObject m_PrefabToSpawn; + + private NetworkObject m_AsNetworkObject; + private NetworkObject m_SpawnedObjectOnClient; + + protected override void OnServerAndClientsCreated() + { + m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject"); + m_PrefabToSpawn.AddComponent(); + m_PrefabToSpawn.AddComponent(); + } + + private IEnumerator RefreshNetworkObjects() + { + var clientId = m_ClientNetworkManagers[0].LocalClientId; + yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(clientId) && + s_GlobalNetworkObjects[clientId].ContainsKey(m_AsNetworkObject.NetworkObjectId)); + + Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for client side {nameof(NetworkObject)} ID of {m_AsNetworkObject.NetworkObjectId}"); + m_SpawnedObjectOnClient = s_GlobalNetworkObjects[clientId][m_AsNetworkObject.NetworkObjectId]; + // make sure the objects are set with the right network manager + m_SpawnedObjectOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0]; + } + + [UnityTest] + public IEnumerator TransformInterpolationTest() + { + m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId; + + // create an object + var spawnedObject = Object.Instantiate(m_PrefabToSpawn); + var baseObject = Object.Instantiate(m_PrefabToSpawn); + baseObject.GetComponent().NetworkManagerOwner = m_ServerNetworkManager; + baseObject.GetComponent().Spawn(); + + m_AsNetworkObject = spawnedObject.GetComponent(); + m_AsNetworkObject.NetworkManagerOwner = m_ServerNetworkManager; + + m_AsNetworkObject.TrySetParent(baseObject); + + m_AsNetworkObject.Spawn(); + + yield return RefreshNetworkObjects(); + + m_AsNetworkObject.TrySetParent(baseObject); + + baseObject.GetComponent().IsFixed = true; + spawnedObject.GetComponent().IsMoving = true; + + // Give two seconds for the object to settle + yield return new WaitForSeconds(2.0f); + + m_SpawnedObjectOnClient.GetComponent().CheckPosition = true; + + // Test that interpolation works correctly for 10 seconds + // Increasing this duration gives you the opportunity to go check in the Editor how the objects are setup + // and how they move + yield return new WaitForSeconds(10.0f); + } + } +} diff --git a/Tests/Runtime/TransformInterpolationTests.cs.meta b/Tests/Runtime/TransformInterpolationTests.cs.meta new file mode 100644 index 0000000..79d5dfa --- /dev/null +++ b/Tests/Runtime/TransformInterpolationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99e1c086232df436498da7c180b59898 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Tests/Runtime/Transport.meta b/Tests/Runtime/Transport.meta index b909388..652b01e 100644 --- a/Tests/Runtime/Transport.meta +++ b/Tests/Runtime/Transport.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: d764f651f0e54e8281952933cc49be97 +guid: be6919760502fc9419376258fd62ec0e folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Tests/Editor/NetworkManagerMessageHandlerTests.cs b/Tests/Runtime/Transport/DummyTransport.cs similarity index 77% rename from Tests/Editor/NetworkManagerMessageHandlerTests.cs rename to Tests/Runtime/Transport/DummyTransport.cs index df53283..08d873f 100644 --- a/Tests/Editor/NetworkManagerMessageHandlerTests.cs +++ b/Tests/Runtime/Transport/DummyTransport.cs @@ -1,11 +1,9 @@ using System; -using Unity.Netcode.Editor; -namespace Unity.Netcode.EditorTests + +namespace Unity.Netcode.RuntimeTests { - // Should probably have one of these for more files? In the future we could use the SIPTransport? - [DontShowInTransportDropdown] - internal class DummyTransport : NetworkTransport + internal class DummyTransport : TestingNetworkTransport { public override ulong ServerClientId { get; } = 0; public override void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery) @@ -47,7 +45,7 @@ namespace Unity.Netcode.EditorTests { } - public override void Initialize() + public override void Initialize(NetworkManager networkManager = null) { } } diff --git a/Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta b/Tests/Runtime/Transport/DummyTransport.cs.meta similarity index 100% rename from Tests/Editor/NetworkManagerMessageHandlerTests.cs.meta rename to Tests/Runtime/Transport/DummyTransport.cs.meta diff --git a/Tests/Runtime/Transport/SIPTransportTests.cs b/Tests/Runtime/Transport/SIPTransportTests.cs index c438d18..38f02ea 100644 --- a/Tests/Runtime/Transport/SIPTransportTests.cs +++ b/Tests/Runtime/Transport/SIPTransportTests.cs @@ -2,8 +2,9 @@ using System; using System.Text; using NUnit.Framework; using UnityEngine; +using Unity.Netcode.TestHelpers.Runtime; -namespace Unity.Netcode.RuntimeTests.Transport +namespace Unity.Netcode.RuntimeTests { public class SIPTransportTests { diff --git a/Tests/Runtime/com.unity.netcode.runtimetests.asmdef b/Tests/Runtime/com.unity.netcode.runtimetests.asmdef index 295cdb3..3a54749 100644 --- a/Tests/Runtime/com.unity.netcode.runtimetests.asmdef +++ b/Tests/Runtime/com.unity.netcode.runtimetests.asmdef @@ -3,32 +3,39 @@ "rootNamespace": "Unity.Netcode.RuntimeTests", "references": [ "Unity.Netcode.Runtime", - "Unity.Netcode.Editor", "Unity.Netcode.Components", "Unity.Collections", - "UnityEngine.TestRunner", "Unity.Multiplayer.MetricTypes", "Unity.Multiplayer.NetStats", + "Unity.Multiplayer.Tools.MetricTypes", + "Unity.Multiplayer.Tools.NetStats", "Unity.Netcode.Adapter.UTP", - "ClientNetworkTransform" + "ClientNetworkTransform", + "Unity.Netcode.TestHelpers.Runtime" ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": true, - "precompiledReferences": [ - "nunit.framework.dll" - ], - "autoReferenced": false, - "defineConstraints": [ - "UNITY_INCLUDE_TESTS" + "optionalUnityReferences": [ + "TestAssemblies" ], "versionDefines": [ { "name": "com.unity.multiplayer.tools", "expression": "", "define": "MULTIPLAYER_TOOLS" + }, + { + "name": "Unity", + "expression": "(0,2022.2.0a5)", + "define": "UNITY_UNET_PRESENT" + }, + { + "name": "com.unity.netcode.adapter.utp", + "expression": "", + "define": "UTP_ADAPTER" + }, + { + "name": "com.unity.multiplayer.tools", + "expression": "1.0.0-pre.4", + "define": "MULTIPLAYER_TOOLS_1_0_0_PRE_4" } - ], - "noEngineReferences": false + ] } \ No newline at end of file diff --git a/package.json b/package.json index 6139d91..2b58a63 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.0.0-pre.5", + "version": "1.0.0-pre.6", "unity": "2020.3", "dependencies": { "com.unity.modules.animation": "1.0.0", @@ -11,13 +11,16 @@ "com.unity.nuget.mono-cecil": "1.10.1", "com.unity.collections": "1.1.0" }, + "upm": { + "changelog": "### Added,- NetworkAnimator now properly synchrhonizes all animation layers as well as runtime-adjusted weighting between them (#1765),- Added first set of tests for NetworkAnimator - parameter syncing, trigger set / reset, override network animator (#1735),### Changed,### Fixed,- Fixed an issue where sometimes the first client to connect to the server could see messages from the server as coming from itself. (#1683),- Fixed an issue where clients seemed to be able to send messages to ClientId 1, but these messages would actually still go to the server (id 0) instead of that client. (#1683),- Improved clarity of error messaging when a client attempts to send a message to a destination other than the server, which isn't allowed. (#1683),- Disallowed async keyword in RPCs (#1681),- Fixed an issue where Alpha release versions of Unity (version 2022.2.0a5 and later) will not compile due to the UNet Transport no longer existing (#1678),- Fixed messages larger than 64k being written with incorrectly truncated message size in header (#1686) (credit: @kaen),- Fixed overloading RPC methods causing collisions and failing on IL2CPP targets. (#1694),- Fixed spawn flow to propagate `IsSceneObject` down to children NetworkObjects, decouple implicit relationship between object spawning & `IsSceneObject` flag (#1685),- Fixed error when serializing ConnectionApprovalMessage with scene management disabled when one or more objects is hidden via the CheckObjectVisibility delegate (#1720),- Fixed CheckObjectVisibility delegate not being properly invoked for connecting clients when Scene Management is enabled. (#1680),- Fixed NetworkList to properly call INetworkSerializable's NetworkSerialize() method (#1682),- Fixed NetworkVariables containing more than 1300 bytes of data (such as large NetworkLists) no longer cause an OverflowException (the limit on data size is now whatever limit the chosen transport imposes on fragmented NetworkDelivery mechanisms) (#1725),- Fixed ServerRpcParams and ClientRpcParams must be the last parameter of an RPC in order to function properly. Added a compile-time check to ensure this is the case and trigger an error if they're placed elsewhere (#1721),- Fixed FastBufferReader being created with a length of 1 if provided an input of length 0 (#1724),- Fixed The NetworkConfig's checksum hash includes the NetworkTick so that clients with a different tickrate than the server are identified and not allowed to connect (#1728),- Fixed OwnedObjects not being properly modified when using ChangeOwnership (#1731),- Improved performance in NetworkAnimator (#1735),- Removed the \"always sync\" network animator (aka \"autosend\") parameters (#1746)" + }, "upmCi": { - "footprint": "770504b1f691c766a300b616c1d8106335265f3c" + "footprint": "86275b5331ab1e0e0a176d177bcd429a69c058af" }, "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "d1f990d97b80d49ce12fce7357cbd5f6b794ce01" + "revision": "cc3c088aad2bfb1b3da51137057df65b882cad45" }, "samples": [ {