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:
49
CHANGELOG.md
49
CHANGELOG.md
@@ -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
159
Components/HalfVector3.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/HalfVector3.cs.meta
Normal file
11
Components/HalfVector3.cs.meta
Normal 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
137
Components/HalfVector4.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/HalfVector4.cs.meta
Normal file
11
Components/HalfVector4.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 03c78136f41ff84499e2a6ac4a7dd7a5
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
205
Components/NetworkDeltaPosition.cs
Normal file
205
Components/NetworkDeltaPosition.cs
Normal 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))
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/NetworkDeltaPosition.cs.meta
Normal file
11
Components/NetworkDeltaPosition.cs.meta
Normal 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
123
Components/QuaternionCompressor.cs
Normal file
123
Components/QuaternionCompressor.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/QuaternionCompressor.cs.meta
Normal file
11
Components/QuaternionCompressor.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb9d8b98d3c8bca469c8ee152353336f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -3,7 +3,8 @@
|
||||
"rootNamespace": "Unity.Netcode.Components",
|
||||
"references": [
|
||||
"Unity.Netcode.Runtime",
|
||||
"Unity.Collections"
|
||||
"Unity.Collections",
|
||||
"Unity.Mathematics"
|
||||
],
|
||||
"allowUnsafeCode": true,
|
||||
"versionDefines": [
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.Editor
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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")]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
@@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using UnityEngine;
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
|
||||
377
Runtime/SceneManagement/DefaultSceneManagerHandler.cs
Normal file
377
Runtime/SceneManagement/DefaultSceneManagerHandler.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Runtime/SceneManagement/DefaultSceneManagerHandler.cs.meta
Normal file
11
Runtime/SceneManagement/DefaultSceneManagerHandler.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8c18076bb9734cf4ea7297f85b7729be
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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
@@ -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>
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
10
Runtime/Timing/IRealTimeProvider.cs
Normal file
10
Runtime/Timing/IRealTimeProvider.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
namespace Unity.Netcode
|
||||
{
|
||||
internal interface IRealTimeProvider
|
||||
{
|
||||
float RealTimeSinceStartup { get; }
|
||||
float UnscaledTime { get; }
|
||||
float UnscaledDeltaTime { get; }
|
||||
float DeltaTime { get; }
|
||||
}
|
||||
}
|
||||
3
Runtime/Timing/IRealTimeProvider.cs.meta
Normal file
3
Runtime/Timing/IRealTimeProvider.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 73bdda41e36846e893fd14dbd6de9978
|
||||
timeCreated: 1679413210
|
||||
12
Runtime/Timing/RealTimeProvider.cs
Normal file
12
Runtime/Timing/RealTimeProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
3
Runtime/Timing/RealTimeProvider.cs.meta
Normal file
3
Runtime/Timing/RealTimeProvider.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5aa5470767d64d8e89ac69ff52a8c404
|
||||
timeCreated: 1679413182
|
||||
@@ -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()
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -12,7 +12,8 @@
|
||||
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
|
||||
"Unity.Networking.Transport",
|
||||
"Unity.Collections",
|
||||
"Unity.Burst"
|
||||
"Unity.Burst",
|
||||
"Unity.Mathematics"
|
||||
],
|
||||
"allowUnsafeCode": true,
|
||||
"versionDefines": [
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
79
TestHelpers/Runtime/IntegrationTestWithApproximation.cs
Normal file
79
TestHelpers/Runtime/IntegrationTestWithApproximation.cs
Normal 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));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
11
TestHelpers/Runtime/IntegrationTestWithApproximation.cs.meta
Normal file
11
TestHelpers/Runtime/IntegrationTestWithApproximation.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 50a3b194bb5b8714d883dafd911db1ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
30
TestHelpers/Runtime/MockTimeProvider.cs
Normal file
30
TestHelpers/Runtime/MockTimeProvider.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
TestHelpers/Runtime/MockTimeProvider.cs.meta
Normal file
3
TestHelpers/Runtime/MockTimeProvider.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcc9a7faadea4b8ebeb041ee6e395a92
|
||||
timeCreated: 1679414015
|
||||
89
TestHelpers/Runtime/MockTransport.cs
Normal file
89
TestHelpers/Runtime/MockTransport.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
TestHelpers/Runtime/MockTransport.cs.meta
Normal file
3
TestHelpers/Runtime/MockTransport.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 335908e9a37f428ba087acf00563c7be
|
||||
timeCreated: 1679415868
|
||||
@@ -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[] { });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode.Editor.Configuration;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.EditorTests
|
||||
{
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Netcode.TestHelpers.Runtime;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
258
Tests/Runtime/NetworkManagerEventsTests.cs
Normal file
258
Tests/Runtime/NetworkManagerEventsTests.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Tests/Runtime/NetworkManagerEventsTests.cs.meta
Normal file
11
Tests/Runtime/NetworkManagerEventsTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 238d8724ba5ce3947bc20f5d6c056b6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using NUnit.Framework;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
using NUnit.Framework;
|
||||
|
||||
namespace Unity.Netcode.RuntimeTests
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user