com.unity.netcode.gameobjects@1.0.0-pre.2

# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.0-pre.2] - 2020-12-20

### Added

- Associated Known Issues for the 1.0.0-pre.1 release in the changelog

### Changed

- Updated label for `1.0.0-pre.1` changelog section

## [1.0.0-pre.1] - 2020-12-20

### Added

- Added `ClientNetworkTransform` sample to the SDK package (#1168)
- Added `Bootstrap` sample to the SDK package (#1140)
- Enhanced `NetworkSceneManager` implementation with additive scene loading capabilities (#1080, #955, #913)
  - `NetworkSceneManager.OnSceneEvent` provides improved scene event notificaitons
- Enhanced `NetworkTransform` implementation with per axis/component based and threshold based state replication (#1042, #1055, #1061, #1084, #1101)
- Added a jitter-resistent `BufferedLinearInterpolator<T>` for `NetworkTransform` (#1060)
- Implemented `NetworkPrefabHandler` that provides support for object pooling and `NetworkPrefab` overrides (#1073, #1004, #977, #905,#749, #727)
- Implemented auto `NetworkObject` transform parent synchronization at runtime over the network (#855)
- Adopted Unity C# Coding Standards in the codebase with `.editorconfig` ruleset (#666, #670)
- When a client tries to spawn a `NetworkObject` an exception is thrown to indicate unsupported behavior. (#981)
- Added a `NetworkTime` and `NetworkTickSystem` which allows for improved control over time and ticks. (#845)
- Added a `OnNetworkDespawn` function to `NetworkObject` which gets called when a `NetworkObject` gets despawned and can be overriden. (#865)
- Added `SnapshotSystem` that would allow variables and spawn/despawn messages to be sent in blocks (#805, #852, #862, #963, #1012, #1013, #1021, #1040, #1062, #1064, #1083, #1091, #1111, #1129, #1166, #1192)
  - Disabled by default for now, except spawn/despawn messages
  - Will leverage unreliable messages with eventual consistency
- `NetworkBehaviour` and `NetworkObject`'s `NetworkManager` instances can now be overriden (#762)
- Added metrics reporting for the new network profiler if the Multiplayer Tools package is present (#1104, #1089, #1096, #1086, #1072, #1058, #960, #897, #891, #878)
- `NetworkBehaviour.IsSpawned` a quick (and stable) way to determine if the associated NetworkObject is spawned (#1190)
- Added `NetworkRigidbody` and `NetworkRigidbody2D` components to support networking `Rigidbody` and `Rigidbody2D` components (#1202, #1175)
- Added `NetworkObjectReference` and `NetworkBehaviourReference` structs which allow to sending `NetworkObject/Behaviours` over RPCs/`NetworkVariable`s (#1173)
- Added `NetworkAnimator` component to support networking `Animator` component (#1281, #872)

### Changed

- Bumped minimum Unity version, renamed package as "Unity Netcode for GameObjects", replaced `MLAPI` namespace and its variants with `Unity.Netcode` namespace and per asm-def variants (#1007, #1009, #1015, #1017, #1019, #1025, #1026, #1065)
  - Minimum Unity version:
    - 2019.4 → 2020.3+
  - Package rename:
    - Display name: `MLAPI Networking Library` → `Netcode for GameObjects`
    - Name: `com.unity.multiplayer.mlapi` → `com.unity.netcode.gameobjects`
    - Updated package description
  - All `MLAPI.x` namespaces are replaced with `Unity.Netcode`
    - `MLAPI.Messaging` → `Unity.Netcode`
    - `MLAPI.Connection` → `Unity.Netcode`
    - `MLAPI.Logging` → `Unity.Netcode`
    - `MLAPI.SceneManagement` → `Unity.Netcode`
    - and other `MLAPI.x` variants to `Unity.Netcode`
  - All assembly definitions are renamed with `Unity.Netcode.x` variants
    - `Unity.Multiplayer.MLAPI.Runtime` → `Unity.Netcode.Runtime`
    - `Unity.Multiplayer.MLAPI.Editor` → `Unity.Netcode.Editor`
    - and other `Unity.Multiplayer.MLAPI.x` variants to `Unity.Netcode.x` variants
- Renamed `Prototyping` namespace and assembly definition to `Components` (#1145)
- Changed `NetworkObject.Despawn(bool destroy)` API to default to `destroy = true` for better usability (#1217)
- Scene registration in `NetworkManager` is now replaced by Build Setttings → Scenes in Build List (#1080)
- `NetworkSceneManager.SwitchScene` has been replaced by `NetworkSceneManager.LoadScene` (#955)
- `NetworkManager, NetworkConfig, and NetworkSceneManager` scene registration replaced with scenes in build list (#1080)
- `GlobalObjectIdHash` replaced `PrefabHash` and `PrefabHashGenerator` for stability and consistency (#698)
- `NetworkStart` has been renamed to `OnNetworkSpawn`. (#865)
- Network variable cleanup - eliminated shared mode, variables are server-authoritative (#1059, #1074)
- `NetworkManager` and other systems are no longer singletons/statics (#696, #705, #706, #737, #738, #739, #746, #747, #763, #765, #766, #783, #784, #785, #786, #787, #788)
- Changed `INetworkSerializable.NetworkSerialize` method signature to use `BufferSerializer<T>` instead of `NetworkSerializer` (#1187)
- Changed `CustomMessagingManager`'s methods to use `FastBufferWriter` and `FastBufferReader` instead of `Stream` (#1187)
- Reduced internal runtime allocations by removing LINQ calls and replacing managed lists/arrays with native collections (#1196)

### Removed

- Removed `NetworkNavMeshAgent` (#1150)
- Removed `NetworkDictionary`, `NetworkSet` (#1149)
- Removed `NetworkVariableSettings` (#1097)
- Removed predefined `NetworkVariable<T>` types (#1093)
    - Removed `NetworkVariableBool`, `NetworkVariableByte`, `NetworkVariableSByte`, `NetworkVariableUShort`, `NetworkVariableShort`, `NetworkVariableUInt`, `NetworkVariableInt`, `NetworkVariableULong`, `NetworkVariableLong`, `NetworkVariableFloat`, `NetworkVariableDouble`, `NetworkVariableVector2`, `NetworkVariableVector3`, `NetworkVariableVector4`, `NetworkVariableColor`, `NetworkVariableColor32`, `NetworkVariableRay`, `NetworkVariableQuaternion`
- Removed `NetworkChannel` and `MultiplexTransportAdapter` (#1133)
- Removed ILPP backend for 2019.4, minimum required version is 2020.3+ (#895)
- `NetworkManager.NetworkConfig` had the following properties removed: (#1080)
  - Scene Registrations no longer exists
  - Allow Runtime Scene Changes was no longer needed and was removed
- Removed the NetworkObject.Spawn payload parameter (#1005)
- Removed `ProfilerCounter`, the original MLAPI network profiler, and the built-in network profiler module (2020.3). A replacement can now be found in the Multiplayer Tools package. (#1048)
- Removed UNet RelayTransport and related relay functionality in UNetTransport (#1081)
- Removed `UpdateStage` parameter from `ServerRpcSendParams` and `ClientRpcSendParams` (#1187)
- Removed `NetworkBuffer`, `NetworkWriter`, `NetworkReader`, `NetworkSerializer`, `PooledNetworkBuffer`, `PooledNetworkWriter`, and `PooledNetworkReader` (#1187)
- Removed `EnableNetworkVariable` in `NetworkConfig`, it is always enabled now (#1179)
- Removed `NetworkTransform`'s FixedSendsPerSecond, AssumeSyncedSends, InterpolateServer, ExtrapolatePosition, MaxSendsToExtrapolate, Channel, EnableNonProvokedResendChecks, DistanceSendrate (#1060) (#826) (#1042, #1055, #1061, #1084, #1101)
- Removed `NetworkManager`'s `StopServer()`, `StopClient()` and `StopHost()` methods and replaced with single `NetworkManager.Shutdown()` method for all (#1108)

### Fixed

- Fixed ServerRpc ownership check to `Debug.LogError` instead of `Debug.LogWarning` (#1126)
- Fixed `NetworkObject.OwnerClientId` property changing before `NetworkBehaviour.OnGainedOwnership()` callback (#1092)
- Fixed `NetworkBehaviourILPP` to iterate over all types in an assembly (#803)
- Fixed cross-asmdef RPC ILPP by importing types into external assemblies (#678)
- Fixed `NetworkManager` shutdown when quitting the application or switching scenes (#1011)
  - Now `NetworkManager` shutdowns correctly and despawns existing `NetworkObject`s
- Fixed Only one `PlayerPrefab` can be selected on `NetworkManager` inspector UI in the editor (#676)
- Fixed connection approval not being triggered for host (#675)
- Fixed various situations where messages could be processed in an invalid order, resulting in errors (#948, #1187, #1218)
- Fixed `NetworkVariable`s being default-initialized on the client instead of being initialized with the desired value (#1266)
- Improved runtime performance and reduced GC pressure (#1187)
- Fixed #915 - clients are receiving data from objects not visible to them (#1099)
- Fixed `NetworkTransform`'s "late join" issues, `NetworkTransform` now uses `NetworkVariable`s instead of RPCs (#826)
- Throw an exception for silent failure when a client tries to get another player's `PlayerObject`, it is now only allowed on the server-side (#844)

### Known Issues

- `NetworkVariable` does not serialize `INetworkSerializable` types through their `NetworkSerialize` implementation
- `NetworkObjects` marked as `DontDestroyOnLoad` are disabled during some network scene transitions
- `NetworkTransform` interpolates from the origin when switching Local Space synchronization
- Exceptions thrown in `OnNetworkSpawn` user code for an object will prevent the callback in other objects
- Cannot send an array of `INetworkSerializable` in RPCs
- ILPP generation fails with special characters in project path

## [0.2.0] - 2021-06-03

WIP version increment to pass package validation checks. Changelog & final version number TBD.

## [0.1.1] - 2021-06-01

This is hotfix v0.1.1 for the initial experimental Unity MLAPI Package.

### Changed

- Fixed issue with the Unity Registry package version missing some fixes from the v0.1.0 release.

## [0.1.0] - 2021-03-23

This is the initial experimental Unity MLAPI Package, v0.1.0.

### Added

- Refactored a new standard for Remote Procedure Call (RPC) in MLAPI which provides increased performance, significantly reduced boilerplate code, and extensibility for future-proofed code. MLAPI RPC includes `ServerRpc` and `ClientRpc` to execute logic on the server and client-side. This provides a single performant unified RPC solution, replacing MLAPI Convenience and Performance RPC (see [here](#removed-features)).
- Added standarized serialization types, including built-in and custom serialization flows. See [RFC #2](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0002-serializable-types.md) for details.
- `INetworkSerializable` interface replaces `IBitWritable`.
- Added `NetworkSerializer`..., which is the main aggregator that implements serialization code for built-in supported types and holds `NetworkReader` and `NetworkWriter` instances internally.
- Added a Network Update Loop infrastructure that aids Netcode systems to update (such as RPC queue and transport) outside of the standard `MonoBehaviour` event cycle. See [RFC #8](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0008-network-update-loop.md) and the following details:
  - It uses Unity's [low-level Player Loop API](https://docs.unity3d.com/ScriptReference/LowLevel.PlayerLoop.html) and allows for registering `INetworkUpdateSystem`s with `NetworkUpdate` methods to be executed at specific `NetworkUpdateStage`s, which may also be before or after `MonoBehaviour`-driven game logic execution.
  - You will typically interact with `NetworkUpdateLoop` for registration and `INetworkUpdateSystem` for implementation.
  - `NetworkVariable`s are now tick-based using the `NetworkTickSystem`, tracking time through network interactions and syncs.
- Added message batching to handle consecutive RPC requests sent to the same client. `RpcBatcher` sends batches based on requests from the `RpcQueueProcessing`, by batch size threshold or immediately.
- [GitHub 494](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/494): Added a constraint to allow one `NetworkObject` per `GameObject`, set through the `DisallowMultipleComponent` attribute.
- Integrated MLAPI with the Unity Profiler for versions 2020.2 and later:
  - Added new profiler modules for MLAPI that report important network data.
  - Attached the profiler to a remote player to view network data over the wire.
- A test project is available for building and experimenting with MLAPI features. This project is available in the MLAPI GitHub [testproject folder](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/tree/release/0.1.0/testproject).
- Added a [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) new GitHub repository to accept extensions from the MLAPI community. Current extensions include moved MLAPI features for lag compensation (useful for Server Authoritative actions) and `TrackedObject`.

### Changed

- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): MLAPI now uses the Unity Package Manager for installation management.
- Added functionality and usability to `NetworkVariable`, previously called `NetworkVar`. Updates enhance options and fully replace the need for `SyncedVar`s.
- [GitHub 507](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/507): Reimplemented `NetworkAnimator`, which synchronizes animation states for networked objects.
- GitHub [444](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/444) and [455](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/455): Channels are now represented as bytes instead of strings.

For users of previous versions of MLAPI, this release renames APIs due to refactoring. All obsolete marked APIs have been removed as per [GitHub 513](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/513) and [GitHub 514](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/514).

| Previous MLAPI Versions | V 0.1.0 Name |
| -- | -- |
| `NetworkingManager` | `NetworkManager` |
| `NetworkedObject` | `NetworkObject` |
| `NetworkedBehaviour` | `NetworkBehaviour` |
| `NetworkedClient` | `NetworkClient` |
| `NetworkedPrefab` | `NetworkPrefab` |
| `NetworkedVar` | `NetworkVariable` |
| `NetworkedTransform` | `NetworkTransform` |
| `NetworkedAnimator` | `NetworkAnimator` |
| `NetworkedAnimatorEditor` | `NetworkAnimatorEditor` |
| `NetworkedNavMeshAgent` | `NetworkNavMeshAgent` |
| `SpawnManager` | `NetworkSpawnManager` |
| `BitStream` | `NetworkBuffer` |
| `BitReader` | `NetworkReader` |
| `BitWriter` | `NetworkWriter` |
| `NetEventType` | `NetworkEventType` |
| `ChannelType` | `NetworkDelivery` |
| `Channel` | `NetworkChannel` |
| `Transport` | `NetworkTransport` |
| `NetworkedDictionary` | `NetworkDictionary` |
| `NetworkedList` | `NetworkList` |
| `NetworkedSet` | `NetworkSet` |
| `MLAPIConstants` | `NetworkConstants` |
| `UnetTransport` | `UNetTransport` |

### Fixed

- [GitHub 460](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/460): Fixed an issue for RPC where the host-server was not receiving RPCs from the host-client and vice versa without the loopback flag set in `NetworkingManager`.
- Fixed an issue where data in the Profiler was incorrectly aggregated and drawn, which caused the profiler data to increment indefinitely instead of resetting each frame.
- Fixed an issue the client soft-synced causing PlayMode client-only scene transition issues, caused when running the client in the editor and the host as a release build. Users may have encountered a soft sync of `NetworkedInstanceId` issues in the `SpawnManager.ClientCollectSoftSyncSceneObjectSweep` method.
- [GitHub 458](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/458): Fixed serialization issues in `NetworkList` and `NetworkDictionary` when running in Server mode.
- [GitHub 498](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/498): Fixed numerical precision issues to prevent not a number (NaN) quaternions.
- [GitHub 438](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/438): Fixed booleans by reaching or writing bytes instead of bits.
- [GitHub 519](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/519): Fixed an issue where calling `Shutdown()` before making `NetworkManager.Singleton = null` is null on `NetworkManager.OnDestroy()`.

### Removed

With a new release of MLAPI in Unity, some features have been removed:

- SyncVars have been removed from MLAPI. Use `NetworkVariable`s in place of this functionality. <!-- MTT54 -->
- [GitHub 527](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/527): Lag compensation systems and `TrackedObject` have moved to the new [MLAPI Community Contributions](https://github.com/Unity-Technologies/mlapi-community-contributions/tree/master/com.mlapi.contrib.extensions) repo.
- [GitHub 509](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/509): Encryption has been removed from MLAPI. The `Encryption` option in `NetworkConfig` on the `NetworkingManager` is not available in this release. This change will not block game creation or running. A current replacement for this functionality is not available, and may be developed in future releases. See the following changes:
    - Removed `SecuritySendFlags` from all APIs.
    - Removed encryption, cryptography, and certificate configurations from APIs including `NetworkManager` and `NetworkConfig`.
    - Removed "hail handshake", including `NetworkManager` implementation and `NetworkConstants` entries.
    - Modified `RpcQueue` and `RpcBatcher` internals to remove encryption and authentication from reading and writing.
- Removed the previous MLAPI Profiler editor window from Unity versions 2020.2 and later.
- Removed previous MLAPI Convenience and Performance RPC APIs with the new standard RPC API. See [RFC #1](https://github.com/Unity-Technologies/com.unity.multiplayer.rfcs/blob/master/text/0001-std-rpc-api.md) for details.
- [GitHub 520](https://github.com/Unity-Technologies/com.unity.multiplayer.mlapi/pull/520): Removed the MLAPI Installer.

### Known Issues

- `NetworkNavMeshAgent` does not synchronize mesh data, Agent Size, Steering, Obstacle Avoidance, or Path Finding settings. It only synchronizes the destination and velocity, not the path to the destination.
- For `RPC`, methods with a `ClientRpc` or `ServerRpc` suffix which are not marked with [ServerRpc] or [ClientRpc] will cause a compiler error.
- For `NetworkAnimator`, Animator Overrides are not supported. Triggers do not work.
- For `NetworkVariable`, the `NetworkDictionary` `List` and `Set` must use the `reliableSequenced` channel.
- `NetworkObjects`s are supported but when spawning a prefab with nested child network objects you have to manually call spawn on them
- `NetworkTransform` have the following issues:
  - Replicated objects may have jitter.
  - The owner is always authoritative about the object's position.
  - Scale is not synchronized.
- Connection Approval is not called on the host client.
- For `NamedMessages`, always use `NetworkBuffer` as the underlying stream for sending named and unnamed messages.
- For `NetworkManager`, connection management is limited. Use `IsServer`, `IsClient`, `IsConnectedClient`, or other code to check if MLAPI connected correctly.

## [0.0.1-preview.1] - 2020-12-20

This was an internally-only-used version of the Unity MLAPI Package
This commit is contained in:
Unity Technologies
2020-12-20 00:00:00 +00:00
commit 22d877d1b2
489 changed files with 43246 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("TestProject.EditorTests")]
[assembly: InternalsVisibleTo("TestProject.RuntimeTests")]
[assembly: InternalsVisibleTo("TestProject.ToolsIntegration.RuntimeTests")]

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5751b3c3bb5621e4686249b8083be068
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,178 @@
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<NetworkObject>().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);
}
/// <summary>
/// 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
/// </summary>
private static bool VerifySceneIsValidForClientsToLoad(int sceneIndex, string sceneName, LoadSceneMode loadSceneMode)
{
// exclude test runner scene
if (sceneName.StartsWith(k_FirstPartOfTestRunnerSceneName))
{
return false;
}
return true;
}
/// <summary>
/// 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.
/// </summary>
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);
}
}
/// <summary>
/// Utility to spawn some clients and a server and set them up
/// </summary>
/// <param name="nbClients"></param>
/// <param name="updatePlayerPrefab">Update the prefab with whatever is needed before players spawn</param>
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while this multi instance test is running. Will be reset on teardown.</param>
/// <returns></returns>
public IEnumerator StartSomeClientsAndServerWithPlayers(bool useHost, int nbClients, Action<GameObject> 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<NetworkObject>().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<NetworkObject>();
/*
* 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));
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 789a3189410645aca48f11a51c823418
timeCreated: 1621620979

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 09f6601e441556642ab8217941b24e5c
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,109 @@
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Used in conjunction with the RpcQueueTest to validate from 1 byte to (n) MaximumBufferSize
/// - Sending and Receiving a continually growing buffer up to (MaximumBufferSize)
/// - Default maximum buffer size is 1MB
/// </summary>
public class BufferDataValidationComponent : NetworkBehaviour
{
/// <summary>
/// Allows the external RPCQueueTest to begin testing or stop it
/// </summary>
public bool EnableTesting;
/// <summary>
/// The maximum size of the buffer to send
/// </summary>
public int MaximumBufferSize = 1 << 15;
/// <summary>
/// The rate at which the buffer size increases until it reaches MaximumBufferSize
/// (the default starting buffer size is 1 bytes)
/// </summary>
public int BufferSizeStart = 1;
/// <summary>
/// Is checked to determine if the test exited because it failed
/// </summary>
public bool TestFailed { get; internal set; }
private bool m_WaitForValidation;
private int m_CurrentBufferSize;
private List<byte> m_SendBuffer;
private List<byte> m_PreCalculatedBufferValues;
// Start is called before the first frame update
private void Start()
{
m_WaitForValidation = false;
m_CurrentBufferSize = BufferSizeStart;
m_SendBuffer = new List<byte>(MaximumBufferSize + 1);
m_PreCalculatedBufferValues = new List<byte>(MaximumBufferSize + 1);
while (m_PreCalculatedBufferValues.Count <= MaximumBufferSize)
{
m_PreCalculatedBufferValues.Add((byte)Random.Range(0, 255));
}
}
/// <summary>
/// Returns back whether the test has completed the total number of iterations
/// </summary>
/// <returns></returns>
public bool IsTestComplete()
{
if (m_CurrentBufferSize > MaximumBufferSize || TestFailed)
{
return true;
}
return false;
}
// Update is called once per frame
private void Update()
{
if (NetworkManager.Singleton.IsListening && EnableTesting && !IsTestComplete() && !m_WaitForValidation)
{
m_SendBuffer.Clear();
//Keep the current contents of the bufffer and fill the buffer with the delta difference of the buffer's current size and new size from the m_PreCalculatedBufferValues
m_SendBuffer.AddRange(m_PreCalculatedBufferValues.GetRange(0, m_CurrentBufferSize));
//Make sure we don't do anything until we finish validating buffer
m_WaitForValidation = true;
//Send the buffer
SendBufferServerRpc(m_SendBuffer.ToArray());
}
}
/// <summary>
/// Server side RPC for testing
/// </summary>
/// <param name="parameters">server rpc parameters</param>
[ServerRpc]
private void SendBufferServerRpc(byte[] buffer)
{
TestFailed = !NetworkManagerHelper.BuffersMatch(0, buffer.Length, buffer, m_SendBuffer.ToArray());
if (!TestFailed)
{
Debug.Log($"Tested buffer size of {m_SendBuffer.Count} -- OK");
}
if (m_CurrentBufferSize == MaximumBufferSize)
{
m_CurrentBufferSize++;
}
else
{
//Increasse buffer size
m_CurrentBufferSize = m_CurrentBufferSize << 1;
}
m_WaitForValidation = false;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1bb4bf89a220a8b409a4afb1f0f4eced
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,242 @@
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// This provides coverage for all of the predefined NetworkVariable types
/// The initial goal is for generalized full coverage of NetworkVariables:
/// Covers all of the various constructor calls (i.e. various parameters or no parameters)
/// Covers the local NetworkVariable's OnValueChanged functionality (i.e. when a specific type changes do we get a notification?)
/// This was built as a NetworkBehaviour for further client-server unit testing patterns when this capability is available.
/// </summary>
internal class NetworkVariableTestComponent : NetworkBehaviour
{
private NetworkVariable<bool> m_NetworkVariableBool;
private NetworkVariable<byte> m_NetworkVariableByte;
private NetworkVariable<Color> m_NetworkVariableColor;
private NetworkVariable<Color32> m_NetworkVariableColor32;
private NetworkVariable<double> m_NetworkVariableDouble;
private NetworkVariable<float> m_NetworkVariableFloat;
private NetworkVariable<int> m_NetworkVariableInt;
private NetworkVariable<long> m_NetworkVariableLong;
private NetworkVariable<sbyte> m_NetworkVariableSByte;
private NetworkVariable<Quaternion> m_NetworkVariableQuaternion;
private NetworkVariable<short> m_NetworkVariableShort;
private NetworkVariable<Vector4> m_NetworkVariableVector4;
private NetworkVariable<Vector3> m_NetworkVariableVector3;
private NetworkVariable<Vector2> m_NetworkVariableVector2;
private NetworkVariable<Ray> m_NetworkVariableRay;
private NetworkVariable<ulong> m_NetworkVariableULong;
private NetworkVariable<uint> m_NetworkVariableUInt;
private NetworkVariable<ushort> m_NetworkVariableUShort;
public NetworkVariableHelper<bool> Bool_Var;
public NetworkVariableHelper<byte> Byte_Var;
public NetworkVariableHelper<Color> Color_Var;
public NetworkVariableHelper<Color32> Color32_Var;
public NetworkVariableHelper<double> Double_Var;
public NetworkVariableHelper<float> Float_Var;
public NetworkVariableHelper<int> Int_Var;
public NetworkVariableHelper<long> Long_Var;
public NetworkVariableHelper<sbyte> Sbyte_Var;
public NetworkVariableHelper<Quaternion> Quaternion_Var;
public NetworkVariableHelper<short> Short_Var;
public NetworkVariableHelper<Vector4> Vector4_Var;
public NetworkVariableHelper<Vector3> Vector3_Var;
public NetworkVariableHelper<Vector2> Vector2_Var;
public NetworkVariableHelper<Ray> Ray_Var;
public NetworkVariableHelper<ulong> Ulong_Var;
public NetworkVariableHelper<uint> Uint_Var;
public NetworkVariableHelper<ushort> Ushort_Var;
public bool EnableTesting;
private bool m_Initialized;
private bool m_FinishedTests;
private bool m_ChangesAppliedToNetworkVariables;
private float m_WaitForChangesTimeout;
// Start is called before the first frame update
private void InitializeTest()
{
// Generic Constructor Test Coverage
m_NetworkVariableBool = new NetworkVariable<bool>();
m_NetworkVariableByte = new NetworkVariable<byte>();
m_NetworkVariableColor = new NetworkVariable<Color>();
m_NetworkVariableColor32 = new NetworkVariable<Color32>();
m_NetworkVariableDouble = new NetworkVariable<double>();
m_NetworkVariableFloat = new NetworkVariable<float>();
m_NetworkVariableInt = new NetworkVariable<int>();
m_NetworkVariableLong = new NetworkVariable<long>();
m_NetworkVariableSByte = new NetworkVariable<sbyte>();
m_NetworkVariableQuaternion = new NetworkVariable<Quaternion>();
m_NetworkVariableShort = new NetworkVariable<short>();
m_NetworkVariableVector4 = new NetworkVariable<Vector4>();
m_NetworkVariableVector3 = new NetworkVariable<Vector3>();
m_NetworkVariableVector2 = new NetworkVariable<Vector2>();
m_NetworkVariableRay = new NetworkVariable<Ray>();
m_NetworkVariableULong = new NetworkVariable<ulong>();
m_NetworkVariableUInt = new NetworkVariable<uint>();
m_NetworkVariableUShort = new NetworkVariable<ushort>();
// NetworkVariable Value Type Constructor Test Coverage
m_NetworkVariableBool = new NetworkVariable<bool>(true);
m_NetworkVariableByte = new NetworkVariable<byte>((byte)0);
m_NetworkVariableColor = new NetworkVariable<Color>(new Color(1, 1, 1, 1));
m_NetworkVariableColor32 = new NetworkVariable<Color32>(new Color32(1, 1, 1, 1));
m_NetworkVariableDouble = new NetworkVariable<double>(1.0);
m_NetworkVariableFloat = new NetworkVariable<float>(1.0f);
m_NetworkVariableInt = new NetworkVariable<int>(1);
m_NetworkVariableLong = new NetworkVariable<long>(1);
m_NetworkVariableSByte = new NetworkVariable<sbyte>((sbyte)0);
m_NetworkVariableQuaternion = new NetworkVariable<Quaternion>(Quaternion.identity);
m_NetworkVariableShort = new NetworkVariable<short>(256);
m_NetworkVariableVector4 = new NetworkVariable<Vector4>(new Vector4(1, 1, 1, 1));
m_NetworkVariableVector3 = new NetworkVariable<Vector3>(new Vector3(1, 1, 1));
m_NetworkVariableVector2 = new NetworkVariable<Vector2>(new Vector2(1, 1));
m_NetworkVariableRay = new NetworkVariable<Ray>(new Ray());
m_NetworkVariableULong = new NetworkVariable<ulong>(1);
m_NetworkVariableUInt = new NetworkVariable<uint>(1);
m_NetworkVariableUShort = new NetworkVariable<ushort>(1);
m_NetworkVariableBool = new NetworkVariable<bool>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableByte = new NetworkVariable<byte>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableColor = new NetworkVariable<Color>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableColor32 = new NetworkVariable<Color32>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableDouble = new NetworkVariable<double>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableFloat = new NetworkVariable<float>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableInt = new NetworkVariable<int>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableLong = new NetworkVariable<long>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableSByte = new NetworkVariable<sbyte>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableQuaternion = new NetworkVariable<Quaternion>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableShort = new NetworkVariable<short>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableVector4 = new NetworkVariable<Vector4>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableVector3 = new NetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableVector2 = new NetworkVariable<Vector2>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableRay = new NetworkVariable<Ray>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableULong = new NetworkVariable<ulong>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableUInt = new NetworkVariable<uint>(NetworkVariableReadPermission.Everyone);
m_NetworkVariableUShort = new NetworkVariable<ushort>(NetworkVariableReadPermission.Everyone);
// NetworkVariable Value Type and NetworkVariableSettings Constructor Test Coverage
m_NetworkVariableBool = new NetworkVariable<bool>(NetworkVariableReadPermission.Everyone, true);
m_NetworkVariableByte = new NetworkVariable<byte>(NetworkVariableReadPermission.Everyone, 0);
m_NetworkVariableColor = new NetworkVariable<Color>(NetworkVariableReadPermission.Everyone, new Color(1, 1, 1, 1));
m_NetworkVariableColor32 = new NetworkVariable<Color32>(NetworkVariableReadPermission.Everyone, new Color32(1, 1, 1, 1));
m_NetworkVariableDouble = new NetworkVariable<double>(NetworkVariableReadPermission.Everyone, 1.0);
m_NetworkVariableFloat = new NetworkVariable<float>(NetworkVariableReadPermission.Everyone, 1.0f);
m_NetworkVariableInt = new NetworkVariable<int>(NetworkVariableReadPermission.Everyone, 1);
m_NetworkVariableLong = new NetworkVariable<long>(NetworkVariableReadPermission.Everyone, 1);
m_NetworkVariableSByte = new NetworkVariable<sbyte>(NetworkVariableReadPermission.Everyone, 0);
m_NetworkVariableQuaternion = new NetworkVariable<Quaternion>(NetworkVariableReadPermission.Everyone, Quaternion.identity);
m_NetworkVariableShort = new NetworkVariable<short>(NetworkVariableReadPermission.Everyone, 1);
m_NetworkVariableVector4 = new NetworkVariable<Vector4>(NetworkVariableReadPermission.Everyone, new Vector4(1, 1, 1, 1));
m_NetworkVariableVector3 = new NetworkVariable<Vector3>(NetworkVariableReadPermission.Everyone, new Vector3(1, 1, 1));
m_NetworkVariableVector2 = new NetworkVariable<Vector2>(NetworkVariableReadPermission.Everyone, new Vector2(1, 1));
m_NetworkVariableRay = new NetworkVariable<Ray>(NetworkVariableReadPermission.Everyone, new Ray());
m_NetworkVariableULong = new NetworkVariable<ulong>(NetworkVariableReadPermission.Everyone, 1);
m_NetworkVariableUInt = new NetworkVariable<uint>(NetworkVariableReadPermission.Everyone, 1);
m_NetworkVariableUShort = new NetworkVariable<ushort>(NetworkVariableReadPermission.Everyone, 1);
// Use this nifty class: NetworkVariableHelper
// Tracks if NetworkVariable changed invokes the OnValueChanged callback for the given instance type
Bool_Var = new NetworkVariableHelper<bool>(m_NetworkVariableBool);
Byte_Var = new NetworkVariableHelper<byte>(m_NetworkVariableByte);
Color_Var = new NetworkVariableHelper<Color>(m_NetworkVariableColor);
Color32_Var = new NetworkVariableHelper<Color32>(m_NetworkVariableColor32);
Double_Var = new NetworkVariableHelper<double>(m_NetworkVariableDouble);
Float_Var = new NetworkVariableHelper<float>(m_NetworkVariableFloat);
Int_Var = new NetworkVariableHelper<int>(m_NetworkVariableInt);
Long_Var = new NetworkVariableHelper<long>(m_NetworkVariableLong);
Sbyte_Var = new NetworkVariableHelper<sbyte>(m_NetworkVariableSByte);
Quaternion_Var = new NetworkVariableHelper<Quaternion>(m_NetworkVariableQuaternion);
Short_Var = new NetworkVariableHelper<short>(m_NetworkVariableShort);
Vector4_Var = new NetworkVariableHelper<Vector4>(m_NetworkVariableVector4);
Vector3_Var = new NetworkVariableHelper<Vector3>(m_NetworkVariableVector3);
Vector2_Var = new NetworkVariableHelper<Vector2>(m_NetworkVariableVector2);
Ray_Var = new NetworkVariableHelper<Ray>(m_NetworkVariableRay);
Ulong_Var = new NetworkVariableHelper<ulong>(m_NetworkVariableULong);
Uint_Var = new NetworkVariableHelper<uint>(m_NetworkVariableUInt);
Ushort_Var = new NetworkVariableHelper<ushort>(m_NetworkVariableUShort);
}
/// <summary>
/// Test result for all values changed the expected number of times (once per unique NetworkVariable type)
/// </summary>
public bool DidAllValuesChange()
{
if (NetworkVariableBaseHelper.VarChangedCount == NetworkVariableBaseHelper.InstanceCount)
{
return true;
}
else
{
return false;
}
}
/// <summary>
/// Returns back whether the test has completed the total number of iterations
/// </summary>
public bool IsTestComplete()
{
return m_FinishedTests;
}
// Update is called once per frame
private void Update()
{
if (EnableTesting)
{
//Added timeout functionality for near future changes to NetworkVariables and the Snapshot system
if (!m_FinishedTests && m_ChangesAppliedToNetworkVariables)
{
//We finish testing if all NetworkVariables changed their value or we timed out waiting for
//all NetworkVariables to change their value
m_FinishedTests = DidAllValuesChange() || (m_WaitForChangesTimeout < Time.realtimeSinceStartup);
}
else
{
if (NetworkManager != null && NetworkManager.IsListening)
{
if (!m_Initialized)
{
InitializeTest();
m_Initialized = true;
}
else
{
//Now change all of the values to make sure we are at least testing the local callback
m_NetworkVariableBool.Value = false;
m_NetworkVariableByte.Value = 255;
m_NetworkVariableColor.Value = new Color(100, 100, 100);
m_NetworkVariableColor32.Value = new Color32(100, 100, 100, 100);
m_NetworkVariableDouble.Value = 1000;
m_NetworkVariableFloat.Value = 1000.0f;
m_NetworkVariableInt.Value = 1000;
m_NetworkVariableLong.Value = 100000;
m_NetworkVariableSByte.Value = -127;
m_NetworkVariableQuaternion.Value = new Quaternion(100, 100, 100, 100);
m_NetworkVariableShort.Value = short.MaxValue;
m_NetworkVariableVector4.Value = new Vector4(1000, 1000, 1000, 1000);
m_NetworkVariableVector3.Value = new Vector3(1000, 1000, 1000);
m_NetworkVariableVector2.Value = new Vector2(1000, 1000);
m_NetworkVariableRay.Value = new Ray(Vector3.one, Vector3.right);
m_NetworkVariableULong.Value = ulong.MaxValue;
m_NetworkVariableUInt.Value = uint.MaxValue;
m_NetworkVariableUShort.Value = ushort.MaxValue;
//Set the timeout (i.e. how long we will wait for all NetworkVariables to have registered their changes)
m_WaitForChangesTimeout = Time.realtimeSinceStartup + 0.50f;
m_ChangesAppliedToNetworkVariables = true;
}
}
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef882d7ba8231eb45839424f54a12486
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using System;
using System.Text;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
namespace Unity.Netcode.RuntimeTests
{
public class ConnectionApprovalTests
{
private Guid m_ValidationToken;
private bool m_IsValidated;
[SetUp]
public void Setup()
{
// Create, instantiate, and host
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None));
m_ValidationToken = Guid.NewGuid();
}
[UnityTest]
public IEnumerator ConnectionApproval()
{
NetworkManagerHelper.NetworkManagerObject.ConnectionApprovalCallback += NetworkManagerObject_ConnectionApprovalCallback;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionApproval = true;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.PlayerPrefab = null;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionData = Encoding.UTF8.GetBytes(m_ValidationToken.ToString());
m_IsValidated = false;
NetworkManagerHelper.NetworkManagerObject.StartHost();
var timeOut = Time.realtimeSinceStartup + 3.0f;
var timedOut = false;
while (!m_IsValidated)
{
yield return new WaitForSeconds(0.01f);
if (timeOut < Time.realtimeSinceStartup)
{
timedOut = true;
}
}
//Make sure we didn't time out
Assert.False(timedOut);
Assert.True(m_IsValidated);
}
private void NetworkManagerObject_ConnectionApprovalCallback(byte[] connectionData, ulong clientId, NetworkManager.ConnectionApprovedDelegate callback)
{
var stringGuid = Encoding.UTF8.GetString(connectionData);
if (m_ValidationToken.ToString() == stringGuid)
{
m_IsValidated = true;
}
callback(false, null, m_IsValidated, null, null);
}
[TearDown]
public void TearDown()
{
// Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 52ef2017d72b57f418907e98e1d8b90a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
using System.Collections;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class DisconnectTests
{
[UnityTest]
public IEnumerator RemoteDisconnectPlayerObjectCleanup()
{
// create server and client instances
MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients);
// create prefab
var gameObject = new GameObject("PlayerObject");
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.DontDestroyWithOwner = true;
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);
server.NetworkConfig.PlayerPrefab = gameObject;
for (int i = 0; i < clients.Length; i++)
{
clients[i].NetworkConfig.PlayerPrefab = gameObject;
}
// start server and connect clients
MultiInstanceHelpers.Start(false, server, clients);
// wait for connection on client side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients));
// wait for connection on server side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server));
// disconnect the remote client
server.DisconnectClient(clients[0].LocalClientId);
// wait 1 frame because destroys are delayed
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// ensure the object was destroyed
Assert.False(server.SpawnManager.SpawnedObjects.Any(x => x.Value.IsPlayerObject && x.Value.OwnerClientId == clients[0].LocalClientId));
// cleanup
MultiInstanceHelpers.Destroy();
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: b05b4daca3854ff6b01a1f002d433dd6
timeCreated: 1631652586

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fb1b6e801936c7f4a9af28dbed5ea2ff
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,273 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
using Unity.Netcode.Transports.UNET;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Helper class to instantiate a NetworkManager
/// This also provides the ability to:
/// --- instantiate GameObjects with NetworkObject components that returns a Guid for accessing it later.
/// --- add NetworkBehaviour components to the instantiated GameObjects
/// --- spawn a NetworkObject using its parent GameObject's Guid
/// Call StartNetworkManager in the constructor of your runtime unit test class.
/// Call ShutdownNetworkManager in the destructor of your runtime unit test class.
///
/// Includes a useful "BuffersMatch" method that allows you to compare two buffers (returns true if they match false if not)
/// </summary>
public static class NetworkManagerHelper
{
public static NetworkManager NetworkManagerObject { get; internal set; }
public static GameObject NetworkManagerGameObject { get; internal set; }
internal static Dictionary<Guid, GameObject> InstantiatedGameObjects = new Dictionary<Guid, GameObject>();
internal static Dictionary<Guid, NetworkObject> InstantiatedNetworkObjects = new Dictionary<Guid, NetworkObject>();
internal static NetworkManagerOperatingMode CurrentNetworkManagerMode;
/// <summary>
/// This provides the ability to start NetworkManager in various modes
/// </summary>
public enum NetworkManagerOperatingMode
{
None,
Host,
Server,
Client,
}
/// <summary>
/// Called upon the RpcQueueTests being instantiated.
/// This creates an instance of the NetworkManager to be used during unit tests.
/// Currently, the best method to run unit tests is by starting in host mode as you can
/// send messages to yourself (i.e. Host-Client to Host-Server and vice versa).
/// As such, the default setting is to start in Host mode.
/// </summary>
/// <param name="managerMode">parameter to specify which mode you want to start the NetworkManager</param>
/// <param name="networkConfig">parameter to specify custom NetworkConfig settings</param>
/// <returns>true if it was instantiated or is already instantiate otherwise false means it failed to instantiate</returns>
public static bool StartNetworkManager(out NetworkManager networkManager, NetworkManagerOperatingMode managerMode = NetworkManagerOperatingMode.Host, NetworkConfig networkConfig = null)
{
// If we are changing the current manager mode and the current manager mode is not "None", then stop the NetworkManager mode
if (CurrentNetworkManagerMode != managerMode && CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
{
StopNetworkManagerMode();
}
if (NetworkManagerGameObject == null)
{
NetworkManagerGameObject = new GameObject(nameof(NetworkManager));
NetworkManagerObject = NetworkManagerGameObject.AddComponent<NetworkManager>();
if (NetworkManagerObject == null)
{
networkManager = null;
return false;
}
Debug.Log($"{nameof(NetworkManager)} Instantiated.");
var unetTransport = NetworkManagerGameObject.AddComponent<UNetTransport>();
if (networkConfig == null)
{
networkConfig = new NetworkConfig
{
EnableSceneManagement = false,
};
}
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;
// Starts the network manager in the mode specified
StartNetworkManagerMode(managerMode);
}
networkManager = NetworkManagerObject;
return true;
}
/// <summary>
/// Add a GameObject with a NetworkObject component
/// </summary>
/// <param name="nameOfGameObject">the name of the object</param>
/// <returns></returns>
public static Guid AddGameNetworkObject(string nameOfGameObject)
{
var gameObjectId = Guid.NewGuid();
// Create the player object that we will spawn as a host
var gameObject = new GameObject(nameOfGameObject);
Assert.IsNotNull(gameObject);
var networkObject = gameObject.AddComponent<NetworkObject>();
Assert.IsNotNull(networkObject);
Assert.IsFalse(InstantiatedGameObjects.ContainsKey(gameObjectId));
Assert.IsFalse(InstantiatedNetworkObjects.ContainsKey(gameObjectId));
InstantiatedGameObjects.Add(gameObjectId, gameObject);
InstantiatedNetworkObjects.Add(gameObjectId, networkObject);
return gameObjectId;
}
/// <summary>
/// Helper class to add a component to the GameObject with a NetoworkObject component
/// </summary>
/// <typeparam name="T">NetworkBehaviour component being added to the GameObject</typeparam>
/// <param name="gameObjectIdentifier">ID returned to reference the game object</param>
/// <returns></returns>
public static T AddComponentToObject<T>(Guid gameObjectIdentifier) where T : NetworkBehaviour
{
Assert.IsTrue(InstantiatedGameObjects.ContainsKey(gameObjectIdentifier));
return InstantiatedGameObjects[gameObjectIdentifier].AddComponent<T>();
}
/// <summary>
/// Spawn the NetworkObject, so Rpcs can flow
/// </summary>
/// <param name="gameObjectIdentifier">ID returned to reference the game object</param>
public static void SpawnNetworkObject(Guid gameObjectIdentifier)
{
Assert.IsTrue(InstantiatedNetworkObjects.ContainsKey(gameObjectIdentifier));
if (!InstantiatedNetworkObjects[gameObjectIdentifier].IsSpawned)
{
InstantiatedNetworkObjects[gameObjectIdentifier].Spawn();
}
}
/// <summary>
/// Starts the NetworkManager in the current mode specified by managerMode
/// </summary>
/// <param name="managerMode">the mode to start the NetworkManager as</param>
private static void StartNetworkManagerMode(NetworkManagerOperatingMode managerMode)
{
CurrentNetworkManagerMode = managerMode;
switch (CurrentNetworkManagerMode)
{
case NetworkManagerOperatingMode.Host:
{
// Starts the host
NetworkManagerObject.StartHost();
break;
}
case NetworkManagerOperatingMode.Server:
{
// Starts the server
NetworkManagerObject.StartServer();
break;
}
case NetworkManagerOperatingMode.Client:
{
// Starts the client
NetworkManagerObject.StartClient();
break;
}
}
// If we started an netcode session
if (CurrentNetworkManagerMode != NetworkManagerOperatingMode.None)
{
// With some unit tests the Singleton can still be from a previous unit test
// depending upon the order of operations that occurred.
if (NetworkManager.Singleton != NetworkManagerObject)
{
NetworkManagerObject.SetSingleton();
}
// Only log this if we started an netcode session
Debug.Log($"{CurrentNetworkManagerMode} started.");
}
}
/// <summary>
/// Stops the current mode of the NetworkManager
/// </summary>
private static void StopNetworkManagerMode()
{
NetworkManagerObject.Shutdown();
Debug.Log($"{CurrentNetworkManagerMode} stopped.");
CurrentNetworkManagerMode = NetworkManagerOperatingMode.None;
}
// This is called, even if we assert and exit early from a test
public static void ShutdownNetworkManager()
{
// clean up any game objects created with custom unit testing components
foreach (var entry in InstantiatedGameObjects)
{
UnityEngine.Object.DestroyImmediate(entry.Value);
}
InstantiatedGameObjects.Clear();
if (NetworkManagerGameObject != null)
{
Debug.Log($"{nameof(NetworkManager)} shutdown.");
StopNetworkManagerMode();
UnityEngine.Object.DestroyImmediate(NetworkManagerGameObject);
Debug.Log($"{nameof(NetworkManager)} destroyed.");
}
NetworkManagerGameObject = null;
NetworkManagerObject = null;
}
public static bool BuffersMatch(int indexOffset, long targetSize, byte[] sourceArray, byte[] originalArray)
{
long largeInt64Blocks = targetSize >> 3; // Divide by 8
int originalArrayOffset = 0;
// process by 8 byte blocks if we can
for (long i = 0; i < largeInt64Blocks; i++)
{
if (BitConverter.ToInt64(sourceArray, indexOffset) != BitConverter.ToInt64(originalArray, originalArrayOffset))
{
return false;
}
indexOffset += 8;
originalArrayOffset += 8;
}
long offset = largeInt64Blocks * 8;
long remainder = targetSize - offset;
// 4 byte block
if (remainder >= 4)
{
if (BitConverter.ToInt32(sourceArray, indexOffset) != BitConverter.ToInt32(originalArray, originalArrayOffset))
{
return false;
}
indexOffset += 4;
originalArrayOffset += 4;
offset += 4;
}
// Remainder of bytes < 4
if (targetSize - offset > 0)
{
for (long i = 0; i < (targetSize - offset); i++)
{
if (sourceArray[indexOffset + i] != originalArray[originalArrayOffset + i])
{
return false;
}
}
}
return true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c920be150fd14ad4ca1936e1a259417c
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,153 @@
using System;
using System.Collections.Generic;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Will automatically register for the NetworkVariable OnValueChanged
/// delegate handler. It then will expose that single delegate invocation
/// to anything that registers for this NetworkVariableHelper's instance's OnValueChanged event.
/// This allows us to register any NetworkVariable type as well as there are basically two "types of types":
/// IEquatable<T>
/// ValueType
/// From both we can then at least determine if the value indeed changed
/// </summary>
/// <typeparam name="T"></typeparam>
internal class NetworkVariableHelper<T> : NetworkVariableBaseHelper where T : unmanaged
{
private readonly NetworkVariable<T> m_NetworkVariable;
public delegate void OnMyValueChangedDelegateHandler(T previous, T next);
public event OnMyValueChangedDelegateHandler OnValueChanged;
/// <summary>
/// IEquatable<T> Equals Check
/// </summary>
private void CheckVariableChanged(IEquatable<T> previous, IEquatable<T> next)
{
if (!previous.Equals(next))
{
ValueChanged();
}
}
/// <summary>
/// ValueType Equals Check
/// </summary>
private void CheckVariableChanged(ValueType previous, ValueType next)
{
if (!previous.Equals(next))
{
ValueChanged();
}
}
/// <summary>
/// INetworkVariable's OnVariableChanged delegate callback
/// </summary>
/// <param name="previous"></param>
/// <param name="next"></param>
private void OnVariableChanged(T previous, T next)
{
if (previous is ValueType testValueType)
{
CheckVariableChanged(previous, next);
}
else
{
CheckVariableChanged(previous as IEquatable<T>, next as IEquatable<T>);
}
OnValueChanged?.Invoke(previous, next);
}
public NetworkVariableHelper(NetworkVariableBase networkVariable) : base(networkVariable)
{
m_NetworkVariable = networkVariable as NetworkVariable<T>;
m_NetworkVariable.OnValueChanged = OnVariableChanged;
}
}
/// <summary>
/// The BaseNetworkVariableHelper keeps track of:
/// The number of instances and associates the instance with the NetworkVariable
/// 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
/// </summary>
internal class NetworkVariableBaseHelper
{
private static Dictionary<NetworkVariableBaseHelper, NetworkVariableBase> s_Instances;
private static Dictionary<NetworkVariableBase, int> s_InstanceChangedCount;
/// <summary>
/// Returns the total number of registered INetworkVariables
/// </summary>
public static int InstanceCount
{
get
{
if (s_Instances != null)
{
return s_Instances.Count;
}
return 0;
}
}
/// <summary>
/// Returns total number of changes that occurred for all registered INetworkVariables
/// </summary>
public static int VarChangedCount
{
get
{
if (s_InstanceChangedCount != null)
{
var changeCount = 0;
foreach (var keyPair in s_InstanceChangedCount)
{
changeCount += keyPair.Value;
}
return changeCount;
}
return 0;
}
}
/// <summary>
/// Called by the child class NetworkVariableHelper when a value changed
/// </summary>
protected void ValueChanged()
{
if (s_Instances.ContainsKey(this))
{
if (s_InstanceChangedCount.ContainsKey(s_Instances[this]))
{
s_InstanceChangedCount[s_Instances[this]]++;
}
}
}
public NetworkVariableBaseHelper(NetworkVariableBase networkVariable)
{
if (s_Instances == null)
{
s_Instances = new Dictionary<NetworkVariableBaseHelper, NetworkVariableBase>();
}
if (s_InstanceChangedCount == null)
{
s_InstanceChangedCount = new Dictionary<NetworkVariableBase, int>();
}
// Register new instance and associated INetworkVariable
if (!s_Instances.ContainsKey(this))
{
s_Instances.Add(this, networkVariable);
if (!s_InstanceChangedCount.ContainsKey(networkVariable))
{
s_InstanceChangedCount.Add(networkVariable, 0);
}
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 218bb185a48c6f9449e1c74f4855a774
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,245 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class HiddenVariableTest : NetworkBehaviour
{
}
public class HiddenVariableObject : NetworkBehaviour
{
public NetworkVariable<int> MyNetworkVariable = new NetworkVariable<int>();
public NetworkList<int> MyNetworkList = new NetworkList<int>();
public static Dictionary<ulong, int> ValueOnClient = new Dictionary<ulong, int>();
public static int ExpectedSize = 0;
public static int SpawnCount = 0;
public override void OnNetworkSpawn()
{
Debug.Log($"{nameof(HiddenVariableObject)}.{nameof(OnNetworkSpawn)}() with value {MyNetworkVariable.Value}");
MyNetworkVariable.OnValueChanged += Changed;
MyNetworkList.OnListChanged += ListChanged;
SpawnCount++;
base.OnNetworkSpawn();
}
public void Changed(int before, int after)
{
Debug.Log($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}");
ValueOnClient[NetworkManager.LocalClientId] = after;
}
public void ListChanged(NetworkListEvent<int> listEvent)
{
Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
Debug.Assert(ExpectedSize == MyNetworkList.Count);
}
}
public class HiddenVariableTests : BaseMultiInstanceTest
{
protected override int NbClients => 4;
private NetworkObject m_NetSpawnedObject;
private List<NetworkObject> m_NetSpawnedObjectOnClient = new List<NetworkObject>();
private GameObject m_TestNetworkPrefab;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients,
updatePlayerPrefab: playerPrefab =>
{
var networkTransform = playerPrefab.AddComponent<HiddenVariableTest>();
m_TestNetworkPrefab = PreparePrefab();
});
}
public GameObject PreparePrefab()
{
var prefabToSpawn = new GameObject("MyTestObject");
var networkObjectPrefab = prefabToSpawn.AddComponent<NetworkObject>();
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObjectPrefab);
prefabToSpawn.AddComponent<HiddenVariableObject>();
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);
}
}
public IEnumerator WaitForSpawnCount(int targetCount)
{
var endTime = Time.realtimeSinceStartup + 1.0;
while (HiddenVariableObject.SpawnCount != targetCount &&
Time.realtimeSinceStartup < endTime)
{
yield return new WaitForSeconds(0.01f);
}
}
public void VerifyLists()
{
NetworkList<int> prev = null;
int numComparison = 0;
// for all the instances of NetworkList
foreach (var gameObject in m_NetSpawnedObjectOnClient)
{
// this skips despawned/hidden objects
if (gameObject != null)
{
// if we've seen another one before
if (prev != null)
{
var curr = gameObject.GetComponent<HiddenVariableObject>().MyNetworkList;
// check that the two lists are identical
Debug.Assert(curr.Count == prev.Count);
for (int index = 0; index < curr.Count; index++)
{
Debug.Assert(curr[index] == prev[index]);
}
numComparison++;
}
// store the list
prev = gameObject.GetComponent<HiddenVariableObject>().MyNetworkList;
}
}
Debug.Log($"{numComparison} comparisons done.");
}
public IEnumerator RefreshGameObects()
{
m_NetSpawnedObjectOnClient.Clear();
foreach (var netMan in m_ClientNetworkManagers)
{
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.NetworkObjectId == m_NetSpawnedObject.NetworkObjectId,
netMan,
serverClientPlayerResult));
m_NetSpawnedObjectOnClient.Add(serverClientPlayerResult.Result);
}
}
[UnityTest]
public IEnumerator HiddenVariableTest()
{
HiddenVariableObject.SpawnCount = 0;
HiddenVariableObject.ValueOnClient.Clear();
HiddenVariableObject.ExpectedSize = 0;
HiddenVariableObject.SpawnCount = 0;
Debug.Log("Running test");
var spawnedObject = Object.Instantiate(m_TestNetworkPrefab);
m_NetSpawnedObject = spawnedObject.GetComponent<NetworkObject>();
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);
yield return RefreshGameObects();
// === Check spawn occured
yield return WaitForSpawnCount(NbClients + 1);
Debug.Assert(HiddenVariableObject.SpawnCount == NbClients + 1);
Debug.Log("Objects spawned");
// ==== Set the NetworkVariable value to 2
HiddenVariableObject.ExpectedSize = 1;
HiddenVariableObject.SpawnCount = 0;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 2;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(2);
yield return new WaitForSeconds(1.0f);
foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 2);
}
VerifyLists();
Debug.Log("Value changed");
// ==== Hide our object to a different client
HiddenVariableObject.ExpectedSize = 2;
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
// ==== Change the NetworkVariable value
// we should get one less notification of value changing and no errors or exception
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 3;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(3);
yield return new WaitForSeconds(1.0f);
foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
if (id != otherClient.ClientId)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 3);
}
}
VerifyLists();
Debug.Log("Values changed");
// ==== Show our object again to this client
HiddenVariableObject.ExpectedSize = 3;
m_NetSpawnedObject.NetworkShow(otherClient.ClientId);
// ==== Wait for object to be spawned
yield return WaitForSpawnCount(1);
Debug.Assert(HiddenVariableObject.SpawnCount == 1);
Debug.Log("Object spawned");
// ==== We need a refresh for the newly re-spawned object
yield return RefreshGameObects();
// ==== Change the NetworkVariable value
// we should get all notifications of value changing and no errors or exception
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = 4;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(4);
yield return new WaitForSeconds(1.0f);
foreach (var id in m_ServerNetworkManager.ConnectedClientsIds)
{
Debug.Assert(HiddenVariableObject.ValueOnClient[id] == 4);
}
VerifyLists();
Debug.Log("Values changed");
// ==== Hide our object to that different client again, and then destroy it
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
yield return new WaitForSeconds(0.2f);
m_NetSpawnedObject.Despawn();
yield return new WaitForSeconds(0.2f);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0c64eb7c36fc44eadac730241b23e006
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 60efb82fa0154d6caf94fea440f167d4
timeCreated: 1627407732

View File

@@ -0,0 +1,156 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NamedMessageTests : BaseMultiInstanceTest
{
protected override int NbClients => 2;
private NetworkManager FirstClient => m_ClientNetworkManagers[0];
private NetworkManager SecondClient => m_ClientNetworkManagers[1];
[UnityTest]
public IEnumerator NamedMessageIsReceivedOnClientWithContent()
{
var messageName = Guid.NewGuid().ToString();
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(
messageName,
FirstClient.LocalClientId,
writer);
}
ulong receivedMessageSender = 0;
var receivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(
messageName,
(ulong sender, FastBufferReader reader) =>
{
receivedMessageSender = sender;
reader.ReadValueSafe(out receivedMessageContent);
});
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, receivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender);
}
[UnityTest]
public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent()
{
var messageName = Guid.NewGuid().ToString();
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(
messageName,
new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId },
writer);
}
ulong firstReceivedMessageSender = 0;
var firstReceivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(
messageName,
(ulong sender, FastBufferReader reader) =>
{
firstReceivedMessageSender = sender;
reader.ReadValueSafe(out firstReceivedMessageContent);
});
ulong secondReceivedMessageSender = 0;
var secondReceivedMessageContent = new Guid();
SecondClient.CustomMessagingManager.RegisterNamedMessageHandler(
messageName,
(ulong sender, FastBufferReader reader) =>
{
secondReceivedMessageSender = sender;
reader.ReadValueSafe(out secondReceivedMessageContent);
});
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, firstReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender);
Assert.AreEqual(messageContent, secondReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender);
}
[UnityTest]
public IEnumerator WhenSendingNamedMessageToAll_AllClientsReceiveIt()
{
var messageName = Guid.NewGuid().ToString();
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessageToAll(messageName, writer);
}
ulong firstReceivedMessageSender = 0;
var firstReceivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(
messageName,
(ulong sender, FastBufferReader reader) =>
{
firstReceivedMessageSender = sender;
reader.ReadValueSafe(out firstReceivedMessageContent);
});
ulong secondReceivedMessageSender = 0;
var secondReceivedMessageContent = new Guid();
SecondClient.CustomMessagingManager.RegisterNamedMessageHandler(
messageName,
(ulong sender, FastBufferReader reader) =>
{
secondReceivedMessageSender = sender;
reader.ReadValueSafe(out secondReceivedMessageContent);
});
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, firstReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender);
Assert.AreEqual(messageContent, secondReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender);
}
[Test]
public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrown()
{
var messageName = Guid.NewGuid().ToString();
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
Assert.Throws<ArgumentNullException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, null, writer);
});
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 50770f69eb9d3604184f918a2d0674e7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,145 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class UnnamedMessageTests : BaseMultiInstanceTest
{
protected override int NbClients => 2;
private NetworkManager FirstClient => m_ClientNetworkManagers[0];
private NetworkManager SecondClient => m_ClientNetworkManagers[1];
[UnityTest]
public IEnumerator UnnamedMessageIsReceivedOnClientWithContent()
{
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(
FirstClient.LocalClientId,
writer);
}
ulong receivedMessageSender = 0;
var receivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.OnUnnamedMessage +=
(ulong sender, FastBufferReader reader) =>
{
receivedMessageSender = sender;
reader.ReadValueSafe(out receivedMessageContent);
};
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, receivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender);
}
[UnityTest]
public IEnumerator UnnamedMessageIsReceivedOnMultipleClientsWithContent()
{
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(
new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId },
writer);
}
ulong firstReceivedMessageSender = 0;
var firstReceivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.OnUnnamedMessage +=
(ulong sender, FastBufferReader reader) =>
{
firstReceivedMessageSender = sender;
reader.ReadValueSafe(out firstReceivedMessageContent);
};
ulong secondReceivedMessageSender = 0;
var secondReceivedMessageContent = new Guid();
SecondClient.CustomMessagingManager.OnUnnamedMessage +=
(ulong sender, FastBufferReader reader) =>
{
secondReceivedMessageSender = sender;
reader.ReadValueSafe(out secondReceivedMessageContent);
};
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, firstReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender);
Assert.AreEqual(messageContent, secondReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender);
}
[UnityTest]
public IEnumerator WhenSendingUnnamedMessageToAll_AllClientsReceiveIt()
{
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessageToAll(writer);
}
ulong firstReceivedMessageSender = 0;
var firstReceivedMessageContent = new Guid();
FirstClient.CustomMessagingManager.OnUnnamedMessage +=
(ulong sender, FastBufferReader reader) =>
{
firstReceivedMessageSender = sender;
reader.ReadValueSafe(out firstReceivedMessageContent);
};
ulong secondReceivedMessageSender = 0;
var secondReceivedMessageContent = new Guid();
SecondClient.CustomMessagingManager.OnUnnamedMessage +=
(ulong sender, FastBufferReader reader) =>
{
secondReceivedMessageSender = sender;
reader.ReadValueSafe(out secondReceivedMessageContent);
};
yield return new WaitForSeconds(0.2f);
Assert.AreEqual(messageContent, firstReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, firstReceivedMessageSender);
Assert.AreEqual(messageContent, secondReceivedMessageContent);
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, secondReceivedMessageSender);
}
[Test]
public void WhenSendingNamedMessageToNullClientList_ArgumentNullExceptionIsThrown()
{
var messageContent = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.WriteValueSafe(messageContent);
Assert.Throws<ArgumentNullException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(null, writer);
});
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f5affb61a1b56d44c80d0e2d55cc04aa
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4cd7fa97c73f3674b9cce18b1e0a6874
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,275 @@
#if MULTIPLAYER_TOOLS
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Collections;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Netcode.RuntimeTests.Metrics.Utility;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Metrics
{
public class MessagingMetricsTests : DualClientMetricTestBase
{
const uint MessageNameHashSize = 8;
const uint MessageOverhead = MessageNameHashSize;
protected override int NbClients => 2;
[UnityTest]
public IEnumerator TrackNetworkMessageSentMetric()
{
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), FirstClient.LocalClientId, writer);
}
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);
}
[UnityTest]
public IEnumerator TrackNetworkMessageSentMetricToMultipleClients()
{
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId }, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var networkMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, networkMessageSentMetricValues.Count(x => x.Name.Equals(nameof(NamedMessage))));
}
[UnityTest]
public IEnumerator TrackNetworkMessageReceivedMetric()
{
var messageName = Guid.NewGuid();
LogAssert.Expect(LogType.Log, $"Received from {Server.LocalClientId}");
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(messageName.ToString(), (ulong sender, FastBufferReader payload) =>
{
Debug.Log($"Received from {sender}");
});
var waitForMetricValues = new WaitForMetricValues<NetworkMessageEvent>(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NetworkMessageReceived);
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), FirstClient.LocalClientId, writer);
}
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);
}
[UnityTest]
public IEnumerator TrackNamedMessageSentMetric()
{
var waitForMetricValues = new WaitForMetricValues<NamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), FirstClient.LocalClientId, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var namedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, namedMessageSentMetricValues.Count);
var namedMessageSent = namedMessageSentMetricValues.First();
Assert.AreEqual(messageName.ToString(), namedMessageSent.Name);
Assert.AreEqual(FirstClient.LocalClientId, namedMessageSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageSent.BytesCount);
}
[UnityTest]
public IEnumerator TrackNamedMessageSentMetricToMultipleClients()
{
var waitForMetricValues = new WaitForMetricValues<NamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId }, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var namedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, namedMessageSentMetricValues.Count);
Assert.That(namedMessageSentMetricValues.Select(x => x.Name), Has.All.EqualTo(messageName.ToString()));
Assert.That(namedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead));
}
[UnityTest]
public IEnumerator TrackNamedMessageSentMetricToSelf()
{
var waitForMetricValues = new WaitForMetricValues<NamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NamedMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), Server.LocalClientId, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
waitForMetricValues.AssertMetricValuesHaveNotBeenFound();
}
[UnityTest]
public IEnumerator TrackNamedMessageReceivedMetric()
{
var waitForMetricValues = new WaitForMetricValues<NamedMessageEvent>(FirstClientMetrics.Dispatcher, NetworkMetricTypes.NamedMessageReceived);
var messageName = Guid.NewGuid();
LogAssert.Expect(LogType.Log, $"Received from {Server.LocalClientId}");
FirstClient.CustomMessagingManager.RegisterNamedMessageHandler(messageName.ToString(), (ulong sender, FastBufferReader payload) =>
{
Debug.Log($"Received from {sender}");
});
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), FirstClient.LocalClientId, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var namedMessageReceivedValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, namedMessageReceivedValues.Count);
var namedMessageReceived = namedMessageReceivedValues.First();
Assert.AreEqual(messageName.ToString(), namedMessageReceived.Name);
Assert.AreEqual(Server.LocalClientId, namedMessageReceived.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, namedMessageReceived.BytesCount);
}
[UnityTest]
public IEnumerator TrackUnnamedMessageSentMetric()
{
var message = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(message);
Server.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
}
var waitForMetricValues = new WaitForMetricValues<UnnamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent);
yield return waitForMetricValues.WaitForMetricsReceived();
var unnamedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, unnamedMessageSentMetricValues.Count);
var unnamedMessageSent = unnamedMessageSentMetricValues.First();
Assert.AreEqual(FirstClient.LocalClientId, unnamedMessageSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageSent.BytesCount);
}
[UnityTest]
public IEnumerator TrackUnnamedMessageSentMetricToMultipleClients()
{
var message = Guid.NewGuid();
var waitForMetricValues = new WaitForMetricValues<UnnamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent);
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(message);
Server.CustomMessagingManager.SendUnnamedMessage(new List<ulong> { FirstClient.LocalClientId, SecondClient.LocalClientId }, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var unnamedMessageSentMetricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, unnamedMessageSentMetricValues.Count);
Assert.That(unnamedMessageSentMetricValues.Select(x => x.BytesCount), Has.All.EqualTo(FastBufferWriter.GetWriteSize(message)));
var clientIds = unnamedMessageSentMetricValues.Select(x => x.Connection.Id).ToList();
Assert.Contains(FirstClient.LocalClientId, clientIds);
Assert.Contains(SecondClient.LocalClientId, clientIds);
}
[UnityTest]
public IEnumerator TrackUnnamedMessageSentMetricToSelf()
{
var waitForMetricValues = new WaitForMetricValues<UnnamedMessageEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageSent);
var messageName = Guid.NewGuid();
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendUnnamedMessage(Server.LocalClientId, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
waitForMetricValues.AssertMetricValuesHaveNotBeenFound();
}
[UnityTest]
public IEnumerator TrackUnnamedMessageReceivedMetric()
{
var message = Guid.NewGuid();
var waitForMetricValues = new WaitForMetricValues<UnnamedMessageEvent>(FirstClientMetrics.Dispatcher, NetworkMetricTypes.UnnamedMessageReceived);
using (var writer = new FastBufferWriter(1300, Allocator.Temp))
{
writer.WriteValueSafe(message);
Server.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
}
yield return waitForMetricValues.WaitForMetricsReceived();
var unnamedMessageReceivedValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, unnamedMessageReceivedValues.Count);
var unnamedMessageReceived = unnamedMessageReceivedValues.First();
Assert.AreEqual(Server.LocalClientId, unnamedMessageReceived.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize(message), unnamedMessageReceived.BytesCount);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 2490ed51138306e4d92f8e9dcfc34462
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
#if MULTIPLAYER_TOOLS
using System;
using System.Collections;
using NUnit.Framework;
using Unity.Multiplayer.Tools.NetStats;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Metrics
{
public class MetricsDispatchTests
{
private int m_NbDispatches;
private NetworkManager m_NetworkManager;
[SetUp]
public void SetUp()
{
var networkManagerStarted = NetworkManagerHelper.StartNetworkManager(
out m_NetworkManager,
NetworkManagerHelper.NetworkManagerOperatingMode.Host,
new NetworkConfig
{
TickRate = 1,
});
Assert.IsTrue(networkManagerStarted);
var networkMetrics = m_NetworkManager.NetworkMetrics as NetworkMetrics;
networkMetrics.Dispatcher.RegisterObserver(new MockMetricsObserver(() => m_NbDispatches++));
}
[TearDown]
public void TearDown()
{
NetworkManagerHelper.ShutdownNetworkManager();
}
[UnityTest]
public IEnumerator VerifyNetworkMetricsDispatchesOncePerFrame()
{
var nbDispatchesBeforeFrame = m_NbDispatches;
yield return null; // Wait one frame so dispatch occurs
var nbDispatchesAfterFrame = m_NbDispatches;
Assert.AreEqual(1, nbDispatchesAfterFrame - nbDispatchesBeforeFrame);
}
private class MockMetricsObserver : IMetricObserver
{
private readonly Action m_OnObserve;
public MockMetricsObserver(Action onObserve)
{
m_OnObserve = onObserve;
}
public void Observe(MetricCollection collection)
{
m_OnObserve?.Invoke();
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4833f15c8a59407abbb8532ea64b5683
timeCreated: 1633451646

View File

@@ -0,0 +1,197 @@
#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;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class NetworkObjectMetricsTests : SingleClientMetricTestBase
{
private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn";
private NetworkObject m_NewNetworkPrefab;
protected override Action<GameObject> UpdatePlayerPrefab => _ =>
{
var gameObject = new GameObject(k_NewNetworkObjectName);
m_NewNetworkPrefab = gameObject.AddComponent<NetworkObject>();
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);
}
};
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>();
networkObject.NetworkManagerOwner = Server;
networkObject.Spawn();
return networkObject;
}
[UnityTest]
public IEnumerator TrackNetworkObjectSpawnSentMetric()
{
var waitForMetricEvent = new WaitForMetricValues<ObjectSpawnedEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent);
SpawnNetworkObject();
yield return waitForMetricEvent.WaitForMetricsReceived();
var objectSpawnedSentMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, objectSpawnedSentMetricValues.Count);
var objectSpawned = objectSpawnedSentMetricValues.Last();
Assert.AreEqual(Client.LocalClientId, objectSpawned.Connection.Id);
Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectSpawned.NetworkId.Name);
Assert.AreNotEqual(0, objectSpawned.BytesCount);
}
[UnityTest]
public IEnumerator TrackNetworkObjectSpawnReceivedMetric()
{
var waitForMetricEvent = new WaitForMetricValues<ObjectSpawnedEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedReceived);
var networkObject = SpawnNetworkObject();
yield return waitForMetricEvent.WaitForMetricsReceived();
var objectSpawnedReceivedMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, objectSpawnedReceivedMetricValues.Count);
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.AreNotEqual(0, objectSpawned.BytesCount);
}
[UnityTest]
public IEnumerator TrackNetworkObjectDestroySentMetric()
{
var networkObject = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
var waitForMetricEvent = new WaitForMetricValues<ObjectDestroyedEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent);
Server.SpawnManager.OnDespawnObject(networkObject, true);
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.
var objectDestroyed = objectDestroyedSentMetricValues.Last();
Assert.AreEqual(Client.LocalClientId, objectDestroyed.Connection.Id);
Assert.AreEqual($"{k_NewNetworkObjectName}(Clone)", objectDestroyed.NetworkId.Name);
Assert.AreNotEqual(0, objectDestroyed.BytesCount);
}
[UnityTest]
public IEnumerator TrackNetworkObjectDestroyReceivedMetric()
{
var networkObject = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
var waitForMetricEvent = new WaitForMetricValues<ObjectDestroyedEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedReceived);
Server.SpawnManager.OnDespawnObject(networkObject, true);
yield return waitForMetricEvent.WaitForMetricsReceived();
var objectDestroyedReceivedMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, objectDestroyedReceivedMetricValues.Count);
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.AreNotEqual(0, objectDestroyed.BytesCount);
}
[UnityTest]
public IEnumerator TrackMultipleNetworkObjectSpawnSentMetric()
{
var networkObject1 = SpawnNetworkObject();
var networkObject2 = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
NetworkObject.NetworkHide(new List<NetworkObject> { networkObject1, networkObject2 }, Client.LocalClientId);
yield return new WaitForSeconds(0.2f);
var waitForMetricEvent = new WaitForMetricValues<ObjectSpawnedEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectSpawnedSent);
NetworkObject.NetworkShow(new List<NetworkObject> { networkObject1, networkObject2 }, Client.LocalClientId);
yield return waitForMetricEvent.WaitForMetricsReceived();
var objectSpawnedSentMetricValues = waitForMetricEvent.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, objectSpawnedSentMetricValues.Count); // As there's a client and server, this event is emitted twice.
Assert.That(
objectSpawnedSentMetricValues,
Has.Exactly(1).Matches<ObjectSpawnedEvent>(
x => Client.LocalClientId == x.Connection.Id
&& x.NetworkId.NetworkId == networkObject1.NetworkObjectId
&& x.NetworkId.Name == networkObject1.name));
Assert.That(
objectSpawnedSentMetricValues,
Has.Exactly(1).Matches<ObjectSpawnedEvent>(
x => Client.LocalClientId == x.Connection.Id
&& x.NetworkId.NetworkId == networkObject2.NetworkObjectId
&& x.NetworkId.Name == networkObject2.name));
Assert.AreEqual(1, objectSpawnedSentMetricValues.Select(x => x.BytesCount).Distinct().Count());
Assert.That(objectSpawnedSentMetricValues.Select(x => x.BytesCount), Has.All.Not.EqualTo(0));
}
[UnityTest]
public IEnumerator TrackMultipleNetworkObjectDestroySentMetric()
{
var networkObject1 = SpawnNetworkObject();
var networkObject2 = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
var waitForMetricEvent = new WaitForMetricValues<ObjectDestroyedEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.ObjectDestroyedSent);
NetworkObject.NetworkHide(new List<NetworkObject> { networkObject1, networkObject2 }, Client.LocalClientId);
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.That(
objectDestroyedSentMetricValues,
Has.Exactly(1).Matches<ObjectDestroyedEvent>(
x => Client.LocalClientId == x.Connection.Id
&& x.NetworkId.NetworkId == networkObject1.NetworkObjectId
&& x.NetworkId.Name == networkObject1.name));
Assert.That(
objectDestroyedSentMetricValues,
Has.Exactly(1).Matches<ObjectDestroyedEvent>(
x => Client.LocalClientId == x.Connection.Id
&& x.NetworkId.NetworkId == networkObject2.NetworkObjectId
&& x.NetworkId.Name == networkObject2.name));
Assert.AreEqual(1, objectDestroyedSentMetricValues.Select(x => x.BytesCount).Distinct().Count());
Assert.That(objectDestroyedSentMetricValues.Select(x => x.BytesCount), Has.All.Not.EqualTo(0));
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c4cb56c442bed164da4908e54590dfeb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,52 @@
#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;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class NetworkVariableMetricsTests : SingleClientMetricTestBase
{
protected override Action<GameObject> UpdatePlayerPrefab => prefab => prefab.AddComponent<NetworkVariableComponent>();
[UnityTest]
public IEnumerator TrackNetworkVariableDeltaSentMetric()
{
var waitForMetricValues = new WaitForMetricValues<NetworkVariableEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaSent);
yield return waitForMetricValues.WaitForMetricsReceived();
var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
var networkVariableDeltaSent = metricValues.First();
Assert.AreEqual(nameof(NetworkVariableComponent.MyNetworkVariable), networkVariableDeltaSent.Name);
Assert.AreEqual(Server.LocalClientId, networkVariableDeltaSent.Connection.Id);
Assert.AreNotEqual(0, networkVariableDeltaSent.BytesCount);
}
[UnityTest]
public IEnumerator TrackNetworkVariableDeltaReceivedMetric()
{
var waitForMetricValues = new WaitForMetricValues<NetworkVariableEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.NetworkVariableDeltaReceived);
yield return waitForMetricValues.WaitForMetricsReceived();
var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, metricValues.Count); // We have an instance each of the player prefabs
var first = metricValues.First();
Assert.AreEqual(nameof(NetworkVariableComponent.MyNetworkVariable), first.Name);
Assert.AreNotEqual(0, first.BytesCount);
var last = metricValues.Last();
Assert.AreEqual(nameof(NetworkVariableComponent.MyNetworkVariable), last.Name);
Assert.AreNotEqual(0, last.BytesCount);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: abbea688505c90d4f82bfa5ea3ee1cd9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
#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;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class OwnershipChangeMetricsTests : SingleClientMetricTestBase
{
private const string k_NewNetworkObjectName = "TestNetworkObjectToSpawn";
private NetworkObject m_NewNetworkPrefab;
protected override Action<GameObject> UpdatePlayerPrefab => _ =>
{
var gameObject = new GameObject(k_NewNetworkObjectName);
m_NewNetworkPrefab = gameObject.AddComponent<NetworkObject>();
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);
}
};
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>();
networkObject.NetworkManagerOwner = Server;
networkObject.Spawn();
return networkObject;
}
[UnityTest]
public IEnumerator TrackOwnershipChangeSentMetric()
{
var networkObject = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
var waitForMetricValues = new WaitForMetricValues<OwnershipChangeEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeSent);
networkObject.ChangeOwnership(1);
yield return waitForMetricValues.WaitForMetricsReceived();
var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
var ownershipChangeSent = metricValues.First();
Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeSent.NetworkId.NetworkId);
Assert.AreEqual(Server.LocalClientId, ownershipChangeSent.Connection.Id);
Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>(), ownershipChangeSent.BytesCount);
}
[UnityTest]
public IEnumerator TrackOwnershipChangeReceivedMetric()
{
var networkObject = SpawnNetworkObject();
yield return new WaitForSeconds(0.2f);
var waitForMetricValues = new WaitForMetricValues<OwnershipChangeEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.OwnershipChangeReceived);
networkObject.ChangeOwnership(1);
yield return waitForMetricValues.WaitForMetricsReceived();
var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, metricValues.Count);
var ownershipChangeReceived = metricValues.First();
Assert.AreEqual(networkObject.NetworkObjectId, ownershipChangeReceived.NetworkId.NetworkId);
Assert.AreEqual(FastBufferWriter.GetWriteSize<ChangeOwnershipMessage>(), ownershipChangeReceived.BytesCount);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 86daf1dbe91c9ad40818743a805d0052
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,106 @@
#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;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class RpcMetricsTests : SingleClientMetricTestBase
{
protected override Action<GameObject> UpdatePlayerPrefab => prefab => prefab.AddComponent<RpcTestComponent>();
[UnityTest]
public IEnumerator TrackRpcSentMetricOnServer()
{
var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Server, clientPlayer));
var waitForMetricValues = new WaitForMetricValues<RpcEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcSent);
clientPlayer.Result.GetComponent<RpcTestComponent>().MyClientRpc();
yield return waitForMetricValues.WaitForMetricsReceived();
var serverRpcSentValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(2, serverRpcSentValues.Count); // Server will receive this, since it's host
Assert.That(serverRpcSentValues, Has.All.Matches<RpcEvent>(x => x.Name == nameof(RpcTestComponent.MyClientRpc)));
Assert.That(serverRpcSentValues, Has.All.Matches<RpcEvent>(x => x.NetworkBehaviourName == nameof(RpcTestComponent)));
Assert.That(serverRpcSentValues, Has.All.Matches<RpcEvent>(x => x.BytesCount != 0));
Assert.Contains(Server.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray());
Assert.Contains(Client.LocalClientId, serverRpcSentValues.Select(x => x.Connection.Id).ToArray());
}
[UnityTest]
public IEnumerator TrackRpcSentMetricOnClient()
{
var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Client, clientPlayer));
var waitForClientMetricsValues = new WaitForMetricValues<RpcEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.RpcSent);
clientPlayer.Result.GetComponent<RpcTestComponent>().MyServerRpc();
yield return waitForClientMetricsValues.WaitForMetricsReceived();
var clientRpcSentValues = waitForClientMetricsValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, clientRpcSentValues.Count);
var rpcSent = clientRpcSentValues.First();
Assert.AreEqual(Server.LocalClientId, rpcSent.Connection.Id);
Assert.AreEqual(nameof(RpcTestComponent.MyServerRpc), rpcSent.Name);
Assert.AreEqual(nameof(RpcTestComponent), rpcSent.NetworkBehaviourName);
Assert.AreNotEqual(0, rpcSent.BytesCount);
}
[UnityTest]
public IEnumerator TrackRpcReceivedMetricOnServer()
{
var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Client, clientPlayer));
var waitForServerMetricsValues = new WaitForMetricValues<RpcEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived);
clientPlayer.Result.GetComponent<RpcTestComponent>().MyServerRpc();
yield return waitForServerMetricsValues.WaitForMetricsReceived();
var serverRpcReceivedValues = waitForServerMetricsValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, serverRpcReceivedValues.Count);
var rpcReceived = serverRpcReceivedValues.First();
Assert.AreEqual(Client.LocalClientId, rpcReceived.Connection.Id);
Assert.AreEqual(nameof(RpcTestComponent.MyServerRpc), rpcReceived.Name);
Assert.AreEqual(nameof(RpcTestComponent), rpcReceived.NetworkBehaviourName);
Assert.AreNotEqual(0, rpcReceived.BytesCount);
}
[UnityTest]
public IEnumerator TrackRpcReceivedMetricOnClient()
{
var clientPlayer = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == Client.LocalClientId, Server, clientPlayer));
var waitForServerMetricsValues = new WaitForMetricValues<RpcEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.RpcReceived);
clientPlayer.Result.GetComponent<RpcTestComponent>().MyClientRpc();
yield return waitForServerMetricsValues.WaitForMetricsReceived();
var clientRpcReceivedValues = waitForServerMetricsValues.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, clientRpcReceivedValues.Count);
var rpcReceived = clientRpcReceivedValues.First();
Assert.AreEqual(Server.LocalClientId, rpcReceived.Connection.Id);
Assert.AreEqual(nameof(RpcTestComponent.MyClientRpc), rpcReceived.Name);
Assert.AreEqual(nameof(RpcTestComponent), rpcReceived.NetworkBehaviourName);
Assert.AreNotEqual(0, rpcReceived.BytesCount);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 02a31cf8be00f8b46b65477d648b297d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,53 @@
#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.TestTools;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class ServerLogsMetricTests : SingleClientMetricTestBase
{
[UnityTest]
public IEnumerator TrackServerLogSentMetric()
{
var waitForSentMetric = new WaitForMetricValues<ServerLogEvent>(ClientMetrics.Dispatcher, NetworkMetricTypes.ServerLogSent);
var message = Guid.NewGuid().ToString();
NetworkLog.LogWarningServer(message);
yield return waitForSentMetric.WaitForMetricsReceived();
var sentMetrics = waitForSentMetric.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, sentMetrics.Count);
var sentMetric = sentMetrics.First();
Assert.AreEqual(Server.LocalClientId, sentMetric.Connection.Id);
Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)sentMetric.LogLevel);
Assert.AreEqual(message.Length + 2, sentMetric.BytesCount);
}
[UnityTest]
public IEnumerator TrackServerLogReceivedMetric()
{
var waitForReceivedMetric = new WaitForMetricValues<ServerLogEvent>(ServerMetrics.Dispatcher, NetworkMetricTypes.ServerLogReceived);
var message = Guid.NewGuid().ToString();
NetworkLog.LogWarningServer(message);
yield return waitForReceivedMetric.WaitForMetricsReceived();
var receivedMetrics = waitForReceivedMetric.AssertMetricValuesHaveBeenFound();
Assert.AreEqual(1, receivedMetrics.Count);
var receivedMetric = receivedMetrics.First();
Assert.AreEqual(Client.LocalClientId, receivedMetric.Connection.Id);
Assert.AreEqual((uint)NetworkLog.LogType.Warning, (uint)receivedMetric.LogLevel);
Assert.AreEqual(message.Length + 2, receivedMetric.BytesCount);
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 5ba082aac78de5d488f621a0cbaf16a6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,102 @@
#if MULTIPLAYER_TOOLS
using System;
using System.Collections;
using System.IO;
using NUnit.Framework;
using Unity.Collections;
using Unity.Multiplayer.Tools.MetricTypes;
using Unity.Multiplayer.Tools.NetStats;
using Unity.Netcode.RuntimeTests.Metrics.Utility;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Metrics
{
internal class TransportBytesMetricsTests : SingleClientMetricTestBase
{
static readonly long MessageOverhead = 8 + FastBufferWriter.GetWriteSize<BatchHeader>() + FastBufferWriter.GetWriteSize<MessageHeader>();
[UnityTest]
public IEnumerator TrackTotalNumberOfBytesSent()
{
var messageName = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
var observer = new TotalBytesObserver(ClientMetrics.Dispatcher, NetworkMetricTypes.TotalBytesReceived);
try
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), Client.LocalClientId, writer);
}
finally
{
writer.Dispose();
}
var nbFrames = 0;
while (!observer.Found || nbFrames < 10)
{
yield return null;
nbFrames++;
}
Assert.True(observer.Found);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, observer.Value);
}
[UnityTest]
public IEnumerator TrackTotalNumberOfBytesReceived()
{
var messageName = Guid.NewGuid();
var writer = new FastBufferWriter(1300, Allocator.Temp);
var observer = new TotalBytesObserver(ClientMetrics.Dispatcher, NetworkMetricTypes.TotalBytesReceived);
try
{
writer.WriteValueSafe(messageName);
Server.CustomMessagingManager.SendNamedMessage(messageName.ToString(), Client.LocalClientId, writer);
}
finally
{
writer.Dispose();
}
var nbFrames = 0;
while (!observer.Found || nbFrames < 10)
{
yield return null;
nbFrames++;
}
Assert.True(observer.Found);
Assert.AreEqual(FastBufferWriter.GetWriteSize(messageName) + MessageOverhead, observer.Value);
}
private class TotalBytesObserver : IMetricObserver
{
private readonly DirectionalMetricInfo m_MetricInfo;
public TotalBytesObserver(IMetricDispatcher dispatcher, DirectionalMetricInfo metricInfo)
{
m_MetricInfo = metricInfo;
dispatcher.RegisterObserver(this);
}
public bool Found { get; private set; }
public long Value { get; private set; }
public void Observe(MetricCollection collection)
{
if (collection.TryGetCounter(m_MetricInfo.Id, out var counter) && counter.Value > 0)
{
Found = true;
Value = counter.Value;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a8be98b50d230114f853054c479341ee
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4e60372130aba464f9f9ae4a24bb9fe0
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,71 @@
#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
{
internal abstract class SingleClientMetricTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 1;
protected virtual Action<GameObject> UpdatePlayerPrefab => _ => { };
internal NetworkManager Server { get; private set; }
internal NetworkMetrics ServerMetrics { get; private set; }
internal NetworkManager Client { get; private set; }
internal NetworkMetrics ClientMetrics { get; private set; }
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab);
Server = m_ServerNetworkManager;
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
Client = m_ClientNetworkManagers[0];
ClientMetrics = Client.NetworkMetrics as NetworkMetrics;
}
}
public abstract class DualClientMetricTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 2;
protected virtual Action<GameObject> UpdatePlayerPrefab => _ => { };
internal NetworkManager Server { get; private set; }
internal NetworkMetrics ServerMetrics { get; private set; }
internal NetworkManager FirstClient { get; private set; }
internal NetworkMetrics FirstClientMetrics { get; private set; }
internal NetworkManager SecondClient { get; private set; }
internal NetworkMetrics SecondClientMetrics { get; private set; }
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, UpdatePlayerPrefab);
Server = m_ServerNetworkManager;
ServerMetrics = Server.NetworkMetrics as NetworkMetrics;
FirstClient = m_ClientNetworkManagers[0];
FirstClientMetrics = FirstClient.NetworkMetrics as NetworkMetrics;
SecondClient = m_ClientNetworkManagers[0];
SecondClientMetrics = SecondClient.NetworkMetrics as NetworkMetrics;
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c726f5bc421c3874d9c1a26bcac3f091
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,19 @@
#if MULTIPLAYER_TOOLS
using UnityEngine;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
{
public class NetworkVariableComponent : NetworkBehaviour
{
public NetworkVariable<int> MyNetworkVariable { get; } = new NetworkVariable<int>();
private void Update()
{
if (IsServer)
{
MyNetworkVariable.Value = Random.Range(100, 999);
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 124489f89ef59d449ab4bed1f5ef2f59
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,22 @@
using System;
namespace Unity.Netcode.RuntimeTests.Metrics.Utility
{
public class RpcTestComponent : NetworkBehaviour
{
public event Action OnServerRpcAction;
public event Action OnClientRpcAction;
[ServerRpc]
public void MyServerRpc()
{
OnServerRpcAction?.Invoke();
}
[ClientRpc]
public void MyClientRpc()
{
OnClientRpcAction?.Invoke();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: fdfa28da9866545428083671c445a9ef
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,118 @@
#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
{
internal class WaitForMetricValues<TMetric> : IMetricObserver
{
readonly string m_MetricName;
bool m_Found;
bool m_HasError;
string m_Error;
uint m_NbFrames = 0;
IReadOnlyCollection<TMetric> m_Values;
public delegate bool Filter(TMetric metric);
Filter m_FilterDelegate;
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<TMetric> 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;
}
public void AssertMetricValuesHaveNotBeenFound()
{
if (m_HasError)
{
Assert.Fail(m_Error);
}
if (!m_Found)
{
Assert.Pass();
}
else
{
Assert.Fail();
}
}
public void Observe(MetricCollection collection)
{
if (m_Found || m_HasError)
{
return;
}
var 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;
}
var typedMetric = metric as IEventMetric<TMetric>;
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;
}
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;
}
}
private IEnumerator WaitForFrames(uint maxNbFrames)
{
while (!m_Found && m_NbFrames < maxNbFrames)
{
m_NbFrames++;
yield return null;
}
}
}
}
#endif

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 176888f06e2c5e14db33783fd0299668
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,505 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.SceneManagement;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Provides helpers for running multi instance tests.
/// </summary>
public static class MultiInstanceHelpers
{
public const int DefaultMinFrames = 1;
public const int DefaultMaxFrames = 64;
private static List<NetworkManager> s_NetworkManagerInstances = new List<NetworkManager>();
private static bool s_IsStarted;
private static int s_ClientCount;
private static int s_OriginalTargetFrameRate = -1;
public static List<NetworkManager> NetworkManagerInstances => s_NetworkManagerInstances;
/// <summary>
/// Creates NetworkingManagers and configures them for use in a multi instance setting.
/// </summary>
/// <param name="clientCount">The amount of clients</param>
/// <param name="server">The server NetworkManager</param>
/// <param name="clients">The clients NetworkManagers</param>
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown.</param>
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60)
{
s_NetworkManagerInstances = new List<NetworkManager>();
CreateNewClients(clientCount, out clients);
// Create gameObject
var go = new GameObject("NetworkManager - Server");
// Create networkManager component
server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);
// Set the NetworkConfig
server.NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = go.AddComponent<SIPTransport>()
};
s_OriginalTargetFrameRate = Application.targetFrameRate;
Application.targetFrameRate = targetFrameRate;
return true;
}
/// <summary>
/// Used to add a client to the already existing list of clients
/// </summary>
/// <param name="clientCount">The amount of clients</param>
/// <param name="clients"></param>
/// <returns></returns>
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
{
clients = new NetworkManager[clientCount];
var activeSceneName = SceneManager.GetActiveScene().name;
for (int i = 0; i < clientCount; i++)
{
// Create gameObject
var go = new GameObject("NetworkManager - Client - " + i);
// Create networkManager component
clients[i] = go.AddComponent<NetworkManager>();
// Set the NetworkConfig
clients[i].NetworkConfig = new NetworkConfig()
{
// Set transport
NetworkTransport = go.AddComponent<SIPTransport>()
};
}
NetworkManagerInstances.AddRange(clients);
return true;
}
/// <summary>
/// Stops one single client and makes sure to cleanup any static variables in this helper
/// </summary>
/// <param name="clientToStop"></param>
public static void StopOneClient(NetworkManager clientToStop)
{
clientToStop.Shutdown();
Object.Destroy(clientToStop.gameObject);
NetworkManagerInstances.Remove(clientToStop);
}
/// <summary>
/// Should always be invoked when finished with a single unit test
/// (i.e. during TearDown)
/// </summary>
public static void Destroy()
{
if (s_IsStarted == false)
{
return;
}
s_IsStarted = false;
// Shutdown the server which forces clients to disconnect
foreach (var networkManager in NetworkManagerInstances)
{
networkManager.Shutdown();
}
// Destroy the network manager instances
foreach (var networkManager in NetworkManagerInstances)
{
Object.DestroyImmediate(networkManager.gameObject);
}
NetworkManagerInstances.Clear();
// Destroy the temporary GameObject used to run co-routines
if (s_CoroutineRunner != null)
{
s_CoroutineRunner.StopAllCoroutines();
Object.DestroyImmediate(s_CoroutineRunner);
}
Application.targetFrameRate = s_OriginalTargetFrameRate;
}
/// <summary>
/// Starts NetworkManager instances created by the Create method.
/// </summary>
/// <param name="host">Whether or not to create a Host instead of Server</param>
/// <param name="server">The Server NetworkManager</param>
/// <param name="clients">The Clients NetworkManager</param>
/// <param name="startInitializationCallback">called immediately after server and client(s) are started</param>
/// <returns></returns>
public static bool Start(bool host, NetworkManager server, NetworkManager[] clients, Action<NetworkManager> startInitializationCallback = null)
{
if (s_IsStarted)
{
throw new InvalidOperationException("MultiInstanceHelper already started. Did you forget to Destroy?");
}
s_IsStarted = true;
s_ClientCount = clients.Length;
if (host)
{
server.StartHost();
}
else
{
server.StartServer();
}
// if set, then invoke this for the server
startInitializationCallback?.Invoke(server);
for (int i = 0; i < clients.Length; i++)
{
clients[i].StartClient();
// if set, then invoke this for the client
startInitializationCallback?.Invoke(clients[i]);
}
return true;
}
// Empty MonoBehaviour that is a holder of coroutine
private class CoroutineRunner : MonoBehaviour
{
}
private static CoroutineRunner s_CoroutineRunner;
/// <summary>
/// Runs a IEnumerator as a Coroutine on a dummy GameObject. Used to get exceptions coming from the coroutine
/// </summary>
/// <param name="enumerator">The IEnumerator to run</param>
public static Coroutine Run(IEnumerator enumerator)
{
if (s_CoroutineRunner == null)
{
s_CoroutineRunner = new GameObject(nameof(CoroutineRunner)).AddComponent<CoroutineRunner>();
}
return s_CoroutineRunner.StartCoroutine(enumerator);
}
public class CoroutineResultWrapper<T>
{
public T Result;
}
private static uint s_AutoIncrementGlobalObjectIdHashCounter = 111111;
/// <summary>
/// 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.
/// </summary>
/// <param name="networkObject">The networkObject to be treated as Prefab</param>
/// <param name="globalObjectIdHash">The GlobalObjectId to force</param>
public static void MakeNetworkObjectTestPrefab(NetworkObject networkObject, uint globalObjectIdHash = default)
{
// Override `GlobalObjectIdHash` if `globalObjectIdHash` param is set
if (globalObjectIdHash != default)
{
networkObject.GlobalObjectIdHash = globalObjectIdHash;
}
// Fallback to auto-increment if `GlobalObjectIdHash` was never set
if (networkObject.GlobalObjectIdHash == default)
{
networkObject.GlobalObjectIdHash = ++s_AutoIncrementGlobalObjectIdHashCounter;
}
// Prevent object from being snapped up as a scene object
networkObject.IsSceneObject = false;
}
// We use GameObject instead of SceneObject to be able to keep hierarchy
public static void MarkAsSceneObjectRoot(GameObject networkObjectRoot, NetworkManager server, NetworkManager[] clients)
{
networkObjectRoot.name += " - Server";
NetworkObject[] serverNetworkObjects = networkObjectRoot.GetComponentsInChildren<NetworkObject>();
for (int i = 0; i < serverNetworkObjects.Length; i++)
{
serverNetworkObjects[i].NetworkManagerOwner = server;
}
for (int i = 0; i < clients.Length; i++)
{
GameObject root = Object.Instantiate(networkObjectRoot);
root.name += " - Client - " + i;
NetworkObject[] clientNetworkObjects = root.GetComponentsInChildren<NetworkObject>();
for (int j = 0; j < clientNetworkObjects.Length; j++)
{
clientNetworkObjects[j].NetworkManagerOwner = clients[i];
}
}
}
/// <summary>
/// Waits on the client side to be connected.
/// </summary>
/// <param name="client">The client</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientConnected(NetworkManager client, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
{
yield return WaitForClientsConnected(new NetworkManager[] { client }, result, maxFrames);
}
/// <summary>
/// Similar to WaitForClientConnected, this waits for multiple clients to be connected.
/// </summary>
/// <param name="clients">The clients to be connected</param>
/// <param name="result">The result. If null, it will automatically assert<</param>
/// <param name="maxFrames">The max frames to wait for</param>
/// <returns></returns>
public static IEnumerator WaitForClientsConnected(NetworkManager[] clients, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
{
// Make sure none are the host client
foreach (var client in clients)
{
if (client.IsServer)
{
throw new InvalidOperationException("Cannot wait for connected as server");
}
}
var startFrameNumber = Time.frameCount;
var allConnected = true;
while (Time.frameCount - startFrameNumber <= maxFrames)
{
allConnected = true;
foreach (var client in clients)
{
if (!client.IsConnectedClient)
{
allConnected = false;
break;
}
}
if (allConnected)
{
break;
}
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
}
if (result != null)
{
result.Result = allConnected;
}
else
{
for (var i = 0; i < clients.Length; ++i)
{
var client = clients[i];
// Logging i+1 because that's the local client ID they'll get (0 is server)
// Can't use client.LocalClientId because that doesn't get assigned until IsConnectedClient == true,
Assert.True(client.IsConnectedClient, $"Client {i + 1} never connected");
}
}
}
/// <summary>
/// Waits on the server side for 1 client to be connected
/// </summary>
/// <param name="server">The server</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientConnectedToServer(NetworkManager server, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
{
yield return WaitForClientsConnectedToServer(server, server.IsHost ? s_ClientCount + 1 : s_ClientCount, result, maxFrames);
}
/// <summary>
/// Waits on the server side for 1 client to be connected
/// </summary>
/// <param name="server">The server</param>
/// <param name="result">The result. If null, it will automatically assert</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForClientsConnectedToServer(NetworkManager server, int clientCount = 1, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames)
{
if (!server.IsServer)
{
throw new InvalidOperationException("Cannot wait for connected as client");
}
var startFrameNumber = Time.frameCount;
while (Time.frameCount - startFrameNumber <= maxFrames && server.ConnectedClients.Count != clientCount)
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
}
var res = server.ConnectedClients.Count == clientCount;
if (result != null)
{
result.Result = res;
}
else
{
Assert.True(res, "A client never connected to server");
}
}
/// <summary>
/// Gets a NetworkObject instance as it's represented by a certain peer.
/// </summary>
/// <param name="networkObjectId">The networkObjectId to get</param>
/// <param name="representation">The representation to get the object from</param>
/// <param name="result">The result</param>
/// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator GetNetworkObjectByRepresentation(ulong networkObjectId, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = DefaultMaxFrames)
{
if (result == null)
{
throw new ArgumentNullException("Result cannot be null");
}
var startFrameNumber = Time.frameCount;
while (Time.frameCount - startFrameNumber <= maxFrames && representation.SpawnManager.SpawnedObjects.All(x => x.Value.NetworkObjectId != networkObjectId))
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
}
result.Result = representation.SpawnManager.SpawnedObjects.First(x => x.Value.NetworkObjectId == networkObjectId).Value;
if (failIfNull && result.Result == null)
{
Assert.Fail("NetworkObject could not be found");
}
}
/// <summary>
/// Gets a NetworkObject instance as it's represented by a certain peer.
/// </summary>
/// <param name="predicate">The predicate used to filter for your target NetworkObject</param>
/// <param name="representation">The representation to get the object from</param>
/// <param name="result">The result</param>
/// <param name="failIfNull">Whether or not to fail if no object is found and result is null</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator GetNetworkObjectByRepresentation(Func<NetworkObject, bool> predicate, NetworkManager representation, CoroutineResultWrapper<NetworkObject> result, bool failIfNull = true, int maxFrames = DefaultMaxFrames)
{
if (result == null)
{
throw new ArgumentNullException("Result cannot be null");
}
if (predicate == null)
{
throw new ArgumentNullException("Predicate cannot be null");
}
var startFrame = Time.frameCount;
while (Time.frameCount - startFrame <= maxFrames && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value)))
{
var nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
}
result.Result = representation.SpawnManager.SpawnedObjects.FirstOrDefault(x => predicate(x.Value)).Value;
if (failIfNull && result.Result == null)
{
Assert.Fail("NetworkObject could not be found");
}
}
/// <summary>
/// Runs some code, then verifies the condition (combines 'Run' and 'WaitForCondition')
/// </summary>
/// <param name="workload">Action / code to run</param>
/// <param name="predicate">The predicate to wait for</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator RunAndWaitForCondition(Action workload, Func<bool> predicate, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames)
{
var waitResult = new CoroutineResultWrapper<bool>();
workload();
yield return Run(WaitForCondition(
predicate,
waitResult,
maxFrames: maxFrames,
minFrames: minFrames));
if (!waitResult.Result)
{
throw new Exception();
}
}
/// <summary>
/// Waits for a predicate condition to be met
/// </summary>
/// <param name="predicate">The predicate to wait for</param>
/// <param name="result">The result. If null, it will fail if the predicate is not met</param>
/// <param name="minFrames">The min frames to wait for</param>
/// <param name="maxFrames">The max frames to wait for</param>
public static IEnumerator WaitForCondition(Func<bool> predicate, CoroutineResultWrapper<bool> result = null, int maxFrames = DefaultMaxFrames, int minFrames = DefaultMinFrames)
{
if (predicate == null)
{
throw new ArgumentNullException("Predicate cannot be null");
}
var startFrameNumber = Time.frameCount;
if (minFrames > 0)
{
yield return new WaitUntil(() =>
{
return Time.frameCount >= minFrames;
});
}
while (Time.frameCount - startFrameNumber <= maxFrames &&
!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;
});
}
var res = predicate();
if (result != null)
{
result.Result = res;
}
else
{
Assert.True(res, "PREDICATE CONDITION");
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: a1c62160e3e5b4489b2143fc21b56e55
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,178 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkBehaviourUpdaterTests : BaseMultiInstanceTest
{
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()
{
yield break;
}
/// <summary>
/// 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
/// </summary>
/// <param name="nbClients"></param>
/// <param name="useHost"></param>
/// <param name="nbSpawnedObjects"></param>
/// <param name="firstNetworkBehaviour"></param>
/// <param name="secondNetworkBehaviour"></param>
/// <param name="thirdNetworkBehaviour"></param>
/// <returns></returns>
[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)
{
// Create multiple NetworkManager instances
if (!MultiInstanceHelpers.Create(nbClients, out NetworkManager server, out NetworkManager[] clients))
{
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<NetworkObject>();
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<NetworkVariable<int>>();
for (int i = 0; i < nbSpawnedObjects; i++)
{
var spawnedObject = Object.Instantiate(prefabToSpawn);
var networkSpawnedObject = spawnedObject.GetComponent<NetworkObject>();
networkSpawnedObject.NetworkManagerOwner = m_ServerNetworkManager;
networkSpawnedObject.Spawn();
int nbBehaviours = 0;
foreach (var networkBehaviour in spawnedObject.GetComponents<NetworkBehaviour>())
{
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
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<bool>();
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<NetworkBehaviour>())
{
foreach (var networkVariable in behaviour.NetworkVariableFields)
{
var varInt = networkVariable as NetworkVariable<int>;
var varUpdateResult = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();
yield return MultiInstanceHelpers.WaitForCondition(() => varInt.Value == updatedValue, varUpdateResult);
Assert.That(varUpdateResult.Result, Is.True);
nbVarsCheckedClientSide++;
Assert.That(varInt.Value, Is.EqualTo(updatedValue));
}
}
}
Assert.That(nbVarsCheckedClientSide, Is.EqualTo(m_ClientNetworkManagers.Length > 0 ? serverNetVarCount : 0));
}
}
}
public interface INetVarInfo
{
public List<NetworkVariable<int>> AllNetVars { get; }
}
public class ZeroNetVar : NetworkBehaviour, INetVarInfo
{
public List<NetworkVariable<int>> AllNetVars => new List<NetworkVariable<int>>(); // Needed to be independant from NetworkBehaviour's list of fields. This way, if that changes, we can still do this validation in this test
}
public class OneNetVar : NetworkBehaviour, INetVarInfo
{
private NetworkVariable<int> m_SomeValue = new NetworkVariable<int>();
public List<NetworkVariable<int>> AllNetVars => new List<NetworkVariable<int>>() { m_SomeValue };
}
public class TwoNetVar : NetworkBehaviour, INetVarInfo
{
private NetworkVariable<int> m_SomeValue = new NetworkVariable<int>();
private NetworkVariable<int> m_SomeOtherValue = new NetworkVariable<int>();
public List<NetworkVariable<int>> AllNetVars => new List<NetworkVariable<int>>() { m_SomeValue, m_SomeOtherValue };
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ef76141a46cb34c44a8ca73bc15d2bd6
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3a1f0974a98eaa1498ac39be872eeed4
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Tests calling destroy on spawned / unspawned <see cref="NetworkObject"/>s. Expected behavior:
/// - Server or client destroy on unspawned => Object gets destroyed, no exceptions
/// - Server destroy spawned => Object gets destroyed and despawned/destroyed on all clients. Server does not run <see cref="NetworkPrefaInstanceHandler.HandleNetworkPrefabDestroy"/>. Client runs it.
/// - Client destroy spawned => throw exception.
/// </summary>
public class NetworkObjectDestroyTests : BaseMultiInstanceTest
{
protected override int NbClients => 1;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
// playerPrefab.AddComponent<TestDestroy>();
});
}
/// <summary>
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestNetworkObjectServerDestroy()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
Assert.IsNotNull(serverClientPlayerResult.Result.gameObject);
Assert.IsNotNull(clientClientPlayerResult.Result.gameObject);
// destroy the server player
Object.Destroy(serverClientPlayerResult.Result.gameObject);
yield return null;
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
var go = new GameObject();
go.AddComponent<NetworkObject>();
Object.Destroy(go);
yield return null;
Assert.IsTrue(go == null);
}
/// <summary>
/// Tests that a client cannot destroy a spawned networkobject.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestNetworkObjectClientDestroy()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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.");
Object.DestroyImmediate(clientClientPlayerResult.Result.gameObject);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: adef71bb3a6193e498dedf91a2f378b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,77 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkObjectDontDestroyWithOwnerTests
{
[UnityTest]
public IEnumerator DontDestroyWithOwnerTest()
{
// create server and client instances
MultiInstanceHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients);
// create prefab
var gameObject = new GameObject("ClientOwnedObject");
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.DontDestroyWithOwner = true;
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(networkObject);
server.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab()
{
Prefab = gameObject
});
for (int i = 0; i < clients.Length; i++)
{
clients[i].NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab()
{
Prefab = gameObject
});
}
// start server and connect clients
MultiInstanceHelpers.Start(false, server, clients);
// wait for connection on client side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(clients));
// wait for connection on server side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(server));
// network objects
var networkObjects = new List<NetworkObject>();
// create instances
for (int i = 0; i < 32; i++)
{
var no = Object.Instantiate(gameObject).GetComponent<NetworkObject>();
no.NetworkManagerOwner = server;
networkObjects.Add(no);
no.SpawnWithOwnership(clients[0].LocalClientId);
}
// wait for object spawn on client
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => clients[0].SpawnManager.SpawnedObjects.Count == 32));
// disconnect the client that owns all the clients
MultiInstanceHelpers.StopOneClient(clients[0]);
// wait for disconnect
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => server.ConnectedClients.Count == 0));
for (int i = 0; i < networkObjects.Count; i++)
{
// ensure ownership was transferred back
Assert.That(networkObjects[i].OwnerClientId == server.ServerClientId);
}
// cleanup
MultiInstanceHelpers.Destroy();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 216add57838067047b8f67fed02d6c71
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,183 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkObjectOnSpawnTests : BaseMultiInstanceTest
{
private GameObject m_TestNetworkObjectPrefab;
private GameObject m_TestNetworkObjectInstance;
protected override int NbClients => 2;
/// <summary>
/// Tests that instantiating a <see cref="NetworkObject"/> and destroying without spawning it
/// does not run <see cref="NetworkBehaviour.OnNetworkSpawn"/> or <see cref="NetworkBehaviour.OnNetworkSpawn"/>.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator InstantiateDestroySpawnNotCalled()
{
m_TestNetworkObjectPrefab = new GameObject("InstantiateDestroySpawnNotCalled_Object");
var networkObject = m_TestNetworkObjectPrefab.AddComponent<NetworkObject>();
var fail = m_TestNetworkObjectPrefab.AddComponent<FailWhenSpawned>();
// instantiate
m_TestNetworkObjectInstance = Object.Instantiate(m_TestNetworkObjectPrefab);
yield return null;
}
private class FailWhenSpawned : NetworkBehaviour
{
public override void OnNetworkSpawn()
{
Assert.Fail("Spawn should not be called on not spawned object");
}
public override void OnNetworkDespawn()
{
Assert.Fail("Depawn should not be called on not spawned object");
}
}
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
// add test component
playerPrefab.AddComponent<TrackOnSpawnFunctions>();
});
}
[UnityTearDown]
public override IEnumerator Teardown()
{
if (m_TestNetworkObjectPrefab != null)
{
Object.Destroy(m_TestNetworkObjectPrefab);
}
if (m_TestNetworkObjectInstance != null)
{
Object.Destroy(m_TestNetworkObjectInstance);
}
yield return base.Teardown();
}
/// <summary>
/// Test that callbacks are run for playerobject spawn, despawn, regular spawn, destroy on server.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestOnNetworkSpawnCallbacks()
{
// [Host-Side] Get the Host owned instance
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ServerNetworkManager, serverClientPlayerResult));
var serverInstance = serverClientPlayerResult.Result.GetComponent<TrackOnSpawnFunctions>();
var clientInstances = new List<TrackOnSpawnFunctions>();
foreach (var client in m_ClientNetworkManagers)
{
var clientClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), client, clientClientPlayerResult));
var clientRpcTests = clientClientPlayerResult.Result.GetComponent<TrackOnSpawnFunctions>();
Assert.IsNotNull(clientRpcTests);
clientInstances.Add(clientRpcTests);
}
// -------------- step 1 check player spawn despawn
// check spawned on server
Assert.AreEqual(1, serverInstance.OnNetworkSpawnCalledCount);
// safety check despawned
Assert.AreEqual(0, serverInstance.OnNetworkDespawnCalledCount);
// check spawned on client
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkSpawnCalledCount);
// safety check despawned
Assert.AreEqual(0, clientInstance.OnNetworkDespawnCalledCount);
}
// despawn on server. However, since we'll be using this object later in the test, don't delete it (false)
serverInstance.GetComponent<NetworkObject>().Despawn(false);
// check despawned on server
Assert.AreEqual(1, serverInstance.OnNetworkDespawnCalledCount);
// wait long enough for player object to be despawned
int nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// check despawned on clients
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount);
}
//----------- step 2 check spawn again and destroy
serverInstance.GetComponent<NetworkObject>().Spawn();
// wait long enough for player object to be spawned
nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
// 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);
}
// destroy the server object
Object.Destroy(serverInstance.gameObject);
// wait one frame for destroy to kick in
yield return null;
// 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);
// check despawned on clients
foreach (var clientInstance in clientInstances)
{
Assert.AreEqual(1, clientInstance.OnNetworkDespawnCalledCount);
}
}
private class TrackOnSpawnFunctions : NetworkBehaviour
{
public int OnNetworkSpawnCalledCount { get; private set; }
public int OnNetworkDespawnCalledCount { get; private set; }
public override void OnNetworkSpawn()
{
OnNetworkSpawnCalledCount++;
}
public override void OnNetworkDespawn()
{
OnNetworkDespawnCalledCount++;
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: dbb32425abaa21a44aeacf00f37ca4e2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,153 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkObjectOwnershipComponent : NetworkBehaviour
{
public bool OnLostOwnershipFired = false;
public ulong CachedOwnerIdOnLostOwnership = 0;
public override void OnLostOwnership()
{
OnLostOwnershipFired = true;
CachedOwnerIdOnLostOwnership = OwnerClientId;
}
public bool OnGainedOwnershipFired = false;
public ulong CachedOwnerIdOnGainedOwnership = 0;
public override void OnGainedOwnership()
{
OnGainedOwnershipFired = true;
CachedOwnerIdOnGainedOwnership = OwnerClientId;
}
}
[TestFixture(true)]
[TestFixture(false)]
public class NetworkObjectOwnershipTests
{
private const int k_ClientInstanceCount = 1;
private NetworkManager m_ServerNetworkManager;
private NetworkManager[] m_ClientNetworkManagers;
private GameObject m_DummyPrefab;
private GameObject m_DummyGameObject;
private readonly bool m_IsHost;
public NetworkObjectOwnershipTests(bool isHost)
{
m_IsHost = isHost;
}
[UnitySetUp]
public IEnumerator Setup()
{
// we need at least 1 client for tests
Assert.That(k_ClientInstanceCount, Is.GreaterThan(0));
// create NetworkManager instances
Assert.That(MultiInstanceHelpers.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));
// create and register our ad-hoc DummyPrefab (we'll spawn it later during tests)
m_DummyPrefab = new GameObject("DummyPrefabPrototype");
m_DummyPrefab.AddComponent<NetworkObject>();
m_DummyPrefab.AddComponent<NetworkObjectOwnershipComponent>();
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(m_DummyPrefab.GetComponent<NetworkObject>());
m_ServerNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab { Prefab = m_DummyPrefab });
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
clientNetworkManager.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab { Prefab = m_DummyPrefab });
}
// start server and client NetworkManager instances
Assert.That(MultiInstanceHelpers.Start(m_IsHost, m_ServerNetworkManager, m_ClientNetworkManagers));
// wait for connection on client side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientsConnected(m_ClientNetworkManagers));
// wait for connection on server side
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForClientConnectedToServer(m_ServerNetworkManager));
}
[TearDown]
public void Teardown()
{
MultiInstanceHelpers.Destroy();
if (m_DummyGameObject != null)
{
Object.DestroyImmediate(m_DummyGameObject);
}
if (m_DummyPrefab != null)
{
Object.DestroyImmediate(m_DummyPrefab);
}
}
[UnityTest]
public IEnumerator TestOwnershipCallbacks()
{
m_DummyGameObject = Object.Instantiate(m_DummyPrefab);
var dummyNetworkObject = m_DummyGameObject.GetComponent<NetworkObject>();
Assert.That(dummyNetworkObject, Is.Not.Null);
dummyNetworkObject.NetworkManagerOwner = m_ServerNetworkManager;
dummyNetworkObject.Spawn();
var dummyNetworkObjectId = dummyNetworkObject.NetworkObjectId;
Assert.That(dummyNetworkObjectId, Is.GreaterThan(0));
int nextFrameNumber = Time.frameCount + 2;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
Assert.That(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId));
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
Assert.That(clientNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(dummyNetworkObjectId));
}
var serverObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[dummyNetworkObjectId];
var clientObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[dummyNetworkObjectId];
Assert.That(serverObject, Is.Not.Null);
Assert.That(clientObject, Is.Not.Null);
var serverComponent = serverObject.GetComponent<NetworkObjectOwnershipComponent>();
var clientComponent = clientObject.GetComponent<NetworkObjectOwnershipComponent>();
Assert.That(serverComponent, Is.Not.Null);
Assert.That(clientComponent, Is.Not.Null);
Assert.That(serverObject.OwnerClientId, Is.EqualTo(m_ServerNetworkManager.ServerClientId));
Assert.That(clientObject.OwnerClientId, Is.EqualTo(m_ClientNetworkManagers[0].ServerClientId));
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);
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);
Assert.That(serverObject.OwnerClientId, Is.EqualTo(m_ServerNetworkManager.LocalClientId));
Assert.That(clientComponent.OnLostOwnershipFired);
Assert.That(clientComponent.CachedOwnerIdOnLostOwnership, Is.EqualTo(m_ClientNetworkManagers[0].LocalClientId));
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 08f9390096acb44698be8b3eacf242ea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,232 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
using NUnit.Framework;
using Unity.Collections;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkObjectSceneSerializationTests
{
/// <summary>
/// The purpose behind this test is to assure that in-scene NetworkObjects
/// that are serialized into a single stream (approval or switch scene this happens)
/// will continue to be processed even if one of the NetworkObjects is invalid.
/// </summary>
[Test]
public void NetworkObjectSceneSerializationFailure()
{
var networkObjectsToTest = new List<GameObject>();
var writer = new FastBufferWriter(1300, Allocator.Temp, 4096000);
var invalidNetworkObjectOffsets = new List<long>();
var invalidNetworkObjectIdCount = new List<int>();
var invalidNetworkObjects = new List<GameObject>();
var invalidNetworkObjectFrequency = 3;
using (writer)
{
// Construct 50 NetworkObjects
for (int i = 0; i < 50; i++)
{
// Inject an invalid NetworkObject every [invalidNetworkObjectFrequency] entry
if ((i % invalidNetworkObjectFrequency) == 0)
{
// Create the invalid NetworkObject
var gameObject = new GameObject($"InvalidTestObject{i}");
Assert.IsNotNull(gameObject);
var networkObject = gameObject.AddComponent<NetworkObject>();
Assert.IsNotNull(networkObject);
var networkVariableComponent = gameObject.AddComponent<NetworkBehaviourWithNetworkVariables>();
Assert.IsNotNull(networkVariableComponent);
// Add invalid NetworkObject's starting position before serialization to handle trapping for the Debug.LogError message
// that we know will be thrown
invalidNetworkObjectOffsets.Add(writer.Position);
networkObject.GlobalObjectIdHash = (uint)(i);
invalidNetworkObjectIdCount.Add(i);
invalidNetworkObjects.Add(gameObject);
writer.WriteValueSafe((int)networkObject.gameObject.scene.handle);
// Serialize the invalid NetworkObject
var sceneObject = networkObject.GetMessageSceneObject(0);
var prePosition = writer.Position;
sceneObject.Serialize(writer);
Debug.Log(
$"Invalid {nameof(NetworkObject)} Size {writer.Position - prePosition}");
// Now adjust how frequent we will inject invalid NetworkObjects
invalidNetworkObjectFrequency = Random.Range(2, 5);
}
else
{
// Create a valid NetworkObject
var gameObject = new GameObject($"TestObject{i}");
Assert.IsNotNull(gameObject);
var networkObject = gameObject.AddComponent<NetworkObject>();
var networkVariableComponent = gameObject.AddComponent<NetworkBehaviourWithNetworkVariables>();
Assert.IsNotNull(networkVariableComponent);
Assert.IsNotNull(networkObject);
networkObject.GlobalObjectIdHash = (uint)(i + 4096);
networkObjectsToTest.Add(gameObject);
writer.WriteValueSafe((int)networkObject.gameObject.scene.handle);
// Handle populating the scenes loaded list
var scene = networkObject.gameObject.scene;
if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded.ContainsKey(
scene.handle))
{
NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenesLoaded
.Add(scene.handle, scene);
}
// Since this is a unit test, we will fake the server to client handle lookup by just adding the same handle key and value
if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle
.ContainsKey(networkObject.gameObject.scene.handle))
{
NetworkManagerHelper.NetworkManagerObject.SceneManager.ServerSceneHandleToClientSceneHandle
.Add(networkObject.gameObject.scene.handle, networkObject.gameObject.scene.handle);
}
// Serialize the valid NetworkObject
var sceneObject = networkObject.GetMessageSceneObject(0);
sceneObject.Serialize(writer);
if (!NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.ContainsKey(
networkObject.GlobalObjectIdHash))
{
NetworkManagerHelper.NetworkManagerObject.SceneManager.ScenePlacedObjects.Add(
networkObject.GlobalObjectIdHash, new Dictionary<int, NetworkObject>());
}
// Add this valid NetworkObject into the ScenePlacedObjects list
NetworkManagerHelper.NetworkManagerObject.SceneManager
.ScenePlacedObjects[networkObject.GlobalObjectIdHash]
.Add(SceneManager.GetActiveScene().handle, networkObject);
}
}
var totalBufferSize = writer.Position;
var reader = new FastBufferReader(writer, Allocator.Temp);
using (reader)
{
var networkObjectsDeSerialized = new List<NetworkObject>();
var currentLogLevel = NetworkManager.Singleton.LogLevel;
var invalidNetworkObjectCount = 0;
while (reader.Position != totalBufferSize)
{
// If we reach the point where we expect it to fail, then make sure we let TestRunner know it should expect this log error message
if (invalidNetworkObjectOffsets.Count > 0 &&
reader.Position == invalidNetworkObjectOffsets[0])
{
invalidNetworkObjectOffsets.RemoveAt(0);
// Turn off Network Logging to avoid other errors that we know will happen after the below LogAssert.Expect message occurs.
NetworkManager.Singleton.LogLevel = LogLevel.Nothing;
// Trap for this specific error message so we don't make Test Runner think we failed (it will fail on Debug.LogError)
UnityEngine.TestTools.LogAssert.Expect(LogType.Error,
$"Failed to spawn {nameof(NetworkObject)} for Hash {invalidNetworkObjectIdCount[invalidNetworkObjectCount]}.");
invalidNetworkObjectCount++;
}
reader.ReadValueSafe(out int handle);
NetworkManagerHelper.NetworkManagerObject.SceneManager.SetTheSceneBeingSynchronized(handle);
var sceneObject = new NetworkObject.SceneObject();
sceneObject.Deserialize(reader);
var deserializedNetworkObject = NetworkObject.AddSceneObject(sceneObject, reader,
NetworkManagerHelper.NetworkManagerObject);
if (deserializedNetworkObject != null)
{
networkObjectsDeSerialized.Add(deserializedNetworkObject);
}
else
{
// Under this condition, we are expecting null (i.e. no NetworkObject instantiated)
// and will set our log level back to the original value to assure the valid NetworkObjects
// aren't causing any log Errors to occur
NetworkManager.Singleton.LogLevel = currentLogLevel;
}
}
// Now validate all NetworkObjects returned against the original NetworkObjects we created
// after they validate, destroy the objects
foreach (var entry in networkObjectsToTest)
{
var entryNetworkObject = entry.GetComponent<NetworkObject>();
Assert.IsTrue(networkObjectsDeSerialized.Contains(entryNetworkObject));
Object.Destroy(entry);
}
}
}
// Destroy the invalid network objects
foreach (var entry in invalidNetworkObjects)
{
Object.Destroy(entry);
}
}
[SetUp]
public void Setup()
{
// Create, instantiate, and host
NetworkManagerHelper.StartNetworkManager(out NetworkManager networkManager, NetworkManagerHelper.NetworkManagerOperatingMode.None);
networkManager.NetworkConfig.EnableSceneManagement = true;
networkManager.StartHost();
}
[TearDown]
public void TearDown()
{
// Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
}
}
/// <summary>
/// A simple test class that will provide varying NetworkBuffer stream sizes
/// when the NetworkVariable is serialized
/// </summary>
public class NetworkBehaviourWithNetworkVariables : NetworkBehaviour
{
private const uint k_MinDataBlocks = 1;
private const uint k_MaxDataBlocks = 64;
public NetworkList<ulong> NetworkVariableData;
private void Awake()
{
var dataBlocksAssigned = new List<ulong>();
var numberDataBlocks = Random.Range(k_MinDataBlocks, k_MaxDataBlocks);
for (var i = 0; i < numberDataBlocks; i++)
{
dataBlocksAssigned.Add((ulong)Random.Range(0.0f, float.MaxValue));
}
NetworkVariableData = new NetworkList<ulong>(dataBlocksAssigned);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: c8e27bb7c831a8f49884a8af5e59f2f9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,234 @@
using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// The NetworkPrefabHandler unit tests validates:
/// Registering with GameObject, NetworkObject, or GlobalObjectIdHash
/// Newly assigned rotation or position values for newly spawned NetworkObject instances are valid
/// Destroying a newly spawned NetworkObject instance works
/// Removing a INetworkPrefabInstanceHandler is removed and can be verified (very last check)
/// </summary>
public class NetworkPrefabHandlerTests
{
private const string k_TestPrefabObjectName = "NetworkPrefabTestObject";
private uint m_ObjectId = 1;
private GameObject MakeValidNetworkPrefab()
{
Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(k_TestPrefabObjectName + m_ObjectId.ToString());
NetworkObject validPrefab = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID];
MultiInstanceHelpers.MakeNetworkObjectTestPrefab(validPrefab);
m_ObjectId++;
return validPrefab.gameObject;
}
/// <summary>
/// Tests the NetwokConfig NetworkPrefabs initialization during NetworkManager's Init method to make sure that
/// it will still initialize but remove the invalid prefabs
/// </summary>
[Test]
public void NetworkConfigInvalidNetworkPrefabTest()
{
// Add null entry
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(null);
// Add a NetworkPrefab with no prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab());
// Add a NetworkPrefab override with an invalid hash
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 0 });
// Add a NetworkPrefab override with a valid hash but an invalid target prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 654321, OverridingTargetPrefab = null });
// Add a NetworkPrefab override with a valid hash to override but an invalid target prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourceHashToOverride = 654321, OverridingTargetPrefab = null });
// Add a NetworkPrefab override with an invalid source prefab to override
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = null });
// Add a NetworkPrefab override with a valid source prefab to override but an invalid target prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = null });
// Add a valid prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Prefab = MakeValidNetworkPrefab() });
// Add a NetworkPrefab override with a valid hash and valid target prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Hash, SourceHashToOverride = 11111111, OverridingTargetPrefab = MakeValidNetworkPrefab() });
// Add a NetworkPrefab override with a valid prefab and valid target prefab
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabs.Add(new NetworkPrefab() { Override = NetworkPrefabOverride.Prefab, SourcePrefabToOverride = MakeValidNetworkPrefab(), OverridingTargetPrefab = MakeValidNetworkPrefab() });
var exceptionOccurred = false;
try
{
NetworkManagerHelper.NetworkManagerObject.StartHost();
}
catch
{
exceptionOccurred = true;
}
Assert.False(exceptionOccurred);
// In the end we should only have 3 valid registered network prefabs
Assert.True(NetworkManagerHelper.NetworkManagerObject.NetworkConfig.NetworkPrefabOverrideLinks.Count == 3);
}
private const string k_PrefabObjectName = "NetworkPrefabHandlerTestObject";
[Test]
public void NetworkPrefabHandlerClass()
{
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _));
var testPrefabObjectName = k_PrefabObjectName;
Guid baseObjectID = NetworkManagerHelper.AddGameNetworkObject(testPrefabObjectName);
NetworkObject baseObject = NetworkManagerHelper.InstantiatedNetworkObjects[baseObjectID];
var networkPrefabHandler = new NetworkPrefabHandler();
var networkPrefaInstanceHandler = new NetworkPrefaInstanceHandler(baseObject);
var prefabPosition = new Vector3(1.0f, 5.0f, 3.0f);
var prefabRotation = new Quaternion(1.0f, 0.5f, 0.4f, 0.1f);
//Register via GameObject
var gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.gameObject, networkPrefaInstanceHandler);
//Test result of registering via GameObject reference
Assert.True(gameObjectRegistered);
var spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation);
//Test that something was instantiated
Assert.NotNull(spawnedObject);
//Test that this is indeed an instance of our original object
Assert.True(spawnedObject.name.Contains(testPrefabObjectName));
//Test for position and rotation
Assert.True(prefabPosition == spawnedObject.transform.position);
Assert.True(prefabRotation == spawnedObject.transform.rotation);
networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance
networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler
//Register via NetworkObject
gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject, networkPrefaInstanceHandler);
//Test result of registering via NetworkObject reference
Assert.True(gameObjectRegistered);
//Change it up
prefabPosition = new Vector3(2.0f, 1.0f, 5.0f);
prefabRotation = new Quaternion(4.0f, 1.5f, 5.4f, 5.1f);
spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation);
//Test that something was instantiated
Assert.NotNull(spawnedObject);
//Test that this is indeed an instance of our original object
Assert.True(spawnedObject.name.Contains(testPrefabObjectName));
//Test for position and rotation
Assert.True(prefabPosition == spawnedObject.transform.position);
Assert.True(prefabRotation == spawnedObject.transform.rotation);
networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance
networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler
//Register via GlobalObjectIdHash
gameObjectRegistered = networkPrefabHandler.AddHandler(baseObject.GlobalObjectIdHash, networkPrefaInstanceHandler);
//Test result of registering via GlobalObjectIdHash reference
Assert.True(gameObjectRegistered);
//Change it up
prefabPosition = new Vector3(6.0f, 4.0f, 1.0f);
prefabRotation = new Quaternion(3f, 2f, 4f, 1f);
spawnedObject = networkPrefabHandler.HandleNetworkPrefabSpawn(baseObject.GlobalObjectIdHash, 0, prefabPosition, prefabRotation);
//Test that something was instantiated
Assert.NotNull(spawnedObject);
//Test that this is indeed an instance of our original object
Assert.True(spawnedObject.name.Contains(testPrefabObjectName));
//Test for position and rotation
Assert.True(prefabPosition == spawnedObject.transform.position);
Assert.True(prefabRotation == spawnedObject.transform.rotation);
networkPrefabHandler.HandleNetworkPrefabDestroy(spawnedObject); //Destroy our prefab instance
networkPrefabHandler.RemoveHandler(baseObject); //Remove our handler
Assert.False(networkPrefaInstanceHandler.StillHasInstances());
}
[SetUp]
public void Setup()
{
//Create, instantiate, and host
NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None);
}
[TearDown]
public void TearDown()
{
//Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
var networkObjects = UnityEngine.Object.FindObjectsOfType<NetworkObject>().ToList();
var networkObjectsList = networkObjects.Where(c => c.name.Contains(k_PrefabObjectName));
foreach (var networkObject in networkObjectsList)
{
UnityEngine.Object.DestroyImmediate(networkObject);
}
}
}
/// <summary>
/// The Prefab instance handler to use for this test
/// </summary>
public class NetworkPrefaInstanceHandler : INetworkPrefabInstanceHandler
{
private NetworkObject m_NetworkObject;
private List<NetworkObject> m_Instances;
public NetworkObject Instantiate(ulong ownerClientId, Vector3 position, Quaternion rotation)
{
var networkObjectInstance = UnityEngine.Object.Instantiate(m_NetworkObject.gameObject).GetComponent<NetworkObject>();
networkObjectInstance.transform.position = position;
networkObjectInstance.transform.rotation = rotation;
m_Instances.Add(networkObjectInstance);
return networkObjectInstance;
}
public void Destroy(NetworkObject networkObject)
{
var instancesContainsNetworkObject = m_Instances.Contains(networkObject);
Assert.True(instancesContainsNetworkObject);
m_Instances.Remove(networkObject);
UnityEngine.Object.Destroy(networkObject.gameObject);
}
public bool StillHasInstances()
{
return (m_Instances.Count > 0);
}
public NetworkPrefaInstanceHandler(NetworkObject networkObject)
{
m_NetworkObject = networkObject;
m_Instances = new List<NetworkObject>();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 590750036c5d65c43bbc696071aa3541
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,220 @@
using System;
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkShowHideTest : NetworkBehaviour
{
}
public class ShowHideObject : NetworkBehaviour
{
public NetworkVariable<int> MyNetworkVariable;
private void Start()
{
MyNetworkVariable = new NetworkVariable<int>();
MyNetworkVariable.OnValueChanged += Changed;
}
public void Changed(int before, int after)
{
Debug.Log($"Value changed from {before} to {after}");
}
}
public class NetworkShowHideTests : BaseMultiInstanceTest
{
protected override int NbClients => 2;
private ulong m_ClientId0;
private GameObject m_PrefabToSpawn;
private NetworkObject m_NetSpawnedObject1;
private NetworkObject m_NetSpawnedObject2;
private NetworkObject m_NetSpawnedObject3;
private NetworkObject m_Object1OnClient0;
private NetworkObject m_Object2OnClient0;
private NetworkObject m_Object3OnClient0;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients,
updatePlayerPrefab: playerPrefab =>
{
var networkTransform = playerPrefab.AddComponent<NetworkShowHideTest>();
m_PrefabToSpawn = PreparePrefab(typeof(ShowHideObject));
});
}
public GameObject PreparePrefab(Type type)
{
var prefabToSpawn = new GameObject();
prefabToSpawn.AddComponent(type);
var networkObjectPrefab = prefabToSpawn.AddComponent<NetworkObject>();
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 });
}
return prefabToSpawn;
}
// Check that the first client see them, or not, as expected
private IEnumerator CheckVisible(bool target)
{
int count = 0;
do
{
yield return new WaitForSeconds(0.1f);
count++;
if (count > 20)
{
// timeout waiting for object to reach the expect visibility
Assert.Fail("timeout waiting for object to reach the expect visibility");
break;
}
} while (m_NetSpawnedObject1.IsNetworkVisibleTo(m_ClientId0) != target ||
m_NetSpawnedObject2.IsNetworkVisibleTo(m_ClientId0) != target ||
m_NetSpawnedObject3.IsNetworkVisibleTo(m_ClientId0) != target ||
m_Object1OnClient0.IsSpawned != target ||
m_Object2OnClient0.IsSpawned != target ||
m_Object3OnClient0.IsSpawned != target
);
Debug.Assert(m_NetSpawnedObject1.IsNetworkVisibleTo(m_ClientId0) == target);
Debug.Assert(m_NetSpawnedObject2.IsNetworkVisibleTo(m_ClientId0) == target);
Debug.Assert(m_NetSpawnedObject3.IsNetworkVisibleTo(m_ClientId0) == target);
Debug.Assert(m_Object1OnClient0.IsSpawned == target);
Debug.Assert(m_Object2OnClient0.IsSpawned == target);
Debug.Assert(m_Object3OnClient0.IsSpawned == target);
}
// Set the 3 objects visibility
private void Show(bool individually, bool visibility)
{
if (individually)
{
if (!visibility)
{
m_NetSpawnedObject1.NetworkHide(m_ClientId0);
m_NetSpawnedObject2.NetworkHide(m_ClientId0);
m_NetSpawnedObject3.NetworkHide(m_ClientId0);
}
else
{
m_NetSpawnedObject1.NetworkShow(m_ClientId0);
m_NetSpawnedObject2.NetworkShow(m_ClientId0);
m_NetSpawnedObject3.NetworkShow(m_ClientId0);
}
}
else
{
var list = new List<NetworkObject>();
list.Add(m_NetSpawnedObject1);
list.Add(m_NetSpawnedObject2);
list.Add(m_NetSpawnedObject3);
if (!visibility)
{
NetworkObject.NetworkHide(list, m_ClientId0);
}
else
{
NetworkObject.NetworkShow(list, m_ClientId0);
}
}
}
private IEnumerator RefreshNetworkObjects()
{
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.NetworkObjectId == m_NetSpawnedObject1.NetworkObjectId,
m_ClientNetworkManagers[0],
serverClientPlayerResult));
m_Object1OnClient0 = serverClientPlayerResult.Result;
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.NetworkObjectId == m_NetSpawnedObject2.NetworkObjectId,
m_ClientNetworkManagers[0],
serverClientPlayerResult));
m_Object2OnClient0 = serverClientPlayerResult.Result;
serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(
MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.NetworkObjectId == m_NetSpawnedObject3.NetworkObjectId,
m_ClientNetworkManagers[0],
serverClientPlayerResult));
m_Object3OnClient0 = serverClientPlayerResult.Result;
// make sure the objects are set with the right network manager
m_Object1OnClient0.NetworkManagerOwner = m_ClientNetworkManagers[0];
m_Object2OnClient0.NetworkManagerOwner = m_ClientNetworkManagers[0];
m_Object3OnClient0.NetworkManagerOwner = m_ClientNetworkManagers[0];
}
[UnityTest]
public IEnumerator NetworkShowHideTest()
{
m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId;
// create 3 objects
var spawnedObject1 = UnityEngine.Object.Instantiate(m_PrefabToSpawn);
var spawnedObject2 = UnityEngine.Object.Instantiate(m_PrefabToSpawn);
var spawnedObject3 = UnityEngine.Object.Instantiate(m_PrefabToSpawn);
m_NetSpawnedObject1 = spawnedObject1.GetComponent<NetworkObject>();
m_NetSpawnedObject2 = spawnedObject2.GetComponent<NetworkObject>();
m_NetSpawnedObject3 = spawnedObject3.GetComponent<NetworkObject>();
m_NetSpawnedObject1.NetworkManagerOwner = m_ServerNetworkManager;
m_NetSpawnedObject2.NetworkManagerOwner = m_ServerNetworkManager;
m_NetSpawnedObject3.NetworkManagerOwner = m_ServerNetworkManager;
m_NetSpawnedObject1.Spawn();
m_NetSpawnedObject2.Spawn();
m_NetSpawnedObject3.Spawn();
for (int mode = 0; mode < 2; mode++)
{
// get the NetworkObject on a client instance
yield return RefreshNetworkObjects();
// check object start visible
yield return CheckVisible(true);
// hide them on one client
Show(mode == 0, false);
yield return new WaitForSeconds(1.0f);
m_NetSpawnedObject1.GetComponent<ShowHideObject>().MyNetworkVariable.Value = 3;
yield return new WaitForSeconds(1.0f);
// verify they got hidden
yield return CheckVisible(false);
// show them to that client
Show(mode == 0, true);
yield return RefreshNetworkObjects();
// verify they become visible
yield return CheckVisible(true);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: bad53d2f164c74c46a55fa2ccb1f9b57
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,129 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkSpawnManagerTests : BaseMultiInstanceTest
{
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;
[Test]
public void TestServerCanAccessItsOwnPlayer()
{
// server can access its own player
var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(serverSideClientId);
Assert.NotNull(serverSideServerPlayerObject);
Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId);
}
[Test]
public void TestServerCanAccessOtherPlayers()
{
// server can access other players
var serverSideClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(clientSideClientId);
Assert.NotNull(serverSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, serverSideClientPlayerObject.OwnerClientId);
var serverSideOtherClientPlayerObject = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(otherClientSideClientId);
Assert.NotNull(serverSideOtherClientPlayerObject);
Assert.AreEqual(otherClientSideClientId, serverSideOtherClientPlayerObject.OwnerClientId);
}
[Test]
public void TestClientCantAccessServerPlayer()
{
// client can't access server player
Assert.Throws<NotServerException>(() =>
{
m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(serverSideClientId);
});
}
[Test]
public void TestClientCanAccessOwnPlayer()
{
// client can access own player
var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(clientSideClientId);
Assert.NotNull(clientSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId);
}
[Test]
public void TestClientCantAccessOtherPlayer()
{
// client can't access other player
Assert.Throws<NotServerException>(() =>
{
m_ClientNetworkManagers[0].SpawnManager.GetPlayerNetworkObject(otherClientSideClientId);
});
}
[Test]
public void TestServerGetsNullValueIfInvalidId()
{
// server gets null value if invalid id
var nullPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(9999);
Assert.Null(nullPlayer);
}
[Test]
public void TestServerCanUseGetLocalPlayerObject()
{
// test server can use GetLocalPlayerObject
var serverSideServerPlayerObject = m_ServerNetworkManager.SpawnManager.GetLocalPlayerObject();
Assert.NotNull(serverSideServerPlayerObject);
Assert.AreEqual(serverSideClientId, serverSideServerPlayerObject.OwnerClientId);
}
[Test]
public void TestClientCanUseGetLocalPlayerObject()
{
// test client can use GetLocalPlayerObject
var clientSideClientPlayerObject = m_ClientNetworkManagers[0].SpawnManager.GetLocalPlayerObject();
Assert.NotNull(clientSideClientPlayerObject);
Assert.AreEqual(clientSideClientId, clientSideClientPlayerObject.OwnerClientId);
}
[UnityTest]
public IEnumerator TestConnectAndDisconnect()
{
// test when client connects, player object is now available
// connect new client
if (!MultiInstanceHelpers.CreateNewClients(1, out NetworkManager[] clients))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
}
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)));
var newClientLocalClientId = newClientNetworkManager.LocalClientId;
// test new client can get that itself locally
var newPlayerObject = newClientNetworkManager.SpawnManager.GetLocalPlayerObject();
Assert.NotNull(newPlayerObject);
Assert.AreEqual(newClientLocalClientId, newPlayerObject.OwnerClientId);
// test server can get that new client locally
var serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId);
Assert.NotNull(serverSideNewClientPlayer);
Assert.AreEqual(newClientLocalClientId, serverSideNewClientPlayer.OwnerClientId);
// 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));
serverSideNewClientPlayer = m_ServerNetworkManager.SpawnManager.GetPlayerNetworkObject(newClientLocalClientId);
Assert.Null(serverSideNewClientPlayer);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 173bed02aed54db4a4f056c245a67393
timeCreated: 1621449221

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7b88072d3918a44268b5ed6910f6324e
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,349 @@
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkTransformStateTests
{
[Test]
public void TestSyncAxes(
[Values] bool inLocalSpace,
[Values] bool syncPosX, [Values] bool syncPosY, [Values] bool syncPosZ,
[Values] bool syncRotX, [Values] bool syncRotY, [Values] bool syncRotZ,
[Values] bool syncScaX, [Values] bool syncScaY, [Values] bool syncScaZ)
{
var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestSyncAxes)}");
var networkObject = gameObject.AddComponent<NetworkObject>();
var networkTransform = gameObject.AddComponent<NetworkTransform>();
networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()`
var initialPosition = Vector3.zero;
var initialRotAngles = Vector3.zero;
var initialScale = Vector3.one;
networkTransform.transform.position = initialPosition;
networkTransform.transform.eulerAngles = initialRotAngles;
networkTransform.transform.localScale = initialScale;
networkTransform.SyncPositionX = syncPosX;
networkTransform.SyncPositionY = syncPosY;
networkTransform.SyncPositionZ = syncPosZ;
networkTransform.SyncRotAngleX = syncRotX;
networkTransform.SyncRotAngleY = syncRotY;
networkTransform.SyncRotAngleZ = syncRotZ;
networkTransform.SyncScaleX = syncScaX;
networkTransform.SyncScaleY = syncScaY;
networkTransform.SyncScaleZ = syncScaZ;
networkTransform.InLocalSpace = inLocalSpace;
var networkTransformState = new NetworkTransform.NetworkTransformState
{
PositionX = initialPosition.x,
PositionY = initialPosition.y,
PositionZ = initialPosition.z,
RotAngleX = initialRotAngles.x,
RotAngleY = initialRotAngles.y,
RotAngleZ = initialRotAngles.z,
ScaleX = initialScale.x,
ScaleY = initialScale.y,
ScaleZ = initialScale.z,
HasPositionX = syncPosX,
HasPositionY = syncPosY,
HasPositionZ = syncPosZ,
HasRotAngleX = syncRotX,
HasRotAngleY = syncRotY,
HasRotAngleZ = syncRotZ,
HasScaleX = syncScaX,
HasScaleY = syncScaY,
HasScaleZ = syncScaZ,
InLocalSpace = inLocalSpace
};
// Step 1: change properties, expect state to be dirty
{
networkTransform.transform.position = new Vector3(3, 4, 5);
networkTransform.transform.eulerAngles = new Vector3(30, 45, 90);
networkTransform.transform.localScale = new Vector3(1.1f, 0.5f, 2.5f);
if (syncPosX || syncPosY || syncPosZ || syncRotX || syncRotY || syncRotZ || syncScaX || syncScaY || syncScaZ)
{
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
}
// Step 2: disable a particular sync flag, expect state to be not dirty
{
var position = networkTransform.transform.position;
var rotAngles = networkTransform.transform.eulerAngles;
var scale = networkTransform.transform.localScale;
// SyncPositionX
{
networkTransform.SyncPositionX = false;
position.x++;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncPositionY
{
networkTransform.SyncPositionY = false;
position.y++;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncPositionZ
{
networkTransform.SyncPositionZ = false;
position.z++;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncRotAngleX
{
networkTransform.SyncRotAngleX = false;
rotAngles.x++;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncRotAngleY
{
networkTransform.SyncRotAngleY = false;
rotAngles.y++;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncRotAngleZ
{
networkTransform.SyncRotAngleZ = false;
rotAngles.z++;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncScaleX
{
networkTransform.SyncScaleX = false;
scale.x++;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncScaleY
{
networkTransform.SyncScaleY = false;
scale.y++;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// SyncScaleZ
{
networkTransform.SyncScaleZ = false;
scale.z++;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
}
Object.DestroyImmediate(gameObject);
}
[Test]
public void TestThresholds(
[Values] bool inLocalSpace,
[Values(NetworkTransform.PositionThresholdDefault, 1.0f)] float positionThreshold,
[Values(NetworkTransform.RotAngleThresholdDefault, 1.0f)] float rotAngleThreshold,
[Values(NetworkTransform.ScaleThresholdDefault, 0.5f)] float scaleThreshold)
{
var gameObject = new GameObject($"Test-{nameof(NetworkTransformStateTests)}.{nameof(TestThresholds)}");
var networkTransform = gameObject.AddComponent<NetworkTransform>();
networkTransform.enabled = false; // do not tick `FixedUpdate()` or `Update()`
var initialPosition = Vector3.zero;
var initialRotAngles = Vector3.zero;
var initialScale = Vector3.one;
networkTransform.transform.position = initialPosition;
networkTransform.transform.eulerAngles = initialRotAngles;
networkTransform.transform.localScale = initialScale;
networkTransform.SyncPositionX = true;
networkTransform.SyncPositionY = true;
networkTransform.SyncPositionZ = true;
networkTransform.SyncRotAngleX = true;
networkTransform.SyncRotAngleY = true;
networkTransform.SyncRotAngleZ = true;
networkTransform.SyncScaleX = true;
networkTransform.SyncScaleY = true;
networkTransform.SyncScaleZ = true;
networkTransform.InLocalSpace = inLocalSpace;
networkTransform.PositionThreshold = positionThreshold;
networkTransform.RotAngleThreshold = rotAngleThreshold;
networkTransform.ScaleThreshold = scaleThreshold;
var networkTransformState = new NetworkTransform.NetworkTransformState
{
PositionX = initialPosition.x,
PositionY = initialPosition.y,
PositionZ = initialPosition.z,
RotAngleX = initialRotAngles.x,
RotAngleY = initialRotAngles.y,
RotAngleZ = initialRotAngles.z,
ScaleX = initialScale.x,
ScaleY = initialScale.y,
ScaleZ = initialScale.z,
InLocalSpace = inLocalSpace
};
// Step 1: change properties, expect state to be dirty
{
networkTransform.transform.position = new Vector3(3, 4, 5);
networkTransform.transform.eulerAngles = new Vector3(30, 45, 90);
networkTransform.transform.localScale = new Vector3(1.1f, 0.5f, 2.5f);
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// Step 2: make changes below and above thresholds
// changes below the threshold should not make `NetworkState` dirty
// changes above the threshold should make `NetworkState` dirty
{
// Position
if (!Mathf.Approximately(positionThreshold, 0.0f))
{
var position = networkTransform.transform.position;
// PositionX
{
position.x += positionThreshold / 2;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
position.x += positionThreshold * 2;
networkTransform.transform.position = position;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// PositionY
{
position.y += positionThreshold / 2;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
position.y += positionThreshold * 2;
networkTransform.transform.position = position;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// PositionZ
{
position.z += positionThreshold / 2;
networkTransform.transform.position = position;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
position.z += positionThreshold * 2;
networkTransform.transform.position = position;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
}
// RotAngles
if (!Mathf.Approximately(rotAngleThreshold, 0.0f))
{
var rotAngles = networkTransform.transform.eulerAngles;
// RotAngleX
{
rotAngles.x += rotAngleThreshold / 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
rotAngles.x += rotAngleThreshold * 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// RotAngleY
{
rotAngles.y += rotAngleThreshold / 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
rotAngles.y += rotAngleThreshold * 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// RotAngleZ
{
rotAngles.z += rotAngleThreshold / 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
rotAngles.z += rotAngleThreshold * 2;
networkTransform.transform.eulerAngles = rotAngles;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
}
// Scale
if (!Mathf.Approximately(scaleThreshold, 0.0f) && inLocalSpace)
{
var scale = networkTransform.transform.localScale;
// ScaleX
{
scale.x += scaleThreshold / 2;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
scale.x += scaleThreshold * 2;
networkTransform.transform.localScale = scale;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// ScaleY
{
scale.y += scaleThreshold / 2;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
scale.y += scaleThreshold * 2;
networkTransform.transform.localScale = scale;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
// ScaleZ
{
scale.z += scaleThreshold / 2;
networkTransform.transform.localScale = scale;
Assert.IsFalse(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
scale.z += scaleThreshold * 2;
networkTransform.transform.localScale = scale;
Assert.IsTrue(networkTransform.ApplyTransformToNetworkState(ref networkTransformState, 0, networkTransform.transform));
}
}
}
Object.DestroyImmediate(gameObject);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 89acf93a968324f208e359c47bbf9f9b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,201 @@
using System;
using System.Collections;
#if NGO_TRANSFORM_DEBUG
using System.Text.RegularExpressions;
#endif
using Unity.Netcode.Components;
using NUnit.Framework;
// using Unity.Netcode.Samples;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
// [TestFixture(true, true)]
[TestFixture(true, false)]
// [TestFixture(false, true)]
[TestFixture(false, false)]
public class NetworkTransformTests : BaseMultiInstanceTest
{
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_TestWithClientNetworkTransform = testWithClientNetworkTransform;
}
protected override int NbClients => 1;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients, updatePlayerPrefab: playerPrefab =>
{
if (m_TestWithClientNetworkTransform)
{
// playerPrefab.AddComponent<ClientNetworkTransform>();
}
else
{
playerPrefab.AddComponent<NetworkTransform>();
}
});
#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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId, m_ClientNetworkManagers[0], clientClientPlayerResult));
m_ServerSideClientPlayer = serverClientPlayerResult.Result;
m_ClientSideClientPlayer = clientClientPlayerResult.Result;
}
// TODO: rewrite after perms & authority changes
[UnityTest]
public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool testLocalTransform)
{
var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();
NetworkTransform authoritativeNetworkTransform;
NetworkTransform otherSideNetworkTransform;
// if (m_TestWithClientNetworkTransform)
// {
// // client auth net transform can write from client, not from server
// otherSideNetworkTransform = m_ServerSideClientPlayer.GetComponent<ClientNetworkTransform>();
// authoritativeNetworkTransform = m_ClientSideClientPlayer.GetComponent<ClientNetworkTransform>();
// }
// else
{
// server auth net transform can't write from client, not from client
authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>();
otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>();
}
Assert.That(!otherSideNetworkTransform.CanCommitToTransform);
Assert.That(authoritativeNetworkTransform.CanCommitToTransform);
authoritativeNetworkTransform.Interpolate = false;
otherSideNetworkTransform.Interpolate = false;
if (authoritativeNetworkTransform.CanCommitToTransform)
{
authoritativeNetworkTransform.InLocalSpace = testLocalTransform;
}
if (otherSideNetworkTransform.CanCommitToTransform)
{
otherSideNetworkTransform.InLocalSpace = testLocalTransform;
}
float approximation = 0.05f;
// 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");
}
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");
}
// approximation needed here since eulerAngles isn't super precise.
Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}");
Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}");
Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}");
// test scale
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check
authPlayerTransform.localScale = new Vector3(2, 3, 4);
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");
}
UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost");
// todo reparent and test
// todo test all public API
}
[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<ClientNetworkTransform>();
// authoritativeNetworkTransform = m_ClientSideClientPlayer.GetComponent<ClientNetworkTransform>();
// }
// else
{
// server auth net transform can't write from client, not from client
authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>();
otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>();
}
authoritativeNetworkTransform.Interpolate = false;
otherSideNetworkTransform.Interpolate = false;
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
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!");
#if NGO_TRANSFORM_DEBUG
// We are no longer emitting this warning, and we are banishing tests that rely on console output, so
// needs re-implementation
// TODO: This should be a separate test - verify 1 behavior per test
LogAssert.Expect(LogType.Warning, new Regex(".*without authority detected.*"));
#endif
}
/*
* ownership change
* test teleport with interpolation
* test teleport without interpolation
* test dynamic spawning
*/
[UnityTearDown]
public override IEnumerator Teardown()
{
yield return base.Teardown();
UnityEngine.Object.DestroyImmediate(m_PlayerPrefab);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: cf4ff0d6357bb4474a404b9ce52b22ad
timeCreated: 1620872927

View File

@@ -0,0 +1,437 @@
using System;
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using UnityEngine.LowLevel;
using UnityEngine.PlayerLoop;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkUpdateLoopTests
{
[Test]
public void RegisterCustomLoopInTheMiddle()
{
// caching the current PlayerLoop (to prevent side-effects on other tests)
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
{
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
// we are going to swap it with the default PlayerLoop temporarily for testing
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
NetworkUpdateLoop.RegisterLoopSystems();
var curPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
int initSubsystemCount = curPlayerLoop.subSystemList[0].subSystemList.Length;
var newInitSubsystems = new PlayerLoopSystem[initSubsystemCount + 1];
Array.Copy(curPlayerLoop.subSystemList[0].subSystemList, newInitSubsystems, initSubsystemCount);
newInitSubsystems[initSubsystemCount] = new PlayerLoopSystem { type = typeof(NetworkUpdateLoopTests) };
curPlayerLoop.subSystemList[0].subSystemList = newInitSubsystems;
PlayerLoop.SetPlayerLoop(curPlayerLoop);
NetworkUpdateLoop.UnregisterLoopSystems();
// our custom `PlayerLoopSystem` with the type of `NetworkUpdateLoopTests` should still exist
Assert.AreEqual(typeof(NetworkUpdateLoopTests), PlayerLoop.GetCurrentPlayerLoop().subSystemList[0].subSystemList.Last().type);
}
// replace the current PlayerLoop with the cached PlayerLoop after the test
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
}
[UnityTest]
public IEnumerator RegisterAndUnregisterSystems()
{
// caching the current PlayerLoop (it will have NetworkUpdateLoop systems registered)
var cachedPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
{
// since current PlayerLoop already took NetworkUpdateLoop systems inside,
// we are going to swap it with the default PlayerLoop temporarily for testing
PlayerLoop.SetPlayerLoop(PlayerLoop.GetDefaultPlayerLoop());
var oldPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
NetworkUpdateLoop.RegisterLoopSystems();
int nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
NetworkUpdateLoop.UnregisterLoopSystems();
var newPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
// recursively compare old and new PlayerLoop systems and their subsystems
AssertAreEqualPlayerLoopSystems(newPlayerLoop, oldPlayerLoop);
}
// replace the current PlayerLoop with the cached PlayerLoop after the test
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
}
private void AssertAreEqualPlayerLoopSystems(PlayerLoopSystem leftPlayerLoop, PlayerLoopSystem rightPlayerLoop)
{
Assert.AreEqual(leftPlayerLoop.type, rightPlayerLoop.type);
Assert.AreEqual(leftPlayerLoop.subSystemList?.Length ?? 0, rightPlayerLoop.subSystemList?.Length ?? 0);
for (int i = 0; i < (leftPlayerLoop.subSystemList?.Length ?? 0); i++)
{
AssertAreEqualPlayerLoopSystems(leftPlayerLoop.subSystemList[i], rightPlayerLoop.subSystemList[i]);
}
}
[Test]
public void UpdateStageSystems()
{
var currentPlayerLoop = PlayerLoop.GetCurrentPlayerLoop();
for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++)
{
var playerLoopSystem = currentPlayerLoop.subSystemList[i];
var subsystems = playerLoopSystem.subSystemList.ToList();
if (playerLoopSystem.type == typeof(Initialization))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkInitialization)),
nameof(NetworkUpdateLoop.NetworkInitialization));
}
else if (playerLoopSystem.type == typeof(EarlyUpdate))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkEarlyUpdate)),
nameof(NetworkUpdateLoop.NetworkEarlyUpdate));
}
else if (playerLoopSystem.type == typeof(FixedUpdate))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkFixedUpdate)),
nameof(NetworkUpdateLoop.NetworkFixedUpdate));
}
else if (playerLoopSystem.type == typeof(PreUpdate))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkPreUpdate)),
nameof(NetworkUpdateLoop.NetworkPreUpdate));
}
else if (playerLoopSystem.type == typeof(Update))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkUpdate)),
nameof(NetworkUpdateLoop.NetworkUpdate));
}
else if (playerLoopSystem.type == typeof(PreLateUpdate))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkPreLateUpdate)),
nameof(NetworkUpdateLoop.NetworkPreLateUpdate));
}
else if (playerLoopSystem.type == typeof(PostLateUpdate))
{
Assert.True(
subsystems.Exists(s => s.type == typeof(NetworkUpdateLoop.NetworkPostLateUpdate)),
nameof(NetworkUpdateLoop.NetworkPostLateUpdate));
}
}
}
private struct NetworkUpdateCallbacks
{
public Action OnInitialization;
public Action OnEarlyUpdate;
public Action OnFixedUpdate;
public Action OnPreUpdate;
public Action OnUpdate;
public Action OnPreLateUpdate;
public Action OnPostLateUpdate;
}
private class MyPlainScript : IDisposable, INetworkUpdateSystem
{
public NetworkUpdateCallbacks UpdateCallbacks;
public void Initialize()
{
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate);
}
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.Initialization:
UpdateCallbacks.OnInitialization();
break;
case NetworkUpdateStage.EarlyUpdate:
UpdateCallbacks.OnEarlyUpdate();
break;
case NetworkUpdateStage.FixedUpdate:
UpdateCallbacks.OnFixedUpdate();
break;
case NetworkUpdateStage.PreUpdate:
UpdateCallbacks.OnPreUpdate();
break;
case NetworkUpdateStage.Update:
UpdateCallbacks.OnUpdate();
break;
case NetworkUpdateStage.PreLateUpdate:
UpdateCallbacks.OnPreLateUpdate();
break;
case NetworkUpdateStage.PostLateUpdate:
UpdateCallbacks.OnPostLateUpdate();
break;
}
}
public void Dispose()
{
this.UnregisterAllNetworkUpdates();
}
}
[UnityTest]
public IEnumerator UpdateStagesPlain()
{
const int kNetInitializationIndex = 0;
const int kNetEarlyUpdateIndex = 1;
const int kNetFixedUpdateIndex = 2;
const int kNetPreUpdateIndex = 3;
const int kNetUpdateIndex = 4;
const int kNetPreLateUpdateIndex = 5;
const int kNetPostLateUpdateIndex = 6;
int[] netUpdates = new int[7];
bool isTesting = false;
using var plainScript = new MyPlainScript();
plainScript.UpdateCallbacks = new NetworkUpdateCallbacks
{
OnInitialization = () =>
{
if (isTesting)
{
netUpdates[kNetInitializationIndex]++;
}
},
OnEarlyUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetEarlyUpdateIndex]++;
}
},
OnFixedUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetFixedUpdateIndex]++;
}
},
OnPreUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPreUpdateIndex]++;
}
},
OnUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetUpdateIndex]++;
}
},
OnPreLateUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPreLateUpdateIndex]++;
}
},
OnPostLateUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPostLateUpdateIndex]++;
}
}
};
plainScript.Initialize();
int nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
isTesting = true;
const int kRunTotalFrames = 16;
int waitFrameNumber = Time.frameCount + kRunTotalFrames;
yield return new WaitUntil(() => Time.frameCount >= waitFrameNumber);
Assert.AreEqual(0, netUpdates[kNetInitializationIndex]);
Assert.AreEqual(kRunTotalFrames, netUpdates[kNetEarlyUpdateIndex]);
Assert.AreEqual(0, netUpdates[kNetFixedUpdateIndex]);
Assert.AreEqual(0, netUpdates[kNetPreUpdateIndex]);
Assert.AreEqual(0, netUpdates[kNetUpdateIndex]);
Assert.AreEqual(kRunTotalFrames, netUpdates[kNetPreLateUpdateIndex]);
Assert.AreEqual(0, netUpdates[kNetPostLateUpdateIndex]);
}
private struct MonoBehaviourCallbacks
{
public Action OnFixedUpdate;
public Action OnUpdate;
public Action OnLateUpdate;
}
private class MyGameScript : MonoBehaviour, INetworkUpdateSystem
{
public NetworkUpdateCallbacks UpdateCallbacks;
public MonoBehaviourCallbacks BehaviourCallbacks;
private void Awake()
{
this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
// intentionally try to register for 'PreUpdate' stage twice
// it should be ignored and the instance should not be registered twice
// otherwise test would fail because it would call 'OnPreUpdate()' twice
// which would ultimately increment 'netUpdates[idx]' integer twice
// and cause 'Assert.AreEqual()' to fail the test
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
}
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
switch (updateStage)
{
case NetworkUpdateStage.FixedUpdate:
UpdateCallbacks.OnFixedUpdate();
break;
case NetworkUpdateStage.PreUpdate:
UpdateCallbacks.OnPreUpdate();
break;
case NetworkUpdateStage.PreLateUpdate:
UpdateCallbacks.OnPreLateUpdate();
break;
case NetworkUpdateStage.PostLateUpdate:
UpdateCallbacks.OnPostLateUpdate();
break;
}
}
private void FixedUpdate()
{
BehaviourCallbacks.OnFixedUpdate();
}
private void Update()
{
BehaviourCallbacks.OnUpdate();
}
private void LateUpdate()
{
BehaviourCallbacks.OnLateUpdate();
}
private void OnDestroy()
{
this.UnregisterAllNetworkUpdates();
}
}
[UnityTest]
public IEnumerator UpdateStagesMixed()
{
const int kNetFixedUpdateIndex = 0;
const int kNetPreUpdateIndex = 1;
const int kNetPreLateUpdateIndex = 2;
const int kNetPostLateUpdateIndex = 3;
int[] netUpdates = new int[4];
const int kMonoFixedUpdateIndex = 0;
const int kMonoUpdateIndex = 1;
const int kMonoLateUpdateIndex = 2;
int[] monoUpdates = new int[3];
bool isTesting = false;
{
var gameObject = new GameObject($"{nameof(NetworkUpdateLoopTests)}.{nameof(UpdateStagesMixed)} (Dummy)");
var gameScript = gameObject.AddComponent<MyGameScript>();
gameScript.UpdateCallbacks = new NetworkUpdateCallbacks
{
OnFixedUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetFixedUpdateIndex]++;
Assert.AreEqual(monoUpdates[kMonoFixedUpdateIndex] + 1, netUpdates[kNetFixedUpdateIndex]);
}
},
OnPreUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPreUpdateIndex]++;
Assert.AreEqual(monoUpdates[kMonoUpdateIndex] + 1, netUpdates[kNetPreUpdateIndex]);
}
},
OnPreLateUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPreLateUpdateIndex]++;
Assert.AreEqual(monoUpdates[kMonoLateUpdateIndex] + 1, netUpdates[kNetPreLateUpdateIndex]);
}
},
OnPostLateUpdate = () =>
{
if (isTesting)
{
netUpdates[kNetPostLateUpdateIndex]++;
Assert.AreEqual(netUpdates[kNetPostLateUpdateIndex], netUpdates[kNetPreLateUpdateIndex]);
}
}
};
gameScript.BehaviourCallbacks = new MonoBehaviourCallbacks
{
OnFixedUpdate = () =>
{
if (isTesting)
{
monoUpdates[kMonoFixedUpdateIndex]++;
Assert.AreEqual(netUpdates[kNetFixedUpdateIndex], monoUpdates[kMonoFixedUpdateIndex]);
}
},
OnUpdate = () =>
{
if (isTesting)
{
monoUpdates[kMonoUpdateIndex]++;
Assert.AreEqual(netUpdates[kNetPreUpdateIndex], monoUpdates[kMonoUpdateIndex]);
}
},
OnLateUpdate = () =>
{
if (isTesting)
{
monoUpdates[kMonoLateUpdateIndex]++;
Assert.AreEqual(netUpdates[kNetPreLateUpdateIndex], monoUpdates[kMonoLateUpdateIndex]);
}
}
};
int nextFrameNumber = Time.frameCount + 1;
yield return new WaitUntil(() => Time.frameCount >= nextFrameNumber);
isTesting = true;
const int kRunTotalFrames = 16;
int waitFrameNumber = Time.frameCount + kRunTotalFrames;
yield return new WaitUntil(() => Time.frameCount >= waitFrameNumber);
Assert.AreEqual(kRunTotalFrames, netUpdates[kNetPreUpdateIndex]);
Assert.AreEqual(netUpdates[kNetPreUpdateIndex], monoUpdates[kMonoUpdateIndex]);
UnityEngine.Object.DestroyImmediate(gameObject);
}
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ffdb76de4852b3447b8cd673f0ca3ade
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,135 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkVarBufferCopyTest : BaseMultiInstanceTest
{
public class DummyNetVar : NetworkVariableBase
{
private const int k_DummyValue = 0x13579BDF;
public bool DeltaWritten;
public bool FieldWritten;
public bool DeltaRead;
public bool FieldRead;
public bool Dirty = true;
public override void ResetDirty()
{
Dirty = false;
}
public override bool IsDirty()
{
return Dirty;
}
public override void WriteDelta(FastBufferWriter writer)
{
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(k_DummyValue) + 1);
using (var bitWriter = writer.EnterBitwiseContext())
{
bitWriter.WriteBits((byte)1, 1);
}
writer.WriteValue(k_DummyValue);
DeltaWritten = true;
}
public override void WriteField(FastBufferWriter writer)
{
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(k_DummyValue) + 1);
using (var bitWriter = writer.EnterBitwiseContext())
{
bitWriter.WriteBits((byte)1, 1);
}
writer.WriteValue(k_DummyValue);
FieldWritten = true;
}
public override void ReadField(FastBufferReader reader)
{
reader.TryBeginRead(FastBufferWriter.GetWriteSize(k_DummyValue) + 1);
using (var bitReader = reader.EnterBitwiseContext())
{
bitReader.ReadBits(out byte b, 1);
}
reader.ReadValue(out int i);
Assert.AreEqual(k_DummyValue, i);
FieldRead = true;
}
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
reader.TryBeginRead(FastBufferWriter.GetWriteSize(k_DummyValue) + 1);
using (var bitReader = reader.EnterBitwiseContext())
{
bitReader.ReadBits(out byte b, 1);
}
reader.ReadValue(out int i);
Assert.AreEqual(k_DummyValue, i);
DeltaRead = true;
}
}
public class DummyNetBehaviour : NetworkBehaviour
{
public DummyNetVar NetVar;
}
protected override int NbClients => 1;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: true, nbClients: NbClients,
updatePlayerPrefab: playerPrefab =>
{
var dummyNetBehaviour = playerPrefab.AddComponent<DummyNetBehaviour>();
});
}
[UnityTest]
public IEnumerator TestEntireBufferIsCopiedOnNetworkVariableDelta()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId,
m_ClientNetworkManagers[0], clientClientPlayerResult));
var serverSideClientPlayer = serverClientPlayerResult.Result;
var clientSideClientPlayer = clientClientPlayerResult.Result;
var serverComponent = (serverSideClientPlayer).GetComponent<DummyNetBehaviour>();
var clientComponent = (clientSideClientPlayer).GetComponent<DummyNetBehaviour>();
var waitResult = new MultiInstanceHelpers.CoroutineResultWrapper<bool>();
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");
}
Assert.True(serverComponent.NetVar.FieldWritten);
Assert.True(serverComponent.NetVar.DeltaWritten);
Assert.True(clientComponent.NetVar.FieldRead);
Assert.True(clientComponent.NetVar.DeltaRead);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a7d44caa76a64b02978f5aca0e7b576a
timeCreated: 1627926008

