com.unity.netcode.gameobjects@1.4.0

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.4.0] - 2023-04-10

### Added

- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)

### Changed

- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)

### Fixed

- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
This commit is contained in:
Unity Technologies
2023-04-10 00:00:00 +00:00
parent 8060718e04
commit 0967bfe232
138 changed files with 7892 additions and 1852 deletions

View File

@@ -6,6 +6,55 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.4.0] - 2023-04-10
### Added
- Added a way to access the GlobalObjectIdHash via PrefabIdHash for use in the Connection Approval Callback. (#2437)
- Added `OnServerStarted` and `OnServerStopped` events that will trigger only on the server (or host player) to notify that the server just started or is no longer active (#2420)
- Added `OnClientStarted` and `OnClientStopped` events that will trigger only on the client (or host player) to notify that the client just started or is no longer active (#2420)
- Added `NetworkTransform.UseHalfFloatPrecision` property that, when enabled, will use half float values for position, rotation, and scale. This yields a 50% bandwidth savings a the cost of precision. (#2388)
- Added `NetworkTransform.UseQuaternionSynchronization` property that, when enabled, will synchronize the entire quaternion. (#2388)
- Added `NetworkTransform.UseQuaternionCompression` property that, when enabled, will use a smallest three implementation reducing a full quaternion synchronization update to the size of an unsigned integer. (#2388)
- Added `NetworkTransform.SlerpPosition` property that, when enabled along with interpolation being enabled, will interpolate using `Vector3.Slerp`. (#2388)
- Added `BufferedLinearInterpolatorVector3` that replaces the float version, is now used by `NetworkTransform`, and provides the ability to enable or disable `Slerp`. (#2388)
- Added `HalfVector3` used for scale when half float precision is enabled. (#2388)
- Added `HalfVector4` used for rotation when half float precision and quaternion synchronization is enabled. (#2388)
- Added `HalfVector3DeltaPosition` used for position when half float precision is enabled. This handles loss in position precision by updating only the delta position as opposed to the full position. (#2388)
- Added `NetworkTransform.GetSpaceRelativePosition` and `NetworkTransform.GetSpaceRelativeRotation` helper methods to return the proper values depending upon whether local or world space. (#2388)
- Added `NetworkTransform.OnAuthorityPushTransformState` virtual method that is invoked just prior to sending the `NetworkTransformState` to non-authoritative instances. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnNetworkTransformStateUpdated` virtual method that is invoked just after the authoritative `NetworkTransformState` is applied. This provides users with the ability to obtain more precise delta values for prediction related calculations. (#2388)
- Added `NetworkTransform.OnInitialize`virtual method that is invoked after the `NetworkTransform` has been initialized or re-initialized when ownership changes. This provides for a way to make adjustments when `NetworkTransform` is initialized (i.e. resetting client prediction etc) (#2388)
- Added `NetworkObject.SynchronizeTransform` property (default is true) that provides users with another way to help with bandwidth optimizations where, when set to false, the `NetworkObject`'s associated transform will not be included when spawning and/or synchronizing late joining players. (#2388)
- Added `NetworkSceneManager.ActiveSceneSynchronizationEnabled` property, disabled by default, that enables client synchronization of server-side active scene changes. (#2383)
- Added `NetworkObject.ActiveSceneSynchronization`, disabled by default, that will automatically migrate a `NetworkObject` to a newly assigned active scene. (#2383)
- Added `NetworkObject.SceneMigrationSynchronization`, enabled by default, that will synchronize client(s) when a `NetworkObject` is migrated into a new scene on the server side via `SceneManager.MoveGameObjectToScene`. (#2383)
### Changed
- Made sure the `CheckObjectVisibility` delegate is checked and applied, upon `NetworkShow` attempt. Found while supporting (#2454), although this is not a fix for this (already fixed) issue. (#2463)
- Changed `NetworkTransform` authority handles delta checks on each new network tick and no longer consumes processing cycles checking for deltas for all frames in-between ticks. (#2388)
- Changed the `NetworkTransformState` structure is now public and now has public methods that provide access to key properties of the `NetworkTransformState` structure. (#2388)
- Changed `NetworkTransform` interpolation adjusts its interpolation "ticks ago" to be 2 ticks latent if it is owner authoritative and the instance is not the server or 1 tick latent if the instance is the server and/or is server authoritative. (#2388)
- Updated `NetworkSceneManager` to migrate dynamically spawned `NetworkObject`s with `DestroyWithScene` set to false into the active scene if their current scene is unloaded. (#2383)
- Updated the server to synchronize its local `NetworkSceneManager.ClientSynchronizationMode` during the initial client synchronization. (#2383)
### Fixed
- Fixed issue where during client synchronization the synchronizing client could receive a ObjectSceneChanged message before the client-side NetworkObject instance had been instantiated and spawned. (#2502)
- Fixed issue where `NetworkAnimator` was building client RPC parameters to exclude the host from sending itself messages but was not including it in the ClientRpc parameters. (#2492)
- Fixed issue where `NetworkAnimator` was not properly detecting and synchronizing cross fade initiated transitions. (#2481)
- Fixed issue where `NetworkAnimator` was not properly synchronizing animation state updates. (#2481)
- Fixed float NetworkVariables not being rendered properly in the inspector of NetworkObjects. (#2441)
- Fixed an issue where Named Message Handlers could remove themselves causing an exception when the metrics tried to access the name of the message.(#2426)
- Fixed registry of public `NetworkVariable`s in derived `NetworkBehaviour`s (#2423)
- Fixed issue where runtime association of `Animator` properties to `AnimationCurve`s would cause `NetworkAnimator` to attempt to update those changes. (#2416)
- Fixed issue where `NetworkAnimator` would not check if its associated `Animator` was valid during serialization and would spam exceptions in the editor console. (#2416)
- Fixed issue with a child's rotation rolling over when interpolation is enabled on a `NetworkTransform`. Now using half precision or full quaternion synchronization will always update all axis. (#2388)
- Fixed issue where `NetworkTransform` was not setting the teleport flag when the `NetworkTransform.InLocalSpace` value changed. This issue only impacted `NetworkTransform` when interpolation was enabled. (#2388)
- Fixed issue when the `NetworkSceneManager.ClientSynchronizationMode` is `LoadSceneMode.Additive` and the server changes the currently active scene prior to a client connecting then upon a client connecting and being synchronized the NetworkSceneManager would clear its internal ScenePlacedObjects list that could already be populated. (#2383)
- Fixed issue where a client would load duplicate scenes of already preloaded scenes during the initial client synchronization and `NetworkSceneManager.ClientSynchronizationMode` was set to `LoadSceneMode.Additive`. (#2383)
## [1.3.1] - 2023-03-27
### Added

159
Components/HalfVector3.cs Normal file
View File

@@ -0,0 +1,159 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Half float precision <see cref="Vector3"/>.
/// </summary>
/// <remarks>
/// The Vector3T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type.
/// </remarks>
public struct HalfVector3 : INetworkSerializable
{
internal const int Length = 3;
/// <summary>
/// The half float precision value of the x-axis as a <see cref="half"/>.
/// </summary>
public half X => Axis.x;
/// <summary>
/// The half float precision value of the y-axis as a <see cref="half"/>.
/// </summary>
public half Y => Axis.y;
/// <summary>
/// The half float precision value of the z-axis as a <see cref="half"/>.
/// </summary>
public half Z => Axis.x;
/// <summary>
/// Used to store the half float precision values as a <see cref="half3"/>
/// </summary>
public half3 Axis;
/// <summary>
/// Determine which axis will be synchronized during serialization
/// </summary>
public bool3 AxisToSynchronize;
private void SerializeWrite(FastBufferWriter writer)
{
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
writer.WriteUnmanagedSafe(Axis[i]);
}
}
}
private void SerializeRead(FastBufferReader reader)
{
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
var axisValue = Axis[i];
reader.ReadUnmanagedSafe(out axisValue);
Axis[i] = axisValue;
}
}
}
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>.
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsReader)
{
SerializeRead(serializer.GetFastBufferReader());
}
else
{
SerializeWrite(serializer.GetFastBufferWriter());
}
}
/// <summary>
/// Gets the full precision value as a <see cref="Vector3"/>.
/// </summary>
/// <returns>a <see cref="Vector3"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3()
{
Vector3 fullPrecision = Vector3.zero;
Vector3 fullConversion = math.float3(Axis);
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
fullPrecision[i] = fullConversion[i];
}
}
return fullPrecision;
}
/// <summary>
/// Converts a full precision <see cref="Vector3"/> to half precision and updates the current instance.
/// </summary>
/// <param name="vector3">The <see cref="Vector3"/> to convert.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector3 vector3)
{
var half3Full = math.half3(vector3);
for (int i = 0; i < Length; i++)
{
if (AxisToSynchronize[i])
{
Axis[i] = half3Full[i];
}
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="vector3AxisToSynchronize">The axis to synchronize.</param>
public HalfVector3(Vector3 vector3, bool3 axisToSynchronize)
{
Axis = half3.zero;
AxisToSynchronize = axisToSynchronize;
UpdateFrom(ref vector3);
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
public HalfVector3(Vector3 vector3) : this(vector3, math.bool3(true))
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="axisToSynchronize">The axis to synchronize.</param>
public HalfVector3(float x, float y, float z, bool3 axisToSynchronize) : this(new Vector3(x, y, z), axisToSynchronize)
{
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
public HalfVector3(float x, float y, float z) : this(new Vector3(x, y, z), math.bool3(true))
{
}
}
}

View File

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

137
Components/HalfVector4.cs Normal file
View File

@@ -0,0 +1,137 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Half Precision <see cref="Vector4"/> that can also be used to convert a <see cref="Quaternion"/> to half precision.
/// </summary>
/// <remarks>
/// The Vector4T<ushort> values are half float values returned by <see cref="Mathf.FloatToHalf(float)"/> for each
/// individual axis and the 16 bits of the half float are stored as <see cref="ushort"/> values since C# does not have
/// a half float type.
/// </remarks>
public struct HalfVector4 : INetworkSerializable
{
internal const int Length = 4;
/// <summary>
/// The half float precision value of the x-axis as a <see cref="half"/>.
/// </summary>
public half X => Axis.x;
/// <summary>
/// The half float precision value of the y-axis as a <see cref="half"/>.
/// </summary>
public half Y => Axis.y;
/// <summary>
/// The half float precision value of the z-axis as a <see cref="half"/>.
/// </summary>
public half Z => Axis.z;
/// <summary>
/// The half float precision value of the w-axis as a <see cref="half"/>.
/// </summary>
public half W => Axis.w;
/// <summary>
/// Used to store the half float precision values as a <see cref="half4"/>
/// </summary>
public half4 Axis;
private void SerializeWrite(FastBufferWriter writer)
{
for (int i = 0; i < Length; i++)
{
writer.WriteUnmanagedSafe(Axis[i]);
}
}
private void SerializeRead(FastBufferReader reader)
{
for (int i = 0; i < Length; i++)
{
var axisValue = Axis[i];
reader.ReadUnmanagedSafe(out axisValue);
Axis[i] = axisValue;
}
}
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>.
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
if (serializer.IsReader)
{
SerializeRead(serializer.GetFastBufferReader());
}
else
{
SerializeWrite(serializer.GetFastBufferWriter());
}
}
/// <summary>
/// Converts this instance to a full precision <see cref="Vector4"/>.
/// </summary>
/// <returns>A <see cref="Vector4"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector4 ToVector4()
{
return math.float4(Axis);
}
/// <summary>
/// Converts this instance to a full precision <see cref="Quaternion"/>.
/// </summary>
/// <returns>A <see cref="Quaternion"/> as the full precision value.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Quaternion ToQuaternion()
{
return math.quaternion(Axis);
}
/// <summary>
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
/// </summary>
/// <param name="vector4">The <see cref="Vector4"/> to convert and update this instance with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector4 vector4)
{
Axis = math.half4(vector4);
}
/// <summary>
/// Converts a full precision <see cref="Vector4"/> to half precision and updates the current instance.
/// </summary>
/// <param name="quaternion">The <see cref="Quaternion"/> to convert and update this instance with.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Quaternion quaternion)
{
Axis = math.half4(math.half(quaternion.x), math.half(quaternion.y), math.half(quaternion.z), math.half(quaternion.w));
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector4">The initial axial values (converted to half floats) when instantiated.</param>
public HalfVector4(Vector4 vector4)
{
Axis = default;
UpdateFrom(ref vector4);
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="w">The initial w axis (converted to half float) value when instantiated.</param>
public HalfVector4(float x, float y, float z, float w) : this(new Vector4(x, y, z, w))
{
}
}
}

View File

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

View File

@@ -312,20 +312,79 @@ namespace Unity.Netcode
/// </remarks>
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
{
/// <summary>
/// Use <see cref="Quaternion.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Quaternion.Lerp"/> when <see cref="false"/>
/// </summary>
/// <remarks>
/// When using half precision (due to the imprecision) using <see cref="Quaternion.Lerp"/> is
/// less processor intensive (i.e. precision is already "imprecise").
/// When using full precision (to maintain precision) using <see cref="Quaternion.Slerp"/> is
/// more processor intensive yet yields more precise results.
/// </remarks>
public bool IsSlerp;
/// <inheritdoc />
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
{
// Disabling Extrapolation:
// TODO: Add Jira Ticket
return Quaternion.Slerp(start, end, time);
if (IsSlerp)
{
return Quaternion.Slerp(start, end, time);
}
else
{
return Quaternion.Lerp(start, end, time);
}
}
/// <inheritdoc />
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
{
// Disabling Extrapolation:
// TODO: Add Jira Ticket
return Quaternion.Slerp(start, end, time);
if (IsSlerp)
{
return Quaternion.Slerp(start, end, time);
}
else
{
return Quaternion.Lerp(start, end, time);
}
}
}
/// <summary>
/// A <see cref="BufferedLinearInterpolator<T>"/> <see cref="Vector3"/> implementation.
/// </summary>
public class BufferedLinearInterpolatorVector3 : BufferedLinearInterpolator<Vector3>
{
/// <summary>
/// Use <see cref="Vector3.Slerp"/> when <see cref="true"/>.
/// Use <see cref="Vector3.Lerp"/> when <see cref="false"/>
/// </summary>
public bool IsSlerp;
/// <inheritdoc />
protected override Vector3 InterpolateUnclamped(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
/// <inheritdoc />
protected override Vector3 Interpolate(Vector3 start, Vector3 end, float time)
{
if (IsSlerp)
{
return Vector3.Slerp(start, end, time);
}
else
{
return Vector3.Lerp(start, end, time);
}
}
}
}

View File

@@ -23,6 +23,13 @@ namespace Unity.Netcode.Components
/// </summary>
private void FlushMessages()
{
foreach (var animationUpdate in m_SendAnimationUpdates)
{
m_NetworkAnimator.SendAnimStateClientRpc(animationUpdate.AnimationMessage, animationUpdate.ClientRpcParams);
}
m_SendAnimationUpdates.Clear();
foreach (var sendEntry in m_SendParameterUpdates)
{
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
@@ -64,9 +71,11 @@ namespace Unity.Netcode.Components
m_NetworkAnimator.UpdateParameters(ref parameterUpdate);
}
m_ProcessParameterUpdates.Clear();
var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative();
// Only owners check for Animator changes
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer)
// owners when owner authoritative or the server when server authoritative are the only instances that
// checks for Animator changes
if ((!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && m_NetworkAnimator.IsServer))
{
m_NetworkAnimator.CheckForAnimatorChanges();
}
@@ -157,11 +166,11 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Animator")]
[RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
{
[Serializable]
internal class TransitionStateinfo
{
public bool IsCrossFadeExit;
public int Layer;
public int OriginatingState;
public int DestinationState;
@@ -279,6 +288,11 @@ namespace Unity.Netcode.Components
{
return;
}
if (m_Animator == null)
{
return;
}
TransitionStateInfoList = new List<TransitionStateinfo>();
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
if (animatorController == null)
@@ -312,9 +326,19 @@ namespace Unity.Netcode.Components
internal float NormalizedTime;
internal int Layer;
internal float Weight;
internal float Duration;
// For synchronizing transitions
internal bool Transition;
internal bool CrossFade;
// Flags for bool states
private const byte k_IsTransition = 0x01;
private const byte k_IsCrossFade = 0x02;
// Used to serialize the bool states
private byte m_StateFlags;
// The StateHash is where the transition starts
// and the DestinationStateHash is the destination state
internal int DestinationStateHash;
@@ -324,65 +348,46 @@ namespace Unity.Netcode.Components
if (serializer.IsWriter)
{
var writer = serializer.GetFastBufferWriter();
var writeSize = FastBufferWriter.GetWriteSize(Transition);
writeSize += FastBufferWriter.GetWriteSize(StateHash);
writeSize += FastBufferWriter.GetWriteSize(NormalizedTime);
writeSize += FastBufferWriter.GetWriteSize(Layer);
writeSize += FastBufferWriter.GetWriteSize(Weight);
m_StateFlags = 0x00;
if (Transition)
{
writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
m_StateFlags |= k_IsTransition;
}
if (!writer.TryBeginWrite(writeSize))
if (CrossFade)
{
throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space.");
m_StateFlags |= k_IsCrossFade;
}
serializer.SerializeValue(ref m_StateFlags);
writer.WriteValue(Transition);
writer.WriteValue(StateHash);
writer.WriteValue(NormalizedTime);
writer.WriteValue(Layer);
writer.WriteValue(Weight);
BytePacker.WriteValuePacked(writer, StateHash);
BytePacker.WriteValuePacked(writer, Layer);
if (Transition)
{
writer.WriteValue(DestinationStateHash);
BytePacker.WriteValuePacked(writer, DestinationStateHash);
}
}
else
{
var reader = serializer.GetFastBufferReader();
// Begin reading the Transition flag
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition)))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
reader.ReadValue(out Transition);
serializer.SerializeValue(ref m_StateFlags);
Transition = (m_StateFlags & k_IsTransition) == k_IsTransition;
CrossFade = (m_StateFlags & k_IsCrossFade) == k_IsCrossFade;
// Now determine what remains to be read
var readSize = FastBufferWriter.GetWriteSize(StateHash);
readSize += FastBufferWriter.GetWriteSize(NormalizedTime);
readSize += FastBufferWriter.GetWriteSize(Layer);
readSize += FastBufferWriter.GetWriteSize(Weight);
ByteUnpacker.ReadValuePacked(reader, out StateHash);
ByteUnpacker.ReadValuePacked(reader, out Layer);
if (Transition)
{
readSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
ByteUnpacker.ReadValuePacked(reader, out DestinationStateHash);
}
}
// Now read the remaining information about this AnimationState
if (!reader.TryBeginRead(readSize))
{
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
}
serializer.SerializeValue(ref NormalizedTime);
serializer.SerializeValue(ref Weight);
reader.ReadValue(out StateHash);
reader.ReadValue(out NormalizedTime);
reader.ReadValue(out Layer);
reader.ReadValue(out Weight);
if (Transition)
{
reader.ReadValue(out DestinationStateHash);
}
// Cross fading includes the duration of the cross fade.
if (CrossFade)
{
serializer.SerializeValue(ref Duration);
}
}
}
@@ -565,8 +570,10 @@ namespace Unity.Netcode.Components
// We initialize the m_AnimationMessage for all instances in the event that
// ownership or authority changes during runtime.
m_AnimationMessage = new AnimationMessage();
m_AnimationMessage.AnimationStates = new List<AnimationState>();
m_AnimationMessage = new AnimationMessage
{
AnimationStates = new List<AnimationState>()
};
// Store off our current layer weights and create our animation
// state entries per layer.
@@ -588,17 +595,13 @@ namespace Unity.Netcode.Components
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
m_ParametersToUpdate = new List<int>(parameters.Length);
// Include all parameters including any controlled by an AnimationCurve as this could change during runtime.
// We ignore changes to any parameter controlled by an AnimationCurve when we are checking for changes in
// the Animator's parameters.
for (var i = 0; i < parameters.Length; i++)
{
var parameter = parameters[i];
if (m_Animator.IsParameterControlledByCurve(parameter.nameHash))
{
// we are ignoring parameters that are controlled by animation curves - syncing the layer
// states indirectly syncs the values that are driven by the animation curves
continue;
}
var cacheParam = new AnimatorParamCache
{
Type = UnsafeUtility.EnumToInt(parameter.type),
@@ -643,12 +646,22 @@ namespace Unity.Netcode.Components
/// <inheritdoc/>
public override void OnNetworkSpawn()
{
// If there is no assigned Animator then generate a server network warning (logged locally and if applicable on the server-host side as well).
if (m_Animator == null)
{
NetworkLog.LogWarningServer($"[{gameObject.name}][{nameof(NetworkAnimator)}] {nameof(Animator)} is not assigned! Animation synchronization will not work for this instance!");
}
if (IsServer)
{
m_ClientSendList = new List<ulong>(128);
m_ClientRpcParams = new ClientRpcParams();
m_ClientRpcParams.Send = new ClientRpcSendParams();
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_ClientRpcParams = new ClientRpcParams
{
Send = new ClientRpcSendParams
{
TargetClientIds = m_ClientSendList
}
};
}
// Create a handler for state changes
@@ -691,10 +704,7 @@ namespace Unity.Netcode.Components
for (int layer = 0; layer < m_Animator.layerCount; layer++)
{
var synchronizationStateInfo = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (SynchronizationStateInfo != null)
{
SynchronizationStateInfo.Add(synchronizationStateInfo);
}
SynchronizationStateInfo?.Add(synchronizationStateInfo);
var stateHash = synchronizationStateInfo.fullPathHash;
var normalizedTime = synchronizationStateInfo.normalizedTime;
var isInTransition = m_Animator.IsInTransition(layer);
@@ -767,11 +777,97 @@ namespace Unity.Netcode.Components
else
{
var parameters = new ParametersUpdateMessage();
var animationStates = new AnimationMessage();
var animationMessage = new AnimationMessage();
serializer.SerializeValue(ref parameters);
UpdateParameters(ref parameters);
serializer.SerializeValue(ref animationStates);
HandleAnimStateUpdate(ref animationStates);
serializer.SerializeValue(ref animationMessage);
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
}
/// <summary>
/// Checks for animation state changes in:
/// -Layer weights
/// -Cross fades
/// -Transitions
/// -Layer AnimationStates
/// </summary>
private void CheckForStateChange(int layer)
{
var stateChangeDetected = false;
var animState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
float layerWeightNow = m_Animator.GetLayerWeight(layer);
animState.CrossFade = false;
animState.Transition = false;
animState.NormalizedTime = 0.0f;
animState.Layer = layer;
animState.Duration = 0.0f;
animState.Weight = m_LayerWeights[layer];
animState.DestinationStateHash = 0;
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
stateChangeDetected = true;
animState.Weight = layerWeightNow;
}
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
AnimatorStateInfo nt = m_Animator.GetNextAnimatorStateInfo(layer);
if (tt.anyState && tt.fullPathHash == 0 && m_TransitionHash[layer] != nt.fullPathHash)
{
m_TransitionHash[layer] = nt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.DestinationStateHash = nt.fullPathHash; // Next state is the destination state for cross fade
animState.CrossFade = true;
animState.Transition = true;
animState.Duration = tt.duration;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Cross-Fade] To-Hash: {nt.fullPathHash} | TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) | SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
else
if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
animState.StateHash = tt.fullPathHash; // Transitioning from state
animState.CrossFade = false;
animState.Transition = true;
animState.NormalizedTime = tt.normalizedTime;
stateChangeDetected = true;
//Debug.Log($"[Transition] TI-Duration: ({tt.duration}) | TI-Norm: ({tt.normalizedTime}) | From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
else
{
if (st.fullPathHash != m_AnimationHash[layer])
{
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
animState.StateHash = st.fullPathHash;
animState.NormalizedTime = st.normalizedTime;
}
stateChangeDetected = true;
//Debug.Log($"[State] From-Hash: ({m_AnimationHash[layer]}) |SI-FPHash: ({st.fullPathHash}) | SI-Norm: ({st.normalizedTime})");
}
}
if (stateChangeDetected)
{
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animState;
m_AnimationMessage.IsDirtyCount++;
}
}
@@ -784,11 +880,6 @@ namespace Unity.Netcode.Components
/// </remarks>
internal void CheckForAnimatorChanges()
{
if (!IsSpawned || (!IsOwner && !IsServerAuthoritative()) || (IsServerAuthoritative() && !IsServer))
{
return;
}
if (CheckParametersChanged())
{
SendParametersUpdate();
@@ -803,9 +894,6 @@ namespace Unity.Netcode.Components
return;
}
int stateHash;
float normalizedTime;
// Reset the dirty count before checking for AnimationState updates
m_AnimationMessage.IsDirtyCount = 0;
@@ -815,26 +903,7 @@ namespace Unity.Netcode.Components
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
var totalSpeed = st.speed * st.speedMultiplier;
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
if (!CheckAnimStateChanged(out stateHash, out normalizedTime, layer))
{
continue;
}
// If we made it here, then we need to synchronize this layer's animation state.
// Get one of the preallocated AnimationState entries and populate it with the
// current layer's state.
var animationState = m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount];
animationState.Transition = false; // Only used during synchronization
animationState.StateHash = stateHash;
animationState.NormalizedTime = normalizedTime;
animationState.Layer = layer;
animationState.Weight = m_LayerWeights[layer];
// Apply the changes
m_AnimationMessage.AnimationStates[m_AnimationMessage.IsDirtyCount] = animationState;
m_AnimationMessage.IsDirtyCount++;
CheckForStateChange(layer);
}
// Send an AnimationMessage only if there are dirty AnimationStates to send
@@ -851,7 +920,7 @@ namespace Unity.Netcode.Components
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.LocalClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
SendAnimStateClientRpc(m_AnimationMessage);
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
}
}
}
@@ -885,7 +954,7 @@ namespace Unity.Netcode.Components
/// <summary>
/// Helper function to get the cached value
/// </summary>
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache)
private unsafe T GetValue<T>(ref AnimatorParamCache animatorParamCache)
{
T currentValue;
fixed (void* value = animatorParamCache.Value)
@@ -900,12 +969,20 @@ namespace Unity.Netcode.Components
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters
/// that have changed. Returns true if any parameters changed.
/// </summary>
unsafe private bool CheckParametersChanged()
private unsafe bool CheckParametersChanged()
{
m_ParametersToUpdate.Clear();
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
{
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), i);
// If a parameter gets controlled by a curve during runtime after initialization of NetworkAnimator
// then ignore changes to this parameter. We are not removing the parameter in the event that
// it no longer is controlled by a curve.
if (m_Animator.IsParameterControlledByCurve(cacheValue.Hash))
{
continue;
}
var hash = cacheValue.Hash;
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
{
@@ -941,52 +1018,6 @@ namespace Unity.Netcode.Components
return m_ParametersToUpdate.Count > 0;
}
/// <summary>
/// Checks if any of the Animator's states have changed
/// </summary>
private bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layer)
{
stateHash = 0;
normalizedTime = 0;
float layerWeightNow = m_Animator.GetLayerWeight(layer);
if (layerWeightNow != m_LayerWeights[layer])
{
m_LayerWeights[layer] = layerWeightNow;
return true;
}
if (m_Animator.IsInTransition(layer))
{
AnimatorTransitionInfo tt = m_Animator.GetAnimatorTransitionInfo(layer);
if (tt.fullPathHash != m_TransitionHash[layer])
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
m_AnimationHash[layer] = 0;
return true;
}
}
else
{
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
if (st.fullPathHash != m_AnimationHash[layer])
{
// first time in this animation state
if (m_AnimationHash[layer] != 0)
{
// came from another animation directly - from Play()
stateHash = st.fullPathHash;
normalizedTime = st.normalizedTime;
}
m_TransitionHash[layer] = 0;
m_AnimationHash[layer] = st.fullPathHash;
return true;
}
}
return false;
}
/// <summary>
/// Writes all of the Animator's parameters
/// This uses the m_ParametersToUpdate list to write out only
@@ -1110,14 +1141,14 @@ namespace Unity.Netcode.Components
}
// If there is no state transition then return
if (animationState.StateHash == 0)
if (animationState.StateHash == 0 && !animationState.Transition)
{
return;
}
var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer);
// If it is a transition, then we are synchronizing transitions in progress when a client late joins
if (animationState.Transition)
if (animationState.Transition && !animationState.CrossFade)
{
// We should have all valid entries for any animation state transition update
// Verify the AnimationState's assigned Layer exists
@@ -1150,9 +1181,14 @@ namespace Unity.Netcode.Components
NetworkLog.LogError($"[DestinationState To Transition Info] Layer ({animationState.Layer}) does not exist!");
}
}
else if (animationState.Transition && animationState.CrossFade)
{
m_Animator.CrossFade(animationState.DestinationStateHash, animationState.Duration, animationState.Layer, animationState.NormalizedTime);
}
else
{
if (currentState.fullPathHash != animationState.StateHash)
// Make sure we are not just updating the weight of a layer.
if (currentState.fullPathHash != animationState.StateHash && m_Animator.HasState(animationState.Layer, animationState.StateHash))
{
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
}
@@ -1237,23 +1273,11 @@ namespace Unity.Netcode.Components
}
}
internal void HandleAnimStateUpdate(ref AnimationMessage animationMessage)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
}
/// <summary>
/// Internally-called RPC client receiving function to update some animation state on a client
/// </summary>
[ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
{
// This should never happen
if (IsHost)
@@ -1264,7 +1288,10 @@ namespace Unity.Netcode.Components
}
return;
}
HandleAnimStateUpdate(ref animationMessage);
foreach (var animationState in animationMessage.AnimationStates)
{
UpdateAnimationState(animationState);
}
}
/// <summary>
@@ -1274,44 +1301,31 @@ namespace Unity.Netcode.Components
[ServerRpc]
internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
{
// If it is server authoritative
// Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Owner Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
return;
}
// set the trigger locally on the server
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
if (IsServerAuthoritative())
{
// The only condition where this should (be allowed to) happen is when the owner sends the server a trigger message
if (OwnerClientId == serverRpcParams.Receive.SenderClientId)
{
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage);
}
else if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Server Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
else
else if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
// Ignore if a non-owner sent this.
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[Owner Authoritative] Detected the a non-authoritative client is sending the server animation trigger updates. If you recently changed ownership of the {name} object, then this could be the reason.");
}
return;
}
// set the trigger locally on the server
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
// send the message to all non-authority clients excluding the server and the owner
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
}
}

View File

@@ -0,0 +1,205 @@
using System.Runtime.CompilerServices;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
/// <summary>
/// Used to synchromnize delta position when half float precision is enabled
/// </summary>
public struct NetworkDeltaPosition : INetworkSerializable
{
internal const float MaxDeltaBeforeAdjustment = 64f;
/// <summary>
/// The HalfVector3 used to synchronize the delta in position
/// </summary>
public HalfVector3 HalfVector3;
internal Vector3 CurrentBasePosition;
internal Vector3 PrecisionLossDelta;
internal Vector3 HalfDeltaConvertedBack;
internal Vector3 PreviousPosition;
internal Vector3 DeltaPosition;
internal int NetworkTick;
/// <summary>
/// The serialization implementation of <see cref="INetworkSerializable"/>
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
HalfVector3.NetworkSerialize(serializer);
}
/// <summary>
/// Gets the full precision value of Vector3 position while also potentially updating the current base position.
/// </summary>
/// <param name="networkTick">Use the current network tick value.</param>
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 ToVector3(int networkTick)
{
// When synchronizing, it is possible to have a state update arrive
// for the same synchronization network tick. Under this scenario,
// we only want to return the existing CurrentBasePosition + DeltaPosition
// values and not process the X, Y, or Z values.
// (See the constructors below)
if (networkTick == NetworkTick)
{
return CurrentBasePosition + DeltaPosition;
}
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
DeltaPosition[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
// If we exceed or are equal to the maximum delta value then we need to
// apply the delta to the CurrentBasePosition value and reset the delta
// position for the axis.
if (Mathf.Abs(DeltaPosition[i]) >= MaxDeltaBeforeAdjustment)
{
CurrentBasePosition[i] += DeltaPosition[i];
DeltaPosition[i] = 0.0f;
HalfVector3.Axis[i] = half.zero;
}
}
}
return CurrentBasePosition + DeltaPosition;
}
/// <summary>
/// Returns the current base position (excluding the delta position offset).
/// </summary>
/// <returns>The current base position as a <see cref="Vector3"/></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetCurrentBasePosition()
{
return CurrentBasePosition;
}
/// <summary>
/// Returns the full position which includes the delta offset position.
/// </summary>
/// <returns>The full position as a <see cref="Vector3"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetFullPosition()
{
return CurrentBasePosition + DeltaPosition;
}
/// <summary>
/// The half float vector3 version of the current delta position.
/// </summary>
/// <remarks>
/// Only applies to the authoritative side for <see cref="NetworkTransform"/> instances.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetConvertedDelta()
{
return HalfDeltaConvertedBack;
}
/// <summary>
/// The full precision current delta position.
/// </summary>
/// <remarks>
/// Authoritative: Will have no precision loss
/// Non-Authoritative: Has the current network tick's loss of precision.
/// Precision loss adjustments are one network tick behind on the
/// non-authoritative side.
/// </remarks>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Vector3 GetDeltaPosition()
{
return DeltaPosition;
}
/// <summary>
/// Updates the position delta based off of the current base position.
/// </summary>
/// <param name="vector3">The full precision <see cref="Vector3"/> value to (converted to half floats) used to determine the delta offset positon.</param>
/// <param name="networkTick">Set the current network tick value when updating.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void UpdateFrom(ref Vector3 vector3, int networkTick)
{
NetworkTick = networkTick;
DeltaPosition = (vector3 + PrecisionLossDelta) - CurrentBasePosition;
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
HalfVector3.Axis[i] = math.half(DeltaPosition[i]);
HalfDeltaConvertedBack[i] = Mathf.HalfToFloat(HalfVector3.Axis[i].value);
PrecisionLossDelta[i] = DeltaPosition[i] - HalfDeltaConvertedBack[i];
if (Mathf.Abs(HalfDeltaConvertedBack[i]) >= MaxDeltaBeforeAdjustment)
{
CurrentBasePosition[i] += HalfDeltaConvertedBack[i];
HalfDeltaConvertedBack[i] = 0.0f;
DeltaPosition[i] = 0.0f;
}
}
}
for (int i = 0; i < HalfVector3.Length; i++)
{
if (HalfVector3.AxisToSynchronize[i])
{
PreviousPosition[i] = vector3[i];
}
}
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
public NetworkDeltaPosition(Vector3 vector3, int networkTick, bool3 axisToSynchronize)
{
NetworkTick = networkTick;
CurrentBasePosition = vector3;
PreviousPosition = vector3;
PrecisionLossDelta = Vector3.zero;
DeltaPosition = Vector3.zero;
HalfDeltaConvertedBack = Vector3.zero;
HalfVector3 = new HalfVector3(vector3, axisToSynchronize);
UpdateFrom(ref vector3, networkTick);
}
/// <summary>
/// Constructor that defaults to all axis being synchronized.
/// </summary>
/// <param name="vector3">The initial axial values (converted to half floats) when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
public NetworkDeltaPosition(Vector3 vector3, int networkTick) : this(vector3, networkTick, math.bool3(true))
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
/// <param name="axisToSynchronize">The axis to be synchronized.</param>
public NetworkDeltaPosition(float x, float y, float z, int networkTick, bool3 axisToSynchronize) :
this(new Vector3(x, y, z), networkTick, axisToSynchronize)
{
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="x">The initial x axis (converted to half float) value when instantiated.</param>
/// <param name="y">The initial y axis (converted to half float) value when instantiated.</param>
/// <param name="z">The initial z axis (converted to half float) value when instantiated.</param>
/// <param name="networkTick">Set the network tick value to the current network tick when instantiating.</param>
public NetworkDeltaPosition(float x, float y, float z, int networkTick) :
this(new Vector3(x, y, z), networkTick, math.bool3(true))
{
}
}
}

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,123 @@
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// A Smallest Three Quaternion Compressor Implementation
/// </summary>
/// <remarks>
/// Explanation of why "The smallest three":
/// Since a normalized Quaternion's unit value is 1.0f:
/// x*x + y*y + z*z + w*w = M*M (where M is the magnitude of the vector)
/// If w was the largest value and the quaternion is normalized:
/// M = 1.0f (which M * M would still yield 1.0f)
/// w*w = M*M - (x*x + y*y + z*z) or Mathf.Sqrt(1.0f - (x*x + y*y + z*z))
/// w = Math.Sqrt(1.0f - (x*x + y*y + z*z))
/// Using the largest the number avoids potential loss of precision in the smallest three values.
/// </remarks>
public static class QuaternionCompressor
{
private const ushort k_PrecisionMask = (1 << 9) - 1;
// Square root of 2 over 2 (Mathf.Sqrt(2.0f) / 2.0f == 1.0f / Mathf.Sqrt(2.0f))
// This provides encoding the smallest three components into a (+/-) Mathf.Sqrt(2.0f) / 2.0f range
private const float k_SqrtTwoOverTwoEncoding = 0.70710678118654752440084436210485f;
// We can further improve the encoding compression by dividing k_SqrtTwoOverTwo into 1.0f and multiplying that
// by the precision mask (minor reduction of runtime calculations)
private const float k_CompressionEcodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask;
// Used to shift the negative bit to the 10th bit position when compressing and encoding
private const ushort k_ShiftNegativeBit = 9;
// We can do the same for our decoding and decompression by dividing k_PrecisionMask into 1.0 and multiplying
// that by k_SqrtTwoOverTwo (minor reduction of runtime calculations)
private const float k_DcompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding;
// The sign bit position (10th bit) used when decompressing and decoding
private const ushort k_NegShortBit = 0x200;
// Negative bit set values
private const ushort k_True = 1;
private const ushort k_False = 0;
// Used to store the absolute value of the 4 quaternion elements
private static Quaternion s_QuatAbsValues = Quaternion.identity;
/// <summary>
/// Compresses a Quaternion into an unsigned integer
/// </summary>
/// <param name="quaternion">the <see cref="Quaternion"/> to be compressed</param>
/// <returns>the <see cref="Quaternion"/> compressed as an unsigned integer</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint CompressQuaternion(ref Quaternion quaternion)
{
// Store off the absolute value for each Quaternion element
s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]);
s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]);
s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]);
s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]);
// Get the largest element value of the quaternion to know what the remaining "Smallest Three" values are
var quatMax = Mathf.Max(s_QuatAbsValues[0], s_QuatAbsValues[1], s_QuatAbsValues[2], s_QuatAbsValues[3]);
// Find the index of the largest element so we can skip that element while compressing and decompressing
var indexToSkip = (ushort)(s_QuatAbsValues[0] == quatMax ? 0 : s_QuatAbsValues[1] == quatMax ? 1 : s_QuatAbsValues[2] == quatMax ? 2 : 3);
// Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion.
var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False);
// Start with the index to skip which will be shifted to the highest two bits
var compressed = (uint)indexToSkip;
// Step 1: Start with the first element
var currentIndex = 0;
// Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4
// Step 3: Get the sign of the element we are processing. If it is the not the same as the largest value's sign bit then we set the bit
// Step 4: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEcodingMask and round that result up
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
// Repeat the last 3 steps for the remaining elements
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
currentIndex++;
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
// Return the compress quaternion
return compressed;
}
/// <summary>
/// Decompress a compressed quaternion
/// </summary>
/// <param name="quaternion">quaternion to store the decompressed values within</param>
/// <param name="compressed">the compressed quaternion</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void DecompressQuaternion(ref Quaternion quaternion, uint compressed)
{
// Get the last two bits for the index to skip (0-3)
var indexToSkip = (int)(compressed >> 30);
// Reverse out the values while skipping over the largest value index
var sumOfSquaredMagnitudes = 0.0f;
for (int i = 3; i >= 0; --i)
{
if (i == indexToSkip)
{
continue;
}
// Check the negative bit and multiply that result with the decompressed and decoded value
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask);
sumOfSquaredMagnitudes += quaternion[i] * quaternion[i];
compressed = compressed >> 10;
}
// Since a normalized quaternion's magnitude is 1.0f, we subtract the sum of the squared smallest three from the unit value and take
// the square root of the difference to find the final largest value
quaternion[indexToSkip] = Mathf.Sqrt(1.0f - sumOfSquaredMagnitudes);
}
}
}

View File

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

View File

@@ -3,7 +3,8 @@
"rootNamespace": "Unity.Netcode.Components",
"references": [
"Unity.Netcode.Runtime",
"Unity.Collections"
"Unity.Collections",
"Unity.Mathematics"
],
"allowUnsafeCode": true,
"versionDefines": [

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Cecil.Rocks;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Unity.CompilationPipeline.Common.Diagnostics;

View File

@@ -1,7 +1,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Mono.Cecil;
using Mono.Cecil.Cil;
@@ -9,9 +9,9 @@ using Mono.Cecil.Rocks;
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
using MethodAttributes = Mono.Cecil.MethodAttributes;
using ParameterAttributes = Mono.Cecil.ParameterAttributes;
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
namespace Unity.Netcode.Editor.CodeGen
{
@@ -837,6 +837,58 @@ namespace Unity.Netcode.Editor.CodeGen
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
}
private void GetAllBaseTypesAndResolveGenerics(TypeDefinition type, ref List<TypeReference> baseTypes, Dictionary<string, TypeReference> genericParameters)
{
if (type == null || type.BaseType == null || type.BaseType.Name == "Object")
{
return;
}
var baseType = type.BaseType;
var genericParams = new Dictionary<string, TypeReference>();
if (baseType.IsGenericInstance)
{
var genericType = (GenericInstanceType)baseType;
var newGenericType = new GenericInstanceType(baseType.Resolve());
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
var argument = genericType.GenericArguments[i];
if (genericParameters != null && genericParameters.ContainsKey(argument.Name))
{
newGenericType.GenericArguments.Add(genericParameters[argument.Name]);
genericParams[baseType.Resolve().GenericParameters[newGenericType.GenericArguments.Count - 1].Name] = genericParameters[argument.Name];
}
else
{
newGenericType.GenericArguments.Add(argument);
}
}
baseTypes.Add(newGenericType);
}
else
{
baseTypes.Add(baseType);
}
var resolved = type.BaseType.Resolve();
if (type.BaseType.IsGenericInstance)
{
var genericType = (GenericInstanceType)type.BaseType;
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
if (!genericParams.ContainsKey(resolved.GenericParameters[i].Name))
{
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
}
}
}
GetAllBaseTypesAndResolveGenerics(resolved, ref baseTypes, genericParams);
}
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
{
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
@@ -898,6 +950,34 @@ namespace Unity.Netcode.Editor.CodeGen
}
}
}
{
var baseTypes = new List<TypeReference>();
var genericParams = new Dictionary<string, TypeReference>();
var resolved = type.Resolve();
if (type.IsGenericInstance)
{
var genericType = (GenericInstanceType)type;
for (var i = 0; i < genericType.GenericArguments.Count; ++i)
{
genericParams[resolved.GenericParameters[i].Name] = genericType.GenericArguments[i];
}
}
GetAllBaseTypesAndResolveGenerics(type.Resolve(), ref baseTypes, genericParams);
foreach (var baseType in baseTypes)
{
if (baseType.Resolve().Name == typeof(NetworkVariable<>).Name || baseType.Resolve().Name == typeof(NetworkList<>).Name)
{
var genericInstanceType = (GenericInstanceType)baseType;
var wrappedType = genericInstanceType.GenericArguments[0];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
}
}
}
}
}

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
using UnityEditor;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{
@@ -135,23 +135,23 @@ namespace Unity.Netcode.Editor
}
else if (type == typeof(uint))
{
val = (uint)EditorGUILayout.LongField(variableName, (long)((uint)val));
val = (uint)EditorGUILayout.LongField(variableName, (uint)val);
}
else if (type == typeof(short))
{
val = (short)EditorGUILayout.IntField(variableName, (int)((short)val));
val = (short)EditorGUILayout.IntField(variableName, (short)val);
}
else if (type == typeof(ushort))
{
val = (ushort)EditorGUILayout.IntField(variableName, (int)((ushort)val));
val = (ushort)EditorGUILayout.IntField(variableName, (ushort)val);
}
else if (type == typeof(sbyte))
{
val = (sbyte)EditorGUILayout.IntField(variableName, (int)((sbyte)val));
val = (sbyte)EditorGUILayout.IntField(variableName, (sbyte)val);
}
else if (type == typeof(byte))
{
val = (byte)EditorGUILayout.IntField(variableName, (int)((byte)val));
val = (byte)EditorGUILayout.IntField(variableName, (byte)val);
}
else if (type == typeof(long))
{
@@ -161,6 +161,10 @@ namespace Unity.Netcode.Editor
{
val = (ulong)EditorGUILayout.LongField(variableName, (long)((ulong)val));
}
else if (type == typeof(float))
{
val = EditorGUILayout.FloatField(variableName, (float)((float)val));
}
else if (type == typeof(bool))
{
val = EditorGUILayout.Toggle(variableName, (bool)val);

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.IO;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
using Unity.Netcode.Editor.Configuration;
namespace Unity.Netcode.Editor
{
@@ -231,13 +231,7 @@ namespace Unity.Netcode.Editor
{
ReloadTransports();
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]);
if (transportComponent == null)
{
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
}
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
Repaint();
@@ -355,15 +349,19 @@ namespace Unity.Netcode.Editor
if (s_CenteredWordWrappedLabelStyle == null)
{
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label);
s_CenteredWordWrappedLabelStyle.wordWrap = true;
s_CenteredWordWrappedLabelStyle.alignment = TextAnchor.MiddleLeft;
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label)
{
wordWrap = true,
alignment = TextAnchor.MiddleLeft
};
}
if (s_HelpBoxStyle == null)
{
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox);
s_HelpBoxStyle.padding = new RectOffset(10, 10, 10, 10);
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox)
{
padding = new RectOffset(10, 10, 10, 10)
};
}
var openDocsButtonStyle = GUI.skin.button;

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using System.Linq;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEditor;
namespace Unity.Netcode.Editor
{

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{

View File

@@ -1,6 +1,6 @@
using Unity.Netcode.Components;
using UnityEditor;
using UnityEngine;
using Unity.Netcode.Components;
namespace Unity.Netcode.Editor
{
@@ -25,6 +25,11 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_InLocalSpaceProperty;
private SerializedProperty m_InterpolateProperty;
private SerializedProperty m_UseQuaternionSynchronization;
private SerializedProperty m_UseQuaternionCompression;
private SerializedProperty m_UseHalfFloatPrecision;
private SerializedProperty m_SlerpPosition;
private static int s_ToggleOffset = 45;
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
@@ -48,6 +53,10 @@ namespace Unity.Netcode.Editor
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
m_UseQuaternionSynchronization = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionSynchronization));
m_UseQuaternionCompression = serializedObject.FindProperty(nameof(NetworkTransform.UseQuaternionCompression));
m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision));
m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition));
}
/// <inheritdoc/>
@@ -71,6 +80,8 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal();
}
if (!m_UseQuaternionSynchronization.boolValue)
{
GUILayout.BeginHorizontal();
@@ -88,6 +99,13 @@ namespace Unity.Netcode.Editor
GUILayout.EndHorizontal();
}
else
{
m_SyncRotationXProperty.boolValue = true;
m_SyncRotationYProperty.boolValue = true;
m_SyncRotationZProperty.boolValue = true;
}
{
GUILayout.BeginHorizontal();
@@ -116,6 +134,17 @@ namespace Unity.Netcode.Editor
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty);
EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
{
EditorGUILayout.PropertyField(m_UseQuaternionCompression);
}
else
{
m_UseQuaternionCompression.boolValue = false;
}
EditorGUILayout.PropertyField(m_UseHalfFloatPrecision);
#if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present

View File

@@ -1,5 +1,5 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.Netcode.Components")]
#if UNITY_EDITOR
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Serialization;
namespace Unity.Netcode
@@ -208,6 +208,14 @@ namespace Unity.Netcode
private ulong? m_ConfigHash = null;
/// <summary>
/// Clears out the configuration hash value generated for a specific network session
/// </summary>
internal void ClearConfigHash()
{
m_ConfigHash = null;
}
/// <summary>
/// Gets a SHA256 hash of parts of the NetworkConfig instance
/// </summary>
@@ -273,8 +281,6 @@ namespace Unity.Netcode
Prefabs.Initialize();
}
#region Legacy Network Prefab List
[NonSerialized]
private bool m_DidWarnOldPrefabList = false;
@@ -334,7 +340,5 @@ namespace Unity.Netcode
[FormerlySerializedAs("NetworkPrefabs")]
[SerializeField]
internal List<NetworkPrefab> OldPrefabList;
#endregion
}
}

View File

@@ -52,12 +52,23 @@ namespace Unity.Netcode
}
~NetworkPrefabs()
{
Shutdown();
}
/// <summary>
/// Deregister from add and remove events
/// Clear the list
/// </summary>
internal void Shutdown()
{
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd -= AddTriggeredByNetworkPrefabList;
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
}
NetworkPrefabsLists.Clear();
}
/// <summary>

View File

@@ -52,6 +52,8 @@ namespace Unity.Netcode
public static void SetDefaults()
{
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
SetDefault<IRealTimeProvider>(networkManager => new RealTimeProvider());
}
private static void SetDefault<T>(CreateObjectDelegate creator)

View File

@@ -1,9 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using System.Reflection;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -82,7 +82,7 @@ namespace Unity.Netcode
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
@@ -219,7 +219,7 @@ namespace Unity.Netcode
var context = new NetworkContext
{
SenderId = NetworkManager.ServerClientId,
Timestamp = Time.realtimeSinceStartup,
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
SystemOwner = NetworkManager,
// header information isn't valid since it's not a real message.
// RpcMessage doesn't access this stuff so it's just left empty.
@@ -570,13 +570,10 @@ namespace Unity.Netcode
if (list == null)
{
list = new List<FieldInfo>();
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
}
else
{
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
}
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
{
return GetFieldInfoForTypeRecursive(type.BaseType, list);
@@ -600,13 +597,7 @@ namespace Unity.Netcode
var fieldType = sortedFields[i].FieldType;
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
{
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
if (instance == null)
{
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
}
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this) ?? throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
instance.Initialize(this);
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
@@ -899,11 +890,23 @@ namespace Unity.Netcode
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
/// is in read mode or write mode.
/// </typeparam>
/// <param name="targetClientId">the relative client identifier being synchronized</param>
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
{
}
/// <summary>
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary>
/// <remarks>
/// This value will be set prior to <see cref="OnSynchronize{T}(ref BufferSerializer{T})"/> being invoked.
/// For writing (server-side), this is useful to know which client will receive the serialized data.
/// For reading (client-side), this will be the <see cref="NetworkManager.LocalClientId"/>.
/// When synchronization of this instance is complete, this value will be reset to 0
/// </remarks>
protected ulong m_TargetIdBeingSynchronized { get; private set; }
/// <summary>
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
@@ -913,8 +916,9 @@ namespace Unity.Netcode
/// synchronize any remaining NetworkBehaviours.
/// </remarks>
/// <returns>true if it wrote synchronization data and false if it did not</returns>
internal bool Synchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
internal bool Synchronize<T>(ref BufferSerializer<T> serializer, ulong targetClientId = 0) where T : IReaderWriter
{
m_TargetIdBeingSynchronized = targetClientId;
if (serializer.IsWriter)
{
// Get the writer to handle seeking and determining how many bytes were written
@@ -949,6 +953,8 @@ namespace Unity.Netcode
}
var finalPosition = writer.Position;
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// If we wrote nothing then skip writing anything for this NetworkBehaviour
if (finalPosition == positionBeforeSynchronize || threwException)
{
@@ -1002,6 +1008,9 @@ namespace Unity.Netcode
synchronizationError = true;
}
// Reset before exiting
m_TargetIdBeingSynchronized = default;
// Skip over the entry if deserialization fails
if (synchronizationError)
{

View File

@@ -298,6 +298,8 @@ namespace Unity.Netcode
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
internal IRealTimeProvider RealTimeProvider { get; private set; }
/// <summary>
/// Gets the CustomMessagingManager for this NetworkManager
/// </summary>
@@ -449,10 +451,28 @@ namespace Unity.Netcode
public event Action<ulong> OnClientDisconnectCallback = null;
/// <summary>
/// The callback to invoke once the server is ready
/// This callback is invoked when the local server is started and listening for incoming connections.
/// </summary>
public event Action OnServerStarted = null;
/// <summary>
/// The callback to invoke once the local client is ready
/// </summary>
public event Action OnClientStarted = null;
/// <summary>
/// This callback is invoked once the local server is stopped.
/// </summary>
/// <param name="arg1">The first parameter of this event will be set to <see cref="true"/> when stopping a host instance and <see cref="false"/> when stopping a server instance.</param>
public event Action<bool> OnServerStopped = null;
/// <summary>
/// The callback to invoke once the local client stops
/// </summary>
/// <remarks>The parameter states whether the client was running in host mode</remarks>
/// <param name="arg1">The first parameter of this event will be set to <see cref="true"/> when stopping the host client and <see cref="false"/> when stopping a standard client instance.</param>
public event Action<bool> OnClientStopped = null;
/// <summary>
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
/// </summary>
@@ -735,6 +755,8 @@ namespace Unity.Netcode
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
RealTimeProvider = ComponentFactory.Create<IRealTimeProvider>(this);
CustomMessagingManager = new CustomMessagingManager(this);
SceneManager = new NetworkSceneManager(this);
@@ -908,6 +930,7 @@ namespace Unity.Netcode
IsClient = true;
IsListening = true;
OnClientStarted?.Invoke();
return true;
}
@@ -989,13 +1012,14 @@ namespace Unity.Netcode
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
OnServerStarted?.Invoke();
OnClientStarted?.Invoke();
// This assures that any in-scene placed NetworkObject is spawned and
// any associated NetworkBehaviours' netcode related properties are
// set prior to invoking OnClientConnected.
InvokeOnClientConnectedCallback(LocalClientId);
OnServerStarted?.Invoke();
return true;
}
@@ -1108,13 +1132,13 @@ namespace Unity.Netcode
return isParented;
}
static internal string GenerateNestedNetworkManagerMessage(Transform transform)
internal static string GenerateNestedNetworkManagerMessage(Transform transform)
{
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
}
#if UNITY_EDITOR
static internal INetworkManagerHelper NetworkManagerHelper;
internal static INetworkManagerHelper NetworkManagerHelper;
/// <summary>
/// Interface for NetworkManagerHelper
/// </summary>
@@ -1195,13 +1219,12 @@ namespace Unity.Netcode
NetworkLog.LogInfo(nameof(ShutdownInternal));
}
if (IsServer)
bool wasServer = IsServer;
bool wasClient = IsClient;
if (wasServer)
{
// make sure all messages are flushed before transport disconnect clients
if (MessagingSystem != null)
{
MessagingSystem.ProcessSendQueues();
}
MessagingSystem?.ProcessSendQueues();
var disconnectedIds = new HashSet<ulong>();
@@ -1237,10 +1260,22 @@ namespace Unity.Netcode
}
}
// Unregister network updates before trying to disconnect the client
this.UnregisterAllNetworkUpdates();
if (IsClient && IsListening)
{
// Client only, send disconnect to server
NetworkConfig.NetworkTransport.DisconnectLocalClient();
// If transport throws and exception, log the exception and
// continue the shutdown sequence (or forever be shutting down)
try
{
NetworkConfig.NetworkTransport.DisconnectLocalClient();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
IsConnectedClient = false;
@@ -1261,8 +1296,6 @@ namespace Unity.Netcode
IsServer = false;
IsClient = false;
this.UnregisterAllNetworkUpdates();
if (NetworkTickSystem != null)
{
NetworkTickSystem.Tick -= OnNetworkManagerTick;
@@ -1280,10 +1313,7 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
}
if (DeferredMessageManager != null)
{
DeferredMessageManager.CleanupAllTriggers();
}
DeferredMessageManager?.CleanupAllTriggers();
if (SceneManager != null)
{
@@ -1318,6 +1348,22 @@ namespace Unity.Netcode
m_StopProcessingMessages = false;
ClearClients();
if (wasClient)
{
OnClientStopped?.Invoke(wasServer);
}
if (wasServer)
{
OnServerStopped?.Invoke(wasClient);
}
// This cleans up the internal prefabs list
NetworkConfig?.Prefabs.Shutdown();
// Reset the configuration hash for next session in the event
// that the prefab list changes
NetworkConfig?.ClearConfigHash();
}
/// <inheritdoc />
@@ -1417,7 +1463,7 @@ namespace Unity.Netcode
}
// Only update RTT here, server time is updated by time sync messages
var reset = NetworkTimeSystem.Advance(Time.unscaledDeltaTime);
var reset = NetworkTimeSystem.Advance(RealTimeProvider.UnscaledDeltaTime);
if (reset)
{
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
@@ -1426,7 +1472,7 @@ namespace Unity.Netcode
if (IsServer == false)
{
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + Time.unscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
NetworkTimeSystem.Sync(NetworkTimeSystem.LastSyncedServerTimeSec + RealTimeProvider.UnscaledDeltaTime, NetworkConfig.NetworkTransport.GetCurrentRtt(ServerClientId) / 1000d);
}
}
@@ -1435,6 +1481,10 @@ namespace Unity.Netcode
if (!m_ShuttingDown || !m_StopProcessingMessages)
{
// This should be invoked just prior to the MessagingSystem
// processes its outbound queue.
SceneManager.CheckForAndSendNetworkObjectSceneChanged();
MessagingSystem.ProcessSendQueues();
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
@@ -1486,10 +1536,9 @@ namespace Unity.Netcode
// we should always force the rebuilding of the NetworkConfig hash value
ConfigHash = NetworkConfig.GetConfig(false),
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
ConnectionData = NetworkConfig.ConnectionData
ConnectionData = NetworkConfig.ConnectionData,
MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp)
};
message.MessageVersions = new NativeArray<MessageVersionData>(MessagingSystem.MessageHandlers.Length, Allocator.Temp);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
@@ -1509,7 +1558,7 @@ namespace Unity.Netcode
private IEnumerator ApprovalTimeout(ulong clientId)
{
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
var timeStarted = IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup;
var timedOut = false;
var connectionApproved = false;
var connectionNotApproved = false;
@@ -1519,7 +1568,7 @@ namespace Unity.Netcode
{
yield return null;
// Check if we timed out
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup);
if (IsServer)
{
@@ -1861,8 +1910,10 @@ namespace Unity.Netcode
if (!string.IsNullOrEmpty(reason))
{
var disconnectReason = new DisconnectReasonMessage();
disconnectReason.Reason = reason;
var disconnectReason = new DisconnectReasonMessage
{
Reason = reason
};
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
}
MessagingSystem.ProcessSendQueues();
@@ -2011,15 +2062,19 @@ namespace Unity.Netcode
if (response.CreatePlayerObject)
{
var playerPrefabHash = response.PlayerPrefabHash ?? NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
var prefabNetworkObject = NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash;
// Generate a SceneObject for the player object to spawn
// Note: This is only to create the local NetworkObject,
// many of the serialized properties of the player prefab
// will be set when instantiated.
var sceneObject = new NetworkObject.SceneObject
{
OwnerClientId = ownerClientId,
IsPlayerObject = true,
IsSceneObject = false,
HasTransform = true,
HasTransform = prefabNetworkObject.SynchronizeTransform,
Hash = playerPrefabHash,
TargetClientId = ownerClientId,
Transform = new NetworkObject.SceneObject.TransformData
@@ -2105,8 +2160,10 @@ namespace Unity.Netcode
{
if (!string.IsNullOrEmpty(response.Reason))
{
var disconnectReason = new DisconnectReasonMessage();
disconnectReason.Reason = response.Reason;
var disconnectReason = new DisconnectReasonMessage
{
Reason = response.Reason
};
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
MessagingSystem.ProcessSendQueues();

View File

@@ -17,6 +17,28 @@ namespace Unity.Netcode
[SerializeField]
internal uint GlobalObjectIdHash;
/// <summary>
/// Gets the Prefab Hash Id of this object if the object is registerd as a prefab otherwise it returns 0
/// </summary>
[HideInInspector]
public uint PrefabIdHash
{
get
{
foreach (var prefab in NetworkManager.NetworkConfig.Prefabs.Prefabs)
{
if (prefab.Prefab == gameObject)
{
return GlobalObjectIdHash;
}
}
return 0;
}
}
private bool m_IsPrefab;
#if UNITY_EDITOR
private void OnValidate()
{
@@ -75,6 +97,18 @@ namespace Unity.Netcode
/// </summary>
public bool IsPlayerObject { get; internal set; }
/// <summary>
/// Determines if the associated NetworkObject's transform will get
/// synchronized when spawned.
/// </summary>
/// <remarks>
/// For things like in-scene placed NetworkObjects that have no visual
/// components can help reduce the instance's initial synchronization
/// bandwidth cost. This can also be useful for UI elements that have
/// a predetermined fixed position.
/// </remarks>
public bool SynchronizeTransform = true;
/// <summary>
/// Gets if the object is the personal clients player object
/// </summary>
@@ -105,6 +139,55 @@ namespace Unity.Netcode
/// </summary>
public bool DestroyWithScene { get; set; }
/// <summary>
/// When set to true and the active scene is changed, this will automatically migrate the <see cref="NetworkObject"/>
/// into the new active scene on both the server and client instances.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
///
/// If there are more than one scenes loaded and the currently active scene is unloaded, then typically
/// the <see cref="SceneManager"/> will automatically assign a new active scene. Similar to <see cref="DestroyWithScene"/>
/// being set to <see cref="false"/>, this prevents any <see cref="NetworkObject"/> from being destroyed
/// with the unloaded active scene by migrating it into the automatically assigned active scene.
/// Additionally, this is can be useful in some seamless scene streaming implementations.
/// Note:
/// Only having <see cref="ActiveSceneSynchronization"/> set to true will *not* synchronize clients when
/// changing a <see cref="NetworkObject"/>'s scene via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>.
/// To synchronize clients of a <see cref="NetworkObject"/>'s scene being changed via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>,
/// make sure <see cref="SceneMigrationSynchronization"/> is enabled (it is by default).
/// </remarks>
public bool ActiveSceneSynchronization;
/// <summary>
/// When enabled (the default), if a <see cref="NetworkObject"/> is migrated to a different scene (active or not)
/// via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/> on the server side all client
/// instances will be synchronized and the <see cref="NetworkObject"/> migrated into the newly assigned scene.
/// The updated scene migration will get synchronized with late joining clients as well.
/// </summary>
/// <remarks>
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// Note:
/// You can have both <see cref="ActiveSceneSynchronization"/> and <see cref="SceneMigrationSynchronization"/> enabled.
/// The primary difference between the two is that <see cref="SceneMigrationSynchronization"/> only synchronizes clients
/// when the server migrates a <see cref="NetworkObject"/> to a new scene. If the scene is unloaded and <see cref="DestroyWithScene"/>
/// is <see cref="true"/> and <see cref="ActiveSceneSynchronization"/> is <see cref="false"/> and the scene is not the currently
/// active scene, then the <see cref="NetworkObject"/> will be destroyed.
/// </remarks>
public bool SceneMigrationSynchronization = true;
/// <summary>
/// Notifies when the NetworkObject is migrated into a new scene
/// </summary>
/// <remarks>
/// - <see cref="ActiveSceneSynchronization"/> or <see cref="SceneMigrationSynchronization"/> (or both) need to be enabled
/// - This only applies to dynamically spawned <see cref="NetworkObject"/>s.
/// - This only works when using integrated scene management (<see cref="NetworkSceneManager"/>).
/// </remarks>
public Action OnMigratedToNewScene;
/// <summary>
/// Delegate type for checking visibility
/// </summary>
@@ -188,6 +271,11 @@ namespace Unity.Netcode
/// </summary>
internal int SceneOriginHandle = 0;
/// <summary>
/// The server-side scene origin handle
/// </summary>
internal int NetworkSceneHandle = 0;
private Scene m_SceneOrigin;
/// <summary>
/// The scene where the NetworkObject was first instantiated
@@ -265,6 +353,15 @@ namespace Unity.Netcode
throw new VisibilityChangeException("The object is already visible");
}
if (CheckObjectVisibility != null && !CheckObjectVisibility(clientId))
{
if (NetworkManager.LogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning($"[NetworkShow] Trying to make {nameof(NetworkObject)} {gameObject.name} visible to client ({clientId}) but {nameof(CheckObjectVisibility)} returned false!");
}
return;
}
NetworkManager.MarkObjectForShowingTo(this, clientId);
Observers.Add(clientId);
}
@@ -578,6 +675,22 @@ namespace Unity.Netcode
private Transform m_CachedParent; // What is our last set parent Transform reference?
private bool m_CachedWorldPositionStays = true; // Used to preserve the world position stays parameter passed in TrySetParent
/// <summary>
/// Returns the last known cached WorldPositionStays value for this NetworkObject
/// </summary>
/// <remarks>
/// When parenting NetworkObjects, the optional WorldPositionStays value is cached and synchronized with clients.
/// This method provides access to the instance relative cached value.
/// <see cref="TrySetParent(GameObject, bool)"/>
/// <see cref="TrySetParent(NetworkObject, bool)"/>
/// <see cref="TrySetParent(Transform, bool)"/>
/// </remarks>
/// <returns><see cref="true"/> or <see cref="false"/></returns>
public bool WorldPositionStays()
{
return m_CachedWorldPositionStays;
}
internal void SetCachedParent(Transform parentTransform)
{
m_CachedParent = parentTransform;
@@ -1118,6 +1231,18 @@ namespace Unity.Netcode
set => ByteUtility.SetBit(ref m_BitField, 5, value);
}
/// <summary>
/// Even though the server sends notifications for NetworkObjects that get
/// destroyed when a scene is unloaded, we want to synchronize this so
/// the client side can use it as part of a filter for automatically migrating
/// to the current active scene when its scene is unloaded. (only for dynamically spawned)
/// </summary>
public bool DestroyWithScene
{
get => ByteUtility.GetBit(m_BitField, 6);
set => ByteUtility.SetBit(ref m_BitField, 6, value);
}
//If(Metadata.HasParent)
public ulong ParentObjectId;
@@ -1160,7 +1285,7 @@ namespace Unity.Netcode
var writeSize = 0;
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
writeSize += FastBufferWriter.GetWriteSize<int>();
if (!writer.TryBeginWrite(writeSize))
{
@@ -1172,14 +1297,9 @@ namespace Unity.Netcode
writer.WriteValue(Transform);
}
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only written for in-scene placed NetworkObjects.
if (IsSceneObject)
{
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
}
// The NetworkSceneHandle is the server-side relative
// scene handle that the NetworkObject resides in.
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
// Synchronize NetworkVariables and NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
@@ -1205,7 +1325,7 @@ namespace Unity.Netcode
var readSize = 0;
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
readSize += FastBufferWriter.GetWriteSize<int>();
// Try to begin reading the remaining bytes
if (!reader.TryBeginRead(readSize))
@@ -1218,14 +1338,9 @@ namespace Unity.Netcode
reader.ReadValue(out Transform);
}
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
// this to locate their local instance of the in-scene placed NetworkObject instance.
// Only read for in-scene placed NetworkObjects
if (IsSceneObject)
{
reader.ReadValue(out NetworkSceneHandle);
}
// The NetworkSceneHandle is the server-side relative
// scene handle that the NetworkObject resides in.
reader.ReadValue(out NetworkSceneHandle);
}
}
@@ -1265,7 +1380,7 @@ namespace Unity.Netcode
var synchronizationCount = (byte)0;
foreach (var childBehaviour in ChildNetworkBehaviours)
{
if (childBehaviour.Synchronize(ref serializer))
if (childBehaviour.Synchronize(ref serializer, targetClientId))
{
synchronizationCount++;
}
@@ -1304,7 +1419,7 @@ namespace Unity.Netcode
{
serializer.SerializeValue(ref networkBehaviourId);
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
networkBehaviour.Synchronize(ref serializer);
networkBehaviour.Synchronize(ref serializer, targetClientId);
}
}
}
@@ -1317,6 +1432,7 @@ namespace Unity.Netcode
OwnerClientId = OwnerClientId,
IsPlayerObject = IsPlayerObject,
IsSceneObject = IsSceneObject ?? true,
DestroyWithScene = DestroyWithScene,
Hash = HostCheckForGlobalObjectIdHashOverride(),
OwnerObject = this,
TargetClientId = targetClientId
@@ -1352,7 +1468,7 @@ namespace Unity.Netcode
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
{
obj.HasTransform = true;
obj.HasTransform = SynchronizeTransform;
// We start with the default AutoObjectParentSync values to determine which transform space we will
// be synchronizing clients with.
@@ -1435,11 +1551,126 @@ namespace Unity.Netcode
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
// Spawn the NetworkObject
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);
return networkObject;
}
/// <summary>
/// Subscribes to changes in the currently active scene
/// </summary>
/// <remarks>
/// Only for dynamically spawned NetworkObjects
/// </remarks>
internal void SubscribeToActiveSceneForSynch()
{
if (ActiveSceneSynchronization)
{
if (IsSceneObject.HasValue && !IsSceneObject.Value)
{
// Just in case it is a recycled NetworkObject, unsubscribe first
SceneManager.activeSceneChanged -= CurrentlyActiveSceneChanged;
SceneManager.activeSceneChanged += CurrentlyActiveSceneChanged;
}
}
}
/// <summary>
/// If AutoSynchActiveScene is enabled, then this is the callback that handles updating
/// a NetworkObject's scene information.
/// </summary>
private void CurrentlyActiveSceneChanged(Scene current, Scene next)
{
// Early exit if there is no NetworkManager assigned, the NetworkManager is shutting down, the NetworkObject
// is not spawned, or an in-scene placed NetworkObject
if (NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned || IsSceneObject != false)
{
return;
}
// This check is here in the event a user wants to disable this for some reason but also wants
// the NetworkObject to synchronize to changes in the currently active scene at some later time.
if (ActiveSceneSynchronization)
{
// Only dynamically spawned NetworkObjects that are not already in the newly assigned active scene will migrate
// and update their scene handles
if (IsSceneObject.HasValue && !IsSceneObject.Value && gameObject.scene != next && gameObject.transform.parent == null)
{
SceneManager.MoveGameObjectToScene(gameObject, next);
SceneChangedUpdate(next);
}
}
}
/// <summary>
/// Handles updating the NetworkObject's tracked scene handles
/// </summary>
internal void SceneChangedUpdate(Scene scene, bool notify = false)
{
// Avoiding edge case scenarios, if no NetworkSceneManager exit early
if (NetworkManager.SceneManager == null)
{
return;
}
SceneOriginHandle = scene.handle;
// Clients need to update the NetworkSceneHandle
if (!NetworkManager.IsServer && NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle.ContainsKey(SceneOriginHandle))
{
NetworkSceneHandle = NetworkManager.SceneManager.ClientSceneHandleToServerSceneHandle[SceneOriginHandle];
}
else if (NetworkManager.IsServer)
{
// Since the server is the source of truth for the NetworkSceneHandle,
// the NetworkSceneHandle is the same as the SceneOriginHandle.
NetworkSceneHandle = SceneOriginHandle;
}
else // Otherwise, the client did not find the client to server scene handle
if (NetworkManager.LogLevel == LogLevel.Developer)
{
// There could be a scenario where a user has some client-local scene loaded that they migrate the NetworkObject
// into, but that scenario seemed very edge case and under most instances a user should be notified that this
// server - client scene handle mismatch has occurred. It also seemed pertinent to make the message replicate to
// the server-side too.
NetworkLog.LogWarningServer($"[Client-{NetworkManager.LocalClientId}][{gameObject.name}] Server - " +
$"client scene mismatch detected! Client-side scene handle ({SceneOriginHandle}) for scene ({gameObject.scene.name})" +
$"has no associated server side (network) scene handle!");
}
OnMigratedToNewScene?.Invoke();
// Only the server side will notify clients of non-parented NetworkObject scene changes
if (NetworkManager.IsServer && notify && transform.parent == null)
{
NetworkManager.SceneManager.NotifyNetworkObjectSceneChanged(this);
}
}
/// <summary>
/// Update
/// Detects if a NetworkObject's scene has changed for both server and client instances
/// </summary>
/// <remarks>
/// About In-Scene Placed NetworkObjects:
/// Since the same scene can be loaded more than once and in-scene placed NetworkObjects GlobalObjectIdHash
/// values are only unique to the scene asset itself (and not per scene instance loaded), we will not be able
/// to add this same functionality to in-scene placed NetworkObjects until we have a way to generate
/// per-NetworkObject-instance unique GlobalObjectIdHash values for in-scene placed NetworkObjects.
/// </remarks>
private void Update()
{
// Early exit if SceneMigrationSynchronization is disabled, there is no NetworkManager assigned,
// the NetworkManager is shutting down, the NetworkObject is not spawned, it is an in-scene placed
// NetworkObject, or the GameObject's current scene handle is the same as the SceneOriginHandle
if (!SceneMigrationSynchronization || NetworkManager == null || NetworkManager.ShutdownInProgress || !IsSpawned
|| IsSceneObject != false || gameObject.scene.handle == SceneOriginHandle)
{
return;
}
// Otherwise, this has to be a dynamically spawned NetworkObject that has been
// migrated to a new scene.
SceneChangedUpdate(gameObject.scene, true);
}
/// <summary>
/// Only applies to Host mode.
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.

View File

@@ -167,7 +167,7 @@ namespace Unity.Netcode
/// </summary>
public static NetworkUpdateStage UpdateStage;
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
internal static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
{
UpdateStage = updateStage;

View File

@@ -1,6 +1,6 @@
using System;
using System.Text;
using System.Runtime.CompilerServices;
using System.Text;
namespace Unity.Netcode
{

View File

@@ -151,14 +151,18 @@ namespace Unity.Netcode
// We dont know what size to use. Try every (more collision prone)
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
}
else
@@ -169,15 +173,19 @@ namespace Unity.Netcode
case HashSize.VarIntFourBytes:
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup32[hash];
messageHandler32(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
break;
case HashSize.VarIntEightBytes:
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
{
// handler can remove itself, cache the name for metrics
string messageName = m_MessageHandlerNameLookup64[hash];
messageHandler64(sender, reader);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
}
break;
}

View File

@@ -1,6 +1,5 @@
using System.Collections.Generic;
using Unity.Collections;
using Time = UnityEngine.Time;
namespace Unity.Netcode
{
@@ -49,7 +48,7 @@ namespace Unity.Netcode
{
triggerInfo = new TriggerInfo
{
Expiry = Time.realtimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
Expiry = m_NetworkManager.RealTimeProvider.RealTimeSinceStartup + m_NetworkManager.NetworkConfig.SpawnTimeout,
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
};
triggers[key] = triggerInfo;
@@ -77,7 +76,7 @@ namespace Unity.Netcode
int index = 0;
foreach (var kvp2 in kvp.Value)
{
if (kvp2.Value.Expiry < Time.realtimeSinceStartup)
if (kvp2.Value.Expiry < m_NetworkManager.RealTimeProvider.RealTimeSinceStartup)
{
staleKeys[index++] = kvp2.Key;
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);

View File

@@ -8,11 +8,7 @@ namespace Unity.Netcode
public void Serialize(FastBufferWriter writer, int targetVersion)
{
string reasonSent = Reason;
if (reasonSent == null)
{
reasonSent = string.Empty;
}
string reasonSent = Reason ?? string.Empty;
// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
// itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion

View File

@@ -1,6 +1,6 @@
using System;
using UnityEngine;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{

View File

@@ -365,37 +365,35 @@ namespace Unity.Netcode
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{
if (header.MessageType >= m_HighMessageType)
{
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
reader.Dispose();
return;
}
var context = new NetworkContext
{
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type, reader, ref context))
{
reader.Dispose();
return;
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
var handler = m_MessageHandlers[header.MessageType];
using (reader)
{
if (header.MessageType >= m_HighMessageType)
{
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
return;
}
var context = new NetworkContext
{
SystemOwner = m_Owner,
SenderId = senderId,
Timestamp = timestamp,
Header = header,
SerializedHeaderSize = serializedHeaderSize,
MessageSize = header.MessageSize,
};
var type = m_ReverseTypeMap[header.MessageType];
if (!CanReceive(senderId, type, reader, ref context))
{
return;
}
var handler = m_MessageHandlers[header.MessageType];
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
// This will also log an exception is if the server knows about a message type the client doesn't know
// about. In this case the handler will be null. It is still an issue the user must deal with: If the
// two connecting builds know about different messages, the server should not send a message to a client
@@ -420,10 +418,10 @@ namespace Unity.Netcode
Debug.LogException(e);
}
}
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
}
}
}

View File

@@ -1,5 +1,5 @@
using UnityEngine;
using System;
using UnityEngine;
namespace Unity.Netcode
{

View File

@@ -0,0 +1,377 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Unity.Netcode
{
/// <summary>
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
/// </summary>
internal class DefaultSceneManagerHandler : ISceneManagerHandler
{
private Scene m_InvalidScene = new Scene();
internal struct SceneEntry
{
public bool IsAssigned;
public Scene Scene;
}
internal Dictionary<string, Dictionary<int, SceneEntry>> SceneNameToSceneHandles = new Dictionary<string, Dictionary<int, SceneEntry>>();
public AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.LoadSceneAsync(sceneName, loadSceneMode);
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
public AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress)
{
var operation = SceneManager.UnloadSceneAsync(scene);
sceneEventProgress.SetAsyncOperation(operation);
return operation;
}
/// <summary>
/// Resets scene tracking
/// </summary>
public void ClearSceneTracking(NetworkManager networkManager)
{
SceneNameToSceneHandles.Clear();
}
/// <summary>
/// Stops tracking a specific scene
/// </summary>
public void StopTrackingScene(int handle, string name, NetworkManager networkManager)
{
if (SceneNameToSceneHandles.ContainsKey(name))
{
if (SceneNameToSceneHandles[name].ContainsKey(handle))
{
SceneNameToSceneHandles[name].Remove(handle);
if (SceneNameToSceneHandles[name].Count == 0)
{
SceneNameToSceneHandles.Remove(name);
}
}
}
}
/// <summary>
/// Starts tracking a specific scene
/// </summary>
public void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
{
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = true,
Scene = scene
};
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
}
else
{
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
}
}
/// <summary>
/// Determines if there is an existing scene loaded that matches the scene name but has not been assigned
/// </summary>
public bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager)
{
var scenesWithSceneName = new List<Scene>();
// Get all loaded scenes with the same name
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (scene.name == sceneName)
{
scenesWithSceneName.Add(scene);
}
}
// If there are no scenes of this name loaded then we have no loaded scenes
// to use
if (scenesWithSceneName.Count == 0)
{
return false;
}
// If we have 1 or more scenes with the name and we have no entries, then we do have
// a scene to use
if (scenesWithSceneName.Count > 0 && !SceneNameToSceneHandles.ContainsKey(sceneName))
{
return true;
}
// Determine if any of the loaded scenes has been used for synchronizing
foreach (var scene in scenesWithSceneName)
{
// If we don't have the handle, then we can use that scene
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
return true;
}
// If we have an entry, but it is not yet assigned (i.e. preloaded)
// then we can use that.
if (!SceneNameToSceneHandles[scene.name][scene.handle].IsAssigned)
{
return true;
}
}
// If none were found, then we have no available scene (which most likely means one will get loaded)
return false;
}
/// <summary>
/// This will find any scene entry that hasn't been used/assigned, set the entry to assigned, and
/// return the associated scene. If none are found it returns an invalid scene.
/// </summary>
public Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager)
{
if (SceneNameToSceneHandles.ContainsKey(sceneName))
{
foreach (var sceneHandleEntry in SceneNameToSceneHandles[sceneName])
{
if (!sceneHandleEntry.Value.IsAssigned)
{
var sceneEntry = sceneHandleEntry.Value;
sceneEntry.IsAssigned = true;
SceneNameToSceneHandles[sceneName][sceneHandleEntry.Key] = sceneEntry;
return sceneEntry.Scene;
}
}
}
// If we found nothing return an invalid scene
return m_InvalidScene;
}
/// <summary>
/// Only invoked is client synchronization is additive, this will generate the scene tracking table
/// in order to re-use the same scenes the server is synchronizing instead of having to unload the
/// scenes and reload them when synchronizing (i.e. client disconnects due to external reason, the
/// same application instance is still running, the same scenes are still loaded on the client, and
/// upon reconnecting the client doesn't have to unload the scenes and then reload them)
/// </summary>
public void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager)
{
SceneNameToSceneHandles.Clear();
var sceneCount = SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (!SceneNameToSceneHandles.ContainsKey(scene.name))
{
SceneNameToSceneHandles.Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = false,
Scene = scene
};
SceneNameToSceneHandles[scene.name].Add(scene.handle, sceneEntry);
if (!scenesLoaded.ContainsKey(scene.handle))
{
scenesLoaded.Add(scene.handle, scene);
}
}
else
{
throw new Exception($"[Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
}
}
}
private List<Scene> m_ScenesToUnload = new List<Scene>();
/// <summary>
/// Unloads any scenes that have not been assigned.
/// </summary>
/// <param name="networkManager"></param>
public void UnloadUnassignedScenes(NetworkManager networkManager = null)
{
var sceneManager = networkManager.SceneManager;
SceneManager.sceneUnloaded += SceneManager_SceneUnloaded;
foreach (var sceneEntry in SceneNameToSceneHandles)
{
var scenHandleEntries = SceneNameToSceneHandles[sceneEntry.Key];
foreach (var sceneHandleEntry in scenHandleEntries)
{
if (!sceneHandleEntry.Value.IsAssigned)
{
if (sceneManager.VerifySceneBeforeUnloading == null || sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene))
{
m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene);
}
}
}
}
foreach (var sceneToUnload in m_ScenesToUnload)
{
SceneManager.UnloadSceneAsync(sceneToUnload);
}
}
private void SceneManager_SceneUnloaded(Scene scene)
{
if (SceneNameToSceneHandles.ContainsKey(scene.name))
{
if (SceneNameToSceneHandles[scene.name].ContainsKey(scene.handle))
{
SceneNameToSceneHandles[scene.name].Remove(scene.handle);
}
if (SceneNameToSceneHandles[scene.name].Count == 0)
{
SceneNameToSceneHandles.Remove(scene.name);
}
m_ScenesToUnload.Remove(scene);
if (m_ScenesToUnload.Count == 0)
{
SceneManager.sceneUnloaded -= SceneManager_SceneUnloaded;
}
}
}
/// <summary>
/// Handles determining if a client should attempt to load a scene during synchronization.
/// </summary>
/// <param name="sceneName">name of the scene to be loaded</param>
/// <param name="isPrimaryScene">when in client synchronization mode single, this determines if the scene is the primary active scene</param>
/// <param name="clientSynchronizationMode">the current client synchronization mode</param>
/// <param name="networkManager"><see cref="NetworkManager"/> instance</param>
/// <returns></returns>
public bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager)
{
var shouldPassThrough = clientSynchronizationMode == LoadSceneMode.Single ? false : DoesSceneHaveUnassignedEntry(sceneName, networkManager);
var activeScene = SceneManager.GetActiveScene();
// If shouldPassThrough is not yet true and the scene to be loaded is the currently active scene
if (!shouldPassThrough && sceneName == activeScene.name)
{
// In additive mode we always pass through, but in LoadSceneMode.Single we only pass through if the currently active scene
// is the primary scene to be loaded
if (clientSynchronizationMode == LoadSceneMode.Additive || (isPrimaryScene && clientSynchronizationMode == LoadSceneMode.Single))
{
// don't try to reload this scene and pass through to post load processing.
shouldPassThrough = true;
}
}
return shouldPassThrough;
}
/// <summary>
/// Handles migrating dynamically spawned NetworkObjects to the DDOL when a scene is unloaded
/// </summary>
/// <param name="networkManager"><see cref="NetworkManager"/>relative instance</param>
/// <param name="scene">scene being unloaded</param>
public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene)
{
bool isActiveScene = scene == SceneManager.GetActiveScene();
// Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects
// are despawned.
var localSpawnedObjectsHashSet = new HashSet<NetworkObject>(networkManager.SpawnManager.SpawnedObjectsList);
foreach (var networkObject in localSpawnedObjectsHashSet)
{
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle))
{
continue;
}
// Only NetworkObjects marked to not be destroyed with the scene and are not already in the DDOL are preserved
if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene)
{
// Only move dynamically spawned NetworkObjects with no parent as the children will follow
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
else if (networkManager.IsServer)
{
networkObject.Despawn();
}
else // We are a client, migrate the object into the DDOL temporarily until it receives the destroy command from the server
{
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
}
/// <summary>
/// Sets the client synchronization mode which impacts whether both the server or client take into consideration scenes loaded before
/// starting the <see cref="NetworkManager"/>.
/// </summary>
/// <remarks>
/// <see cref="LoadSceneMode.Single"/>: Does not take preloaded scenes into consideration
/// <see cref="LoadSceneMode.Single"/>: Does take preloaded scenes into consideration
/// </remarks>
/// <param name="networkManager">relative <see cref="NetworkManager"/> instance</param>
/// <param name="mode"><see cref="LoadSceneMode.Single"/> or <see cref="LoadSceneMode.Additive"/></param>
public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode)
{
var sceneManager = networkManager.SceneManager;
// Don't let client's set this value
if (!networkManager.IsServer)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Clients should not set this value as it is automatically synchronized with the server's setting!");
}
return;
}
else // Warn users if they are changing this after there are clients already connected and synchronized
if (networkManager.ConnectedClientsIds.Count > (networkManager.IsServer ? 0 : 1) && sceneManager.ClientSynchronizationMode != mode)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
}
}
// For additive client synchronization, we take into consideration scenes
// already loaded.
if (mode == LoadSceneMode.Additive)
{
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
// If using scene verification
if (sceneManager.VerifySceneBeforeLoading != null)
{
// Determine if we should take this scene into consideration
if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive))
{
continue;
}
}
// If the scene is not already in the ScenesLoaded list, then add it
if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle))
{
sceneManager.ScenesLoaded.Add(scene.handle, scene);
}
}
}
// Set the client synchronization mode
sceneManager.ClientSynchronizationMode = mode;
}
}
}

View File

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

View File

@@ -1,3 +1,4 @@
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -12,5 +13,24 @@ namespace Unity.Netcode
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
AsyncOperation UnloadSceneAsync(Scene scene, SceneEventProgress sceneEventProgress);
void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager = null);
Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager = null);
bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager = null);
void StopTrackingScene(int handle, string name, NetworkManager networkManager = null);
void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager = null);
void ClearSceneTracking(NetworkManager networkManager = null);
void UnloadUnassignedScenes(NetworkManager networkManager = null);
void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene);
void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode);
bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using UnityEngine.SceneManagement;
@@ -80,6 +80,16 @@ namespace Unity.Netcode
/// <b>Event Notification:</b> Both server and client receive a local notification.
/// </summary>
SynchronizeComplete,
/// <summary>
/// Synchronizes clients when the active scene has changed
/// See: <see cref="NetworkObject.ActiveSceneSynchronization"/>
/// </summary>
ActiveSceneChanged,
/// <summary>
/// Synchronizes clients when one or more NetworkObjects are migrated into a new scene
/// See: <see cref="NetworkObject.SceneMigrationSynchronization"/>
/// </summary>
ObjectSceneChanged,
}
/// <summary>
@@ -94,7 +104,7 @@ namespace Unity.Netcode
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
internal uint SceneEventId;
internal uint ActiveSceneHash;
internal uint SceneHash;
internal int SceneHandle;
@@ -139,6 +149,8 @@ namespace Unity.Netcode
internal Queue<uint> ScenesToSynchronize;
internal Queue<uint> SceneHandlesToSynchronize;
internal LoadSceneMode ClientSynchronizationMode;
/// <summary>
/// Server Side:
@@ -315,6 +327,8 @@ namespace Unity.Netcode
case SceneEventType.ReSynchronize:
case SceneEventType.LoadEventCompleted:
case SceneEventType.UnloadEventCompleted:
case SceneEventType.ActiveSceneChanged:
case SceneEventType.ObjectSceneChanged:
{
return true;
}
@@ -384,6 +398,18 @@ namespace Unity.Netcode
// Write the scene event type
writer.WriteValueSafe(SceneEventType);
if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
writer.WriteValueSafe(ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
SerializeObjectsMovedIntoNewScene(writer);
return;
}
// Write the scene loading mode
writer.WriteValueSafe((byte)LoadSceneMode);
@@ -392,6 +418,10 @@ namespace Unity.Netcode
{
writer.WriteValueSafe(SceneEventProgressId);
}
else
{
writer.WriteValueSafe(ClientSynchronizationMode);
}
// Write the scene index and handle
writer.WriteValueSafe(SceneHash);
@@ -401,6 +431,7 @@ namespace Unity.Netcode
{
case SceneEventType.Synchronize:
{
writer.WriteValueSafe(ActiveSceneHash);
WriteSceneSynchronizationData(writer);
break;
}
@@ -445,7 +476,7 @@ namespace Unity.Netcode
// Size Place Holder -- Start
// !!NOTE!!: Since this is a placeholder to be set after we know how much we have written,
// for stream offset purposes this MUST not be a packed value!
writer.WriteValueSafe((int)0);
writer.WriteValueSafe(0);
int totalBytes = 0;
// Write the number of NetworkObjects we are serializing
@@ -458,7 +489,7 @@ namespace Unity.Netcode
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
sceneObject.Serialize(writer);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
totalBytes += noStop - noStart;
}
// Write the number of despawned in-scene placed NetworkObjects
@@ -470,7 +501,7 @@ namespace Unity.Netcode
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GetSceneOriginHandle());
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
var noStop = writer.Position;
totalBytes += (int)(noStop - noStart);
totalBytes += noStop - noStart;
}
// Size Place Holder -- End
@@ -536,6 +567,26 @@ namespace Unity.Netcode
internal void Deserialize(FastBufferReader reader)
{
reader.ReadValueSafe(out SceneEventType);
if (SceneEventType == SceneEventType.ActiveSceneChanged)
{
reader.ReadValueSafe(out ActiveSceneHash);
return;
}
if (SceneEventType == SceneEventType.ObjectSceneChanged)
{
// Defer these scene event types if a client hasn't finished synchronizing
if (!m_NetworkManager.IsConnectedClient)
{
DeferObjectsMovedIntoNewScene(reader);
}
else
{
DeserializeObjectsMovedIntoNewScene(reader);
}
return;
}
reader.ReadValueSafe(out byte loadSceneMode);
LoadSceneMode = (LoadSceneMode)loadSceneMode;
@@ -543,6 +594,10 @@ namespace Unity.Netcode
{
reader.ReadValueSafe(out SceneEventProgressId);
}
else
{
reader.ReadValueSafe(out ClientSynchronizationMode);
}
reader.ReadValueSafe(out SceneHash);
reader.ReadValueSafe(out SceneHandle);
@@ -551,6 +606,7 @@ namespace Unity.Netcode
{
case SceneEventType.Synchronize:
{
reader.ReadValueSafe(out ActiveSceneHash);
CopySceneSynchronizationData(reader);
break;
}
@@ -939,6 +995,143 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Serialize scene handles and associated NetworkObjects that were migrated
/// into a new scene.
/// </summary>
private void SerializeObjectsMovedIntoNewScene(FastBufferWriter writer)
{
var sceneManager = m_NetworkManager.SceneManager;
// Write the number of scene handles
writer.WriteValueSafe(sceneManager.ObjectsMigratedIntoNewScene.Count);
foreach (var sceneHandleObjects in sceneManager.ObjectsMigratedIntoNewScene)
{
// Write the scene handle
writer.WriteValueSafe(sceneHandleObjects.Key);
// Write the number of NetworkObjectIds to expect
writer.WriteValueSafe(sceneHandleObjects.Value.Count);
foreach (var networkObject in sceneHandleObjects.Value)
{
writer.WriteValueSafe(networkObject.NetworkObjectId);
}
}
// Once we are done, clear the table
sceneManager.ObjectsMigratedIntoNewScene.Clear();
}
/// <summary>
/// Deserialize scene handles and associated NetworkObjects that need to
/// be migrated into a new scene.
/// </summary>
private void DeserializeObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
// Just always assure this has no entries
sceneManager.ObjectsMigratedIntoNewScene.Clear();
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
sceneManager.ObjectsMigratedIntoNewScene.Add(sceneHandle, new List<NetworkObject>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
if (!spawnManager.SpawnedObjects.ContainsKey(networkObjectId))
{
NetworkLog.LogError($"[Object Scene Migration] Trying to synchronize NetworkObjectId ({networkObjectId}) but it was not spawned or no longer exists!!");
continue;
}
// Add NetworkObject scene migration to ObjectsMigratedIntoNewScene dictionary that is processed
//
sceneManager.ObjectsMigratedIntoNewScene[sceneHandle].Add(spawnManager.SpawnedObjects[networkObjectId]);
}
}
}
/// <summary>
/// While a client is synchronizing ObjectSceneChanged messages could be received.
/// This defers any ObjectSceneChanged message processing to occur after the client
/// has completed synchronization to assure the associated NetworkObjects being
/// migrated to a new scene are instantiated and spawned.
/// </summary>
private void DeferObjectsMovedIntoNewScene(FastBufferReader reader)
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
var numberOfScenes = 0;
var sceneHandle = 0;
var objectCount = 0;
var networkObjectId = (ulong)0;
var deferredObjectsMovedEvent = new NetworkSceneManager.DeferredObjectsMovedEvent()
{
ObjectsMigratedTable = new Dictionary<int, List<ulong>>()
};
reader.ReadValueSafe(out numberOfScenes);
for (int i = 0; i < numberOfScenes; i++)
{
reader.ReadValueSafe(out sceneHandle);
deferredObjectsMovedEvent.ObjectsMigratedTable.Add(sceneHandle, new List<ulong>());
reader.ReadValueSafe(out objectCount);
for (int j = 0; j < objectCount; j++)
{
reader.ReadValueSafe(out networkObjectId);
deferredObjectsMovedEvent.ObjectsMigratedTable[sceneHandle].Add(networkObjectId);
}
}
sceneManager.DeferredObjectsMovedEvents.Add(deferredObjectsMovedEvent);
}
internal void ProcessDeferredObjectSceneChangedEvents()
{
var sceneManager = m_NetworkManager.SceneManager;
var spawnManager = m_NetworkManager.SpawnManager;
if (sceneManager.DeferredObjectsMovedEvents.Count == 0)
{
return;
}
foreach (var objectsMovedEvent in sceneManager.DeferredObjectsMovedEvents)
{
foreach (var keyEntry in objectsMovedEvent.ObjectsMigratedTable)
{
if (!sceneManager.ObjectsMigratedIntoNewScene.ContainsKey(keyEntry.Key))
{
sceneManager.ObjectsMigratedIntoNewScene.Add(keyEntry.Key, new List<NetworkObject>());
}
foreach (var objectId in keyEntry.Value)
{
if (!spawnManager.SpawnedObjects.ContainsKey(objectId))
{
NetworkLog.LogWarning($"[Deferred][Object Scene Migration] Trying to synchronize NetworkObjectId ({objectId}) but it was not spawned or no longer exists!");
continue;
}
var networkObject = spawnManager.SpawnedObjects[objectId];
if (!sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Contains(networkObject))
{
sceneManager.ObjectsMigratedIntoNewScene[keyEntry.Key].Add(networkObject);
}
}
}
objectsMovedEvent.ObjectsMigratedTable.Clear();
}
sceneManager.DeferredObjectsMovedEvents.Clear();
// If there are any pending objects to migrate, then migrate them
if (sceneManager.ObjectsMigratedIntoNewScene.Count > 0)
{
sceneManager.MigrateNetworkObjectsIntoScenes();
}
}
/// <summary>
/// Used to release the pooled network buffer
/// </summary>

View File

@@ -83,7 +83,7 @@ namespace Unity.Netcode
/// </summary>
internal bool HasTimedOut()
{
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
return WhenSceneEventHasTimedOut <= m_NetworkManager.RealTimeProvider.RealTimeSinceStartup;
}
/// <summary>
@@ -164,7 +164,7 @@ namespace Unity.Netcode
ClientsProcessingSceneEvent.Add(connectedClientId, false);
}
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
}
}

View File

@@ -1299,8 +1299,10 @@ namespace Unity.Netcode
where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
ReadUnmanaged(out int length);
value = new T();
value.Length = length;
value = new T
{
Length = length
};
ReadBytes(value.GetUnsafePtr(), length);
}
@@ -1319,8 +1321,10 @@ namespace Unity.Netcode
where T : unmanaged, INativeList<byte>, IUTF8Bytes
{
ReadUnmanagedSafe(out int length);
value = new T();
value.Length = length;
value = new T
{
Length = length
};
ReadBytesSafe(value.GetUnsafePtr(), length);
}

View File

@@ -54,13 +54,7 @@ namespace Unity.Netcode
throw new ArgumentNullException(nameof(gameObject));
}
var networkObject = gameObject.GetComponent<NetworkObject>();
if (networkObject == null)
{
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
}
var networkObject = gameObject.GetComponent<NetworkObject>() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
@@ -90,7 +84,7 @@ namespace Unity.Netcode
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{
networkManager = networkManager != null ? networkManager : NetworkManager.Singleton;
networkManager = networkManager ?? NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);
return networkObject;

View File

@@ -154,7 +154,7 @@ namespace Unity.Netcode
internal ulong GetNetworkObjectId()
{
if (ReleasedNetworkObjectIds.Count > 0 && NetworkManager.NetworkConfig.RecycleNetworkIds && (Time.unscaledTime - ReleasedNetworkObjectIds.Peek().ReleaseTime) >= NetworkManager.NetworkConfig.NetworkIdRecycleDelay)
if (ReleasedNetworkObjectIds.Count > 0 && NetworkManager.NetworkConfig.RecycleNetworkIds && (NetworkManager.RealTimeProvider.UnscaledTime - ReleasedNetworkObjectIds.Peek().ReleaseTime) >= NetworkManager.NetworkConfig.NetworkIdRecycleDelay)
{
return ReleasedNetworkObjectIds.Dequeue().NetworkId;
}
@@ -405,6 +405,9 @@ namespace Unity.Netcode
if (networkObject != null)
{
networkObject.DestroyWithScene = sceneObject.DestroyWithScene;
networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle;
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
// This is a special case scenario where a late joining client has joined and loaded one or
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
@@ -610,6 +613,12 @@ namespace Unity.Netcode
}
childObject.IsSceneObject = sceneObject;
}
// Only dynamically spawned NetworkObjects are allowed
if (!sceneObject)
{
networkObject.SubscribeToActiveSceneForSynch();
}
}
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
@@ -848,7 +857,7 @@ namespace Unity.Netcode
ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId()
{
NetworkId = networkObject.NetworkObjectId,
ReleaseTime = Time.unscaledTime
ReleaseTime = NetworkManager.RealTimeProvider.UnscaledTime
});
}

View File

@@ -0,0 +1,10 @@
namespace Unity.Netcode
{
internal interface IRealTimeProvider
{
float RealTimeSinceStartup { get; }
float UnscaledTime { get; }
float UnscaledDeltaTime { get; }
float DeltaTime { get; }
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 73bdda41e36846e893fd14dbd6de9978
timeCreated: 1679413210

View File

@@ -0,0 +1,12 @@
using UnityEngine;
namespace Unity.Netcode
{
internal class RealTimeProvider : IRealTimeProvider
{
public float RealTimeSinceStartup => Time.realtimeSinceStartup;
public float UnscaledTime => Time.unscaledTime;
public float UnscaledDeltaTime => Time.unscaledDeltaTime;
public float DeltaTime => Time.deltaTime;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5aa5470767d64d8e89ac69ff52a8c404
timeCreated: 1679413182

View File

@@ -149,7 +149,7 @@ namespace Unity.Netcode.Transports.UNET
var eventType = UnityEngine.Networking.NetworkTransport.Receive(out int hostId, out int connectionId, out _, m_MessageBuffer, m_MessageBuffer.Length, out int receivedSize, out byte error);
clientId = GetNetcodeClientId((byte)hostId, (ushort)connectionId, false);
receiveTime = Time.realtimeSinceStartup;
receiveTime = NetworkManager.RealTimeProvider.RealTimeSinceStartup;
var networkError = (NetworkError)error;
if (networkError == NetworkError.MessageToLong)
@@ -214,7 +214,7 @@ namespace Unity.Netcode.Transports.UNET
{
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
UnityEngine.Networking.NetworkTransport.Disconnect((int)hostId, (int)connectionId, out byte error);
UnityEngine.Networking.NetworkTransport.Disconnect(hostId, connectionId, out byte error);
}
public override void DisconnectLocalClient()
@@ -226,7 +226,7 @@ namespace Unity.Netcode.Transports.UNET
{
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
return (ulong)UnityEngine.Networking.NetworkTransport.GetCurrentRTT((int)hostId, (int)connectionId, out byte error);
return (ulong)UnityEngine.Networking.NetworkTransport.GetCurrentRTT(hostId, connectionId, out byte error);
}
public override void Shutdown()

View File

@@ -450,6 +450,8 @@ namespace Unity.Netcode.Transports.UTP
internal NetworkManager NetworkManager;
private IRealTimeProvider m_RealTimeProvider;
/// <summary>
/// SendQueue dictionary is used to batch events instead of sending them immediately.
/// </summary>
@@ -763,6 +765,10 @@ namespace Unity.Netcode.Transports.UTP
// Send as many batched messages from the queue as possible.
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
{
if (!m_Driver.IsCreated)
{
return;
}
new SendBatchedMessagesJob
{
Driver = m_Driver.ToConcurrent(),
@@ -784,7 +790,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
ParseClientId(connection),
default,
Time.realtimeSinceStartup);
m_RealTimeProvider.RealTimeSinceStartup);
return true;
@@ -819,7 +825,7 @@ namespace Unity.Netcode.Transports.UTP
break;
}
InvokeOnTransportEvent(NetcodeNetworkEvent.Data, clientId, message, Time.realtimeSinceStartup);
InvokeOnTransportEvent(NetcodeNetworkEvent.Data, clientId, message, m_RealTimeProvider.RealTimeSinceStartup);
}
}
@@ -835,7 +841,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
clientId,
default,
Time.realtimeSinceStartup);
m_RealTimeProvider.RealTimeSinceStartup);
m_State = State.Connected;
return true;
@@ -863,7 +869,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
clientId,
default,
Time.realtimeSinceStartup);
m_RealTimeProvider.RealTimeSinceStartup);
return true;
}
@@ -893,7 +899,7 @@ namespace Unity.Netcode.Transports.UTP
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, Time.realtimeSinceStartup);
InvokeOnTransportEvent(NetcodeNetworkEvent.TransportFailure, 0, default, m_RealTimeProvider.RealTimeSinceStartup);
return;
}
@@ -1116,7 +1122,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
m_ServerClientId,
default,
Time.realtimeSinceStartup);
m_RealTimeProvider.RealTimeSinceStartup);
}
}
}
@@ -1179,6 +1185,8 @@ namespace Unity.Netcode.Transports.UTP
NetworkManager = networkManager;
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider();
m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
// If the user sends a message of exactly m_MaxPayloadSize in length, we need to
@@ -1203,7 +1211,7 @@ namespace Unity.Netcode.Transports.UTP
/// </summary>
/// <param name="clientId">The clientId this event is for</param>
/// <param name="payload">The incoming data payload</param>
/// <param name="receiveTime">The time the event was received, as reported by Time.realtimeSinceStartup.</param>
/// <param name="receiveTime">The time the event was received, as reported by m_RealTimeProvider.RealTimeSinceStartup.</param>
/// <returns>Returns the event type</returns>
public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
{
@@ -1276,7 +1284,7 @@ namespace Unity.Netcode.Transports.UTP
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
clientId,
default(ArraySegment<byte>),
Time.realtimeSinceStartup);
m_RealTimeProvider.RealTimeSinceStartup);
}
}
else
@@ -1424,6 +1432,10 @@ namespace Unity.Netcode.Transports.UTP
private string m_ClientCaCertificate;
/// <summary>Set the server parameters for encryption.</summary>
/// <remarks>
/// The public certificate and private key are expected to be in the PEM format, including
/// the begin/end markers like <c>-----BEGIN CERTIFICATE-----</c>.
/// </remarks>
/// <param name="serverCertificate">Public certificate for the server (PEM format).</param>
/// <param name="serverPrivateKey">Private key for the server (PEM format).</param>
public void SetServerSecrets(string serverCertificate, string serverPrivateKey)
@@ -1434,9 +1446,15 @@ namespace Unity.Netcode.Transports.UTP
/// <summary>Set the client parameters for encryption.</summary>
/// <remarks>
/// <para>
/// If the CA certificate is not provided, validation will be done against the OS/browser
/// certificate store. This is what you'd want if using certificates from a known provider.
/// For self-signed certificates, the CA certificate needs to be provided.
/// </para>
/// <para>
/// The CA certificate (if provided) is expected to be in the PEM format, including the
/// begin/end markers like <c>-----BEGIN CERTIFICATE-----</c>.
/// </para>
/// </remarks>
/// <param name="serverCommonName">Common name of the server (typically hostname).</param>
/// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param>

View File

@@ -12,7 +12,8 @@
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
"Unity.Networking.Transport",
"Unity.Collections",
"Unity.Burst"
"Unity.Burst",
"Unity.Mathematics"
],
"allowUnsafeCode": true,
"versionDefines": [

View File

@@ -15,6 +15,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
{
private Scene m_InvalidScene = new Scene();
internal struct SceneEntry
{
public bool IsAssigned;
public Scene Scene;
}
internal static Dictionary<NetworkManager, Dictionary<string, Dictionary<int, SceneEntry>>> SceneNameToSceneHandles = new Dictionary<NetworkManager, Dictionary<string, Dictionary<int, SceneEntry>>>();
// All IntegrationTestSceneHandler instances register their associated NetworkManager
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
@@ -96,7 +106,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Processes scene loading jobs
/// </summary>
/// <param name="queuedSceneJob">job to process</param>
static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
internal static IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
{
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
while (!itegrationTestSceneHandler.OnCanClientsLoad())
@@ -170,7 +180,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Processes scene unloading jobs
/// </summary>
/// <param name="queuedSceneJob">job to process</param>
static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
internal static IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
{
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
while (!itegrationTestSceneHandler.OnCanClientsUnload())
@@ -213,7 +223,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Processes all jobs within the queue.
/// When all jobs are finished, the coroutine stops.
/// </summary>
static internal IEnumerator JobQueueProcessor()
internal static IEnumerator JobQueueProcessor()
{
while (QueuedSceneJobs.Count != 0)
{
@@ -267,8 +277,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
if (m_ServerSceneBeingLoaded == scene.name)
{
ProcessInSceneObjects(scene, NetworkManager);
SceneManager.sceneLoaded -= Sever_SceneLoaded;
ProcessInSceneObjects(scene, NetworkManager);
}
}
@@ -330,6 +340,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
continue;
}
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
{
if (NetworkManager.LogLevel == LogLevel.Developer)
@@ -347,7 +358,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
NetworkLog.LogInfo($"{NetworkManager.name} adding {sceneLoaded.name} with a handle of {sceneLoaded.handle} to its ScenesLoaded.");
}
if (DoesANetworkManagerHoldThisScene(sceneLoaded))
{
continue;
}
NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
StartTrackingScene(sceneLoaded, true, NetworkManager);
return sceneLoaded;
}
}
@@ -365,6 +381,521 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
public void ClearSceneTracking(NetworkManager networkManager)
{
SceneNameToSceneHandles.Clear();
}
public void StopTrackingScene(int handle, string name, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
return;
}
if (SceneNameToSceneHandles[networkManager].ContainsKey(name))
{
if (SceneNameToSceneHandles[networkManager][name].ContainsKey(handle))
{
SceneNameToSceneHandles[networkManager][name].Remove(handle);
if (SceneNameToSceneHandles[networkManager][name].Count == 0)
{
SceneNameToSceneHandles[networkManager].Remove(name);
}
}
}
}
public void StartTrackingScene(Scene scene, bool assigned, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
SceneNameToSceneHandles.Add(networkManager, new Dictionary<string, Dictionary<int, SceneEntry>>());
}
if (!SceneNameToSceneHandles[networkManager].ContainsKey(scene.name))
{
SceneNameToSceneHandles[networkManager].Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[networkManager][scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = true,
Scene = scene
};
SceneNameToSceneHandles[networkManager][scene.name].Add(scene.handle, sceneEntry);
}
}
private bool DoesANetworkManagerHoldThisScene(Scene scene)
{
foreach (var netManEntry in SceneNameToSceneHandles)
{
if (!netManEntry.Value.ContainsKey(scene.name))
{
continue;
}
// The other NetworkManager only has to have an entry to
// disqualify this scene instance
if (netManEntry.Value[scene.name].ContainsKey(scene.handle))
{
return true;
}
}
return false;
}
public bool DoesSceneHaveUnassignedEntry(string sceneName, NetworkManager networkManager)
{
var scenesWithSceneName = new List<Scene>();
var scenesAssigned = new List<Scene>();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
if (scene.name == sceneName)
{
scenesWithSceneName.Add(scene);
}
}
// Check for other NetworkManager instances already having been assigned this scene
foreach (var netManEntry in SceneNameToSceneHandles)
{
// Ignore this NetworkManager instance at this stage
if (netManEntry.Key == networkManager)
{
continue;
}
foreach (var scene in scenesWithSceneName)
{
if (!netManEntry.Value.ContainsKey(scene.name))
{
continue;
}
// The other NetworkManager only has to have an entry to
// disqualify this scene instance
if (netManEntry.Value[scene.name].ContainsKey(scene.handle))
{
scenesAssigned.Add(scene);
}
}
}
// Remove all of the assigned scenes from the list of scenes with the
// passed in scene name.
foreach (var assignedScene in scenesAssigned)
{
if (scenesWithSceneName.Contains(assignedScene))
{
scenesWithSceneName.Remove(assignedScene);
}
}
// If all currently loaded scenes with the scene name are taken
// then we return false
if (scenesWithSceneName.Count == 0)
{
return false;
}
// If we made it here, then no other NetworkManager is tracking this scene
// and if we don't have an entry for this NetworkManager then we can use any
// of the remaining scenes loaded with that name.
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
return true;
}
// If we don't yet have a scene name in this NetworkManager's lookup table,
// then we can use any of the remaining availabel scenes with that scene name
if (!SceneNameToSceneHandles[networkManager].ContainsKey(sceneName))
{
return true;
}
foreach (var scene in scenesWithSceneName)
{
// If we don't have an entry for this scene handle (with the scene name) then we
// can use that scene
if (!SceneNameToSceneHandles[networkManager][scene.name].ContainsKey(scene.handle))
{
return true;
}
// This entry is not assigned, then we can use the associated scene
if (!SceneNameToSceneHandles[networkManager][scene.name][scene.handle].IsAssigned)
{
return true;
}
}
// None of the scenes with the same scene name can be used
return false;
}
public Scene GetSceneFromLoadedScenes(string sceneName, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
return m_InvalidScene;
}
if (SceneNameToSceneHandles[networkManager].ContainsKey(sceneName))
{
foreach (var sceneHandleEntry in SceneNameToSceneHandles[networkManager][sceneName])
{
if (!sceneHandleEntry.Value.IsAssigned)
{
var sceneEntry = sceneHandleEntry.Value;
sceneEntry.IsAssigned = true;
SceneNameToSceneHandles[networkManager][sceneName][sceneHandleEntry.Key] = sceneEntry;
return sceneEntry.Scene;
}
}
}
// This is tricky since NetworkManager instances share the same scene hierarchy during integration tests.
// TODO 2023: Determine if there is a better way to associate the active scene for client NetworkManager instances.
var activeScene = SceneManager.GetActiveScene();
if (sceneName == activeScene.name && networkManager.SceneManager.ClientSynchronizationMode == LoadSceneMode.Additive)
{
// For now, just return the current active scene
// Note: Clients will not be able to synchronize in-scene placed NetworkObjects in an integration test for
// scenes loaded that have in-scene placed NetworkObjects prior to the clients joining (i.e. there will only
// ever be one instance of the active scene). To test in-scene placed NetworkObjects and make an integration
// test loaded scene be the active scene, don't set scene as an active scene on the server side until all
// clients have connected and loaded the scene.
return activeScene;
}
// If we found nothing return an invalid scene
return m_InvalidScene;
}
public void PopulateLoadedScenes(ref Dictionary<int, Scene> scenesLoaded, NetworkManager networkManager)
{
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
SceneNameToSceneHandles.Add(networkManager, new Dictionary<string, Dictionary<int, SceneEntry>>());
}
var sceneCount = SceneManager.sceneCount;
for (int i = 0; i < sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
// Ignore scenes that belong to other NetworkManager instances
if (DoesANetworkManagerHoldThisScene(scene))
{
continue;
}
if (!DoesSceneHaveUnassignedEntry(scene.name, networkManager))
{
continue;
}
if (!SceneNameToSceneHandles[networkManager].ContainsKey(scene.name))
{
SceneNameToSceneHandles[networkManager].Add(scene.name, new Dictionary<int, SceneEntry>());
}
if (!SceneNameToSceneHandles[networkManager][scene.name].ContainsKey(scene.handle))
{
var sceneEntry = new SceneEntry()
{
IsAssigned = false,
Scene = scene
};
SceneNameToSceneHandles[networkManager][scene.name].Add(scene.handle, sceneEntry);
if (!scenesLoaded.ContainsKey(scene.handle))
{
scenesLoaded.Add(scene.handle, scene);
}
}
else
{
throw new Exception($"[{networkManager.LocalClient.PlayerObject.name}][Duplicate Handle] Scene {scene.name} already has scene handle {scene.handle} registered!");
}
}
}
private Dictionary<Scene, NetworkManager> m_ScenesToUnload = new Dictionary<Scene, NetworkManager>();
/// <summary>
/// Handles unloading any scenes that might remain on a client that
/// need to be unloaded.
/// </summary>
/// <param name="networkManager"></param>
public void UnloadUnassignedScenes(NetworkManager networkManager = null)
{
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
return;
}
var relativeSceneNameToSceneHandles = SceneNameToSceneHandles[networkManager];
var sceneManager = networkManager.SceneManager;
SceneManager.sceneUnloaded += SceneManager_SceneUnloaded;
foreach (var sceneEntry in relativeSceneNameToSceneHandles)
{
var scenHandleEntries = relativeSceneNameToSceneHandles[sceneEntry.Key];
foreach (var sceneHandleEntry in scenHandleEntries)
{
if (!sceneHandleEntry.Value.IsAssigned)
{
if (sceneManager.VerifySceneBeforeUnloading == null || sceneManager.VerifySceneBeforeUnloading.Invoke(sceneHandleEntry.Value.Scene))
{
m_ScenesToUnload.Add(sceneHandleEntry.Value.Scene, networkManager);
}
}
}
}
foreach (var sceneToUnload in m_ScenesToUnload)
{
SceneManager.UnloadSceneAsync(sceneToUnload.Key);
}
}
/// <summary>
/// Removes the scene entry from the scene name to scene handle table
/// </summary>
private void SceneManager_SceneUnloaded(Scene scene)
{
if (m_ScenesToUnload.ContainsKey(scene))
{
var networkManager = m_ScenesToUnload[scene];
var relativeSceneNameToSceneHandles = SceneNameToSceneHandles[networkManager];
if (relativeSceneNameToSceneHandles.ContainsKey(scene.name))
{
var scenHandleEntries = relativeSceneNameToSceneHandles[scene.name];
if (scenHandleEntries.ContainsKey(scene.handle))
{
scenHandleEntries.Remove(scene.handle);
if (scenHandleEntries.Count == 0)
{
relativeSceneNameToSceneHandles.Remove(scene.name);
}
m_ScenesToUnload.Remove(scene);
if (m_ScenesToUnload.Count == 0)
{
SceneManager.sceneUnloaded -= SceneManager_SceneUnloaded;
}
}
}
}
}
/// <summary>
/// Integration test version that handles migrating dynamically spawned NetworkObjects to
/// the DDOL when a scene is unloaded
/// </summary>
/// <param name="networkManager"><see cref="NetworkManager"/> relative instance</param>
/// <param name="scene">scene being unloaded</param>
public void MoveObjectsFromSceneToDontDestroyOnLoad(ref NetworkManager networkManager, Scene scene)
{
// Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects
// are despawned.
#if UNITY_2023_1_OR_NEWER
var networkObjects = Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.InstanceID).Where((c) => c.IsSpawned);
#else
var networkObjects = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsSpawned);
#endif
foreach (var networkObject in networkObjects)
{
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene.handle != scene.handle))
{
if (networkObject != null)
{
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Ignoring {networkObject.gameObject.name} because it isn't in scene {networkObject.gameObject.scene.name} ");
}
continue;
}
bool skipPrefab = false;
foreach (var networkPrefab in networkManager.NetworkConfig.Prefabs.Prefabs)
{
if (networkPrefab.Prefab == null)
{
continue;
}
if (networkObject == networkPrefab.Prefab.GetComponent<NetworkObject>())
{
skipPrefab = true;
break;
}
}
if (skipPrefab)
{
continue;
}
// Only NetworkObjects marked to not be destroyed with the scene and are not already in the DDOL are preserved
if (!networkObject.DestroyWithScene && networkObject.gameObject.scene != networkManager.SceneManager.DontDestroyOnLoadScene)
{
// Only move dynamically spawned NetworkObjects with no parent as the children will follow
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
{
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Moving {networkObject.gameObject.name} because it is in scene {networkObject.gameObject.scene.name} with DWS = {networkObject.DestroyWithScene}.");
Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
else if (networkManager.IsServer)
{
if (networkObject.NetworkManager == networkManager)
{
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Destroying {networkObject.gameObject.name} because it is in scene {networkObject.gameObject.scene.name} with DWS = {networkObject.DestroyWithScene}.");
networkObject.Despawn();
}
else //For integration testing purposes, migrate remaining into DDOL
{
VerboseDebug($"[MoveObjects from {scene.name} | {scene.handle}] Temporarily migrating {networkObject.gameObject.name} into DDOL to await server destroy message.");
Object.DontDestroyOnLoad(networkObject.gameObject);
}
}
}
}
/// <summary>
/// Sets the client synchronization mode which impacts whether both the server or client take into consideration scenes loaded before
/// starting the <see cref="NetworkManager"/>.
/// </summary>
/// <remarks>
/// <see cref="LoadSceneMode.Single"/>: Does not take preloaded scenes into consideration
/// <see cref="LoadSceneMode.Single"/>: Does take preloaded scenes into consideration
/// </remarks>
/// <param name="networkManager">relative <see cref="NetworkManager"/> instance</param>
/// <param name="mode"><see cref="LoadSceneMode.Single"/> or <see cref="LoadSceneMode.Additive"/></param>
public void SetClientSynchronizationMode(ref NetworkManager networkManager, LoadSceneMode mode)
{
var sceneManager = networkManager.SceneManager;
// Don't let client's set this value
if (!networkManager.IsServer)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Clients should not set this value as it is automatically synchronized with the server's setting!");
}
return;
}
else if (networkManager.ConnectedClientsIds.Count > (networkManager.IsHost ? 1 : 0) && sceneManager.ClientSynchronizationMode != mode)
{
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{
NetworkLog.LogWarning("Server is changing client synchronization mode after clients have been synchronized! It is recommended to do this before clients are connected!");
}
}
// For additive client synchronization, we take into consideration scenes
// already loaded.
if (mode == LoadSceneMode.Additive)
{
if (networkManager.IsServer)
{
sceneManager.OnSceneEvent -= SceneManager_OnSceneEvent;
sceneManager.OnSceneEvent += SceneManager_OnSceneEvent;
}
if (!SceneNameToSceneHandles.ContainsKey(networkManager))
{
SceneNameToSceneHandles.Add(networkManager, new Dictionary<string, Dictionary<int, SceneEntry>>());
}
var networkManagerScenes = SceneNameToSceneHandles[networkManager];
for (int i = 0; i < SceneManager.sceneCount; i++)
{
var scene = SceneManager.GetSceneAt(i);
// Ignore scenes that belong to other NetworkManager instances
if (!DoesSceneHaveUnassignedEntry(scene.name, networkManager))
{
continue;
}
// If using scene verification
if (sceneManager.VerifySceneBeforeLoading != null)
{
// Determine if we should take this scene into consideration
if (!sceneManager.VerifySceneBeforeLoading.Invoke(scene.buildIndex, scene.name, LoadSceneMode.Additive))
{
continue;
}
}
// If the scene is not already in the ScenesLoaded list, then add it
if (!sceneManager.ScenesLoaded.ContainsKey(scene.handle))
{
StartTrackingScene(scene, true, networkManager);
sceneManager.ScenesLoaded.Add(scene.handle, scene);
}
}
}
// Set the client synchronization mode
sceneManager.ClientSynchronizationMode = mode;
}
/// <summary>
/// During integration testing, if the server loads a scene then
/// we want to start tracking it.
/// </summary>
/// <param name="sceneEvent"></param>
private void SceneManager_OnSceneEvent(SceneEvent sceneEvent)
{
// Filter for server only scene events
if (!NetworkManager.IsServer || sceneEvent.ClientId != NetworkManager.ServerClientId)
{
return;
}
switch (sceneEvent.SceneEventType)
{
case SceneEventType.LoadComplete:
{
StartTrackingScene(sceneEvent.Scene, true, NetworkManager);
break;
}
}
}
/// <summary>
/// Handles determining if a client should attempt to load a scene during synchronization.
/// </summary>
/// <param name="sceneName">name of the scene to be loaded</param>
/// <param name="isPrimaryScene">when in client synchronization mode single, this determines if the scene is the primary active scene</param>
/// <param name="clientSynchronizationMode">the current client synchronization mode</param>
/// <param name="networkManager"><see cref="NetworkManager"/>relative instance</param>
/// <returns></returns>
public bool ClientShouldPassThrough(string sceneName, bool isPrimaryScene, LoadSceneMode clientSynchronizationMode, NetworkManager networkManager)
{
var shouldPassThrough = clientSynchronizationMode == LoadSceneMode.Single ? false : DoesSceneHaveUnassignedEntry(sceneName, networkManager);
var activeScene = SceneManager.GetActiveScene();
// If shouldPassThrough is not yet true and the scene to be loaded is the currently active scene
if (!shouldPassThrough && sceneName == activeScene.name)
{
// In additive client synchronization mode we always pass through.
// Unlike the default behavior(i.e. DefaultSceneManagerHandler), for integration testing we always return false
// if it is the active scene and the client synchronization mode is LoadSceneMode.Single because the client should
// load the active scene additively for this NetworkManager instance (i.e. can't have multiple active scenes).
if (clientSynchronizationMode == LoadSceneMode.Additive)
{
// don't try to reload this scene and pass through to post load processing.
shouldPassThrough = true;
}
}
return shouldPassThrough;
}
/// <summary>
/// Constructor now must take NetworkManager
/// </summary>
@@ -410,7 +941,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
QueuedSceneJobs.Clear();
Object.Destroy(CoroutineRunner.gameObject);
if (CoroutineRunner != null && CoroutineRunner.gameObject != null)
{
Object.Destroy(CoroutineRunner.gameObject);
}
}
}
}

View File

@@ -0,0 +1,79 @@
using System.Runtime.CompilerServices;
using UnityEngine;
namespace Unity.Netcode.TestHelpers.Runtime
{
public abstract class IntegrationTestWithApproximation : NetcodeIntegrationTest
{
private const float k_AproximateDeltaVariance = 0.01f;
protected virtual float GetDeltaVarianceThreshold()
{
return k_AproximateDeltaVariance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected float EulerDelta(float a, float b)
{
return Mathf.DeltaAngle(a, b);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Vector3 EulerDelta(Vector3 a, Vector3 b)
{
return new Vector3(Mathf.DeltaAngle(a.x, b.x), Mathf.DeltaAngle(a.y, b.y), Mathf.DeltaAngle(a.z, b.z));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool ApproximatelyEuler(float a, float b)
{
return Mathf.Abs(EulerDelta(a, b)) <= GetDeltaVarianceThreshold();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Approximately(float a, float b)
{
return Mathf.Abs(a - b) <= GetDeltaVarianceThreshold();
}
protected bool Approximately(Vector2 a, Vector2 b)
{
var deltaVariance = GetDeltaVarianceThreshold();
return Mathf.Abs(a.x - b.x) <= deltaVariance &&
Mathf.Abs(a.y - b.y) <= deltaVariance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Approximately(Vector3 a, Vector3 b)
{
var deltaVariance = GetDeltaVarianceThreshold();
return Mathf.Abs(a.x - b.x) <= deltaVariance &&
Mathf.Abs(a.y - b.y) <= deltaVariance &&
Mathf.Abs(a.z - b.z) <= deltaVariance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool Approximately(Quaternion a, Quaternion b)
{
var deltaVariance = GetDeltaVarianceThreshold();
return Mathf.Abs(a.x - b.x) <= deltaVariance &&
Mathf.Abs(a.y - b.y) <= deltaVariance &&
Mathf.Abs(a.z - b.z) <= deltaVariance &&
Mathf.Abs(a.w - b.w) <= deltaVariance;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected bool ApproximatelyEuler(Vector3 a, Vector3 b)
{
return ApproximatelyEuler(a.x, b.x) && ApproximatelyEuler(a.y, b.y) && ApproximatelyEuler(a.z, b.z);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
protected Vector3 GetRandomVector3(float min, float max)
{
return new Vector3(Random.Range(min, max), Random.Range(min, max), Random.Range(min, max));
}
}
}

View File

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

View File

@@ -0,0 +1,30 @@
namespace Unity.Netcode.TestHelpers.Runtime
{
public class MockTimeProvider : IRealTimeProvider
{
public float RealTimeSinceStartup => (float)s_DoubleRealTime;
public float UnscaledTime => (float)s_DoubleRealTime;
public float UnscaledDeltaTime => (float)s_DoubleDelta;
public float DeltaTime => (float)s_DoubleDelta;
public static float StaticRealTimeSinceStartup => (float)s_DoubleRealTime;
public static float StaticUnscaledTime => (float)s_DoubleRealTime;
public static float StaticUnscaledDeltaTime => (float)s_DoubleDelta;
public static float StaticDeltaTime => (float)s_DoubleDelta;
private static double s_DoubleRealTime = 0;
private static double s_DoubleDelta = 0;
public static void TimeTravel(double amountOfTimeTraveled)
{
s_DoubleDelta = amountOfTimeTraveled;
s_DoubleRealTime += amountOfTimeTraveled;
}
public static void Reset()
{
s_DoubleDelta = 0;
s_DoubleRealTime = 0;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: bcc9a7faadea4b8ebeb041ee6e395a92
timeCreated: 1679414015

View File

@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
namespace Unity.Netcode.TestHelpers.Runtime
{
internal class MockTransport : NetworkTransport
{
private struct MessageData
{
public ulong FromClientId;
public ArraySegment<byte> Payload;
public NetworkEvent Event;
}
private static Dictionary<ulong, Queue<MessageData>> s_MessageQueue = new Dictionary<ulong, Queue<MessageData>>();
public override ulong ServerClientId { get; } = 0;
public static ulong HighTransportId = 0;
public ulong TransportId = 0;
public NetworkManager NetworkManager;
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
{
var copy = new byte[payload.Array.Length];
Array.Copy(payload.Array, copy, payload.Array.Length);
s_MessageQueue[clientId].Enqueue(new MessageData { FromClientId = TransportId, Payload = new ArraySegment<byte>(copy, payload.Offset, payload.Count), Event = NetworkEvent.Data });
}
public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
{
if (s_MessageQueue[TransportId].Count > 0)
{
var data = s_MessageQueue[TransportId].Dequeue();
clientId = data.FromClientId;
payload = data.Payload;
receiveTime = NetworkManager.RealTimeProvider.RealTimeSinceStartup;
if (NetworkManager.IsServer && data.Event == NetworkEvent.Connect)
{
s_MessageQueue[data.FromClientId].Enqueue(new MessageData { Event = NetworkEvent.Connect, FromClientId = ServerClientId, Payload = new ArraySegment<byte>() });
}
return data.Event;
}
clientId = 0;
payload = new ArraySegment<byte>();
receiveTime = 0;
return NetworkEvent.Nothing;
}
public override bool StartClient()
{
TransportId = ++HighTransportId;
s_MessageQueue[TransportId] = new Queue<MessageData>();
s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Connect, FromClientId = TransportId, Payload = new ArraySegment<byte>() });
return true;
}
public override bool StartServer()
{
s_MessageQueue[ServerClientId] = new Queue<MessageData>();
return true;
}
public override void DisconnectRemoteClient(ulong clientId)
{
s_MessageQueue[clientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = TransportId, Payload = new ArraySegment<byte>() });
}
public override void DisconnectLocalClient()
{
s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = TransportId, Payload = new ArraySegment<byte>() });
}
public override ulong GetCurrentRtt(ulong clientId)
{
return 0;
}
public override void Shutdown()
{
}
public override void Initialize(NetworkManager networkManager = null)
{
NetworkManager = networkManager;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 335908e9a37f428ba087acf00563c7be
timeCreated: 1679415868

View File

@@ -2,12 +2,14 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using NUnit.Framework;
using Unity.Netcode.RuntimeTests;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
using System.Runtime.CompilerServices;
using Unity.Netcode.RuntimeTests;
using Object = UnityEngine.Object;
namespace Unity.Netcode.TestHelpers.Runtime
@@ -22,6 +24,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// determine how clients will load scenes
/// </summary>
internal static bool IsRunning { get; private set; }
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
@@ -44,6 +47,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
s_GlobalNetworkObjects.Add(networkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId].ContainsKey(networkObject.NetworkObjectId))
{
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId] == null)
@@ -100,9 +104,9 @@ namespace Unity.Netcode.TestHelpers.Runtime
public enum NetworkManagerInstatiationMode
{
PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class
AllTests, // This will create one set of NetworkManagers used for all tests within a child derived class (destroyed once all tests are finished)
DoNotCreate // This will not create any NetworkManagers, it is up to the derived class to manage.
PerTest, // This will create and destroy new NetworkManagers for each test within a child derived class
AllTests, // This will create one set of NetworkManagers used for all tests within a child derived class (destroyed once all tests are finished)
DoNotCreate // This will not create any NetworkManagers, it is up to the derived class to manage.
}
public enum HostOrServer
@@ -143,6 +147,75 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </remarks>
protected bool m_BypassConnectionTimeout { get; set; }
/// <summary>
/// Enables "Time Travel" within the test, which swaps the time provider for the SDK from Unity's
/// <see cref="Time"/> class to <see cref="MockTimeProvider"/>, and also swaps the transport implementation
/// from <see cref="UnityTransport"/> to <see cref="MockTransport"/>.
///
/// This enables five important things that help with both performance and determinism of tests that involve a
/// lot of time and waiting:
/// 1) It allows time to move in a completely deterministic way (testing that something happens after n seconds,
/// the test will always move exactly n seconds with no chance of any variability in the timing),
/// 2) It allows skipping periods of time without actually waiting that amount of time, while still simulating
/// SDK frames as if that time were passing,
/// 3) It dissociates the SDK's update loop from Unity's update loop, allowing us to simulate SDK frame updates
/// without waiting for Unity to process things like physics, animation, and rendering that aren't relevant to
/// the test,
/// 4) It dissociates the SDK's messaging system from the networking hardware, meaning there's no delay between
/// a message being sent and it being received, allowing us to deterministically rely on the message being
/// received within specific time frames for the test, and
/// 5) It allows tests to be written without the use of coroutines, which not only improves the test's runtime,
/// but also results in easier-to-read callstacks and removes the possibility for an assertion to result in the
/// test hanging.
///
/// When time travel is enabled, the following methods become available:
///
/// <see cref="TimeTravel"/>: Simulates a specific number of frames passing over a specific time period
/// <see cref="TimeTravelToNextTick"/>: Skips forward to the next tick, siumlating at the current application frame rate
/// <see cref="WaitForConditionOrTimeOutWithTimeTravel(Func{bool},int)"/>: Simulates frames at the application frame rate until the given condition is true
/// <see cref="WaitForMessageReceivedWithTimeTravel{T}"/>: Simulates frames at the application frame rate until the required message is received
/// <see cref="WaitForMessagesReceivedWithTimeTravel"/>: Simulates frames at the application frame rate until the required messages are received
/// <see cref="StartServerAndClientsWithTimeTravel"/>: Starts a server and client and allows them to connect via simulated frames
/// <see cref="CreateAndStartNewClientWithTimeTravel"/>: Creates a client and waits for it to connect via simulated frames
/// <see cref="WaitForClientsConnectedOrTimeOutWithTimeTravel(Unity.Netcode.NetworkManager[])"/> Simulates frames at the application frame rate until the given clients are connected
/// <see cref="StopOneClientWithTimeTravel"/>: Stops a client and simulates frames until it's fully disconnected.
///
/// When time travel is enabled, <see cref="NetcodeIntegrationTest"/> will automatically use these in its methods
/// when doing things like automatically connecting clients during SetUp.
///
/// Additionally, the following methods replace their non-time-travel equivalents with variants that are not coroutines:
/// <see cref="OnTimeTravelStartedServerAndClients"/> - called when server and clients are started
/// <see cref="OnTimeTravelServerAndClientsConnected"/> - called when server and clients are connected
///
/// Note that all of the non-time travel functions can still be used even when time travel is enabled - this is
/// sometimes needed for, e.g., testing NetworkAnimator, where the unity update loop needs to run to process animations.
/// However, it's VERY important to note here that, because the SDK will not be operating based on real-world time
/// but based on the frozen time that's locked in from MockTimeProvider, actions that pass 10 seconds apart by
/// real-world clock time will be perceived by the SDK as having happened simultaneously if you don't call
/// <see cref="MockTimeProvider.TimeTravel"/> to cover the equivalent time span in the mock time provider.
/// (Calling <see cref="MockTimeProvider.TimeTravel"/> instead of <see cref="TimeTravel"/>
/// will move time forward without simulating any frames, which, in the case where real-world time has passed,
/// is likely more desirable). In most cases, this desynch won't affect anything, but it is worth noting that
/// it happens just in case a tested system depends on both the unity update loop happening *and* time moving forward.
/// </summary>
protected virtual bool m_EnableTimeTravel => false;
/// <summary>
/// If this is false, SetUp will call OnInlineSetUp instead of OnSetUp.
/// This is a performance advantage when not using the coroutine functionality, as a coroutine that
/// has no yield instructions in it will nonetheless still result in delaying the continuation of the
/// method that called it for a full frame after it returns.
/// </summary>
protected virtual bool m_SetupIsACoroutine => true;
/// <summary>
/// If this is false, TearDown will call OnInlineTearDown instead of OnTearDown.
/// This is a performance advantage when not using the coroutine functionality, as a coroutine that
/// has no yield instructions in it will nonetheless still result in delaying the continuation of the
/// method that called it for a full frame after it returns.
/// </summary>
protected virtual bool m_TearDownIsACoroutine => true;
/// <summary>
/// Used to display the various integration test
/// stages and can be used to log verbose information
@@ -216,20 +289,54 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return null;
}
/// <summary>
/// Called before creating and starting the server and clients
/// Note: For <see cref="NetworkManagerInstatiationMode.AllTests"/> and
/// <see cref="NetworkManagerInstatiationMode.PerTest"/> mode integration tests.
/// For those two modes, if you want to have access to the server or client
/// <see cref="NetworkManager"/>s then override <see cref="OnServerAndClientsCreated"/>.
/// <see cref="m_ServerNetworkManager"/> and <see cref="m_ClientNetworkManagers"/>
/// </summary>
protected virtual void OnInlineSetup()
{
}
[UnitySetUp]
public IEnumerator SetUp()
{
VerboseDebug($"Entering {nameof(SetUp)}");
NetcodeLogAssert = new NetcodeLogAssert();
yield return OnSetup();
if (m_SetupIsACoroutine)
{
yield return OnSetup();
}
else
{
OnInlineSetup();
}
if (m_EnableTimeTravel)
{
MockTimeProvider.Reset();
ComponentFactory.Register<IRealTimeProvider>(manager => new MockTimeProvider());
}
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null ||
m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
{
CreateServerAndClients();
yield return StartServerAndClients();
if (m_EnableTimeTravel)
{
StartServerAndClientsWithTimeTravel();
}
else
{
yield return StartServerAndClients();
}
}
VerboseDebug($"Exiting {nameof(SetUp)}");
}
@@ -294,6 +401,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
clientNetworkManagersList.Remove(networkManager);
}
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
m_NumberOfClients = clientNetworkManagersList.Count;
}
@@ -304,7 +412,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected virtual void OnNewClientCreated(NetworkManager networkManager)
{
}
/// <summary>
@@ -322,7 +429,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
{
}
/// <summary>
@@ -331,7 +437,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected IEnumerator CreateAndStartNewClient()
{
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length);
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
// Notification that the new client (NetworkManager) has been created
@@ -356,13 +462,53 @@ namespace Unity.Netcode.TestHelpers.Runtime
if (s_GlobalTimeoutHelper.TimedOut)
{
AddRemoveNetworkManager(networkManager, false);
Object.Destroy(networkManager.gameObject);
Object.DestroyImmediate(networkManager.gameObject);
}
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
ClientNetworkManagerPostStart(networkManager);
VerboseDebug($"[{networkManager.name}] Created and connected!");
}
/// <summary>
/// This will create, start, and connect a new client while in the middle of an
/// integration test.
/// </summary>
protected void CreateAndStartNewClientWithTimeTravel()
{
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
// Notification that the new client (NetworkManager) has been created
// in the event any modifications need to be made before starting the client
OnNewClientCreated(networkManager);
NetcodeIntegrationTestHelpers.StartOneClient(networkManager);
if (LogAllMessages)
{
networkManager.MessagingSystem.Hook(new DebugNetworkHooks());
}
AddRemoveNetworkManager(networkManager, true);
OnNewClientStarted(networkManager);
// Wait for the new client to connect
var connected = WaitForClientsConnectedOrTimeOutWithTimeTravel();
OnNewClientStartedAndConnected(networkManager);
if (!connected)
{
AddRemoveNetworkManager(networkManager, false);
Object.DestroyImmediate(networkManager.gameObject);
}
Assert.IsTrue(connected, $"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
ClientNetworkManagerPostStart(networkManager);
VerboseDebug($"[{networkManager.name}] Created and connected!");
}
/// <summary>
/// This will stop a client while in the middle of an integration test
/// </summary>
@@ -373,6 +519,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
}
/// <summary>
/// This will stop a client while in the middle of an integration test
/// </summary>
protected void StopOneClientWithTimeTravel(NetworkManager networkManager, bool destroy = false)
{
NetcodeIntegrationTestHelpers.StopOneClient(networkManager, destroy);
AddRemoveNetworkManager(networkManager, false);
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => !networkManager.IsConnectedClient));
}
/// <summary>
/// Creates the server and clients
/// </summary>
@@ -383,8 +539,13 @@ namespace Unity.Netcode.TestHelpers.Runtime
CreatePlayerPrefab();
if (m_EnableTimeTravel)
{
m_TargetFrameRate = -1;
}
// Create multiple NetworkManager instances
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_CreateServerFirst))
if (!NetcodeIntegrationTestHelpers.Create(numberOfClients, out NetworkManager server, out NetworkManager[] clients, m_TargetFrameRate, m_CreateServerFirst, m_EnableTimeTravel))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
@@ -431,6 +592,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return null;
}
/// <summary>
/// Invoked after the server and clients have started.
/// Note: No connection verification has been done at this point
/// </summary>
protected virtual void OnTimeTravelStartedServerAndClients()
{
}
/// <summary>
/// Invoked after the server and clients have started and verified
/// their connections with each other.
@@ -440,6 +609,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return null;
}
/// <summary>
/// Invoked after the server and clients have started and verified
/// their connections with each other.
/// </summary>
protected virtual void OnTimeTravelServerAndClientsConnected()
{
}
private void ClientNetworkManagerPostStart(NetworkManager networkManager)
{
networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}";
@@ -466,6 +643,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
{
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
@@ -495,6 +673,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
ClientNetworkManagerPostStart(networkManager);
}
if (m_UseHost)
{
#if UNITY_2023_1_OR_NEWER
@@ -509,6 +688,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
{
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
@@ -570,6 +750,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
}
}
@@ -585,6 +766,73 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
/// <summary>
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
/// returns true.
/// </summary>
protected void StartServerAndClientsWithTimeTravel()
{
if (CanStartServerAndClients())
{
VerboseDebug($"Entering {nameof(StartServerAndClientsWithTimeTravel)}");
// Start the instances and pass in our SceneManagerInitialization action that is invoked immediately after host-server
// is started and after each client is started.
if (!NetcodeIntegrationTestHelpers.Start(m_UseHost, m_ServerNetworkManager, m_ClientNetworkManagers))
{
Debug.LogError("Failed to start instances");
Assert.Fail("Failed to start instances");
}
if (LogAllMessages)
{
EnableMessageLogging();
}
RegisterSceneManagerHandler();
// Notification that the server and clients have been started
OnTimeTravelStartedServerAndClients();
// When true, we skip everything else (most likely a connection oriented test)
if (!m_BypassConnectionTimeout)
{
// Wait for all clients to connect
WaitForClientsConnectedOrTimeOutWithTimeTravel();
AssertOnTimeout($"{nameof(StartServerAndClients)} timed out waiting for all clients to be connected!");
if (m_UseHost || m_ServerNetworkManager.IsHost)
{
#if UNITY_2023_1_OR_NEWER
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
var serverPlayerClones = Object.FindObjectsByType<NetworkObject>(FindObjectsSortMode.None).Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
#else
// Add the server player instance to all m_ClientSidePlayerNetworkObjects entries
var serverPlayerClones = Object.FindObjectsOfType<NetworkObject>().Where((c) => c.IsPlayerObject && c.OwnerClientId == m_ServerNetworkManager.LocalClientId);
#endif
foreach (var playerNetworkObject in serverPlayerClones)
{
if (!m_PlayerNetworkObjects.ContainsKey(playerNetworkObject.NetworkManager.LocalClientId))
{
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
}
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
}
}
ClientNetworkManagerPostStartInit();
// Notification that at this time the server and client(s) are instantiated,
// started, and connected on both sides.
OnTimeTravelServerAndClientsConnected();
VerboseDebug($"Exiting {nameof(StartServerAndClients)}");
}
}
}
/// <summary>
/// Override this method to control when clients
/// can fake-load a scene.
@@ -660,12 +908,15 @@ namespace Unity.Netcode.TestHelpers.Runtime
m_PlayerNetworkObjects.Clear();
s_GlobalNetworkObjects.Clear();
}
catch (Exception e) { throw e; }
catch (Exception e)
{
throw e;
}
finally
{
if (m_PlayerPrefab != null)
{
Object.Destroy(m_PlayerPrefab);
Object.DestroyImmediate(m_PlayerPrefab);
m_PlayerPrefab = null;
}
}
@@ -689,17 +940,34 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return null;
}
protected virtual void OnInlineTearDown()
{
}
[UnityTearDown]
public IEnumerator TearDown()
{
IntegrationTestSceneHandler.SceneNameToSceneHandles.Clear();
VerboseDebug($"Entering {nameof(TearDown)}");
yield return OnTearDown();
if (m_TearDownIsACoroutine)
{
yield return OnTearDown();
}
else
{
OnInlineTearDown();
}
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
{
ShutdownAndCleanUp();
}
if (m_EnableTimeTravel)
{
ComponentFactory.Deregister<IRealTimeProvider>();
}
VerboseDebug($"Exiting {nameof(TearDown)}");
LogWaitForMessages();
NetcodeLogAssert.Dispose();
@@ -773,6 +1041,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
continue;
}
if (CanDestroyNetworkObject(networkObject))
{
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
@@ -831,10 +1100,49 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Otherwise wait for 1 tick interval
yield return s_DefaultWaitForTick;
}
// Stop checking for a timeout
timeOutHelper.Stop();
}
/// <summary>
/// Waits for the function condition to return true or it will time out. Uses time travel to simulate this
/// for the given number of frames, simulating delta times at the application frame rate.
/// </summary>
public bool WaitForConditionOrTimeOutWithTimeTravel(Func<bool> checkForCondition, int maxTries = 60)
{
if (checkForCondition == null)
{
throw new ArgumentNullException($"checkForCondition cannot be null!");
}
if (!m_EnableTimeTravel)
{
throw new ArgumentException($"Time travel must be enabled to use {nameof(WaitForConditionOrTimeOutWithTimeTravel)}!");
}
var frameRate = Application.targetFrameRate;
if (frameRate <= 0)
{
frameRate = 60;
}
var updateInterval = 1f / frameRate;
for (var i = 0; i < maxTries; ++i)
{
// Simulate a frame passing on all network managers
TimeTravel(updateInterval, 1);
// Update and check to see if the condition has been met
if (checkForCondition.Invoke())
{
return true;
}
}
return false;
}
/// <summary>
/// This version accepts an IConditionalPredicate implementation to provide
/// more flexibility for checking complex conditional cases.
@@ -857,6 +1165,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
conditionalPredicate.Finished(timeOutHelper.TimedOut);
}
/// <summary>
/// This version accepts an IConditionalPredicate implementation to provide
/// more flexibility for checking complex conditional cases. Uses time travel to simulate this
/// for the given number of frames, simulating delta times at the application frame rate.
/// </summary>
public bool WaitForConditionOrTimeOutWithTimeTravel(IConditionalPredicate conditionalPredicate, int maxTries = 60)
{
if (conditionalPredicate == null)
{
throw new ArgumentNullException($"checkForCondition cannot be null!");
}
if (!m_EnableTimeTravel)
{
throw new ArgumentException($"Time travel must be enabled to use {nameof(WaitForConditionOrTimeOutWithTimeTravel)}!");
}
conditionalPredicate.Started();
var success = WaitForConditionOrTimeOutWithTimeTravel(conditionalPredicate.HasConditionBeenReached, maxTries);
conditionalPredicate.Finished(!success);
return success;
}
/// <summary>
/// Validates that all remote clients (i.e. non-server) detect they are connected
/// to the server and that the server reflects the appropriate number of clients
@@ -869,7 +1200,23 @@ namespace Unity.Netcode.TestHelpers.Runtime
var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount;
yield return WaitForConditionOrTimeOut(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount &&
m_ServerNetworkManager.ConnectedClients.Count == serverClientCount);
m_ServerNetworkManager.ConnectedClients.Count == serverClientCount);
}
/// <summary>
/// Validates that all remote clients (i.e. non-server) detect they are connected
/// to the server and that the server reflects the appropriate number of clients
/// have connected or it will time out. Uses time travel to simulate this
/// for the given number of frames, simulating delta times at the application frame rate.
/// </summary>
/// <param name="clientsToCheck">An array of clients to be checked</param>
protected bool WaitForClientsConnectedOrTimeOutWithTimeTravel(NetworkManager[] clientsToCheck)
{
var remoteClientCount = clientsToCheck.Length;
var serverClientCount = m_ServerNetworkManager.IsHost ? remoteClientCount + 1 : remoteClientCount;
return WaitForConditionOrTimeOutWithTimeTravel(() => clientsToCheck.Where((c) => c.IsConnectedClient).Count() == remoteClientCount &&
m_ServerNetworkManager.ConnectedClients.Count == serverClientCount);
}
/// <summary>
@@ -881,6 +1228,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers);
}
/// <summary>
/// Overloaded method that just passes in all clients to
/// <see cref="WaitForClientsConnectedOrTimeOut(NetworkManager[])"/> Uses time travel to simulate this
/// for the given number of frames, simulating delta times at the application frame rate.
/// </summary>
protected bool WaitForClientsConnectedOrTimeOutWithTimeTravel()
{
return WaitForClientsConnectedOrTimeOutWithTimeTravel(m_ClientNetworkManagers);
}
internal IEnumerator WaitForMessageReceived<T>(List<NetworkManager> wiatForReceivedBy, ReceiptType type = ReceiptType.Handled) where T : INetworkMessage
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
@@ -891,17 +1248,18 @@ namespace Unity.Netcode.TestHelpers.Runtime
messageHook.AssignMessageType<T>();
messageHookEntriesForSpawn.Add(messageHook);
}
// Used to determine if all clients received the CreateObjectMessage
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
yield return WaitForConditionOrTimeOut(hooks);
Assert.False(s_GlobalTimeoutHelper.TimedOut);
}
internal IEnumerator WaitForMessagesReceived(List<Type> messagesInOrder, List<NetworkManager> wiatForReceivedBy, ReceiptType type = ReceiptType.Handled)
internal IEnumerator WaitForMessagesReceived(List<Type> messagesInOrder, List<NetworkManager> waitForReceivedBy, ReceiptType type = ReceiptType.Handled)
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
foreach (var clientNetworkManager in wiatForReceivedBy)
foreach (var clientNetworkManager in waitForReceivedBy)
{
foreach (var message in messagesInOrder)
{
@@ -910,12 +1268,49 @@ namespace Unity.Netcode.TestHelpers.Runtime
messageHookEntriesForSpawn.Add(messageHook);
}
}
// Used to determine if all clients received the CreateObjectMessage
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
yield return WaitForConditionOrTimeOut(hooks);
Assert.False(s_GlobalTimeoutHelper.TimedOut);
}
internal void WaitForMessageReceivedWithTimeTravel<T>(List<NetworkManager> waitForReceivedBy, ReceiptType type = ReceiptType.Handled) where T : INetworkMessage
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
foreach (var clientNetworkManager in waitForReceivedBy)
{
var messageHook = new MessageHookEntry(clientNetworkManager, type);
messageHook.AssignMessageType<T>();
messageHookEntriesForSpawn.Add(messageHook);
}
// Used to determine if all clients received the CreateObjectMessage
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks));
}
internal void WaitForMessagesReceivedWithTimeTravel(List<Type> messagesInOrder, List<NetworkManager> waitForReceivedBy, ReceiptType type = ReceiptType.Handled)
{
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
foreach (var clientNetworkManager in waitForReceivedBy)
{
foreach (var message in messagesInOrder)
{
var messageHook = new MessageHookEntry(clientNetworkManager, type);
messageHook.AssignMessageType(message);
messageHookEntriesForSpawn.Add(messageHook);
}
}
// Used to determine if all clients received the CreateObjectMessage
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks));
}
/// <summary>
/// Creates a basic NetworkObject test prefab, assigns it to a new
/// NetworkPrefab entry, and then adds it to the server and client(s)
@@ -926,7 +1321,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
protected GameObject CreateNetworkObjectPrefab(string baseName)
{
var prefabCreateAssertError = $"You can only invoke this method during {nameof(OnServerAndClientsCreated)} " +
$"but before {nameof(OnStartedServerAndClients)}!";
$"but before {nameof(OnStartedServerAndClients)}!";
Assert.IsNotNull(m_ServerNetworkManager, prefabCreateAssertError);
Assert.IsFalse(m_ServerNetworkManager.IsListening, prefabCreateAssertError);
@@ -1000,6 +1395,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
gameObjectsSpawned.Add(SpawnObject(prefabNetworkObject, owner, destroyWithScene));
}
return gameObjectsSpawned;
}
@@ -1008,7 +1404,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
public NetcodeIntegrationTest()
{
}
/// <summary>
@@ -1038,7 +1433,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
protected void AssertOnTimeout(string timeOutErrorMessage, TimeoutHelper assignedTimeoutHelper = null)
{
var timeoutHelper = assignedTimeoutHelper != null ? assignedTimeoutHelper : s_GlobalTimeoutHelper;
var timeoutHelper = assignedTimeoutHelper ?? s_GlobalTimeoutHelper;
Assert.False(timeoutHelper.TimedOut, timeOutErrorMessage);
}
@@ -1054,6 +1449,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
continue;
}
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
var asyncOperation = SceneManager.UnloadSceneAsync(scene);
}
@@ -1093,6 +1489,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
}
m_WaitForLog.Append($"[NetworkManager-{networkManager.LocalClientId}][WaitForTicks-End] Waited for ({networkManager.NetworkTickSystem.LocalTime.Tick - tickStart}) network ticks and ({frameCount}) frames to pass.\n");
yield break;
}
@@ -1114,5 +1511,83 @@ namespace Unity.Netcode.TestHelpers.Runtime
m_WaitForLog.Append($"[NetworkManager-{networkManager.LocalClientId}][WaitForTicks] TickRate ({networkManager.NetworkConfig.TickRate}) | Tick Wait ({count}) | TargetFrameRate ({Application.targetFrameRate}) | Target Frames ({framesPerTick * count})\n");
yield return WaitForTickAndFrames(networkManager, count, totalFrameCount);
}
/// <summary>
/// Simulate a number of frames passing over a specific amount of time.
/// The delta time simulated for each frame will be evenly divided as time/numFrames
/// This will only simulate the netcode update loop, as well as update events on
/// NetworkBehaviour instances, and will not simulate any Unity update processes (physics, etc)
/// </summary>
/// <param name="amountOfTimeInSeconds"></param>
/// <param name="numFramesToSimulate"></param>
protected static void TimeTravel(double amountOfTimeInSeconds, int numFramesToSimulate)
{
var interval = amountOfTimeInSeconds / numFramesToSimulate;
for (var i = 0; i < numFramesToSimulate; ++i)
{
MockTimeProvider.TimeTravel(interval);
SimulateOneFrame();
}
}
/// <summary>
/// Helper function to time travel exactly one tick's worth of time at the current frame and tick rates.
/// </summary>
public static void TimeTravelToNextTick()
{
var timePassed = 1.0f / k_DefaultTickRate;
var frameRate = Application.targetFrameRate;
if (frameRate <= 0)
{
frameRate = 60;
}
var frames = Math.Max((int)(timePassed / frameRate), 1);
TimeTravel(timePassed, frames);
}
/// <summary>
/// Simulates one SDK frame. This can be used even without TimeTravel, though it's of somewhat less use
/// without TimeTravel, as, without the mock transport, it will likely not provide enough time for any
/// sent messages to be received even if called dozens of times.
/// </summary>
public static void SimulateOneFrame()
{
foreach (NetworkUpdateStage stage in Enum.GetValues(typeof(NetworkUpdateStage)))
{
NetworkUpdateLoop.RunNetworkUpdateStage(stage);
string methodName = string.Empty;
switch (stage)
{
case NetworkUpdateStage.FixedUpdate:
methodName = "FixedUpdate"; // mapping NetworkUpdateStage.FixedUpdate to MonoBehaviour.FixedUpdate
break;
case NetworkUpdateStage.Update:
methodName = "Update"; // mapping NetworkUpdateStage.Update to MonoBehaviour.Update
break;
case NetworkUpdateStage.PreLateUpdate:
methodName = "LateUpdate"; // mapping NetworkUpdateStage.PreLateUpdate to MonoBehaviour.LateUpdate
break;
}
if (!string.IsNullOrEmpty(methodName))
{
#if UNITY_2023_1_OR_NEWER
foreach (var behaviour in Object.FindObjectsByType<NetworkBehaviour>(FindObjectsSortMode.InstanceID))
#else
foreach (var behaviour in Object.FindObjectsOfType<NetworkBehaviour>())
#endif
{
var method = behaviour.GetType().GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null)
{
method = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance);
}
method?.Invoke(behaviour, new object[] { });
}
}
}
}
}
}

View File

@@ -186,7 +186,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
networkManager.NetworkConfig.NetworkTransport = unityTransport;
}
public static NetworkManager CreateServer()
private static void AddMockTransport(NetworkManager networkManager)
{
// Create transport
var mockTransport = networkManager.gameObject.AddComponent<MockTransport>();
// Set the NetworkConfig
networkManager.NetworkConfig ??= new NetworkConfig();
networkManager.NetworkConfig.NetworkTransport = mockTransport;
}
public static NetworkManager CreateServer(bool mockTransport = false)
{
// Create gameObject
var go = new GameObject("NetworkManager - Server");
@@ -194,7 +203,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
// Create networkManager component
var server = go.AddComponent<NetworkManager>();
NetworkManagerInstances.Insert(0, server);
AddUnityTransport(server);
if (mockTransport)
{
AddMockTransport(server);
}
else
{
AddUnityTransport(server);
}
return server;
}
@@ -206,20 +222,20 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// <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>
/// <param name="serverFirst">This determines if the server or clients will be instantiated first (defaults to server first)</param>
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, bool serverFirst = true)
public static bool Create(int clientCount, out NetworkManager server, out NetworkManager[] clients, int targetFrameRate = 60, bool serverFirst = true, bool useMockTransport = false)
{
s_NetworkManagerInstances = new List<NetworkManager>();
server = null;
if (serverFirst)
{
server = CreateServer();
server = CreateServer(useMockTransport);
}
CreateNewClients(clientCount, out clients);
CreateNewClients(clientCount, out clients, useMockTransport);
if (!serverFirst)
{
server = CreateServer();
server = CreateServer(useMockTransport);
}
s_OriginalTargetFrameRate = Application.targetFrameRate;
@@ -228,13 +244,20 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
internal static NetworkManager CreateNewClient(int identifier)
internal static NetworkManager CreateNewClient(int identifier, bool mockTransport = false)
{
// Create gameObject
var go = new GameObject("NetworkManager - Client - " + identifier);
// Create networkManager component
var networkManager = go.AddComponent<NetworkManager>();
AddUnityTransport(networkManager);
if (mockTransport)
{
AddMockTransport(networkManager);
}
else
{
AddUnityTransport(networkManager);
}
return networkManager;
}
@@ -244,13 +267,13 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
/// <param name="clientCount">The amount of clients</param>
/// <param name="clients"></param>
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients)
public static bool CreateNewClients(int clientCount, out NetworkManager[] clients, bool useMockTransport = false)
{
clients = new NetworkManager[clientCount];
for (int i = 0; i < clientCount; i++)
{
// Create networkManager component
clients[i] = CreateNewClient(i);
clients[i] = CreateNewClient(i, useMockTransport);
}
NetworkManagerInstances.AddRange(clients);
@@ -314,7 +337,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
if (networkManager.gameObject != null)
{
Object.Destroy(networkManager.gameObject);
Object.DestroyImmediate(networkManager.gameObject);
}
}
@@ -339,6 +362,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
return true;
}
private static bool VerifySceneIsValidForClientsToUnload(Scene scene)
{
// Unless specifically set, we always return false
return false;
}
/// <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
@@ -351,10 +380,21 @@ namespace Unity.Netcode.TestHelpers.Runtime
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);
}
// For testing purposes, all clients always set the VerifySceneBeforeUnloading callback and enabled
// PostSynchronizationSceneUnloading. Where tests that expect clients to unload scenes should override
// the callback and return true for the scenes the client(s) is/are allowed to unload.
if (!networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeUnloading == null)
{
networkManager.SceneManager.VerifySceneBeforeUnloading = VerifySceneIsValidForClientsToUnload;
networkManager.SceneManager.PostSynchronizationSceneUnloading = 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();
@@ -494,8 +534,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
Assert.IsNotNull(server, prefabCreateAssertError);
Assert.IsFalse(server.IsListening, prefabCreateAssertError);
var gameObject = new GameObject();
gameObject.name = baseName;
var gameObject = new GameObject
{
name = baseName
};
var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.NetworkManagerOwner = server;
MakeNetworkObjectTestPrefab(networkObject);
@@ -715,6 +757,40 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
/// <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 void GetNetworkObjectByRepresentationWithTimeTravel(Func<NetworkObject, bool> predicate, NetworkManager representation, ResultWrapper<NetworkObject> result, bool failIfNull = true, int maxTries = 60)
{
if (result == null)
{
throw new ArgumentNullException("Result cannot be null");
}
if (predicate == null)
{
throw new ArgumentNullException("Predicate cannot be null");
}
var tries = 0;
while (++tries < maxTries && !representation.SpawnManager.SpawnedObjects.Any(x => predicate(x.Value)))
{
NetcodeIntegrationTest.SimulateOneFrame();
}
result.Result = representation.SpawnManager.SpawnedObjects.FirstOrDefault(x => predicate(x.Value)).Value;
if (failIfNull && result.Result == null)
{
Assert.Fail("NetworkObject could not be found");
}
}
/// <summary>
/// Waits for a predicate condition to be met
/// </summary>

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections.Generic;
using UnityEngine;
using NUnit.Framework;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
namespace Unity.Netcode.TestHelpers.Runtime
{

View File

@@ -8,30 +8,67 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
public class TimeoutHelper
{
private const float k_DefaultTimeOutWaitPeriod = 2.0f;
protected const float k_DefaultTimeOutWaitPeriod = 2.0f;
private float m_MaximumTimeBeforeTimeOut;
private float m_TimeOutPeriod;
private bool m_IsStarted;
protected bool m_IsStarted { get; private set; }
public bool TimedOut { get; internal set; }
private float m_TimeStarted;
private float m_TimeStopped;
public float GetTimeElapsed()
{
if (m_IsStarted)
{
return Time.realtimeSinceStartup - m_TimeStarted;
}
else
{
return m_TimeStopped - m_TimeStarted;
}
}
protected virtual void OnStart()
{
}
public void Start()
{
m_TimeStopped = 0.0f;
m_TimeStarted = Time.realtimeSinceStartup;
m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod;
m_IsStarted = true;
TimedOut = false;
OnStart();
}
protected virtual void OnStop()
{
}
public void Stop()
{
if (m_TimeStopped == 0.0f)
{
m_TimeStopped = Time.realtimeSinceStartup;
}
TimedOut = HasTimedOut();
m_IsStarted = false;
OnStop();
}
protected virtual bool OnHasTimedOut()
{
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
}
public bool HasTimedOut()
{
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
return OnHasTimedOut();
}
public TimeoutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod)
@@ -39,4 +76,70 @@ namespace Unity.Netcode.TestHelpers.Runtime
m_TimeOutPeriod = timeOutPeriod;
}
}
/// <summary>
/// This can be used in place of TimeoutHelper if you suspect a test is having
/// issues on a system where the frame rate is running slow than expected and
/// allowing a certain number of frame updates is required.
/// </summary>
public class TimeoutFrameCountHelper : TimeoutHelper
{
private const uint k_DefaultTickRate = 30;
private float m_TotalFramesToWait;
private int m_StartFrameCount;
private int m_EndFrameCount;
private bool m_ReachedFrameCount;
public int GetFrameCount()
{
if (m_IsStarted)
{
return Time.frameCount - m_StartFrameCount;
}
else
{
return m_EndFrameCount - m_StartFrameCount;
}
}
protected override void OnStop()
{
if (m_EndFrameCount == 0)
{
m_EndFrameCount = Time.frameCount;
}
base.OnStop();
}
protected override bool OnHasTimedOut()
{
var currentFrameCountDelta = Time.frameCount - m_StartFrameCount;
if (m_IsStarted)
{
m_ReachedFrameCount = currentFrameCountDelta >= m_TotalFramesToWait;
}
// Only time out if we have both exceeded the time period and the expected number of frames has reached the expected number of frames
// (this handles the scenario where some systems are running a much lower frame rate)
return m_ReachedFrameCount && base.OnHasTimedOut();
}
protected override void OnStart()
{
m_EndFrameCount = 0;
m_StartFrameCount = Time.frameCount;
base.OnStart();
}
public TimeoutFrameCountHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod, uint tickRate = k_DefaultTickRate) : base(timeOutPeriod)
{
// Calculate the expected number of frame updates that should occur during the tick count wait period
var frameFrequency = 1.0f / (Application.targetFrameRate >= 60 && Application.targetFrameRate <= 100 ? Application.targetFrameRate : 60.0f);
var tickFrequency = 1.0f / tickRate;
var framesPerTick = tickFrequency / frameFrequency;
var totalExpectedTicks = timeOutPeriod / tickFrequency;
m_TotalFramesToWait = framesPerTick * totalExpectedTicks;
}
}
}

View File

@@ -10,8 +10,10 @@ namespace Unity.Netcode.EditorTests
{
var networkContext = new NetworkContext();
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
var msg = new DisconnectReasonMessage();
msg.Reason = string.Empty;
var msg = new DisconnectReasonMessage
{
Reason = string.Empty
};
msg.Serialize(writer, msg.Version);
var fbr = new FastBufferReader(writer, Allocator.Temp);
@@ -26,8 +28,10 @@ namespace Unity.Netcode.EditorTests
{
var networkContext = new NetworkContext();
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
var msg = new DisconnectReasonMessage();
msg.Reason = "Foo";
var msg = new DisconnectReasonMessage
{
Reason = "Foo"
};
msg.Serialize(writer, msg.Version);
var fbr = new FastBufferReader(writer, Allocator.Temp);
@@ -42,8 +46,10 @@ namespace Unity.Netcode.EditorTests
{
var networkContext = new NetworkContext();
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
var msg = new DisconnectReasonMessage();
msg.Reason = "ThisStringIsWayLongerThanTwentyBytes";
var msg = new DisconnectReasonMessage
{
Reason = "ThisStringIsWayLongerThanTwentyBytes"
};
msg.Serialize(writer, msg.Version);
var fbr = new FastBufferReader(writer, Allocator.Temp);

View File

@@ -222,10 +222,11 @@ namespace Unity.Netcode.EditorTests
{
var listMessages = new List<MessagingSystem.MessageWithHandler>();
var messageWithHandler = new MessagingSystem.MessageWithHandler();
messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage);
messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion<zzzLateLexicographicNetworkMessage>;
var messageWithHandler = new MessagingSystem.MessageWithHandler
{
MessageType = typeof(zzzLateLexicographicNetworkMessage),
GetVersion = MessagingSystem.CreateMessageAndGetVersion<zzzLateLexicographicNetworkMessage>
};
listMessages.Add(messageWithHandler);
messageWithHandler.MessageType = typeof(ConnectionRequestMessage);

View File

@@ -149,7 +149,7 @@ namespace Unity.Netcode.EditorTests
var v1 = new VersionedTestMessage_v1();
v1.Deserialize(reader, ref context, receivedMessageVersion);
A = v1.A;
D = (float)v1.D;
D = v1.D;
E = k_DefaultE;
Upgraded = true;
return true;

View File

@@ -1,9 +1,9 @@
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using Unity.Netcode.Editor;
using Unity.Netcode.Transports.UTP;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.TestTools;
@@ -130,9 +130,11 @@ namespace Unity.Netcode.EditorTests
overridingTargetPrefab.GlobalObjectIdHash = 3;
sourcePrefabToOverride.GlobalObjectIdHash = 4;
networkConfig.OldPrefabList = new List<NetworkPrefab>();
networkConfig.OldPrefabList.Add(new NetworkPrefab { Prefab = regularPrefab.gameObject });
networkConfig.OldPrefabList.Add(new NetworkPrefab { Prefab = overriddenPrefab.gameObject, Override = NetworkPrefabOverride.Prefab, OverridingTargetPrefab = overridingTargetPrefab.gameObject, SourcePrefabToOverride = sourcePrefabToOverride.gameObject, SourceHashToOverride = 123456 });
networkConfig.OldPrefabList = new List<NetworkPrefab>
{
new NetworkPrefab { Prefab = regularPrefab.gameObject },
new NetworkPrefab { Prefab = overriddenPrefab.gameObject, Override = NetworkPrefabOverride.Prefab, OverridingTargetPrefab = overridingTargetPrefab.gameObject, SourcePrefabToOverride = sourcePrefabToOverride.gameObject, SourceHashToOverride = 123456 }
};
networkConfig.InitializePrefabs();
@@ -159,15 +161,20 @@ namespace Unity.Netcode.EditorTests
// Setup
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig();
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
networkManager2.NetworkConfig = new NetworkConfig();
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager2.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
@@ -205,13 +212,17 @@ namespace Unity.Netcode.EditorTests
// Setup
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig();
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
networkManager2.NetworkConfig = new NetworkConfig();
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager2.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
@@ -251,13 +262,17 @@ namespace Unity.Netcode.EditorTests
// Setup
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
networkManager.NetworkConfig = new NetworkConfig();
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
networkManager2.NetworkConfig = new NetworkConfig();
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
networkManager2.NetworkConfig = new NetworkConfig
{
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
};
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();

View File

@@ -1,7 +1,7 @@
using NUnit.Framework;
using UnityEngine;
using Unity.Netcode.Editor.Configuration;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.EditorTests
{

View File

@@ -7,8 +7,6 @@ namespace Unity.Netcode.EditorTests
{
public abstract class BaseFastBufferReaderWriterTest
{
#region Test Types
protected enum ByteEnum : byte
{
A,
@@ -78,7 +76,6 @@ namespace Unity.Netcode.EditorTests
WriteDirect,
WriteSafe
}
#endregion
protected abstract void RunTypeTest<T>(T valueToTest) where T : unmanaged;
@@ -88,7 +85,6 @@ namespace Unity.Netcode.EditorTests
protected abstract void RunTypeArrayTestSafe<T>(T[] valueToTest) where T : unmanaged;
#region Helpers
protected TestStruct GetTestStruct()
{
var random = new Random();
@@ -98,7 +94,7 @@ namespace Unity.Netcode.EditorTests
A = (byte)random.Next(),
B = (short)random.Next(),
C = (ushort)random.Next(),
D = (int)random.Next(),
D = random.Next(),
E = (uint)random.Next(),
F = ((long)random.Next() << 32) + random.Next(),
G = ((ulong)random.Next() << 32) + (ulong)random.Next(),
@@ -111,9 +107,6 @@ namespace Unity.Netcode.EditorTests
return testStruct;
}
#endregion
private void RunTestWithWriteType<T>(T val, WriteType wt, FastBufferWriter.ForPrimitives _ = default) where T : unmanaged
{
switch (wt)
@@ -149,7 +142,7 @@ namespace Unity.Netcode.EditorTests
}
else if (testType == typeof(int))
{
RunTestWithWriteType((int)random.Next(), writeType);
RunTestWithWriteType(random.Next(), writeType);
}
else if (testType == typeof(uint))
{
@@ -354,10 +347,10 @@ namespace Unity.Netcode.EditorTests
else if (testType == typeof(long))
{
RunTypeTestLocal(new[]{
((long)random.Next() << 32) + (long)random.Next(),
((long)random.Next() << 32) + (long)random.Next(),
((long)random.Next() << 32) + (long)random.Next(),
((long)random.Next() << 32) + (long)random.Next()
((long)random.Next() << 32) + random.Next(),
((long)random.Next() << 32) + random.Next(),
((long)random.Next() << 32) + random.Next(),
((long)random.Next() << 32) + random.Next()
}, writeType);
}
else if (testType == typeof(ulong))

View File

@@ -10,8 +10,6 @@ namespace Unity.Netcode.EditorTests
{
public class BytePackerTests
{
#region Test Types
private enum ByteEnum : byte
{
A,
@@ -74,8 +72,6 @@ namespace Unity.Netcode.EditorTests
WriteAsObject
}
#endregion
private unsafe void VerifyBytewiseEquality<T>(T value, T otherValue) where T : unmanaged
{
byte* asBytePointer = (byte*)&value;

View File

@@ -9,7 +9,6 @@ namespace Unity.Netcode.EditorTests
{
public class FastBufferReaderTests : BaseFastBufferReaderWriterTest
{
#region Common Checks
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
{
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
@@ -230,9 +229,7 @@ namespace Unity.Netcode.EditorTests
method.Invoke(reader, args);
value = (T[])args[0];
}
#endregion
#region Generic Checks
protected override unsafe void RunTypeTest<T>(T valueToTest)
{
var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
@@ -343,9 +340,6 @@ namespace Unity.Netcode.EditorTests
}
}
#endregion
#region Tests
[Test]
public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_ValueMatchesWhatWasWritten(
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
@@ -1220,7 +1214,5 @@ namespace Unity.Netcode.EditorTests
Assert.AreEqual(reader.Handle->AllowedReadMark, 25);
}
}
#endregion
}
}

View File

@@ -9,9 +9,6 @@ namespace Unity.Netcode.EditorTests
{
public class FastBufferWriterTests : BaseFastBufferReaderWriterTest
{
#region Common Checks
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
{
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
@@ -66,10 +63,6 @@ namespace Unity.Netcode.EditorTests
VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
}
#endregion
#region Generic Checks
private void RunMethod<T>(string methodName, FastBufferWriter writer, in T value) where T : unmanaged
{
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
@@ -248,11 +241,8 @@ namespace Unity.Netcode.EditorTests
VerifyCheckBytes(underlyingArray, writeSize);
}
}
#endregion
#region Tests
[Test, Description("Tests ")]
[Test, Description("Tests")]
public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly(
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
typeof(long), typeof(ulong), typeof(bool), typeof(char), typeof(float), typeof(double),
@@ -1317,6 +1307,5 @@ namespace Unity.Netcode.EditorTests
Assert.AreEqual(writer.Handle->AllowedWriteMark, 25);
}
}
#endregion
}
}

View File

@@ -28,7 +28,7 @@ namespace Unity.Netcode.EditorTests
var writer = new DataStreamWriter(data);
writer.WriteInt(1);
writer.WriteByte((byte)42);
writer.WriteByte(42);
var reader = new DataStreamReader(data);
var q = new BatchedReceiveQueue(reader);
@@ -52,9 +52,9 @@ namespace Unity.Netcode.EditorTests
var writer = new DataStreamWriter(data);
writer.WriteInt(1);
writer.WriteByte((byte)42);
writer.WriteByte(42);
writer.WriteInt(1);
writer.WriteByte((byte)142);
writer.WriteByte(142);
var reader = new DataStreamReader(data);
var q = new BatchedReceiveQueue(reader);
@@ -132,7 +132,7 @@ namespace Unity.Netcode.EditorTests
var writer = new DataStreamWriter(data);
writer.WriteInt(1);
writer.WriteByte((byte)42);
writer.WriteByte(42);
var reader = new DataStreamReader(data);
var q = new BatchedReceiveQueue(reader);
@@ -168,7 +168,7 @@ namespace Unity.Netcode.EditorTests
var writer = new DataStreamWriter(data);
writer.WriteInt(1);
writer.WriteByte((byte)42);
writer.WriteByte(42);
var reader = new DataStreamReader(data);
var q = new BatchedReceiveQueue(reader);

View File

@@ -10,7 +10,8 @@
"Unity.Multiplayer.NetStats",
"Unity.Multiplayer.Tools.MetricTypes",
"Unity.Multiplayer.Tools.NetStats",
"Unity.Networking.Transport"
"Unity.Networking.Transport",
"Unity.Mathematics"
],
"optionalUnityReferences": [
"TestAssemblies"

View File

@@ -1,8 +1,8 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests

View File

@@ -1,9 +1,9 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,6 +1,6 @@
using System.Collections.Generic;
using UnityEngine;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,8 +1,8 @@
using System;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
@@ -255,14 +255,14 @@ namespace Unity.Netcode.RuntimeTests
// NetworkVariable Value Type Constructor Test Coverage
m_NetworkVariableBool = new NetworkVariable<bool>(true);
m_NetworkVariableByte = new NetworkVariable<byte>((byte)0);
m_NetworkVariableByte = new NetworkVariable<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_NetworkVariableSByte = new NetworkVariable<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));

View File

@@ -1,10 +1,10 @@
using System;
using System.Text;
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
@@ -66,9 +66,11 @@ namespace Unity.Netcode.RuntimeTests
[Test]
public void VerifyUniqueNetworkConfigPerRequest()
{
var networkConfig = new NetworkConfig();
networkConfig.EnableSceneManagement = true;
networkConfig.TickRate = 30;
var networkConfig = new NetworkConfig
{
EnableSceneManagement = true,
TickRate = 30
};
var currentHash = networkConfig.GetConfig();
networkConfig.EnableSceneManagement = false;
networkConfig.TickRate = 60;

View File

@@ -1,11 +1,10 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
@@ -194,7 +193,11 @@ namespace Unity.Netcode.RuntimeTests
private int m_NumberOfClientsToLateJoin = 2;
protected override IEnumerator OnSetup()
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => false;
protected override bool m_TearDownIsACoroutine => false;
protected override void OnInlineSetup()
{
DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Clear();
DeferredMessageTestRpcComponent.ClientInstances.Clear();
@@ -205,15 +208,13 @@ namespace Unity.Netcode.RuntimeTests
// Replace the IDeferredMessageManager component with our test one in the component factory
ComponentFactory.Register<IDeferredMessageManager>(networkManager => new TestDeferredMessageManager(networkManager));
yield return null;
}
protected override IEnumerator OnTearDown()
protected override void OnInlineTearDown()
{
// Revert the IDeferredMessageManager component to its default (DeferredMessageManager)
ComponentFactory.Deregister<IDeferredMessageManager>();
m_ClientSpawnCatchers.Clear();
yield return null;
}
protected override void OnServerAndClientsCreated()
@@ -255,12 +256,12 @@ namespace Unity.Netcode.RuntimeTests
base.OnNewClientCreated(networkManager);
}
private IEnumerator SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)
private void SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)
{
for (int i = 0; i < m_NumberOfClientsToLateJoin; i++)
{
// Create and join client
yield return CreateAndStartNewClient();
CreateAndStartNewClientWithTimeTravel();
}
if (clearTestDeferredMessageManagerCallFlags)
@@ -308,16 +309,15 @@ namespace Unity.Netcode.RuntimeTests
m_ClientSpawnCatchers.Clear();
}
protected override IEnumerator OnServerAndClientsConnected()
protected override void OnTimeTravelServerAndClientsConnected()
{
// Clear out these values from whatever might have set them during the initial startup.
ClearTestDeferredMessageManagerCallFlags();
yield return null;
}
private IEnumerator WaitForClientsToCatchSpawns(int count = 1)
private void WaitForClientsToCatchSpawns(int count = 1)
{
yield return WaitForConditionOrTimeOut(() =>
Assert.IsTrue(WaitForConditionOrTimeOutWithTimeTravel(() =>
{
foreach (var catcher in m_ClientSpawnCatchers)
{
@@ -328,7 +328,7 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
});
}));
}
private void ClearTestDeferredMessageManagerCallFlags()
@@ -348,28 +348,28 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab));
}
private IEnumerator WaitForAllClientsToReceive<T>() where T : INetworkMessage
private void WaitForAllClientsToReceive<T>() where T : INetworkMessage
{
yield return WaitForMessageReceived<T>(m_ClientNetworkManagers.ToList(), ReceiptType.Received);
WaitForMessageReceivedWithTimeTravel<T>(m_ClientNetworkManagers.ToList(), ReceiptType.Received);
}
private IEnumerator WaitForAllClientsToReceive<TFirstMessage, TSecondMessage>()
private void WaitForAllClientsToReceive<TFirstMessage, TSecondMessage>()
where TFirstMessage : INetworkMessage
where TSecondMessage : INetworkMessage
{
yield return WaitForMessagesReceived(new List<Type>
WaitForMessagesReceivedWithTimeTravel(new List<Type>
{
typeof(TFirstMessage),
typeof(TSecondMessage)
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
}
private IEnumerator WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage>()
private void WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage>()
where TFirstMessage : INetworkMessage
where TSecondMessage : INetworkMessage
where TThirdMessage : INetworkMessage
{
yield return WaitForMessagesReceived(new List<Type>
WaitForMessagesReceivedWithTimeTravel(new List<Type>
{
typeof(TFirstMessage),
typeof(TSecondMessage),
@@ -377,13 +377,13 @@ namespace Unity.Netcode.RuntimeTests
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
}
private IEnumerator WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage, TFourthMessage>()
private void WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage, TFourthMessage>()
where TFirstMessage : INetworkMessage
where TSecondMessage : INetworkMessage
where TThirdMessage : INetworkMessage
where TFourthMessage : INetworkMessage
{
yield return WaitForMessagesReceived(new List<Type>
WaitForMessagesReceivedWithTimeTravel(new List<Type>
{
typeof(TFirstMessage),
typeof(TSecondMessage),
@@ -392,19 +392,19 @@ namespace Unity.Netcode.RuntimeTests
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
}
[UnityTest]
public IEnumerator WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred()
[Test]
public void WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred()
{
yield return SpawnClients();
SpawnClients();
CatchSpawns();
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
serverObject.GetComponent<DeferredMessageTestRpcComponent>().SendTestClientRpc();
yield return WaitForAllClientsToReceive<ClientRpcMessage>();
WaitForAllClientsToReceive<ClientRpcMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -415,19 +415,19 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred()
[Test]
public void WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred()
{
yield return SpawnClients();
SpawnClients();
CatchSpawns();
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
serverObject.GetComponent<NetworkObject>().Despawn(false);
yield return WaitForAllClientsToReceive<DestroyObjectMessage>();
WaitForAllClientsToReceive<DestroyObjectMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -438,18 +438,18 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred()
[Test]
public void WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred()
{
yield return SpawnClients();
SpawnClients();
CatchSpawns();
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
foreach (var client in m_ClientNetworkManagers)
{
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -459,22 +459,22 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred()
[Test]
public void WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred()
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
var serverObject = Object.Instantiate(m_NetworkVariablePrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
serverObject.GetComponent<DeferredMessageTestNetworkVariableComponent>().TestNetworkVariable.Value = 1;
yield return WaitForAllClientsToReceive<NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<NetworkVariableDeltaMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -487,17 +487,17 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
[Test]
//[Ignore("Disabling this temporarily until it is migrated into new integration test.")]
public IEnumerator WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred()
public void WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred()
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForAllClientsToReceive<CreateObjectMessage>();
WaitForAllClientsToReceive<CreateObjectMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -514,10 +514,10 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenAnRpcIsDeferred_ItIsProcessedOnSpawn()
[Test]
public void WhenAnRpcIsDeferred_ItIsProcessedOnSpawn()
{
yield return WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred();
WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred();
ReleaseSpawns();
foreach (var client in m_ClientNetworkManagers)
@@ -532,10 +532,10 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenADespawnIsDeferred_ItIsProcessedOnSpawn()
[Test]
public void WhenADespawnIsDeferred_ItIsProcessedOnSpawn()
{
yield return WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred();
WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred();
ReleaseSpawns();
foreach (var client in m_ClientNetworkManagers)
@@ -551,10 +551,10 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenAChangeOwnershipMessageIsDeferred_ItIsProcessedOnSpawn()
[Test]
public void WhenAChangeOwnershipMessageIsDeferred_ItIsProcessedOnSpawn()
{
yield return WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred();
WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred();
ReleaseSpawns();
foreach (var client in m_ClientNetworkManagers)
@@ -568,10 +568,10 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpawn()
[Test]
public void WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpawn()
{
yield return WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred();
WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred();
foreach (var client in m_ClientNetworkManagers)
{
@@ -592,7 +592,7 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
foreach (var client in m_ClientNetworkManagers)
{
@@ -605,12 +605,12 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab()
[Test]
public void WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab()
{
// This will prevent spawned clients from adding prefabs
m_SkipAddingPrefabsToClient = true;
yield return WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred();
WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred();
// Now add the prefabs
foreach (var client in m_ClientNetworkManagers)
@@ -630,7 +630,7 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
// Validate this test
foreach (var client in m_ClientNetworkManagers)
@@ -644,28 +644,26 @@ namespace Unity.Netcode.RuntimeTests
}
}
protected override bool LogAllMessages => true;
[UnityTest]
public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn()
[Test]
public void WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn()
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().SendTestClientRpc();
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
yield return WaitForAllClientsToReceive<ClientRpcMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<ClientRpcMessage, NetworkVariableDeltaMessage>();
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -694,8 +692,8 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
yield return new WaitForSeconds(0.1f);
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
TimeTravel(0.1, 1);
// Validate the spawned objects
foreach (var client in m_ClientNetworkManagers)
@@ -711,11 +709,11 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllProcessedOnAddNetworkPrefab()
[Test]
public void WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllProcessedOnAddNetworkPrefab()
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
@@ -724,7 +722,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject2.GetComponent<NetworkObject>().Spawn();
yield return WaitForAllClientsToReceive<CreateObjectMessage, CreateObjectMessage>();
WaitForAllClientsToReceive<CreateObjectMessage, CreateObjectMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -751,7 +749,7 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
foreach (var client in m_ClientNetworkManagers)
@@ -789,11 +787,11 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingThePrefabCausesThemToBeProcessed()
[Test]
public void WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingThePrefabCausesThemToBeProcessed()
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
@@ -803,11 +801,11 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
// TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59
// Otherwise, we have to wait for at least 1 tick for the NetworkVariableDeltaMessage to be generated before changing ownership
yield return WaitForAllClientsToReceive<CreateObjectMessage, ClientRpcMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<CreateObjectMessage, ClientRpcMessage, NetworkVariableDeltaMessage>();
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
// Validate messages are deferred and pending
foreach (var client in m_ClientNetworkManagers)
@@ -837,9 +835,9 @@ namespace Unity.Netcode.RuntimeTests
}
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
yield return new WaitForSeconds(0.1f);
TimeTravel(0.1, 1);
// Validate the test
foreach (var client in m_ClientNetworkManagers)
@@ -856,11 +854,11 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout)
[Test]
public void WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout)
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
foreach (var client in m_ClientNetworkManagers)
{
@@ -869,7 +867,7 @@ namespace Unity.Netcode.RuntimeTests
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
var start = 0f;
@@ -879,7 +877,7 @@ namespace Unity.Netcode.RuntimeTests
{
if (start == 0)
{
start = Time.realtimeSinceStartup;
start = client.RealTimeProvider.RealTimeSinceStartup;
}
};
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -888,7 +886,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
foreach (var unused in m_ClientNetworkManagers)
{
@@ -901,8 +899,9 @@ namespace Unity.Netcode.RuntimeTests
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
{
++purgeCount;
var elapsed = Time.realtimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
Debug.Log(client.RealTimeProvider.GetType().FullName);
Assert.GreaterOrEqual(elapsed, timeout);
Assert.AreEqual(1, manager.DeferredMessageCountTotal());
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
@@ -912,8 +911,20 @@ namespace Unity.Netcode.RuntimeTests
manager.OnBeforePurge = beforePurge;
}
yield return new WaitForSeconds(timeout + 0.1f);
TimeTravel(timeout - 0.01, 1);
bool HaveAnyClientsPurged()
{
foreach (var client in m_ClientNetworkManagers)
{
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
if (manager.DeferredMessageCountTotal() == 0)
{
return true;
}
}
return false;
}
bool HaveAllClientsPurged()
{
foreach (var client in m_ClientNetworkManagers)
@@ -927,15 +938,18 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsPurged);
AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!");
Assert.IsFalse(HaveAnyClientsPurged());
TimeTravel(0.02, 1);
Assert.IsTrue(HaveAllClientsPurged());
}
[UnityTest]
public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
[Test]
public void WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
foreach (var client in m_ClientNetworkManagers)
@@ -945,7 +959,7 @@ namespace Unity.Netcode.RuntimeTests
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
var start = 0f;
@@ -955,7 +969,7 @@ namespace Unity.Netcode.RuntimeTests
{
if (start == 0)
{
start = Time.realtimeSinceStartup;
start = client.RealTimeProvider.RealTimeSinceStartup;
}
};
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -966,7 +980,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForMessagesReceived(
WaitForMessagesReceivedWithTimeTravel(
new List<Type> {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
@@ -981,8 +995,8 @@ namespace Unity.Netcode.RuntimeTests
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
{
++purgeCount;
var elapsed = Time.realtimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout - 0.25f);
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout);
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
@@ -992,6 +1006,21 @@ namespace Unity.Netcode.RuntimeTests
manager.OnBeforePurge = beforePurge;
}
var timePassedSinceFirstStart = MockTimeProvider.StaticRealTimeSinceStartup - start;
TimeTravel(timeout - 0.01 - timePassedSinceFirstStart, 1);
bool HaveAnyClientsPurged()
{
foreach (var client in m_ClientNetworkManagers)
{
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
if (manager.DeferredMessageCountTotal() == 0)
{
return true;
}
}
return false;
}
bool HaveAllClientsPurged()
{
foreach (var client in m_ClientNetworkManagers)
@@ -1005,15 +1034,18 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
yield return WaitForConditionOrTimeOut(HaveAllClientsPurged);
AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!");
Assert.IsFalse(HaveAnyClientsPurged());
TimeTravel(0.02 + timePassedSinceFirstStart, 1);
Assert.IsTrue(HaveAllClientsPurged());
}
[UnityTest]
public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
[Test]
public void WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
foreach (var client in m_ClientNetworkManagers)
@@ -1028,7 +1060,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject2.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns(2);
WaitForClientsToCatchSpawns(2);
var start = 0f;
@@ -1038,7 +1070,7 @@ namespace Unity.Netcode.RuntimeTests
{
if (start == 0)
{
start = Time.realtimeSinceStartup;
start = client.RealTimeProvider.RealTimeSinceStartup;
}
};
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -1053,7 +1085,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject2.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
serverObject2.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForMessagesReceived(
WaitForMessagesReceivedWithTimeTravel(
new List<Type> {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
@@ -1071,7 +1103,7 @@ namespace Unity.Netcode.RuntimeTests
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
{
++purgeCount;
var elapsed = Time.realtimeSinceStartup - start;
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout - 0.25f);
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountTotal());
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
@@ -1082,7 +1114,7 @@ namespace Unity.Netcode.RuntimeTests
manager.OnBeforePurge = beforePurge;
}
yield return new WaitForSeconds(timeout + 0.1f);
TimeTravel(timeout + 0.1f, 1);
foreach (var client in m_ClientNetworkManagers)
{
AddPrefabsToClient(client);
@@ -1095,11 +1127,11 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([Values(1, 2, 3)] int timeout)
[Test]
public void WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([Values(1, 2, 3)] int timeout)
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
foreach (var client in m_ClientNetworkManagers)
{
@@ -1108,7 +1140,7 @@ namespace Unity.Netcode.RuntimeTests
var serverObject = Object.Instantiate(m_RpcPrefab);
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns();
WaitForClientsToCatchSpawns();
var start = 0f;
@@ -1118,7 +1150,7 @@ namespace Unity.Netcode.RuntimeTests
{
if (start == 0)
{
start = Time.realtimeSinceStartup;
start = client.RealTimeProvider.RealTimeSinceStartup;
}
};
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -1127,9 +1159,9 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
yield return new WaitForSeconds(timeout - 0.5f);
TimeTravel(timeout - 0.5f, 1);
foreach (var client in m_ClientNetworkManagers)
{
@@ -1140,7 +1172,7 @@ namespace Unity.Netcode.RuntimeTests
}
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -1161,7 +1193,7 @@ namespace Unity.Netcode.RuntimeTests
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
{
++purgeCount;
var elapsed = Time.realtimeSinceStartup - start;
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
@@ -1177,7 +1209,7 @@ namespace Unity.Netcode.RuntimeTests
AddPrefabsToClient(client);
}
yield return new WaitForSeconds(0.6f);
TimeTravel(0.6f, 1);
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
foreach (var client in m_ClientNetworkManagers)
@@ -1187,11 +1219,11 @@ namespace Unity.Netcode.RuntimeTests
}
}
[UnityTest]
public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNotRemoved([Values(1, 2, 3)] int timeout)
[Test]
public void WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNotRemoved([Values(1, 2, 3)] int timeout)
{
m_SkipAddingPrefabsToClient = true;
yield return SpawnClients();
SpawnClients();
CatchSpawns();
foreach (var client in m_ClientNetworkManagers)
{
@@ -1203,7 +1235,7 @@ namespace Unity.Netcode.RuntimeTests
var serverObject2 = Object.Instantiate(m_RpcPrefab);
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
serverObject2.GetComponent<NetworkObject>().Spawn();
yield return WaitForClientsToCatchSpawns(2);
WaitForClientsToCatchSpawns(2);
var start = 0f;
@@ -1213,7 +1245,7 @@ namespace Unity.Netcode.RuntimeTests
{
if (start == 0)
{
start = Time.realtimeSinceStartup;
start = client.RealTimeProvider.RealTimeSinceStartup;
}
};
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
@@ -1222,9 +1254,9 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
yield return new WaitForSeconds(timeout - 0.5f);
TimeTravel(timeout - 0.5f, 1);
foreach (var client in m_ClientNetworkManagers)
{
@@ -1236,7 +1268,7 @@ namespace Unity.Netcode.RuntimeTests
}
serverObject2.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -1258,7 +1290,7 @@ namespace Unity.Netcode.RuntimeTests
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
{
++purgeCount;
var elapsed = Time.realtimeSinceStartup - start;
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
@@ -1277,7 +1309,7 @@ namespace Unity.Netcode.RuntimeTests
AddPrefabsToClient(client);
}
yield return new WaitForSeconds(0.6f);
TimeTravel(0.6f, 1);
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
foreach (var client in m_ClientNetworkManagers)

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Linq;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -5,9 +5,9 @@ using System.Linq;
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Debug = UnityEngine.Debug;
namespace Unity.Netcode.RuntimeTests

View File

@@ -1,7 +1,7 @@
using System.Collections;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -2,9 +2,9 @@ using System;
using System.Collections;
using System.Text.RegularExpressions;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -3,8 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -3,8 +3,8 @@ using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Collections;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,7 +1,7 @@
using UnityEngine;
using NUnit.Framework;
using UnityEngine.TestTools;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests

View File

@@ -1,9 +1,9 @@
using System.Collections;
using UnityEngine;
using NUnit.Framework;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
@@ -56,8 +56,10 @@ namespace Unity.Netcode.RuntimeTests
yield return StartServerAndClients();
var parentObject = new GameObject();
var childObject = new GameObject();
childObject.name = "ChildObject";
var childObject = new GameObject
{
name = "ChildObject"
};
childObject.transform.parent = parentObject.transform;
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
var childBehaviour = childObject.AddComponent<NetworkTransform>();

View File

@@ -2,9 +2,9 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests

View File

@@ -12,12 +12,11 @@ namespace Unity.Netcode.RuntimeTests
var networkManager = gameObject.AddComponent<NetworkManager>();
var transport = gameObject.AddComponent<DummyTransport>();
networkManager.NetworkConfig = new NetworkConfig();
// Set dummy transport that does nothing
networkManager.NetworkConfig.NetworkTransport = transport;
networkManager.NetworkConfig = new NetworkConfig
{
// Set dummy transport that does nothing
NetworkTransport = transport
};
CustomMessagingManager preManager = networkManager.CustomMessagingManager;

View File

@@ -0,0 +1,258 @@
using System;
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkManagerEventsTests
{
private NetworkManager m_ClientManager;
private NetworkManager m_ServerManager;
[UnityTest]
public IEnumerator OnServerStoppedCalledWhenServerStops()
{
bool callbackInvoked = false;
var gameObject = new GameObject(nameof(OnServerStoppedCalledWhenServerStops));
m_ServerManager = gameObject.AddComponent<NetworkManager>();
// Set dummy transport that does nothing
var transport = gameObject.AddComponent<DummyTransport>();
m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
Action<bool> onServerStopped = (bool wasAlsoClient) =>
{
callbackInvoked = true;
Assert.IsFalse(wasAlsoClient);
if (m_ServerManager.IsServer)
{
Assert.Fail("OnServerStopped called when the server is still active");
}
};
// Start server to cause initialization process
Assert.True(m_ServerManager.StartServer());
Assert.True(m_ServerManager.IsListening);
m_ServerManager.OnServerStopped += onServerStopped;
m_ServerManager.Shutdown();
UnityEngine.Object.DestroyImmediate(gameObject);
yield return WaitUntilManagerShutsdown();
Assert.False(m_ServerManager.IsListening);
Assert.True(callbackInvoked, "OnServerStopped wasn't invoked");
}
[UnityTest]
public IEnumerator OnClientStoppedCalledWhenClientStops()
{
yield return InitializeServerAndAClient();
bool callbackInvoked = false;
Action<bool> onClientStopped = (bool wasAlsoServer) =>
{
callbackInvoked = true;
Assert.IsFalse(wasAlsoServer);
if (m_ClientManager.IsClient)
{
Assert.Fail("onClientStopped called when the client is still active");
}
};
m_ClientManager.OnClientStopped += onClientStopped;
m_ClientManager.Shutdown();
yield return WaitUntilManagerShutsdown();
Assert.True(callbackInvoked, "OnClientStopped wasn't invoked");
}
[UnityTest]
public IEnumerator OnClientAndServerStoppedCalledWhenHostStops()
{
var gameObject = new GameObject(nameof(OnClientAndServerStoppedCalledWhenHostStops));
m_ServerManager = gameObject.AddComponent<NetworkManager>();
// Set dummy transport that does nothing
var transport = gameObject.AddComponent<DummyTransport>();
m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
int callbacksInvoked = 0;
Action<bool> onClientStopped = (bool wasAlsoServer) =>
{
callbacksInvoked++;
Assert.IsTrue(wasAlsoServer);
if (m_ServerManager.IsClient)
{
Assert.Fail("onClientStopped called when the client is still active");
}
};
Action<bool> onServerStopped = (bool wasAlsoClient) =>
{
callbacksInvoked++;
Assert.IsTrue(wasAlsoClient);
if (m_ServerManager.IsServer)
{
Assert.Fail("OnServerStopped called when the server is still active");
}
};
// Start server to cause initialization process
Assert.True(m_ServerManager.StartHost());
Assert.True(m_ServerManager.IsListening);
m_ServerManager.OnServerStopped += onServerStopped;
m_ServerManager.OnClientStopped += onClientStopped;
m_ServerManager.Shutdown();
UnityEngine.Object.DestroyImmediate(gameObject);
yield return WaitUntilManagerShutsdown();
Assert.False(m_ServerManager.IsListening);
Assert.AreEqual(2, callbacksInvoked, "either OnServerStopped or OnClientStopped wasn't invoked");
}
[UnityTest]
public IEnumerator OnServerStartedCalledWhenServerStarts()
{
var gameObject = new GameObject(nameof(OnServerStartedCalledWhenServerStarts));
m_ServerManager = gameObject.AddComponent<NetworkManager>();
// Set dummy transport that does nothing
var transport = gameObject.AddComponent<DummyTransport>();
m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
bool callbackInvoked = false;
Action onServerStarted = () =>
{
callbackInvoked = true;
if (!m_ServerManager.IsServer)
{
Assert.Fail("OnServerStarted called when the server is not active yet");
}
};
// Start server to cause initialization process
m_ServerManager.OnServerStarted += onServerStarted;
Assert.True(m_ServerManager.StartServer());
Assert.True(m_ServerManager.IsListening);
yield return WaitUntilServerBufferingIsReady();
Assert.True(callbackInvoked, "OnServerStarted wasn't invoked");
}
[UnityTest]
public IEnumerator OnClientStartedCalledWhenClientStarts()
{
bool callbackInvoked = false;
Action onClientStarted = () =>
{
callbackInvoked = true;
if (!m_ClientManager.IsClient)
{
Assert.Fail("onClientStarted called when the client is not active yet");
}
};
yield return InitializeServerAndAClient(onClientStarted);
Assert.True(callbackInvoked, "OnClientStarted wasn't invoked");
}
[UnityTest]
public IEnumerator OnClientAndServerStartedCalledWhenHostStarts()
{
var gameObject = new GameObject(nameof(OnClientAndServerStartedCalledWhenHostStarts));
m_ServerManager = gameObject.AddComponent<NetworkManager>();
// Set dummy transport that does nothing
var transport = gameObject.AddComponent<DummyTransport>();
m_ServerManager.NetworkConfig = new NetworkConfig() { NetworkTransport = transport };
int callbacksInvoked = 0;
Action onClientStarted = () =>
{
callbacksInvoked++;
if (!m_ServerManager.IsClient)
{
Assert.Fail("OnClientStarted called when the client is not active yet");
}
};
Action onServerStarted = () =>
{
callbacksInvoked++;
if (!m_ServerManager.IsServer)
{
Assert.Fail("OnServerStarted called when the server is not active yet");
}
};
m_ServerManager.OnServerStarted += onServerStarted;
m_ServerManager.OnClientStarted += onClientStarted;
// Start server to cause initialization process
Assert.True(m_ServerManager.StartHost());
Assert.True(m_ServerManager.IsListening);
yield return WaitUntilServerBufferingIsReady();
Assert.AreEqual(2, callbacksInvoked, "either OnServerStarted or OnClientStarted wasn't invoked");
}
private IEnumerator WaitUntilManagerShutsdown()
{
/* Need two updates to actually shut down. First one to see the transport failing, which
marks the NetworkManager as shutting down. Second one where actual shutdown occurs. */
yield return null;
yield return null;
}
private IEnumerator InitializeServerAndAClient(Action onClientStarted = null)
{
// Create multiple NetworkManager instances
if (!NetcodeIntegrationTestHelpers.Create(1, out m_ServerManager, out NetworkManager[] clients, 30))
{
Debug.LogError("Failed to create instances");
Assert.Fail("Failed to create instances");
}
// passing no clients on purpose to start them manually later
NetcodeIntegrationTestHelpers.Start(false, m_ServerManager, new NetworkManager[] { });
yield return WaitUntilServerBufferingIsReady();
m_ClientManager = clients[0];
if (onClientStarted != null)
{
m_ClientManager.OnClientStarted += onClientStarted;
}
Assert.True(m_ClientManager.StartClient());
NetcodeIntegrationTestHelpers.RegisterHandlers(clients[0]);
// Wait for connection on client side
yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients);
}
private IEnumerator WaitUntilServerBufferingIsReady()
{
/* wait until at least more than 2 server ticks have passed
Note: Waiting for more than 2 ticks on the server is due
to the time system applying buffering to the received time
in NetworkTimeSystem.Sync */
yield return new WaitUntil(() => m_ServerManager.NetworkTickSystem.ServerTime.Tick > 2);
}
[UnityTearDown]
public virtual IEnumerator Teardown()
{
NetcodeIntegrationTestHelpers.Destroy();
yield return null;
}
}
}

View File

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

View File

@@ -11,9 +11,11 @@ namespace Unity.Netcode.RuntimeTests
var gameObject = new GameObject(nameof(SceneManagerAssigned));
var networkManager = gameObject.AddComponent<NetworkManager>();
var transport = gameObject.AddComponent<DummyTransport>();
networkManager.NetworkConfig = new NetworkConfig();
// Set dummy transport that does nothing
networkManager.NetworkConfig.NetworkTransport = transport;
networkManager.NetworkConfig = new NetworkConfig
{
// Set dummy transport that does nothing
NetworkTransport = transport
};
NetworkSceneManager preManager = networkManager.SceneManager;

View File

@@ -1,8 +1,8 @@
using System;
using System.Collections;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using NUnit.Framework;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,8 +1,8 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests

View File

@@ -1,9 +1,9 @@
using System.Collections;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{

View File

@@ -1,8 +1,8 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests

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