View File

@@ -0,0 +1,418 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using Unity.Collections;
namespace Unity.Netcode.RuntimeTests
{
public struct TestStruct : INetworkSerializable
{
public uint SomeInt;
public bool SomeBool;
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref SomeInt);
serializer.SerializeValue(ref SomeBool);
}
}
public class NetworkVariableTest : NetworkBehaviour
{
public readonly NetworkVariable<int> TheScalar = new NetworkVariable<int>();
public readonly NetworkList<int> TheList = new NetworkList<int>();
public readonly NetworkVariable<FixedString32Bytes> FixedString32 = new NetworkVariable<FixedString32Bytes>();
private void ListChanged(NetworkListEvent<int> e)
{
ListDelegateTriggered = true;
}
public void Awake()
{
TheList.OnListChanged += ListChanged;
}
public readonly NetworkVariable<TestStruct> TheStruct = new NetworkVariable<TestStruct>();
public bool ListDelegateTriggered;
}
public class NetworkVariableTests : BaseMultiInstanceTest
{
private const string k_FixedStringTestValue = "abcdefghijklmnopqrstuvwxyz";
protected override int NbClients => 2;
private const uint k_TestUInt = 0x12345678;
private const int k_TestVal1 = 111;
private const int k_TestVal2 = 222;
private const int k_TestVal3 = 333;
private const int k_TestKey1 = 0x0f0f;
// Player1 component on the server
private NetworkVariableTest m_Player1OnServer;
// Player1 component on client1
private NetworkVariableTest m_Player1OnClient1;
private bool m_TestWithHost;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(useHost: m_TestWithHost, nbClients: NbClients,
updatePlayerPrefab: playerPrefab =>
{
playerPrefab.AddComponent<NetworkVariableTest>();
});
// These are the *SERVER VERSIONS* of the *CLIENT PLAYER 1 & 2*
var result = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId,
m_ServerNetworkManager, result));
m_Player1OnServer = result.Result.GetComponent<NetworkVariableTest>();
// This is client1's view of itself
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation(
x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId,
m_ClientNetworkManagers[0], result));
m_Player1OnClient1 = result.Result.GetComponent<NetworkVariableTest>();
m_Player1OnServer.TheList.Clear();
if (m_Player1OnServer.TheList.Count > 0)
{
throw new Exception("at least one server network container not empty at start");
}
if (m_Player1OnClient1.TheList.Count > 0)
{
throw new Exception("at least one client network container not empty at start");
}
}
/// <summary>
/// Runs generalized tests on all predefined NetworkVariable types
/// </summary>
[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,
// for now we put this within this one test until we migrate it to MIH
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _));
Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("NetworkVariableTestComponent");
var networkVariableTestComponent = NetworkManagerHelper.AddComponentToObject<NetworkVariableTestComponent>(gameObjectId);
NetworkManagerHelper.SpawnNetworkObject(gameObjectId);
// 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();
}
// Stop Testing
networkVariableTestComponent.EnableTesting = false;
Assert.IsTrue(networkVariableTestComponent.DidAllValuesChange());
// 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,
// 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)
{
m_TestWithHost = useHost;
// client must not be allowed to write to a server auth variable
Assert.Throws<InvalidOperationException>(() => m_Player1OnClient1.TheScalar.Value = k_TestVal1);
}
[UnityTest]
public IEnumerator FixedString32Test([Values(true, false)] bool useHost)
{
m_TestWithHost = useHost;
yield return MultiInstanceHelpers.RunAndWaitForCondition(
() =>
{
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;
}
);
}
[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;
}
);
}
[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_TestKey1) &&
m_Player1OnClient1.TheList.Contains(k_TestKey1);
}
);
}
[UnityTest]
public IEnumerator NetworkListRemoveValue([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;
}
);
}
[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;
}
);
}
[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;
}
);
}
[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;
}
);
}
[Test]
public void NetworkListIEnumerator([Values(true, false)] bool useHost)
{
m_TestWithHost = useHost;
var correctVals = new int[3];
correctVals[0] = k_TestVal1;
correctVals[1] = k_TestVal2;
correctVals[2] = k_TestVal3;
m_Player1OnServer.TheList.Add(correctVals[0]);
m_Player1OnServer.TheList.Add(correctVals[1]);
m_Player1OnServer.TheList.Add(correctVals[2]);
Assert.IsTrue(m_Player1OnServer.TheList.Count == 3);
int index = 0;
foreach (var val in m_Player1OnServer.TheList)
{
if (val != correctVals[index++])
{
Assert.Fail();
}
}
}
[UnityTest]
public IEnumerator NetworkListRemoveAt([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.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;
}
);
}
[UnityTest]
public IEnumerator NetworkListClear([Values(true, false)] bool useHost)
{
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;
}
);
}
[UnityTest]
public IEnumerator TestNetworkVariableStruct([Values(true, false)] bool useHost)
{
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;
}
);
}
[UnityTearDown]
public override IEnumerator Teardown()
{
yield return base.Teardown();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d271d8738dbbb5e4aa0cae91b663f183
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58745956b2a1e8346a48c5179cb247eb
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,81 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Physics
{
public class NetworkRigidbody2DDynamicTest : NetworkRigidbody2DTestBase
{
public override bool Kinematic => false;
}
public class NetworkRigidbody2DKinematicTest : NetworkRigidbody2DTestBase
{
public override bool Kinematic => true;
}
public abstract class NetworkRigidbody2DTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 1;
public abstract bool Kinematic { get; }
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
playerPrefab.AddComponent<NetworkTransform>();
playerPrefab.AddComponent<Rigidbody2D>();
playerPrefab.AddComponent<NetworkRigidbody2D>();
playerPrefab.GetComponent<Rigidbody2D>().interpolation = RigidbodyInterpolation2D.Interpolate;
playerPrefab.GetComponent<Rigidbody2D>().isKinematic = Kinematic;
});
}
/// <summary>
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestRigidbodyKinematicEnableDisable()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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);
// server rigidbody has authority and should have a kinematic mode of false
Assert.True(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);
Assert.AreEqual(RigidbodyInterpolation2D.Interpolate, serverPlayer.GetComponent<Rigidbody2D>().interpolation);
// client rigidbody has no authority and should have a kinematic mode of true
Assert.True(clientPlayer.GetComponent<Rigidbody2D>().isKinematic);
Assert.AreEqual(RigidbodyInterpolation2D.None, clientPlayer.GetComponent<Rigidbody2D>().interpolation);
// despawn the server player, (but keep it around on the server)
serverPlayer.GetComponent<NetworkObject>().Despawn(false);
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
Assert.IsTrue(serverPlayer.GetComponent<Rigidbody2D>().isKinematic == Kinematic);
yield return NetworkRigidbodyTestBase.WaitForFrames(5);
Assert.IsTrue(clientPlayer == null); // safety check that object is actually despawned.
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f33b2f298cbb7b248b2a76ba48ee1d53
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,86 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Physics
{
public class NetworkRigidbodyDynamicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => false;
}
public class NetworkRigidbodyKinematicTest : NetworkRigidbodyTestBase
{
public override bool Kinematic => true;
}
public abstract class NetworkRigidbodyTestBase : BaseMultiInstanceTest
{
protected override int NbClients => 1;
public abstract bool Kinematic { get; }
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
playerPrefab.AddComponent<NetworkTransform>();
playerPrefab.AddComponent<Rigidbody>();
playerPrefab.AddComponent<NetworkRigidbody>();
playerPrefab.GetComponent<Rigidbody>().interpolation = RigidbodyInterpolation.Interpolate;
playerPrefab.GetComponent<Rigidbody>().isKinematic = Kinematic;
});
}
/// <summary>
/// Tests that a server can destroy a NetworkObject and that it gets despawned correctly.
/// </summary>
/// <returns></returns>
[UnityTest]
public IEnumerator TestRigidbodyKinematicEnableDisable()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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 WaitForFrames(5);
// server rigidbody has authority and should have a kinematic mode of false
Assert.True(serverPlayer.GetComponent<Rigidbody>().isKinematic == Kinematic);
Assert.AreEqual(RigidbodyInterpolation.Interpolate, serverPlayer.GetComponent<Rigidbody>().interpolation);
// client rigidbody has no authority and should have a kinematic mode of true
Assert.True(clientPlayer.GetComponent<Rigidbody>().isKinematic);
Assert.AreEqual(RigidbodyInterpolation.None, clientPlayer.GetComponent<Rigidbody>().interpolation);
// despawn the server player (but keep it around on the server)
serverPlayer.GetComponent<NetworkObject>().Despawn(false);
yield return WaitForFrames(5);
Assert.IsTrue(serverPlayer.GetComponent<Rigidbody>().isKinematic == Kinematic);
yield return WaitForFrames(5);
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);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 344ba3f81f24b6d40afb926333fd118d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 6b613c8dd4ba7d046b62971a3cb631ed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,38 @@
using System;
using NUnit.Framework;
namespace Unity.Netcode.RuntimeTests
{
public sealed class NetworkVariableNameTests
{
private NetworkVariableNameComponent m_NetworkVariableNameComponent;
[SetUp]
public void SetUp()
{
NetworkManagerHelper.StartNetworkManager(out _);
var gameObjectId = NetworkManagerHelper.AddGameNetworkObject(Guid.NewGuid().ToString());
m_NetworkVariableNameComponent = NetworkManagerHelper.AddComponentToObject<NetworkVariableNameComponent>(gameObjectId);
NetworkManagerHelper.SpawnNetworkObject(gameObjectId);
}
[TearDown]
public void TearDown()
{
NetworkManagerHelper.ShutdownNetworkManager();
}
[Test]
public void VerifyNetworkVariableNameInitialization()
{
// Fields have regular naming
Assert.AreEqual(nameof(NetworkVariableNameComponent.NetworkVarList), m_NetworkVariableNameComponent.NetworkVarList.Name);
}
private class NetworkVariableNameComponent : NetworkBehaviour
{
public NetworkList<ulong> NetworkVarList = new NetworkList<ulong>();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e10a16550007020409234a1efb1fa383
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,148 @@
using System;
using System.Collections.Generic;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// Used in conjunction with the RpcQueueTest to validate:
/// - Sending and Receiving pipeline to validate that both sending and receiving pipelines are functioning properly.
/// - Usage of the ServerRpcParams.Send.UpdateStage and ClientRpcParams.Send.UpdateStage functionality.
/// - Rpcs receive will be invoked at the appropriate NetworkUpdateStage.
/// </summary>
public class RpcPipelineTestComponent : NetworkBehaviour
{
/// <summary>
/// Allows the external RPCQueueTest to begin testing or stop it
/// </summary>
public bool PingSelfEnabled;
/// <summary>
/// How many times will we iterate through the various NetworkUpdateStage values?
/// (defaults to 2)
/// </summary>
public int MaxIterations = 2;
// Start is called before the first frame update
private void Start()
{
m_MaxStagesSent = Enum.GetValues(typeof(NetworkUpdateStage)).Length * MaxIterations;
//Start out with this being true (for first sequence)
m_ClientReceivedRpc = true;
}
/// <summary>
/// Determine if we have iterated over more than our maximum stages we want to test
/// </summary>
/// <returns>true or false (did we exceed the max iterations or not?)</returns>
public bool ExceededMaxIterations()
{
if (m_StagesSent.Count > m_MaxStagesSent && m_MaxStagesSent > 0)
{
return true;
}
return false;
}
/// <summary>
/// Returns back whether the test has completed the total number of iterations
/// </summary>
/// <returns></returns>
public bool IsTestComplete()
{
if (m_Counter >= MaxIterations)
{
return true;
}
return false;
}
private bool m_ClientReceivedRpc;
private int m_Counter = 0;
private int m_MaxStagesSent = 0;
private ServerRpcParams m_ServerParams;
private ClientRpcParams m_ClientParams;
private NetworkUpdateStage m_LastUpdateStage;
// Update is called once per frame
private void Update()
{
if (NetworkManager.Singleton.IsListening && PingSelfEnabled && m_ClientReceivedRpc)
{
//Reset this for the next sequence of rpcs
m_ClientReceivedRpc = false;
//As long as testing isn't completed, keep testing
if (!IsTestComplete())
{
PingMySelfServerRpc(m_StagesSent.Count, m_ServerParams);
}
}
}
private readonly List<NetworkUpdateStage> m_ServerStagesReceived = new List<NetworkUpdateStage>();
private readonly List<NetworkUpdateStage> m_ClientStagesReceived = new List<NetworkUpdateStage>();
private readonly List<NetworkUpdateStage> m_StagesSent = new List<NetworkUpdateStage>();
/// <summary>
/// Assures all update stages were in alginment with one another
/// </summary>
/// <returns>true or false</returns>
public bool ValidateUpdateStages()
{
var validated = false;
if (m_ServerStagesReceived.Count == m_ClientStagesReceived.Count && m_ClientStagesReceived.Count == m_StagesSent.Count)
{
for (int i = 0; i < m_StagesSent.Count; i++)
{
var currentStage = m_StagesSent[i];
if (m_ServerStagesReceived[i] != currentStage)
{
Debug.Log($"ServerRpc Update Stage ({m_ServerStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
return validated;
}
if (m_ClientStagesReceived[i] != currentStage)
{
Debug.Log($"ClientRpc Update Stage ({m_ClientStagesReceived[i]}) is not equal to the current update stage ({currentStage})");
return validated;
}
}
validated = true;
}
return validated;
}
/// <summary>
/// Server side RPC for testing
/// </summary>
/// <param name="parameters">server rpc parameters</param>
[ServerRpc]
private void PingMySelfServerRpc(int currentCount, ServerRpcParams parameters = default)
{
Debug.Log($"{nameof(PingMySelfServerRpc)}: [HostClient][ServerRpc][{currentCount}] invoked.");
PingMySelfClientRpc(currentCount, m_ClientParams);
}
/// <summary>
/// Client Side RPC called by PingMySelfServerRPC to validate both Client->Server and Server-Client pipeline is working
/// </summary>
/// <param name="parameters">client rpc parameters</param>
[ClientRpc]
private void PingMySelfClientRpc(int currentCount, ClientRpcParams parameters = default)
{
Debug.Log($"{nameof(PingMySelfClientRpc)}: [HostServer][ClientRpc][{currentCount}] invoked. (previous output line should confirm this)");
m_ClientReceivedRpc = true;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e946a6fdcfcb9dd48b76b38871c0a77b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,66 @@
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
namespace Unity.Netcode.RuntimeTests
{
/// <summary>
/// The RpcQueue unit tests validate:
/// - Maximum buffer size that can be sent (currently 1MB is the default maximum `MessageQueueHistoryFrame` size)
/// - That all RPCs invoke at the appropriate `NetworkUpdateStage` (Client and Server)
/// - A lower level `MessageQueueContainer` test that validates `MessageQueueFrameItems` after they have been put into the queue
/// </summary>
public class RpcQueueTests
{
[SetUp]
public void Setup()
{
// Create, instantiate, and host
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _));
}
/// <summary>
/// This tests the RPC Queue outbound and inbound buffer capabilities.
/// </summary>
/// <returns>IEnumerator</returns>
[UnityTest, Order(2)]
public IEnumerator BufferDataValidation()
{
Guid gameObjectId = NetworkManagerHelper.AddGameNetworkObject("GrowingBufferObject");
var growingRpcBufferSizeComponent = NetworkManagerHelper.AddComponentToObject<BufferDataValidationComponent>(gameObjectId);
NetworkManagerHelper.SpawnNetworkObject(gameObjectId);
// Start Testing
growingRpcBufferSizeComponent.EnableTesting = true;
var testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete();
// Wait for the RPC pipeline test to complete or if we exceeded the maximum iterations bail
while (!testsAreComplete)
{
yield return new WaitForSeconds(0.003f);
testsAreComplete = growingRpcBufferSizeComponent.IsTestComplete();
}
// Stop Testing
growingRpcBufferSizeComponent.EnableTesting = false;
// Just disable this once we are done.
growingRpcBufferSizeComponent.gameObject.SetActive(false);
Assert.IsTrue(testsAreComplete);
}
[TearDown]
public void TearDown()
{
// Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 9a96d73d8acaa2a4aadc4e6be8b10c7b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

95
Tests/Runtime/RpcTests.cs Normal file
View File

@@ -0,0 +1,95 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine.TestTools;
using Debug = UnityEngine.Debug;
namespace Unity.Netcode.RuntimeTests
{
public class RpcTests : BaseMultiInstanceTest
{
public class RpcTestNB : NetworkBehaviour
{
public event Action OnServer_Rpc;
public event Action OnClient_Rpc;
[ServerRpc]
public void MyServerRpc()
{
OnServer_Rpc();
}
[ClientRpc]
public void MyClientRpc()
{
OnClient_Rpc();
}
}
protected override int NbClients => 1;
[UnitySetUp]
public override IEnumerator Setup()
{
yield return StartSomeClientsAndServerWithPlayers(true, NbClients, playerPrefab =>
{
playerPrefab.AddComponent<RpcTestNB>();
});
}
[UnityTest]
public IEnumerator TestRpcs()
{
// This is the *SERVER VERSION* of the *CLIENT PLAYER*
var serverClientPlayerResult = new MultiInstanceHelpers.CoroutineResultWrapper<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.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<NetworkObject>();
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.GetNetworkObjectByRepresentation((x => x.IsPlayerObject && x.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId), m_ClientNetworkManagers[0], clientClientPlayerResult));
// Setup state
bool hasReceivedServerRpc = false;
bool hasReceivedClientRpcRemotely = false;
bool hasReceivedClientRpcLocally = false;
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnClient_Rpc += () =>
{
Debug.Log("ClientRpc received on client object");
hasReceivedClientRpcRemotely = true;
};
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () =>
{
// The RPC invoked locally. (Weaver failure?)
Assert.Fail("ServerRpc invoked locally. Weaver failure?");
};
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnServer_Rpc += () =>
{
Debug.Log("ServerRpc received on server object");
hasReceivedServerRpc = true;
};
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().OnClient_Rpc += () =>
{
// The RPC invoked locally. (Weaver failure?)
Debug.Log("ClientRpc received on server object");
hasReceivedClientRpcLocally = true;
};
// Send ServerRpc
clientClientPlayerResult.Result.GetComponent<RpcTestNB>().MyServerRpc();
// Send ClientRpc
serverClientPlayerResult.Result.GetComponent<RpcTestNB>().MyClientRpc();
// Wait for RPCs to be received
yield return MultiInstanceHelpers.Run(MultiInstanceHelpers.WaitForCondition(() => hasReceivedServerRpc && hasReceivedClientRpcLocally && hasReceivedClientRpcRemotely));
Assert.True(hasReceivedServerRpc, "ServerRpc was not received");
Assert.True(hasReceivedClientRpcLocally, "ClientRpc was not locally received on the server");
Assert.True(hasReceivedClientRpcRemotely, "ClientRpc was not remotely received on the client");
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 46681ff78c6154f87acf5e1cccbfc0e1
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 90d51da7691e302498265bba08c43636
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,151 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests.Serialization
{
/// <summary>
/// Unit tests to test:
/// - Serializing NetworkObject to NetworkObjectReference
/// - Deserializing NetworkObjectReference to NetworkObject
/// - Implicit operators of NetworkObjectReference
/// </summary>
public class NetworkBehaviourReferenceTests : IDisposable
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public NetworkVariable<NetworkBehaviourReference> TestVariable = new NetworkVariable<NetworkBehaviourReference>();
public TestNetworkBehaviour RpcReceivedBehaviour;
[ServerRpc]
public void SendReferenceServerRpc(NetworkBehaviourReference value)
{
RpcReceivedBehaviour = (TestNetworkBehaviour)value;
}
}
[UnityTest]
public IEnumerator TestRpc()
{
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
using var otherObjectContext = UnityObjectContext.CreateNetworkObject();
otherObjectContext.Object.Spawn();
testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(testNetworkBehaviour));
// wait for rpc completion
float t = 0;
while (testNetworkBehaviour.RpcReceivedBehaviour == null)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[UnityTest]
public IEnumerator TestRpcImplicitNetworkBehaviour()
{
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
using var otherObjectContext = UnityObjectContext.CreateNetworkObject();
otherObjectContext.Object.Spawn();
testNetworkBehaviour.SendReferenceServerRpc(testNetworkBehaviour);
// wait for rpc completion
float t = 0;
while (testNetworkBehaviour.RpcReceivedBehaviour == null)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[Test]
public void TestNetworkVariable()
{
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
using var otherObjectContext = UnityObjectContext.CreateNetworkObject();
otherObjectContext.Object.Spawn();
// check default value is null
Assert.IsNull((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value);
testNetworkBehaviour.TestVariable.Value = testNetworkBehaviour;
Assert.AreEqual((NetworkBehaviour)testNetworkBehaviour.TestVariable.Value, testNetworkBehaviour);
}
[Test]
public void FailSerializeNonSpawnedNetworkObject()
{
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var component = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
Assert.Throws<ArgumentException>(() =>
{
NetworkBehaviourReference outReference = component;
});
}
[Test]
public void FailSerializeGameObjectWithoutNetworkObject()
{
using var gameObjectContext = UnityObjectContext.CreateGameObject();
var component = gameObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
Assert.Throws<ArgumentException>(() =>
{
NetworkBehaviourReference outReference = component;
});
}
[Test]
public void FailSerializeNullBehaviour()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkBehaviourReference outReference = null;
});
}
public void Dispose()
{
//Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
}
public NetworkBehaviourReferenceTests()
{
//Create, instantiate, and host
NetworkManagerHelper.StartNetworkManager(out _);
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 0fca807d195f9fc49a400cfce86b085d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

Some files were not shown because too many files have changed in this diff Show More