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).
|
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
|
## [1.3.1] - 2023-03-27
|
||||||
|
|
||||||
### Added
|
### 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>
|
/// </remarks>
|
||||||
public class BufferedLinearInterpolatorQuaternion : BufferedLinearInterpolator<Quaternion>
|
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 />
|
/// <inheritdoc />
|
||||||
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
protected override Quaternion InterpolateUnclamped(Quaternion start, Quaternion end, float time)
|
||||||
{
|
{
|
||||||
// Disabling Extrapolation:
|
if (IsSlerp)
|
||||||
// TODO: Add Jira Ticket
|
{
|
||||||
return Quaternion.Slerp(start, end, time);
|
return Quaternion.Slerp(start, end, time);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Quaternion.Lerp(start, end, time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
protected override Quaternion Interpolate(Quaternion start, Quaternion end, float time)
|
||||||
{
|
{
|
||||||
// Disabling Extrapolation:
|
if (IsSlerp)
|
||||||
// TODO: Add Jira Ticket
|
{
|
||||||
return Quaternion.Slerp(start, end, time);
|
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>
|
/// </summary>
|
||||||
private void FlushMessages()
|
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)
|
foreach (var sendEntry in m_SendParameterUpdates)
|
||||||
{
|
{
|
||||||
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
|
m_NetworkAnimator.SendParametersUpdateClientRpc(sendEntry.ParametersUpdateMessage, sendEntry.ClientRpcParams);
|
||||||
@@ -64,9 +71,11 @@ namespace Unity.Netcode.Components
|
|||||||
m_NetworkAnimator.UpdateParameters(ref parameterUpdate);
|
m_NetworkAnimator.UpdateParameters(ref parameterUpdate);
|
||||||
}
|
}
|
||||||
m_ProcessParameterUpdates.Clear();
|
m_ProcessParameterUpdates.Clear();
|
||||||
|
var isServerAuthority = m_NetworkAnimator.IsServerAuthoritative();
|
||||||
|
|
||||||
// Only owners check for Animator changes
|
// owners when owner authoritative or the server when server authoritative are the only instances that
|
||||||
if (m_NetworkAnimator.IsOwner && !m_NetworkAnimator.IsServerAuthoritative() || m_NetworkAnimator.IsServerAuthoritative() && m_NetworkAnimator.NetworkManager.IsServer)
|
// checks for Animator changes
|
||||||
|
if ((!isServerAuthority && m_NetworkAnimator.IsOwner) || (isServerAuthority && m_NetworkAnimator.IsServer))
|
||||||
{
|
{
|
||||||
m_NetworkAnimator.CheckForAnimatorChanges();
|
m_NetworkAnimator.CheckForAnimatorChanges();
|
||||||
}
|
}
|
||||||
@@ -157,11 +166,11 @@ namespace Unity.Netcode.Components
|
|||||||
[AddComponentMenu("Netcode/Network Animator")]
|
[AddComponentMenu("Netcode/Network Animator")]
|
||||||
[RequireComponent(typeof(Animator))]
|
[RequireComponent(typeof(Animator))]
|
||||||
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
|
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
|
||||||
|
|
||||||
{
|
{
|
||||||
[Serializable]
|
[Serializable]
|
||||||
internal class TransitionStateinfo
|
internal class TransitionStateinfo
|
||||||
{
|
{
|
||||||
|
public bool IsCrossFadeExit;
|
||||||
public int Layer;
|
public int Layer;
|
||||||
public int OriginatingState;
|
public int OriginatingState;
|
||||||
public int DestinationState;
|
public int DestinationState;
|
||||||
@@ -279,6 +288,11 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (m_Animator == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TransitionStateInfoList = new List<TransitionStateinfo>();
|
TransitionStateInfoList = new List<TransitionStateinfo>();
|
||||||
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
|
var animatorController = m_Animator.runtimeAnimatorController as AnimatorController;
|
||||||
if (animatorController == null)
|
if (animatorController == null)
|
||||||
@@ -312,9 +326,19 @@ namespace Unity.Netcode.Components
|
|||||||
internal float NormalizedTime;
|
internal float NormalizedTime;
|
||||||
internal int Layer;
|
internal int Layer;
|
||||||
internal float Weight;
|
internal float Weight;
|
||||||
|
internal float Duration;
|
||||||
|
|
||||||
// For synchronizing transitions
|
// For synchronizing transitions
|
||||||
internal bool Transition;
|
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
|
// The StateHash is where the transition starts
|
||||||
// and the DestinationStateHash is the destination state
|
// and the DestinationStateHash is the destination state
|
||||||
internal int DestinationStateHash;
|
internal int DestinationStateHash;
|
||||||
@@ -324,65 +348,46 @@ namespace Unity.Netcode.Components
|
|||||||
if (serializer.IsWriter)
|
if (serializer.IsWriter)
|
||||||
{
|
{
|
||||||
var writer = serializer.GetFastBufferWriter();
|
var writer = serializer.GetFastBufferWriter();
|
||||||
var writeSize = FastBufferWriter.GetWriteSize(Transition);
|
m_StateFlags = 0x00;
|
||||||
writeSize += FastBufferWriter.GetWriteSize(StateHash);
|
|
||||||
writeSize += FastBufferWriter.GetWriteSize(NormalizedTime);
|
|
||||||
writeSize += FastBufferWriter.GetWriteSize(Layer);
|
|
||||||
writeSize += FastBufferWriter.GetWriteSize(Weight);
|
|
||||||
if (Transition)
|
if (Transition)
|
||||||
{
|
{
|
||||||
writeSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
|
m_StateFlags |= k_IsTransition;
|
||||||
}
|
}
|
||||||
|
if (CrossFade)
|
||||||
if (!writer.TryBeginWrite(writeSize))
|
|
||||||
{
|
{
|
||||||
throw new OverflowException($"[{GetType().Name}] Could not serialize: Out of buffer space.");
|
m_StateFlags |= k_IsCrossFade;
|
||||||
}
|
}
|
||||||
|
serializer.SerializeValue(ref m_StateFlags);
|
||||||
|
|
||||||
writer.WriteValue(Transition);
|
BytePacker.WriteValuePacked(writer, StateHash);
|
||||||
writer.WriteValue(StateHash);
|
BytePacker.WriteValuePacked(writer, Layer);
|
||||||
writer.WriteValue(NormalizedTime);
|
|
||||||
writer.WriteValue(Layer);
|
|
||||||
writer.WriteValue(Weight);
|
|
||||||
if (Transition)
|
if (Transition)
|
||||||
{
|
{
|
||||||
writer.WriteValue(DestinationStateHash);
|
BytePacker.WriteValuePacked(writer, DestinationStateHash);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var reader = serializer.GetFastBufferReader();
|
var reader = serializer.GetFastBufferReader();
|
||||||
// Begin reading the Transition flag
|
serializer.SerializeValue(ref m_StateFlags);
|
||||||
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Transition)))
|
Transition = (m_StateFlags & k_IsTransition) == k_IsTransition;
|
||||||
{
|
CrossFade = (m_StateFlags & k_IsCrossFade) == k_IsCrossFade;
|
||||||
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
|
|
||||||
}
|
|
||||||
reader.ReadValue(out Transition);
|
|
||||||
|
|
||||||
// Now determine what remains to be read
|
ByteUnpacker.ReadValuePacked(reader, out StateHash);
|
||||||
var readSize = FastBufferWriter.GetWriteSize(StateHash);
|
ByteUnpacker.ReadValuePacked(reader, out Layer);
|
||||||
readSize += FastBufferWriter.GetWriteSize(NormalizedTime);
|
|
||||||
readSize += FastBufferWriter.GetWriteSize(Layer);
|
|
||||||
readSize += FastBufferWriter.GetWriteSize(Weight);
|
|
||||||
if (Transition)
|
if (Transition)
|
||||||
{
|
{
|
||||||
readSize += FastBufferWriter.GetWriteSize(DestinationStateHash);
|
ByteUnpacker.ReadValuePacked(reader, out DestinationStateHash);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now read the remaining information about this AnimationState
|
serializer.SerializeValue(ref NormalizedTime);
|
||||||
if (!reader.TryBeginRead(readSize))
|
serializer.SerializeValue(ref Weight);
|
||||||
{
|
|
||||||
throw new OverflowException($"[{GetType().Name}] Could not deserialize: Out of buffer space.");
|
|
||||||
}
|
|
||||||
|
|
||||||
reader.ReadValue(out StateHash);
|
// Cross fading includes the duration of the cross fade.
|
||||||
reader.ReadValue(out NormalizedTime);
|
if (CrossFade)
|
||||||
reader.ReadValue(out Layer);
|
|
||||||
reader.ReadValue(out Weight);
|
|
||||||
if (Transition)
|
|
||||||
{
|
{
|
||||||
reader.ReadValue(out DestinationStateHash);
|
serializer.SerializeValue(ref Duration);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -565,8 +570,10 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
// We initialize the m_AnimationMessage for all instances in the event that
|
// We initialize the m_AnimationMessage for all instances in the event that
|
||||||
// ownership or authority changes during runtime.
|
// ownership or authority changes during runtime.
|
||||||
m_AnimationMessage = new AnimationMessage();
|
m_AnimationMessage = new AnimationMessage
|
||||||
m_AnimationMessage.AnimationStates = new List<AnimationState>();
|
{
|
||||||
|
AnimationStates = new List<AnimationState>()
|
||||||
|
};
|
||||||
|
|
||||||
// Store off our current layer weights and create our animation
|
// Store off our current layer weights and create our animation
|
||||||
// state entries per layer.
|
// state entries per layer.
|
||||||
@@ -588,17 +595,13 @@ namespace Unity.Netcode.Components
|
|||||||
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
|
m_CachedAnimatorParameters = new NativeArray<AnimatorParamCache>(parameters.Length, Allocator.Persistent);
|
||||||
m_ParametersToUpdate = new List<int>(parameters.Length);
|
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++)
|
for (var i = 0; i < parameters.Length; i++)
|
||||||
{
|
{
|
||||||
var parameter = parameters[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
|
var cacheParam = new AnimatorParamCache
|
||||||
{
|
{
|
||||||
Type = UnsafeUtility.EnumToInt(parameter.type),
|
Type = UnsafeUtility.EnumToInt(parameter.type),
|
||||||
@@ -643,12 +646,22 @@ namespace Unity.Netcode.Components
|
|||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public override void OnNetworkSpawn()
|
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)
|
if (IsServer)
|
||||||
{
|
{
|
||||||
m_ClientSendList = new List<ulong>(128);
|
m_ClientSendList = new List<ulong>(128);
|
||||||
m_ClientRpcParams = new ClientRpcParams();
|
m_ClientRpcParams = new ClientRpcParams
|
||||||
m_ClientRpcParams.Send = new ClientRpcSendParams();
|
{
|
||||||
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
Send = new ClientRpcSendParams
|
||||||
|
{
|
||||||
|
TargetClientIds = m_ClientSendList
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a handler for state changes
|
// Create a handler for state changes
|
||||||
@@ -691,10 +704,7 @@ namespace Unity.Netcode.Components
|
|||||||
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
for (int layer = 0; layer < m_Animator.layerCount; layer++)
|
||||||
{
|
{
|
||||||
var synchronizationStateInfo = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
var synchronizationStateInfo = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
||||||
if (SynchronizationStateInfo != null)
|
SynchronizationStateInfo?.Add(synchronizationStateInfo);
|
||||||
{
|
|
||||||
SynchronizationStateInfo.Add(synchronizationStateInfo);
|
|
||||||
}
|
|
||||||
var stateHash = synchronizationStateInfo.fullPathHash;
|
var stateHash = synchronizationStateInfo.fullPathHash;
|
||||||
var normalizedTime = synchronizationStateInfo.normalizedTime;
|
var normalizedTime = synchronizationStateInfo.normalizedTime;
|
||||||
var isInTransition = m_Animator.IsInTransition(layer);
|
var isInTransition = m_Animator.IsInTransition(layer);
|
||||||
@@ -767,11 +777,97 @@ namespace Unity.Netcode.Components
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var parameters = new ParametersUpdateMessage();
|
var parameters = new ParametersUpdateMessage();
|
||||||
var animationStates = new AnimationMessage();
|
var animationMessage = new AnimationMessage();
|
||||||
serializer.SerializeValue(ref parameters);
|
serializer.SerializeValue(ref parameters);
|
||||||
UpdateParameters(ref parameters);
|
UpdateParameters(ref parameters);
|
||||||
serializer.SerializeValue(ref animationStates);
|
serializer.SerializeValue(ref animationMessage);
|
||||||
HandleAnimStateUpdate(ref animationStates);
|
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>
|
/// </remarks>
|
||||||
internal void CheckForAnimatorChanges()
|
internal void CheckForAnimatorChanges()
|
||||||
{
|
{
|
||||||
if (!IsSpawned || (!IsOwner && !IsServerAuthoritative()) || (IsServerAuthoritative() && !IsServer))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (CheckParametersChanged())
|
if (CheckParametersChanged())
|
||||||
{
|
{
|
||||||
SendParametersUpdate();
|
SendParametersUpdate();
|
||||||
@@ -803,9 +894,6 @@ namespace Unity.Netcode.Components
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int stateHash;
|
|
||||||
float normalizedTime;
|
|
||||||
|
|
||||||
// Reset the dirty count before checking for AnimationState updates
|
// Reset the dirty count before checking for AnimationState updates
|
||||||
m_AnimationMessage.IsDirtyCount = 0;
|
m_AnimationMessage.IsDirtyCount = 0;
|
||||||
|
|
||||||
@@ -815,26 +903,7 @@ namespace Unity.Netcode.Components
|
|||||||
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
AnimatorStateInfo st = m_Animator.GetCurrentAnimatorStateInfo(layer);
|
||||||
var totalSpeed = st.speed * st.speedMultiplier;
|
var totalSpeed = st.speed * st.speedMultiplier;
|
||||||
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
|
var adjustedNormalizedMaxTime = totalSpeed > 0.0f ? 1.0f / totalSpeed : 0.0f;
|
||||||
|
CheckForStateChange(layer);
|
||||||
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++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an AnimationMessage only if there are dirty AnimationStates to send
|
// 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.AddRange(NetworkManager.ConnectedClientsIds);
|
||||||
m_ClientSendList.Remove(NetworkManager.LocalClientId);
|
m_ClientSendList.Remove(NetworkManager.LocalClientId);
|
||||||
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
||||||
SendAnimStateClientRpc(m_AnimationMessage);
|
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,7 +954,7 @@ namespace Unity.Netcode.Components
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Helper function to get the cached value
|
/// Helper function to get the cached value
|
||||||
/// </summary>
|
/// </summary>
|
||||||
unsafe private T GetValue<T>(ref AnimatorParamCache animatorParamCache)
|
private unsafe T GetValue<T>(ref AnimatorParamCache animatorParamCache)
|
||||||
{
|
{
|
||||||
T currentValue;
|
T currentValue;
|
||||||
fixed (void* value = animatorParamCache.Value)
|
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
|
/// If so, it fills out m_ParametersToUpdate with the indices of the parameters
|
||||||
/// that have changed. Returns true if any parameters changed.
|
/// that have changed. Returns true if any parameters changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
unsafe private bool CheckParametersChanged()
|
private unsafe bool CheckParametersChanged()
|
||||||
{
|
{
|
||||||
m_ParametersToUpdate.Clear();
|
m_ParametersToUpdate.Clear();
|
||||||
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
for (int i = 0; i < m_CachedAnimatorParameters.Length; i++)
|
||||||
{
|
{
|
||||||
ref var cacheValue = ref UnsafeUtility.ArrayElementAsRef<AnimatorParamCache>(m_CachedAnimatorParameters.GetUnsafePtr(), 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;
|
var hash = cacheValue.Hash;
|
||||||
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
if (cacheValue.Type == AnimationParamEnumWrapper.AnimatorControllerParameterInt)
|
||||||
{
|
{
|
||||||
@@ -941,52 +1018,6 @@ namespace Unity.Netcode.Components
|
|||||||
return m_ParametersToUpdate.Count > 0;
|
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>
|
/// <summary>
|
||||||
/// Writes all of the Animator's parameters
|
/// Writes all of the Animator's parameters
|
||||||
/// This uses the m_ParametersToUpdate list to write out only
|
/// 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 there is no state transition then return
|
||||||
if (animationState.StateHash == 0)
|
if (animationState.StateHash == 0 && !animationState.Transition)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer);
|
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 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
|
// We should have all valid entries for any animation state transition update
|
||||||
// Verify the AnimationState's assigned Layer exists
|
// 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!");
|
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
|
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);
|
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>
|
/// <summary>
|
||||||
/// Internally-called RPC client receiving function to update some animation state on a client
|
/// Internally-called RPC client receiving function to update some animation state on a client
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[ClientRpc]
|
[ClientRpc]
|
||||||
private unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
|
internal unsafe void SendAnimStateClientRpc(AnimationMessage animationMessage, ClientRpcParams clientRpcParams = default)
|
||||||
{
|
{
|
||||||
// This should never happen
|
// This should never happen
|
||||||
if (IsHost)
|
if (IsHost)
|
||||||
@@ -1264,7 +1288,10 @@ namespace Unity.Netcode.Components
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
HandleAnimStateUpdate(ref animationMessage);
|
foreach (var animationState in animationMessage.AnimationStates)
|
||||||
|
{
|
||||||
|
UpdateAnimationState(animationState);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1273,21 +1300,6 @@ namespace Unity.Netcode.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
[ServerRpc]
|
[ServerRpc]
|
||||||
internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
|
internal void SendAnimTriggerServerRpc(AnimationTriggerMessage animationTriggerMessage, ServerRpcParams serverRpcParams = default)
|
||||||
{
|
|
||||||
// If it is server authoritative
|
|
||||||
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.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// Ignore if a non-owner sent this.
|
// Ignore if a non-owner sent this.
|
||||||
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
|
if (serverRpcParams.Receive.SenderClientId != OwnerClientId)
|
||||||
@@ -1302,16 +1314,18 @@ namespace Unity.Netcode.Components
|
|||||||
// set the trigger locally on the server
|
// set the trigger locally on the server
|
||||||
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
|
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.Clear();
|
||||||
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
|
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
|
||||||
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
|
|
||||||
m_ClientSendList.Remove(NetworkManager.ServerClientId);
|
m_ClientSendList.Remove(NetworkManager.ServerClientId);
|
||||||
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
|
|
||||||
|
if (IsServerAuthoritative())
|
||||||
|
{
|
||||||
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
|
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);
|
||||||
}
|
}
|
||||||
|
else if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
|
||||||
|
{
|
||||||
|
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",
|
"rootNamespace": "Unity.Netcode.Components",
|
||||||
"references": [
|
"references": [
|
||||||
"Unity.Netcode.Runtime",
|
"Unity.Netcode.Runtime",
|
||||||
"Unity.Collections"
|
"Unity.Collections",
|
||||||
|
"Unity.Mathematics"
|
||||||
],
|
],
|
||||||
"allowUnsafeCode": true,
|
"allowUnsafeCode": true,
|
||||||
"versionDefines": [
|
"versionDefines": [
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Mono.Cecil.Rocks;
|
using Mono.Cecil.Rocks;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using Mono.Cecil;
|
using Mono.Cecil;
|
||||||
using Mono.Cecil.Cil;
|
using Mono.Cecil.Cil;
|
||||||
@@ -9,9 +9,9 @@ using Mono.Cecil.Rocks;
|
|||||||
using Unity.CompilationPipeline.Common.Diagnostics;
|
using Unity.CompilationPipeline.Common.Diagnostics;
|
||||||
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
using Unity.CompilationPipeline.Common.ILPostProcessing;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
||||||
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
using MethodAttributes = Mono.Cecil.MethodAttributes;
|
||||||
using ParameterAttributes = Mono.Cecil.ParameterAttributes;
|
using ParameterAttributes = Mono.Cecil.ParameterAttributes;
|
||||||
using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor.CodeGen
|
namespace Unity.Netcode.Editor.CodeGen
|
||||||
{
|
{
|
||||||
@@ -837,6 +837,58 @@ namespace Unity.Netcode.Editor.CodeGen
|
|||||||
GetAllFieldsAndResolveGenerics(resolved, ref fieldTypes, genericParams);
|
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)
|
private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] assemblyDefines)
|
||||||
{
|
{
|
||||||
var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler)>();
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEditor;
|
|
||||||
using Unity.Netcode.Editor.Configuration;
|
using Unity.Netcode.Editor.Configuration;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor
|
namespace Unity.Netcode.Editor
|
||||||
{
|
{
|
||||||
@@ -135,23 +135,23 @@ namespace Unity.Netcode.Editor
|
|||||||
}
|
}
|
||||||
else if (type == typeof(uint))
|
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))
|
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))
|
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))
|
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))
|
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))
|
else if (type == typeof(long))
|
||||||
{
|
{
|
||||||
@@ -161,6 +161,10 @@ namespace Unity.Netcode.Editor
|
|||||||
{
|
{
|
||||||
val = (ulong)EditorGUILayout.LongField(variableName, (long)((ulong)val));
|
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))
|
else if (type == typeof(bool))
|
||||||
{
|
{
|
||||||
val = EditorGUILayout.Toggle(variableName, (bool)val);
|
val = EditorGUILayout.Toggle(variableName, (bool)val);
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using Unity.Netcode.Editor.Configuration;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Unity.Netcode.Editor.Configuration;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor
|
namespace Unity.Netcode.Editor
|
||||||
{
|
{
|
||||||
@@ -231,13 +231,7 @@ namespace Unity.Netcode.Editor
|
|||||||
{
|
{
|
||||||
ReloadTransports();
|
ReloadTransports();
|
||||||
|
|
||||||
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]);
|
var transportComponent = m_NetworkManager.gameObject.GetComponent(m_TransportTypes[selection - 1]) ?? m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
||||||
|
|
||||||
if (transportComponent == null)
|
|
||||||
{
|
|
||||||
transportComponent = m_NetworkManager.gameObject.AddComponent(m_TransportTypes[selection - 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
m_NetworkTransportProperty.objectReferenceValue = transportComponent;
|
||||||
|
|
||||||
Repaint();
|
Repaint();
|
||||||
@@ -355,15 +349,19 @@ namespace Unity.Netcode.Editor
|
|||||||
|
|
||||||
if (s_CenteredWordWrappedLabelStyle == null)
|
if (s_CenteredWordWrappedLabelStyle == null)
|
||||||
{
|
{
|
||||||
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label);
|
s_CenteredWordWrappedLabelStyle = new GUIStyle(GUI.skin.label)
|
||||||
s_CenteredWordWrappedLabelStyle.wordWrap = true;
|
{
|
||||||
s_CenteredWordWrappedLabelStyle.alignment = TextAnchor.MiddleLeft;
|
wordWrap = true,
|
||||||
|
alignment = TextAnchor.MiddleLeft
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_HelpBoxStyle == null)
|
if (s_HelpBoxStyle == null)
|
||||||
{
|
{
|
||||||
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox);
|
s_HelpBoxStyle = new GUIStyle(EditorStyles.helpBox)
|
||||||
s_HelpBoxStyle.padding = new RectOffset(10, 10, 10, 10);
|
{
|
||||||
|
padding = new RectOffset(10, 10, 10, 10)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
var openDocsButtonStyle = GUI.skin.button;
|
var openDocsButtonStyle = GUI.skin.button;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Unity.Netcode.Editor.Configuration;
|
using Unity.Netcode.Editor.Configuration;
|
||||||
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEditor;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor
|
namespace Unity.Netcode.Editor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor
|
namespace Unity.Netcode.Editor
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
using Unity.Netcode.Components;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using Unity.Netcode.Components;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.Editor
|
namespace Unity.Netcode.Editor
|
||||||
{
|
{
|
||||||
@@ -25,6 +25,11 @@ namespace Unity.Netcode.Editor
|
|||||||
private SerializedProperty m_InLocalSpaceProperty;
|
private SerializedProperty m_InLocalSpaceProperty;
|
||||||
private SerializedProperty m_InterpolateProperty;
|
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 int s_ToggleOffset = 45;
|
||||||
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
|
private static float s_MaxRowWidth = EditorGUIUtility.labelWidth + EditorGUIUtility.fieldWidth + 5;
|
||||||
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
|
private static GUIContent s_PositionLabel = EditorGUIUtility.TrTextContent("Position");
|
||||||
@@ -48,6 +53,10 @@ namespace Unity.Netcode.Editor
|
|||||||
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
|
m_ScaleThresholdProperty = serializedObject.FindProperty(nameof(NetworkTransform.ScaleThreshold));
|
||||||
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
|
m_InLocalSpaceProperty = serializedObject.FindProperty(nameof(NetworkTransform.InLocalSpace));
|
||||||
m_InterpolateProperty = serializedObject.FindProperty(nameof(NetworkTransform.Interpolate));
|
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/>
|
/// <inheritdoc/>
|
||||||
@@ -71,6 +80,8 @@ namespace Unity.Netcode.Editor
|
|||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_UseQuaternionSynchronization.boolValue)
|
||||||
{
|
{
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
|
|
||||||
@@ -88,6 +99,13 @@ namespace Unity.Netcode.Editor
|
|||||||
|
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_SyncRotationXProperty.boolValue = true;
|
||||||
|
m_SyncRotationYProperty.boolValue = true;
|
||||||
|
m_SyncRotationZProperty.boolValue = true;
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
GUILayout.BeginHorizontal();
|
GUILayout.BeginHorizontal();
|
||||||
|
|
||||||
@@ -116,6 +134,17 @@ namespace Unity.Netcode.Editor
|
|||||||
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
|
||||||
EditorGUILayout.PropertyField(m_InterpolateProperty);
|
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 COM_UNITY_MODULES_PHYSICS
|
||||||
// if rigidbody is present but network rigidbody is not present
|
// if rigidbody is present but network rigidbody is not present
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
[assembly: InternalsVisibleTo("Unity.Netcode.Components")]
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
[assembly: InternalsVisibleTo("Unity.Netcode.Editor")]
|
||||||
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
[assembly: InternalsVisibleTo("Unity.Netcode.Editor.CodeGen")]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.Serialization;
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
@@ -208,6 +208,14 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private ulong? m_ConfigHash = null;
|
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>
|
/// <summary>
|
||||||
/// Gets a SHA256 hash of parts of the NetworkConfig instance
|
/// Gets a SHA256 hash of parts of the NetworkConfig instance
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -273,8 +281,6 @@ namespace Unity.Netcode
|
|||||||
Prefabs.Initialize();
|
Prefabs.Initialize();
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Legacy Network Prefab List
|
|
||||||
|
|
||||||
[NonSerialized]
|
[NonSerialized]
|
||||||
private bool m_DidWarnOldPrefabList = false;
|
private bool m_DidWarnOldPrefabList = false;
|
||||||
|
|
||||||
@@ -334,7 +340,5 @@ namespace Unity.Netcode
|
|||||||
[FormerlySerializedAs("NetworkPrefabs")]
|
[FormerlySerializedAs("NetworkPrefabs")]
|
||||||
[SerializeField]
|
[SerializeField]
|
||||||
internal List<NetworkPrefab> OldPrefabList;
|
internal List<NetworkPrefab> OldPrefabList;
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,12 +52,23 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
~NetworkPrefabs()
|
~NetworkPrefabs()
|
||||||
|
{
|
||||||
|
Shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deregister from add and remove events
|
||||||
|
/// Clear the list
|
||||||
|
/// </summary>
|
||||||
|
internal void Shutdown()
|
||||||
{
|
{
|
||||||
foreach (var list in NetworkPrefabsLists)
|
foreach (var list in NetworkPrefabsLists)
|
||||||
{
|
{
|
||||||
list.OnAdd -= AddTriggeredByNetworkPrefabList;
|
list.OnAdd -= AddTriggeredByNetworkPrefabList;
|
||||||
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
|
list.OnRemove -= RemoveTriggeredByNetworkPrefabList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NetworkPrefabsLists.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -52,6 +52,8 @@ namespace Unity.Netcode
|
|||||||
public static void SetDefaults()
|
public static void SetDefaults()
|
||||||
{
|
{
|
||||||
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
|
SetDefault<IDeferredMessageManager>(networkManager => new DeferredMessageManager(networkManager));
|
||||||
|
|
||||||
|
SetDefault<IRealTimeProvider>(networkManager => new RealTimeProvider());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetDefault<T>(CreateObjectDelegate creator)
|
private static void SetDefault<T>(CreateObjectDelegate creator)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -82,7 +82,7 @@ namespace Unity.Netcode
|
|||||||
var context = new NetworkContext
|
var context = new NetworkContext
|
||||||
{
|
{
|
||||||
SenderId = NetworkManager.ServerClientId,
|
SenderId = NetworkManager.ServerClientId,
|
||||||
Timestamp = Time.realtimeSinceStartup,
|
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
|
||||||
SystemOwner = NetworkManager,
|
SystemOwner = NetworkManager,
|
||||||
// header information isn't valid since it's not a real message.
|
// header information isn't valid since it's not a real message.
|
||||||
// RpcMessage doesn't access this stuff so it's just left empty.
|
// RpcMessage doesn't access this stuff so it's just left empty.
|
||||||
@@ -219,7 +219,7 @@ namespace Unity.Netcode
|
|||||||
var context = new NetworkContext
|
var context = new NetworkContext
|
||||||
{
|
{
|
||||||
SenderId = NetworkManager.ServerClientId,
|
SenderId = NetworkManager.ServerClientId,
|
||||||
Timestamp = Time.realtimeSinceStartup,
|
Timestamp = NetworkManager.RealTimeProvider.RealTimeSinceStartup,
|
||||||
SystemOwner = NetworkManager,
|
SystemOwner = NetworkManager,
|
||||||
// header information isn't valid since it's not a real message.
|
// header information isn't valid since it's not a real message.
|
||||||
// RpcMessage doesn't access this stuff so it's just left empty.
|
// RpcMessage doesn't access this stuff so it's just left empty.
|
||||||
@@ -570,12 +570,9 @@ namespace Unity.Netcode
|
|||||||
if (list == null)
|
if (list == null)
|
||||||
{
|
{
|
||||||
list = new List<FieldInfo>();
|
list = new List<FieldInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
|
list.AddRange(type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
list.AddRange(type.GetFields(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
if (type.BaseType != null && type.BaseType != typeof(NetworkBehaviour))
|
||||||
{
|
{
|
||||||
@@ -600,13 +597,7 @@ namespace Unity.Netcode
|
|||||||
var fieldType = sortedFields[i].FieldType;
|
var fieldType = sortedFields[i].FieldType;
|
||||||
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
if (fieldType.IsSubclassOf(typeof(NetworkVariableBase)))
|
||||||
{
|
{
|
||||||
var instance = (NetworkVariableBase)sortedFields[i].GetValue(this);
|
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.");
|
||||||
|
|
||||||
if (instance == null)
|
|
||||||
{
|
|
||||||
throw new Exception($"{GetType().FullName}.{sortedFields[i].Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
instance.Initialize(this);
|
instance.Initialize(this);
|
||||||
|
|
||||||
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
|
var instanceNameProperty = fieldType.GetProperty(nameof(NetworkVariableBase.Name));
|
||||||
@@ -899,11 +890,23 @@ namespace Unity.Netcode
|
|||||||
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
|
/// Either BufferSerializerReader or BufferSerializerWriter, depending whether the serializer
|
||||||
/// is in read mode or write mode.
|
/// is in read mode or write mode.
|
||||||
/// </typeparam>
|
/// </typeparam>
|
||||||
|
/// <param name="targetClientId">the relative client identifier being synchronized</param>
|
||||||
protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) where T : IReaderWriter
|
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>
|
/// <summary>
|
||||||
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
|
/// Internal method that determines if a NetworkBehaviour has additional synchronization data to
|
||||||
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
|
/// be synchronized when first instantiated prior to its associated NetworkObject being spawned.
|
||||||
@@ -913,8 +916,9 @@ namespace Unity.Netcode
|
|||||||
/// synchronize any remaining NetworkBehaviours.
|
/// synchronize any remaining NetworkBehaviours.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <returns>true if it wrote synchronization data and false if it did not</returns>
|
/// <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)
|
if (serializer.IsWriter)
|
||||||
{
|
{
|
||||||
// Get the writer to handle seeking and determining how many bytes were written
|
// Get the writer to handle seeking and determining how many bytes were written
|
||||||
@@ -949,6 +953,8 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
var finalPosition = writer.Position;
|
var finalPosition = writer.Position;
|
||||||
|
|
||||||
|
// Reset before exiting
|
||||||
|
m_TargetIdBeingSynchronized = default;
|
||||||
// If we wrote nothing then skip writing anything for this NetworkBehaviour
|
// If we wrote nothing then skip writing anything for this NetworkBehaviour
|
||||||
if (finalPosition == positionBeforeSynchronize || threwException)
|
if (finalPosition == positionBeforeSynchronize || threwException)
|
||||||
{
|
{
|
||||||
@@ -1002,6 +1008,9 @@ namespace Unity.Netcode
|
|||||||
synchronizationError = true;
|
synchronizationError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reset before exiting
|
||||||
|
m_TargetIdBeingSynchronized = default;
|
||||||
|
|
||||||
// Skip over the entry if deserialization fails
|
// Skip over the entry if deserialization fails
|
||||||
if (synchronizationError)
|
if (synchronizationError)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -298,6 +298,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
|
internal IDeferredMessageManager DeferredMessageManager { get; private set; }
|
||||||
|
|
||||||
|
internal IRealTimeProvider RealTimeProvider { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the CustomMessagingManager for this NetworkManager
|
/// Gets the CustomMessagingManager for this NetworkManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -449,10 +451,28 @@ namespace Unity.Netcode
|
|||||||
public event Action<ulong> OnClientDisconnectCallback = null;
|
public event Action<ulong> OnClientDisconnectCallback = null;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
public event Action OnServerStarted = null;
|
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>
|
/// <summary>
|
||||||
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
/// The callback to invoke if the <see cref="NetworkTransport"/> fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -735,6 +755,8 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
|
DeferredMessageManager = ComponentFactory.Create<IDeferredMessageManager>(this);
|
||||||
|
|
||||||
|
RealTimeProvider = ComponentFactory.Create<IRealTimeProvider>(this);
|
||||||
|
|
||||||
CustomMessagingManager = new CustomMessagingManager(this);
|
CustomMessagingManager = new CustomMessagingManager(this);
|
||||||
|
|
||||||
SceneManager = new NetworkSceneManager(this);
|
SceneManager = new NetworkSceneManager(this);
|
||||||
@@ -908,6 +930,7 @@ namespace Unity.Netcode
|
|||||||
IsClient = true;
|
IsClient = true;
|
||||||
IsListening = true;
|
IsListening = true;
|
||||||
|
|
||||||
|
OnClientStarted?.Invoke();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -989,13 +1012,14 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
SpawnManager.ServerSpawnSceneObjectsOnStartSweep();
|
||||||
|
|
||||||
|
OnServerStarted?.Invoke();
|
||||||
|
OnClientStarted?.Invoke();
|
||||||
|
|
||||||
// This assures that any in-scene placed NetworkObject is spawned and
|
// This assures that any in-scene placed NetworkObject is spawned and
|
||||||
// any associated NetworkBehaviours' netcode related properties are
|
// any associated NetworkBehaviours' netcode related properties are
|
||||||
// set prior to invoking OnClientConnected.
|
// set prior to invoking OnClientConnected.
|
||||||
InvokeOnClientConnectedCallback(LocalClientId);
|
InvokeOnClientConnectedCallback(LocalClientId);
|
||||||
|
|
||||||
OnServerStarted?.Invoke();
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1108,13 +1132,13 @@ namespace Unity.Netcode
|
|||||||
return isParented;
|
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";
|
return $"{transform.name} is nested under {transform.root.name}. NetworkManager cannot be nested.\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
static internal INetworkManagerHelper NetworkManagerHelper;
|
internal static INetworkManagerHelper NetworkManagerHelper;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Interface for NetworkManagerHelper
|
/// Interface for NetworkManagerHelper
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -1195,13 +1219,12 @@ namespace Unity.Netcode
|
|||||||
NetworkLog.LogInfo(nameof(ShutdownInternal));
|
NetworkLog.LogInfo(nameof(ShutdownInternal));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsServer)
|
bool wasServer = IsServer;
|
||||||
|
bool wasClient = IsClient;
|
||||||
|
if (wasServer)
|
||||||
{
|
{
|
||||||
// make sure all messages are flushed before transport disconnect clients
|
// make sure all messages are flushed before transport disconnect clients
|
||||||
if (MessagingSystem != null)
|
MessagingSystem?.ProcessSendQueues();
|
||||||
{
|
|
||||||
MessagingSystem.ProcessSendQueues();
|
|
||||||
}
|
|
||||||
|
|
||||||
var disconnectedIds = new HashSet<ulong>();
|
var disconnectedIds = new HashSet<ulong>();
|
||||||
|
|
||||||
@@ -1237,11 +1260,23 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unregister network updates before trying to disconnect the client
|
||||||
|
this.UnregisterAllNetworkUpdates();
|
||||||
|
|
||||||
if (IsClient && IsListening)
|
if (IsClient && IsListening)
|
||||||
{
|
{
|
||||||
// Client only, send disconnect to server
|
// Client only, send disconnect to server
|
||||||
|
// If transport throws and exception, log the exception and
|
||||||
|
// continue the shutdown sequence (or forever be shutting down)
|
||||||
|
try
|
||||||
|
{
|
||||||
NetworkConfig.NetworkTransport.DisconnectLocalClient();
|
NetworkConfig.NetworkTransport.DisconnectLocalClient();
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Debug.LogException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
IsConnectedClient = false;
|
IsConnectedClient = false;
|
||||||
IsApproved = false;
|
IsApproved = false;
|
||||||
@@ -1261,8 +1296,6 @@ namespace Unity.Netcode
|
|||||||
IsServer = false;
|
IsServer = false;
|
||||||
IsClient = false;
|
IsClient = false;
|
||||||
|
|
||||||
this.UnregisterAllNetworkUpdates();
|
|
||||||
|
|
||||||
if (NetworkTickSystem != null)
|
if (NetworkTickSystem != null)
|
||||||
{
|
{
|
||||||
NetworkTickSystem.Tick -= OnNetworkManagerTick;
|
NetworkTickSystem.Tick -= OnNetworkManagerTick;
|
||||||
@@ -1280,10 +1313,7 @@ namespace Unity.Netcode
|
|||||||
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
|
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DeferredMessageManager != null)
|
DeferredMessageManager?.CleanupAllTriggers();
|
||||||
{
|
|
||||||
DeferredMessageManager.CleanupAllTriggers();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SceneManager != null)
|
if (SceneManager != null)
|
||||||
{
|
{
|
||||||
@@ -1318,6 +1348,22 @@ namespace Unity.Netcode
|
|||||||
m_StopProcessingMessages = false;
|
m_StopProcessingMessages = false;
|
||||||
|
|
||||||
ClearClients();
|
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 />
|
/// <inheritdoc />
|
||||||
@@ -1417,7 +1463,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only update RTT here, server time is updated by time sync messages
|
// 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)
|
if (reset)
|
||||||
{
|
{
|
||||||
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
|
NetworkTickSystem.Reset(NetworkTimeSystem.LocalTime, NetworkTimeSystem.ServerTime);
|
||||||
@@ -1426,7 +1472,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (IsServer == false)
|
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)
|
if (!m_ShuttingDown || !m_StopProcessingMessages)
|
||||||
{
|
{
|
||||||
|
// This should be invoked just prior to the MessagingSystem
|
||||||
|
// processes its outbound queue.
|
||||||
|
SceneManager.CheckForAndSendNetworkObjectSceneChanged();
|
||||||
|
|
||||||
MessagingSystem.ProcessSendQueues();
|
MessagingSystem.ProcessSendQueues();
|
||||||
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
|
NetworkMetrics.UpdateNetworkObjectsCount(SpawnManager.SpawnedObjects.Count);
|
||||||
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
|
NetworkMetrics.UpdateConnectionsCount((IsServer) ? ConnectedClients.Count : 1);
|
||||||
@@ -1486,10 +1536,9 @@ namespace Unity.Netcode
|
|||||||
// we should always force the rebuilding of the NetworkConfig hash value
|
// we should always force the rebuilding of the NetworkConfig hash value
|
||||||
ConfigHash = NetworkConfig.GetConfig(false),
|
ConfigHash = NetworkConfig.GetConfig(false),
|
||||||
ShouldSendConnectionData = NetworkConfig.ConnectionApproval,
|
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++)
|
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
|
||||||
{
|
{
|
||||||
if (MessagingSystem.MessageTypes[index] != null)
|
if (MessagingSystem.MessageTypes[index] != null)
|
||||||
@@ -1509,7 +1558,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private IEnumerator ApprovalTimeout(ulong clientId)
|
private IEnumerator ApprovalTimeout(ulong clientId)
|
||||||
{
|
{
|
||||||
var timeStarted = IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup;
|
var timeStarted = IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup;
|
||||||
var timedOut = false;
|
var timedOut = false;
|
||||||
var connectionApproved = false;
|
var connectionApproved = false;
|
||||||
var connectionNotApproved = false;
|
var connectionNotApproved = false;
|
||||||
@@ -1519,7 +1568,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
yield return null;
|
yield return null;
|
||||||
// Check if we timed out
|
// Check if we timed out
|
||||||
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : Time.realtimeSinceStartup);
|
timedOut = timeoutMarker < (IsServer ? LocalTime.TimeAsFloat : RealTimeProvider.RealTimeSinceStartup);
|
||||||
|
|
||||||
if (IsServer)
|
if (IsServer)
|
||||||
{
|
{
|
||||||
@@ -1861,8 +1910,10 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (!string.IsNullOrEmpty(reason))
|
if (!string.IsNullOrEmpty(reason))
|
||||||
{
|
{
|
||||||
var disconnectReason = new DisconnectReasonMessage();
|
var disconnectReason = new DisconnectReasonMessage
|
||||||
disconnectReason.Reason = reason;
|
{
|
||||||
|
Reason = reason
|
||||||
|
};
|
||||||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
|
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, clientId);
|
||||||
}
|
}
|
||||||
MessagingSystem.ProcessSendQueues();
|
MessagingSystem.ProcessSendQueues();
|
||||||
@@ -2011,15 +2062,19 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (response.CreatePlayerObject)
|
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
|
// 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
|
var sceneObject = new NetworkObject.SceneObject
|
||||||
{
|
{
|
||||||
OwnerClientId = ownerClientId,
|
OwnerClientId = ownerClientId,
|
||||||
IsPlayerObject = true,
|
IsPlayerObject = true,
|
||||||
IsSceneObject = false,
|
IsSceneObject = false,
|
||||||
HasTransform = true,
|
HasTransform = prefabNetworkObject.SynchronizeTransform,
|
||||||
Hash = playerPrefabHash,
|
Hash = playerPrefabHash,
|
||||||
TargetClientId = ownerClientId,
|
TargetClientId = ownerClientId,
|
||||||
Transform = new NetworkObject.SceneObject.TransformData
|
Transform = new NetworkObject.SceneObject.TransformData
|
||||||
@@ -2105,8 +2160,10 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrEmpty(response.Reason))
|
if (!string.IsNullOrEmpty(response.Reason))
|
||||||
{
|
{
|
||||||
var disconnectReason = new DisconnectReasonMessage();
|
var disconnectReason = new DisconnectReasonMessage
|
||||||
disconnectReason.Reason = response.Reason;
|
{
|
||||||
|
Reason = response.Reason
|
||||||
|
};
|
||||||
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
|
SendMessage(ref disconnectReason, NetworkDelivery.Reliable, ownerClientId);
|
||||||
|
|
||||||
MessagingSystem.ProcessSendQueues();
|
MessagingSystem.ProcessSendQueues();
|
||||||
|
|||||||
@@ -17,6 +17,28 @@ namespace Unity.Netcode
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
internal uint GlobalObjectIdHash;
|
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
|
#if UNITY_EDITOR
|
||||||
private void OnValidate()
|
private void OnValidate()
|
||||||
{
|
{
|
||||||
@@ -75,6 +97,18 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsPlayerObject { get; internal set; }
|
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>
|
/// <summary>
|
||||||
/// Gets if the object is the personal clients player object
|
/// Gets if the object is the personal clients player object
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -105,6 +139,55 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DestroyWithScene { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Delegate type for checking visibility
|
/// Delegate type for checking visibility
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -188,6 +271,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal int SceneOriginHandle = 0;
|
internal int SceneOriginHandle = 0;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The server-side scene origin handle
|
||||||
|
/// </summary>
|
||||||
|
internal int NetworkSceneHandle = 0;
|
||||||
|
|
||||||
private Scene m_SceneOrigin;
|
private Scene m_SceneOrigin;
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The scene where the NetworkObject was first instantiated
|
/// The scene where the NetworkObject was first instantiated
|
||||||
@@ -265,6 +353,15 @@ namespace Unity.Netcode
|
|||||||
throw new VisibilityChangeException("The object is already visible");
|
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);
|
NetworkManager.MarkObjectForShowingTo(this, clientId);
|
||||||
Observers.Add(clientId);
|
Observers.Add(clientId);
|
||||||
}
|
}
|
||||||
@@ -578,6 +675,22 @@ namespace Unity.Netcode
|
|||||||
private Transform m_CachedParent; // What is our last set parent Transform reference?
|
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
|
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)
|
internal void SetCachedParent(Transform parentTransform)
|
||||||
{
|
{
|
||||||
m_CachedParent = parentTransform;
|
m_CachedParent = parentTransform;
|
||||||
@@ -1118,6 +1231,18 @@ namespace Unity.Netcode
|
|||||||
set => ByteUtility.SetBit(ref m_BitField, 5, value);
|
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)
|
//If(Metadata.HasParent)
|
||||||
public ulong ParentObjectId;
|
public ulong ParentObjectId;
|
||||||
|
|
||||||
@@ -1160,7 +1285,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var writeSize = 0;
|
var writeSize = 0;
|
||||||
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
writeSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||||
writeSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
writeSize += FastBufferWriter.GetWriteSize<int>();
|
||||||
|
|
||||||
if (!writer.TryBeginWrite(writeSize))
|
if (!writer.TryBeginWrite(writeSize))
|
||||||
{
|
{
|
||||||
@@ -1172,14 +1297,9 @@ namespace Unity.Netcode
|
|||||||
writer.WriteValue(Transform);
|
writer.WriteValue(Transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
// The NetworkSceneHandle is the server-side relative
|
||||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
// scene handle that the NetworkObject resides in.
|
||||||
// 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());
|
writer.WriteValue(OwnerObject.GetSceneOriginHandle());
|
||||||
}
|
|
||||||
|
|
||||||
// Synchronize NetworkVariables and NetworkBehaviours
|
// Synchronize NetworkVariables and NetworkBehaviours
|
||||||
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
var bufferSerializer = new BufferSerializer<BufferSerializerWriter>(new BufferSerializerWriter(writer));
|
||||||
@@ -1205,7 +1325,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var readSize = 0;
|
var readSize = 0;
|
||||||
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
readSize += HasTransform ? FastBufferWriter.GetWriteSize<TransformData>() : 0;
|
||||||
readSize += IsSceneObject ? FastBufferWriter.GetWriteSize<int>() : 0;
|
readSize += FastBufferWriter.GetWriteSize<int>();
|
||||||
|
|
||||||
// Try to begin reading the remaining bytes
|
// Try to begin reading the remaining bytes
|
||||||
if (!reader.TryBeginRead(readSize))
|
if (!reader.TryBeginRead(readSize))
|
||||||
@@ -1218,16 +1338,11 @@ namespace Unity.Netcode
|
|||||||
reader.ReadValue(out Transform);
|
reader.ReadValue(out Transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
// In-Scene NetworkObjects are uniquely identified NetworkPrefabs defined by their
|
// The NetworkSceneHandle is the server-side relative
|
||||||
// NetworkSceneHandle and GlobalObjectIdHash. Client-side NetworkSceneManagers use
|
// scene handle that the NetworkObject resides in.
|
||||||
// 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);
|
reader.ReadValue(out NetworkSceneHandle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
internal void PostNetworkVariableWrite()
|
internal void PostNetworkVariableWrite()
|
||||||
{
|
{
|
||||||
@@ -1265,7 +1380,7 @@ namespace Unity.Netcode
|
|||||||
var synchronizationCount = (byte)0;
|
var synchronizationCount = (byte)0;
|
||||||
foreach (var childBehaviour in ChildNetworkBehaviours)
|
foreach (var childBehaviour in ChildNetworkBehaviours)
|
||||||
{
|
{
|
||||||
if (childBehaviour.Synchronize(ref serializer))
|
if (childBehaviour.Synchronize(ref serializer, targetClientId))
|
||||||
{
|
{
|
||||||
synchronizationCount++;
|
synchronizationCount++;
|
||||||
}
|
}
|
||||||
@@ -1304,7 +1419,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
serializer.SerializeValue(ref networkBehaviourId);
|
serializer.SerializeValue(ref networkBehaviourId);
|
||||||
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
|
var networkBehaviour = GetNetworkBehaviourAtOrderIndex(networkBehaviourId);
|
||||||
networkBehaviour.Synchronize(ref serializer);
|
networkBehaviour.Synchronize(ref serializer, targetClientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1317,6 +1432,7 @@ namespace Unity.Netcode
|
|||||||
OwnerClientId = OwnerClientId,
|
OwnerClientId = OwnerClientId,
|
||||||
IsPlayerObject = IsPlayerObject,
|
IsPlayerObject = IsPlayerObject,
|
||||||
IsSceneObject = IsSceneObject ?? true,
|
IsSceneObject = IsSceneObject ?? true,
|
||||||
|
DestroyWithScene = DestroyWithScene,
|
||||||
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
Hash = HostCheckForGlobalObjectIdHashOverride(),
|
||||||
OwnerObject = this,
|
OwnerObject = this,
|
||||||
TargetClientId = targetClientId
|
TargetClientId = targetClientId
|
||||||
@@ -1352,7 +1468,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (IncludeTransformWhenSpawning == null || IncludeTransformWhenSpawning(OwnerClientId))
|
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
|
// We start with the default AutoObjectParentSync values to determine which transform space we will
|
||||||
// be synchronizing clients with.
|
// be synchronizing clients with.
|
||||||
@@ -1435,11 +1551,126 @@ namespace Unity.Netcode
|
|||||||
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
|
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);
|
||||||
|
|
||||||
// Spawn the NetworkObject
|
// Spawn the NetworkObject
|
||||||
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, false);
|
networkManager.SpawnManager.SpawnNetworkObjectLocally(networkObject, sceneObject, sceneObject.DestroyWithScene);
|
||||||
|
|
||||||
return networkObject;
|
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>
|
/// <summary>
|
||||||
/// Only applies to Host mode.
|
/// Only applies to Host mode.
|
||||||
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.
|
/// Will return the registered source NetworkPrefab's GlobalObjectIdHash if one exists.
|
||||||
|
|||||||
@@ -167,7 +167,7 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static NetworkUpdateStage UpdateStage;
|
public static NetworkUpdateStage UpdateStage;
|
||||||
|
|
||||||
private static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
|
internal static void RunNetworkUpdateStage(NetworkUpdateStage updateStage)
|
||||||
{
|
{
|
||||||
UpdateStage = updateStage;
|
UpdateStage = updateStage;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -151,14 +151,18 @@ namespace Unity.Netcode
|
|||||||
// We dont know what size to use. Try every (more collision prone)
|
// We dont know what size to use. Try every (more collision prone)
|
||||||
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
|
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);
|
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))
|
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);
|
messageHandler64(sender, reader);
|
||||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
|
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -169,15 +173,19 @@ namespace Unity.Netcode
|
|||||||
case HashSize.VarIntFourBytes:
|
case HashSize.VarIntFourBytes:
|
||||||
if (m_NamedMessageHandlers32.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler32))
|
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);
|
messageHandler32(sender, reader);
|
||||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup32[hash], bytesCount);
|
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case HashSize.VarIntEightBytes:
|
case HashSize.VarIntEightBytes:
|
||||||
if (m_NamedMessageHandlers64.TryGetValue(hash, out HandleNamedMessageDelegate messageHandler64))
|
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);
|
messageHandler64(sender, reader);
|
||||||
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, m_MessageHandlerNameLookup64[hash], bytesCount);
|
m_NetworkManager.NetworkMetrics.TrackNamedMessageReceived(sender, messageName, bytesCount);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using Time = UnityEngine.Time;
|
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
@@ -49,7 +48,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
triggerInfo = new TriggerInfo
|
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)
|
TriggerData = new NativeList<TriggerData>(Allocator.Persistent)
|
||||||
};
|
};
|
||||||
triggers[key] = triggerInfo;
|
triggers[key] = triggerInfo;
|
||||||
@@ -77,7 +76,7 @@ namespace Unity.Netcode
|
|||||||
int index = 0;
|
int index = 0;
|
||||||
foreach (var kvp2 in kvp.Value)
|
foreach (var kvp2 in kvp.Value)
|
||||||
{
|
{
|
||||||
if (kvp2.Value.Expiry < Time.realtimeSinceStartup)
|
if (kvp2.Value.Expiry < m_NetworkManager.RealTimeProvider.RealTimeSinceStartup)
|
||||||
{
|
{
|
||||||
staleKeys[index++] = kvp2.Key;
|
staleKeys[index++] = kvp2.Key;
|
||||||
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);
|
PurgeTrigger(kvp.Key, kvp2.Key, kvp2.Value);
|
||||||
|
|||||||
@@ -8,11 +8,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
{
|
{
|
||||||
string reasonSent = Reason;
|
string reasonSent = Reason ?? string.Empty;
|
||||||
if (reasonSent == null)
|
|
||||||
{
|
|
||||||
reasonSent = string.Empty;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we don't send a ConnectionApprovedMessage, the version for this message is encded with the message
|
// 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
|
// itself. However, note that we HAVE received a ConnectionRequestMessage, so we DO have a valid targetVersion
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using UnityEngine;
|
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -364,11 +364,12 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
|
||||||
|
{
|
||||||
|
using (reader)
|
||||||
{
|
{
|
||||||
if (header.MessageType >= m_HighMessageType)
|
if (header.MessageType >= m_HighMessageType)
|
||||||
{
|
{
|
||||||
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
|
Debug.LogWarning($"Received a message with invalid message type value {header.MessageType}");
|
||||||
reader.Dispose();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var context = new NetworkContext
|
var context = new NetworkContext
|
||||||
@@ -384,18 +385,15 @@ namespace Unity.Netcode
|
|||||||
var type = m_ReverseTypeMap[header.MessageType];
|
var type = m_ReverseTypeMap[header.MessageType];
|
||||||
if (!CanReceive(senderId, type, reader, ref context))
|
if (!CanReceive(senderId, type, reader, ref context))
|
||||||
{
|
{
|
||||||
reader.Dispose();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var handler = m_MessageHandlers[header.MessageType];
|
||||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||||
{
|
{
|
||||||
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
m_Hooks[hookIdx].OnBeforeReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||||
}
|
}
|
||||||
|
|
||||||
var handler = m_MessageHandlers[header.MessageType];
|
|
||||||
using (reader)
|
|
||||||
{
|
|
||||||
// This will also log an exception is if the server knows about a message type the client doesn't know
|
// 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
|
// 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
|
// two connecting builds know about different messages, the server should not send a message to a client
|
||||||
@@ -420,12 +418,12 @@ namespace Unity.Netcode
|
|||||||
Debug.LogException(e);
|
Debug.LogException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
|
||||||
{
|
{
|
||||||
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal unsafe void ProcessIncomingMessageQueue()
|
internal unsafe void ProcessIncomingMessageQueue()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using UnityEngine;
|
|
||||||
using System;
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
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;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
|
|
||||||
@@ -12,5 +13,24 @@ namespace Unity.Netcode
|
|||||||
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
|
AsyncOperation LoadSceneAsync(string sceneName, LoadSceneMode loadSceneMode, SceneEventProgress sceneEventProgress);
|
||||||
|
|
||||||
AsyncOperation UnloadSceneAsync(Scene scene, 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
@@ -19,7 +19,7 @@ namespace Unity.Netcode
|
|||||||
public class SceneEvent
|
public class SceneEvent
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/><BR/>
|
/// The <see cref="UnityEngine.AsyncOperation"/> returned by <see cref="SceneManager"/><br />
|
||||||
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
||||||
@@ -34,7 +34,7 @@ namespace Unity.Netcode
|
|||||||
public SceneEventType SceneEventType;
|
public SceneEventType SceneEventType;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// If applicable, this reflects the type of scene loading or unloading that is occurring.<BR/>
|
/// If applicable, this reflects the type of scene loading or unloading that is occurring.<br />
|
||||||
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
||||||
@@ -48,7 +48,7 @@ namespace Unity.Netcode
|
|||||||
public LoadSceneMode LoadSceneMode;
|
public LoadSceneMode LoadSceneMode;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This will be set to the scene name that the event pertains to.<BR/>
|
/// This will be set to the scene name that the event pertains to.<br />
|
||||||
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
/// <item><term><see cref="SceneEventType.Load"/></term></item>
|
||||||
@@ -62,7 +62,7 @@ namespace Unity.Netcode
|
|||||||
public string SceneName;
|
public string SceneName;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When a scene is loaded, the Scene structure is returned.<BR/>
|
/// When a scene is loaded, the Scene structure is returned.<br />
|
||||||
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
/// This is set for the following <see cref="Netcode.SceneEventType"/>s:
|
||||||
/// <list type="bullet">
|
/// <list type="bullet">
|
||||||
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
|
/// <item><term><see cref="SceneEventType.LoadComplete"/></term></item>
|
||||||
@@ -322,37 +322,79 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delegate handler defined by <see cref="VerifySceneBeforeLoadingDelegateHandler"/> that is invoked before the
|
/// Delegate handler defined by <see cref="VerifySceneBeforeLoadingDelegateHandler"/> that is invoked before the
|
||||||
/// server or client loads a scene during an active netcode game session.<br/>
|
/// server or client loads a scene during an active netcode game session.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
/// <b>Client Side:</b> In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler.<br />
|
/// <b>Client Side:</b> In order for clients to be notified of this condition you must assign the <see cref="VerifySceneBeforeLoading"/> delegate handler.<br />
|
||||||
/// <b>Server Side:</b> <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus"/>.
|
/// <b>Server Side:</b> <see cref="LoadScene(string, LoadSceneMode)"/> will return <see cref="SceneEventProgressStatus"/>.
|
||||||
/// </summary>
|
/// </remarks>
|
||||||
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
public VerifySceneBeforeLoadingDelegateHandler VerifySceneBeforeLoading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate declaration for the <see cref="VerifySceneBeforeUnloading"/> handler that provides
|
||||||
|
/// an additional level of scene unloading validation to assure the scene being unloaded should
|
||||||
|
/// be unloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="scene">The scene to be unloaded</param>
|
||||||
|
/// <returns>true (valid) or false (not valid)</returns>
|
||||||
|
public delegate bool VerifySceneBeforeUnloadingDelegateHandler(Scene scene);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Client Side Only: <br />
|
||||||
|
/// Delegate handler defined by <see cref="VerifySceneBeforeUnloadingDelegateHandler"/> that is only invoked when the client
|
||||||
|
/// is finished synchronizing and when <see cref="ClientSynchronizationMode"/> is set to <see cref="LoadSceneMode.Additive"/>.
|
||||||
|
/// </summary>
|
||||||
|
public VerifySceneBeforeUnloadingDelegateHandler VerifySceneBeforeUnloading;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// When enabled and <see cref="ClientSynchronizationMode"/> is <see cref="LoadSceneMode.Additive"/>, any scenes not synchronized with
|
||||||
|
/// the server will be unloaded unless <see cref="VerifySceneBeforeUnloading"/> returns true. This provides more granular control over
|
||||||
|
/// which already loaded client-side scenes not synchronized with the server should be unloaded.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// If the <see cref="VerifySceneBeforeUnloading"/> delegate callback is not set then any scene loaded on the just synchronized client
|
||||||
|
/// will be unloaded.
|
||||||
|
/// One scenario is a synchronized client is disconnected for unexpected reasons and attempts to reconnect to the same network session
|
||||||
|
/// but still has all scenes that were loaded through server synchronization (initially or through scene events). However, during the
|
||||||
|
/// client disconnection period the server unloads one (or more) of the scenes loaded and as such the reconnecting client could still
|
||||||
|
/// have the now unloaded scenes still loaded. Enabling this flag coupled with assignment of the assignment of the <see cref="VerifySceneBeforeUnloading"/>
|
||||||
|
/// delegate callback provides you with the ability to keep scenes loaded by the client (i.e. UI etc) while discarding any artifact
|
||||||
|
/// scenes that no longer need to be loaded.
|
||||||
|
/// </remarks>
|
||||||
|
public bool PostSynchronizationSceneUnloading;
|
||||||
|
|
||||||
|
private bool m_ActiveSceneSynchronizationEnabled;
|
||||||
|
/// <summary>
|
||||||
|
/// When enabled, the server or host will synchronize clients with changes to the currently active scene
|
||||||
|
/// </summary>
|
||||||
|
public bool ActiveSceneSynchronizationEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
return m_ActiveSceneSynchronizationEnabled;
|
||||||
|
}
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (m_ActiveSceneSynchronizationEnabled != value)
|
||||||
|
{
|
||||||
|
m_ActiveSceneSynchronizationEnabled = value;
|
||||||
|
if (m_ActiveSceneSynchronizationEnabled)
|
||||||
|
{
|
||||||
|
SceneManager.activeSceneChanged += SceneManager_ActiveSceneChanged;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SceneManager.activeSceneChanged -= SceneManager_ActiveSceneChanged;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The SceneManagerHandler implementation
|
/// The SceneManagerHandler implementation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
internal ISceneManagerHandler SceneManagerHandler = new DefaultSceneManagerHandler();
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The default SceneManagerHandler that interfaces between the SceneManager and NetworkSceneManager
|
|
||||||
/// </summary>
|
|
||||||
private class DefaultSceneManagerHandler : ISceneManagerHandler
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
internal readonly Dictionary<Guid, SceneEventProgress> SceneEventProgressTracking = new Dictionary<Guid, SceneEventProgress>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -385,6 +427,77 @@ namespace Unity.Netcode
|
|||||||
/// instances with client unique scene instances
|
/// instances with client unique scene instances
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal Dictionary<int, int> ServerSceneHandleToClientSceneHandle = new Dictionary<int, int>();
|
internal Dictionary<int, int> ServerSceneHandleToClientSceneHandle = new Dictionary<int, int>();
|
||||||
|
internal Dictionary<int, int> ClientSceneHandleToServerSceneHandle = new Dictionary<int, int>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add the client to server (and vice versa) scene handle lookup.
|
||||||
|
/// Add the client-side handle to scene entry in the HandleToScene table.
|
||||||
|
/// If it fails (i.e. already added) it returns false.
|
||||||
|
/// </summary>
|
||||||
|
internal bool UpdateServerClientSceneHandle(int serverHandle, int clientHandle, Scene localScene)
|
||||||
|
{
|
||||||
|
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(serverHandle))
|
||||||
|
{
|
||||||
|
ServerSceneHandleToClientSceneHandle.Add(serverHandle, clientHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ClientSceneHandleToServerSceneHandle.ContainsKey(clientHandle))
|
||||||
|
{
|
||||||
|
ClientSceneHandleToServerSceneHandle.Add(clientHandle, serverHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// It is "Ok" if this already has an entry
|
||||||
|
if (!ScenesLoaded.ContainsKey(clientHandle))
|
||||||
|
{
|
||||||
|
ScenesLoaded.Add(clientHandle, localScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the client to server (and vice versa) scene handles.
|
||||||
|
/// If it fails (i.e. already removed) it returns false.
|
||||||
|
/// </summary>
|
||||||
|
internal bool RemoveServerClientSceneHandle(int serverHandle, int clientHandle)
|
||||||
|
{
|
||||||
|
if (ServerSceneHandleToClientSceneHandle.ContainsKey(serverHandle))
|
||||||
|
{
|
||||||
|
ServerSceneHandleToClientSceneHandle.Remove(serverHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ClientSceneHandleToServerSceneHandle.ContainsKey(clientHandle))
|
||||||
|
{
|
||||||
|
ClientSceneHandleToServerSceneHandle.Remove(clientHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ScenesLoaded.ContainsKey(clientHandle))
|
||||||
|
{
|
||||||
|
ScenesLoaded.Remove(clientHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Hash to build index lookup table
|
/// Hash to build index lookup table
|
||||||
@@ -413,16 +526,22 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private NetworkManager m_NetworkManager { get; }
|
private NetworkManager m_NetworkManager { get; }
|
||||||
|
|
||||||
|
// Keep track of this scene until the NetworkSceneManager is destroyed.
|
||||||
internal Scene DontDestroyOnLoadScene;
|
internal Scene DontDestroyOnLoadScene;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
|
/// This setting changes how clients handle scene loading when initially synchronizing with the server.<br />
|
||||||
/// the server's currently active scene will be loaded in single mode on the client
|
/// See: <see cref="SetClientSynchronizationMode(LoadSceneMode)"/>
|
||||||
/// unless it was already loaded.<br/>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and the
|
||||||
|
/// server's currently active scene will be loaded in single mode on the client unless it was already
|
||||||
|
/// loaded.<br />
|
||||||
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
|
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
|
||||||
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
|
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
|
||||||
/// <see cref="VerifySceneBeforeLoading"/> method.
|
/// <see cref="VerifySceneBeforeLoading"/> and, if <see cref="PostSynchronizationSceneUnloading"/> is
|
||||||
/// </summary>
|
/// set, <see cref="VerifySceneBeforeUnloading"/> callback(s).
|
||||||
|
/// </remarks>
|
||||||
public LoadSceneMode ClientSynchronizationMode { get; internal set; }
|
public LoadSceneMode ClientSynchronizationMode { get; internal set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -435,11 +554,12 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
|
// Always assure we no longer listen to scene changes when disposed.
|
||||||
|
SceneManager.activeSceneChanged -= SceneManager_ActiveSceneChanged;
|
||||||
SceneUnloadEventHandler.Shutdown();
|
SceneUnloadEventHandler.Shutdown();
|
||||||
|
|
||||||
foreach (var keypair in SceneEventDataStore)
|
foreach (var keypair in SceneEventDataStore)
|
||||||
{
|
{
|
||||||
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
|
if (NetworkLog.CurrentLogLevel == LogLevel.Developer)
|
||||||
{
|
{
|
||||||
NetworkLog.LogInfo($"{nameof(SceneEventDataStore)} is disposing {nameof(SceneEventData.SceneEventId)} '{keypair.Key}'.");
|
NetworkLog.LogInfo($"{nameof(SceneEventDataStore)} is disposing {nameof(SceneEventData.SceneEventId)} '{keypair.Key}'.");
|
||||||
}
|
}
|
||||||
@@ -493,6 +613,11 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void GenerateScenesInBuild()
|
internal void GenerateScenesInBuild()
|
||||||
{
|
{
|
||||||
|
// TODO 2023: We could support addressable or asset bundle scenes by
|
||||||
|
// adding a method that would allow users to add scenes to this.
|
||||||
|
// The method would be server-side only and require an additional SceneEventType
|
||||||
|
// that would be used to notify clients of the added scene. This might need
|
||||||
|
// to include information about the addressable or asset bundle (i.e. address to load assets)
|
||||||
HashToBuildIndex.Clear();
|
HashToBuildIndex.Clear();
|
||||||
BuildIndexToHash.Clear();
|
BuildIndexToHash.Clear();
|
||||||
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
|
for (int i = 0; i < SceneManager.sceneCountInBuildSettings; i++)
|
||||||
@@ -581,18 +706,22 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This will change how clients are initially synchronized.<br/>
|
/// This setting changes how clients handle scene loading when initially synchronizing with the server.<br />
|
||||||
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and
|
/// The server or host should set this value as clients will automatically be synchronized with the server (or host) side.
|
||||||
/// the server's currently active scene will be loaded in single mode on the client
|
/// <remarks>
|
||||||
/// unless it was already loaded. <br/>
|
/// <b>LoadSceneMode.Single:</b> All currently loaded scenes on the client will be unloaded and the
|
||||||
|
/// server's currently active scene will be loaded in single mode on the client unless it was already
|
||||||
|
/// loaded.<br />
|
||||||
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
|
/// <b>LoadSceneMode.Additive:</b> All currently loaded scenes are left as they are and any newly loaded
|
||||||
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
|
/// scenes will be loaded additively. Users need to determine which scenes are valid to load via the
|
||||||
/// <see cref="VerifySceneBeforeLoading"/> method.
|
/// <see cref="VerifySceneBeforeLoading"/> and, if <see cref="PostSynchronizationSceneUnloading"/> is
|
||||||
/// </summary>
|
/// set, <see cref="VerifySceneBeforeUnloading"/> callback(s).
|
||||||
|
/// </remarks>
|
||||||
/// <param name="mode"><see cref="LoadSceneMode"/> for initial client synchronization</param>
|
/// <param name="mode"><see cref="LoadSceneMode"/> for initial client synchronization</param>
|
||||||
public void SetClientSynchronizationMode(LoadSceneMode mode)
|
public void SetClientSynchronizationMode(LoadSceneMode mode)
|
||||||
{
|
{
|
||||||
ClientSynchronizationMode = mode;
|
var networkManager = m_NetworkManager;
|
||||||
|
SceneManagerHandler.SetClientSynchronizationMode(ref networkManager, mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -605,13 +734,46 @@ namespace Unity.Netcode
|
|||||||
m_NetworkManager = networkManager;
|
m_NetworkManager = networkManager;
|
||||||
SceneEventDataStore = new Dictionary<uint, SceneEventData>();
|
SceneEventDataStore = new Dictionary<uint, SceneEventData>();
|
||||||
|
|
||||||
|
// Generates the scene name to hash value
|
||||||
GenerateScenesInBuild();
|
GenerateScenesInBuild();
|
||||||
|
|
||||||
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
// Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene
|
||||||
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
DontDestroyOnLoadScene = networkManager.gameObject.scene;
|
||||||
|
|
||||||
ServerSceneHandleToClientSceneHandle.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle);
|
// Add to the server to client scene handle table
|
||||||
ScenesLoaded.Add(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Synchronizes clients when the currently active scene is changed
|
||||||
|
/// </summary>
|
||||||
|
private void SceneManager_ActiveSceneChanged(Scene current, Scene next)
|
||||||
|
{
|
||||||
|
// If no clients are connected, then don't worry about notifications
|
||||||
|
if (!(m_NetworkManager.ConnectedClientsIds.Count > (m_NetworkManager.IsHost ? 1 : 0)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't notify if a scene event is in progress
|
||||||
|
foreach (var sceneEventEntry in SceneEventProgressTracking)
|
||||||
|
{
|
||||||
|
if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the scene's build index is in the hash table
|
||||||
|
if (BuildIndexToHash.ContainsKey(next.buildIndex))
|
||||||
|
{
|
||||||
|
// Notify clients of the change in active scene
|
||||||
|
var sceneEvent = BeginSceneEvent();
|
||||||
|
sceneEvent.SceneEventType = SceneEventType.ActiveSceneChanged;
|
||||||
|
sceneEvent.ActiveSceneHash = BuildIndexToHash[next.buildIndex];
|
||||||
|
SendSceneEventData(sceneEvent.SceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
|
||||||
|
EndSceneEvent(sceneEvent.SceneEventId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -629,7 +791,7 @@ namespace Unity.Netcode
|
|||||||
var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName);
|
var sceneIndex = SceneUtility.GetBuildIndexByScenePath(sceneName);
|
||||||
if (VerifySceneBeforeLoading != null)
|
if (VerifySceneBeforeLoading != null)
|
||||||
{
|
{
|
||||||
validated = VerifySceneBeforeLoading.Invoke((int)sceneIndex, sceneName, loadSceneMode);
|
validated = VerifySceneBeforeLoading.Invoke(sceneIndex, sceneName, loadSceneMode);
|
||||||
}
|
}
|
||||||
if (!validated && !m_DisableValidationWarningMessages)
|
if (!validated && !m_DisableValidationWarningMessages)
|
||||||
{
|
{
|
||||||
@@ -675,11 +837,11 @@ namespace Unity.Netcode
|
|||||||
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
if (!ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||||
{
|
{
|
||||||
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||||
|
SceneManagerHandler.StartTrackingScene(sceneLoaded, true, m_NetworkManager);
|
||||||
return sceneLoaded;
|
return sceneLoaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
throw new Exception($"Failed to find any loaded scene named {sceneName}!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -788,7 +950,7 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="scene">the scene to be unloaded</param>
|
/// <param name="scene">the scene to be unloaded</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
private SceneEventProgress ValidateSceneEventUnLoading(Scene scene)
|
private SceneEventProgress ValidateSceneEventUnloading(Scene scene)
|
||||||
{
|
{
|
||||||
if (!m_NetworkManager.IsServer)
|
if (!m_NetworkManager.IsServer)
|
||||||
{
|
{
|
||||||
@@ -798,9 +960,9 @@ namespace Unity.Netcode
|
|||||||
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
//Log message about enabling SceneManagement
|
//Log message about enabling SceneManagement
|
||||||
throw new Exception($"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
throw new Exception(
|
||||||
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling " +
|
$"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
||||||
$"{nameof(NetworkSceneManager.LoadScene)} or {nameof(NetworkSceneManager.UnloadScene)}.");
|
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling {nameof(LoadScene)} or {nameof(UnloadScene)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!scene.isLoaded)
|
if (!scene.isLoaded)
|
||||||
@@ -826,9 +988,9 @@ namespace Unity.Netcode
|
|||||||
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
if (!m_NetworkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
//Log message about enabling SceneManagement
|
//Log message about enabling SceneManagement
|
||||||
throw new Exception($"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
throw new Exception(
|
||||||
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling " +
|
$"{nameof(NetworkConfig.EnableSceneManagement)} flag is not enabled in the {nameof(NetworkManager)}'s {nameof(NetworkConfig)}. " +
|
||||||
$"{nameof(NetworkSceneManager.LoadScene)} or {nameof(NetworkSceneManager.UnloadScene)}.");
|
$"Please set {nameof(NetworkConfig.EnableSceneManagement)} flag to true before calling {nameof(LoadScene)} or {nameof(UnloadScene)}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ValidateSceneEvent(sceneName);
|
return ValidateSceneEvent(sceneName);
|
||||||
@@ -939,7 +1101,7 @@ namespace Unity.Netcode
|
|||||||
return SceneEventProgressStatus.SceneNotLoaded;
|
return SceneEventProgressStatus.SceneNotLoaded;
|
||||||
}
|
}
|
||||||
|
|
||||||
var sceneEventProgress = ValidateSceneEventUnLoading(scene);
|
var sceneEventProgress = ValidateSceneEventUnloading(scene);
|
||||||
if (sceneEventProgress.Status != SceneEventProgressStatus.Started)
|
if (sceneEventProgress.Status != SceneEventProgressStatus.Started)
|
||||||
{
|
{
|
||||||
return sceneEventProgress.Status;
|
return sceneEventProgress.Status;
|
||||||
@@ -950,6 +1112,13 @@ namespace Unity.Netcode
|
|||||||
Debug.LogError($"{nameof(UnloadScene)} internal error! {sceneName} with handle {scene.handle} is not within the internal scenes loaded dictionary!");
|
Debug.LogError($"{nameof(UnloadScene)} internal error! {sceneName} with handle {scene.handle} is not within the internal scenes loaded dictionary!");
|
||||||
return SceneEventProgressStatus.InternalNetcodeError;
|
return SceneEventProgressStatus.InternalNetcodeError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any NetworkObjects marked to not be destroyed with a scene and reside within the scene about to be unloaded
|
||||||
|
// should be migrated temporarily into the DDOL, once the scene is unloaded they will be migrated into the
|
||||||
|
// currently active scene.
|
||||||
|
var networkManager = m_NetworkManager;
|
||||||
|
SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene);
|
||||||
|
|
||||||
var sceneEventData = BeginSceneEvent();
|
var sceneEventData = BeginSceneEvent();
|
||||||
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
|
sceneEventData.SceneEventProgressId = sceneEventProgress.Guid;
|
||||||
sceneEventData.SceneEventType = SceneEventType.Unload;
|
sceneEventData.SceneEventType = SceneEventType.Unload;
|
||||||
@@ -964,7 +1133,6 @@ namespace Unity.Netcode
|
|||||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
||||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
||||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
|
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
|
||||||
|
|
||||||
// Notify local server that a scene is going to be unloaded
|
// Notify local server that a scene is going to be unloaded
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
{
|
{
|
||||||
@@ -1006,16 +1174,30 @@ namespace Unity.Netcode
|
|||||||
throw new Exception($"Client failed to unload scene {sceneName} " +
|
throw new Exception($"Client failed to unload scene {sceneName} " +
|
||||||
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
|
$"because the client scene handle {sceneHandle} was not found in ScenesLoaded!");
|
||||||
}
|
}
|
||||||
m_IsSceneEventActive = true;
|
|
||||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
|
||||||
sceneEventProgress.SceneEventId = sceneEventData.SceneEventId;
|
|
||||||
sceneEventProgress.OnSceneEventCompleted = OnSceneUnloaded;
|
|
||||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(ScenesLoaded[sceneHandle], sceneEventProgress);
|
|
||||||
|
|
||||||
ScenesLoaded.Remove(sceneHandle);
|
var scene = ScenesLoaded[sceneHandle];
|
||||||
|
// Any NetworkObjects marked to not be destroyed with a scene and reside within the scene about to be unloaded
|
||||||
|
// should be migrated temporarily into the DDOL, once the scene is unloaded they will be migrated into the
|
||||||
|
// currently active scene.
|
||||||
|
var networkManager = m_NetworkManager;
|
||||||
|
SceneManagerHandler.MoveObjectsFromSceneToDontDestroyOnLoad(ref networkManager, scene);
|
||||||
|
|
||||||
|
m_IsSceneEventActive = true;
|
||||||
|
var sceneEventProgress = new SceneEventProgress(m_NetworkManager)
|
||||||
|
{
|
||||||
|
SceneEventId = sceneEventData.SceneEventId,
|
||||||
|
OnSceneEventCompleted = OnSceneUnloaded
|
||||||
|
};
|
||||||
|
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(scene, sceneEventProgress);
|
||||||
|
|
||||||
|
SceneManagerHandler.StopTrackingScene(sceneHandle, sceneName, m_NetworkManager);
|
||||||
|
|
||||||
// Remove our server to scene handle lookup
|
// Remove our server to scene handle lookup
|
||||||
ServerSceneHandleToClientSceneHandle.Remove(sceneEventData.SceneHandle);
|
if (!RemoveServerClientSceneHandle(sceneEventData.SceneHandle, sceneHandle))
|
||||||
|
{
|
||||||
|
// If the exact same handle exists then there are problems with using handles
|
||||||
|
throw new Exception($"Failed to remove server scene handle ({sceneEventData.SceneHandle}) or client scene handle({sceneHandle})! Happened during scene unload for {sceneName}.");
|
||||||
|
}
|
||||||
|
|
||||||
// Notify the local client that a scene is going to be unloaded
|
// Notify the local client that a scene is going to be unloaded
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
@@ -1042,6 +1224,9 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migrate the NetworkObjects marked to not be destroyed with the scene into the currently active scene
|
||||||
|
MoveObjectsFromDontDestroyOnLoadToScene(SceneManager.GetActiveScene());
|
||||||
|
|
||||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
// First thing we do, if we are a server, is to send the unload scene event.
|
// First thing we do, if we are a server, is to send the unload scene event.
|
||||||
if (m_NetworkManager.IsServer)
|
if (m_NetworkManager.IsServer)
|
||||||
@@ -1103,15 +1288,18 @@ namespace Unity.Netcode
|
|||||||
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
|
// Validate the scene as well as ignore the DDOL (which will have a negative buildIndex)
|
||||||
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
|
if (currentActiveScene.name != keyHandleEntry.Value.name && keyHandleEntry.Value.buildIndex >= 0)
|
||||||
{
|
{
|
||||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
var sceneEventProgress = new SceneEventProgress(m_NetworkManager)
|
||||||
sceneEventProgress.SceneEventId = sceneEventId;
|
{
|
||||||
sceneEventProgress.OnSceneEventCompleted = EmptySceneUnloadedOperation;
|
SceneEventId = sceneEventId,
|
||||||
|
OnSceneEventCompleted = EmptySceneUnloadedOperation
|
||||||
|
};
|
||||||
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
|
var sceneUnload = SceneManagerHandler.UnloadSceneAsync(keyHandleEntry.Value, sceneEventProgress);
|
||||||
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
SceneUnloadEventHandler.RegisterScene(this, keyHandleEntry.Value, LoadSceneMode.Additive, sceneUnload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// clear out our scenes loaded list
|
// clear out our scenes loaded list
|
||||||
ScenesLoaded.Clear();
|
ScenesLoaded.Clear();
|
||||||
|
SceneManagerHandler.ClearSceneTracking(m_NetworkManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1339,9 +1527,11 @@ namespace Unity.Netcode
|
|||||||
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
SceneUnloadEventHandler.RegisterScene(this, SceneManager.GetActiveScene(), LoadSceneMode.Single);
|
||||||
|
|
||||||
}
|
}
|
||||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
var sceneEventProgress = new SceneEventProgress(m_NetworkManager)
|
||||||
sceneEventProgress.SceneEventId = sceneEventId;
|
{
|
||||||
sceneEventProgress.OnSceneEventCompleted = OnSceneLoaded;
|
SceneEventId = sceneEventId,
|
||||||
|
OnSceneEventCompleted = OnSceneLoaded
|
||||||
|
};
|
||||||
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
|
var sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, sceneEventData.LoadSceneMode, sceneEventProgress);
|
||||||
|
|
||||||
OnSceneEvent?.Invoke(new SceneEvent()
|
OnSceneEvent?.Invoke(new SceneEvent()
|
||||||
@@ -1403,11 +1593,7 @@ namespace Unity.Netcode
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// For the client, we make a server scene handle to client scene handle look up table
|
// For the client, we make a server scene handle to client scene handle look up table
|
||||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.SceneHandle))
|
if (!UpdateServerClientSceneHandle(sceneEventData.SceneHandle, nextScene.handle, nextScene))
|
||||||
{
|
|
||||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.SceneHandle, nextScene.handle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// If the exact same handle exists then there are problems with using handles
|
// If the exact same handle exists then there are problems with using handles
|
||||||
throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})");
|
throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})");
|
||||||
@@ -1530,12 +1716,16 @@ namespace Unity.Netcode
|
|||||||
m_NetworkManager.SpawnManager.UpdateObservedNetworkObjects(clientId);
|
m_NetworkManager.SpawnManager.UpdateObservedNetworkObjects(clientId);
|
||||||
|
|
||||||
var sceneEventData = BeginSceneEvent();
|
var sceneEventData = BeginSceneEvent();
|
||||||
|
sceneEventData.ClientSynchronizationMode = ClientSynchronizationMode;
|
||||||
sceneEventData.InitializeForSynch();
|
sceneEventData.InitializeForSynch();
|
||||||
sceneEventData.TargetClientId = clientId;
|
sceneEventData.TargetClientId = clientId;
|
||||||
sceneEventData.LoadSceneMode = ClientSynchronizationMode;
|
sceneEventData.LoadSceneMode = ClientSynchronizationMode;
|
||||||
var activeScene = SceneManager.GetActiveScene();
|
var activeScene = SceneManager.GetActiveScene();
|
||||||
sceneEventData.SceneEventType = SceneEventType.Synchronize;
|
sceneEventData.SceneEventType = SceneEventType.Synchronize;
|
||||||
|
if (BuildIndexToHash.ContainsKey(activeScene.buildIndex))
|
||||||
|
{
|
||||||
|
sceneEventData.ActiveSceneHash = BuildIndexToHash[activeScene.buildIndex];
|
||||||
|
}
|
||||||
|
|
||||||
// Organize how (and when) we serialize our NetworkObjects
|
// Organize how (and when) we serialize our NetworkObjects
|
||||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||||
@@ -1549,6 +1739,11 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (scene == DontDestroyOnLoadScene)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
var sceneHash = SceneHashFromNameOrPath(scene.path);
|
||||||
|
|
||||||
// This would depend upon whether we are additive or not
|
// This would depend upon whether we are additive or not
|
||||||
@@ -1620,9 +1815,6 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
|
|
||||||
OnSynchronize?.Invoke(m_NetworkManager.LocalClientId);
|
OnSynchronize?.Invoke(m_NetworkManager.LocalClientId);
|
||||||
|
|
||||||
// Clear the in-scene placed NetworkObjects when we load the first scene in our synchronization process
|
|
||||||
ScenePlacedObjects.Clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always check to see if the scene needs to be validated
|
// Always check to see if the scene needs to be validated
|
||||||
@@ -1636,23 +1828,23 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var shouldPassThrough = false;
|
|
||||||
var sceneLoad = (AsyncOperation)null;
|
var sceneLoad = (AsyncOperation)null;
|
||||||
|
|
||||||
// Check to see if the client already has loaded the scene to be loaded
|
// Determines if the client has the scene to be loaded already loaded, if so will return true and the client will skip loading this scene
|
||||||
if (sceneName == activeScene.name)
|
// For ClientSynchronizationMode LoadSceneMode.Single, we pass in whether the scene being loaded is the first/primary active scene and if it is already loaded
|
||||||
{
|
// it should pass through to post load processing (ClientLoadedSynchronization).
|
||||||
// If the client is already in the same scene, then pass through and
|
// For ClientSynchronizationMode LoadSceneMode.Additive, if the scene is already loaded or the active scene is the scene to be loaded (does not require it to
|
||||||
// don't try to reload it.
|
// be the initial primary scene) then go ahead and pass through to post load processing (ClientLoadedSynchronization).
|
||||||
shouldPassThrough = true;
|
var shouldPassThrough = SceneManagerHandler.ClientShouldPassThrough(sceneName, sceneHash == sceneEventData.SceneHash, ClientSynchronizationMode, m_NetworkManager);
|
||||||
}
|
|
||||||
|
|
||||||
if (!shouldPassThrough)
|
if (!shouldPassThrough)
|
||||||
{
|
{
|
||||||
// If not, then load the scene
|
// If not, then load the scene
|
||||||
var sceneEventProgress = new SceneEventProgress(m_NetworkManager);
|
var sceneEventProgress = new SceneEventProgress(m_NetworkManager)
|
||||||
sceneEventProgress.SceneEventId = sceneEventId;
|
{
|
||||||
sceneEventProgress.OnSceneEventCompleted = ClientLoadedSynchronization;
|
SceneEventId = sceneEventId,
|
||||||
|
OnSceneEventCompleted = ClientLoadedSynchronization
|
||||||
|
};
|
||||||
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
sceneLoad = SceneManagerHandler.LoadSceneAsync(sceneName, loadSceneMode, sceneEventProgress);
|
||||||
|
|
||||||
// Notify local client that a scene load has begun
|
// Notify local client that a scene load has begun
|
||||||
@@ -1683,7 +1875,11 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
var sceneName = SceneNameFromHash(sceneEventData.ClientSceneHash);
|
var sceneName = SceneNameFromHash(sceneEventData.ClientSceneHash);
|
||||||
var nextScene = GetAndAddNewlyLoadedSceneByName(sceneName);
|
var nextScene = SceneManagerHandler.GetSceneFromLoadedScenes(sceneName, m_NetworkManager);
|
||||||
|
if (!nextScene.IsValid())
|
||||||
|
{
|
||||||
|
nextScene = GetAndAddNewlyLoadedSceneByName(sceneName);
|
||||||
|
}
|
||||||
|
|
||||||
if (!nextScene.isLoaded || !nextScene.IsValid())
|
if (!nextScene.isLoaded || !nextScene.IsValid())
|
||||||
{
|
{
|
||||||
@@ -1698,11 +1894,8 @@ namespace Unity.Netcode
|
|||||||
SceneManager.SetActiveScene(nextScene);
|
SceneManager.SetActiveScene(nextScene);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEventData.NetworkSceneHandle))
|
// For the client, we make a server scene handle to client scene handle look up table
|
||||||
{
|
if (!UpdateServerClientSceneHandle(sceneEventData.NetworkSceneHandle, nextScene.handle, nextScene))
|
||||||
ServerSceneHandleToClientSceneHandle.Add(sceneEventData.NetworkSceneHandle, nextScene.handle);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
// If the exact same handle exists then there are problems with using handles
|
// If the exact same handle exists then there are problems with using handles
|
||||||
throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})");
|
throw new Exception($"Server Scene Handle ({sceneEventData.SceneHandle}) already exist! Happened during scene load of {nextScene.name} with Client Handle ({nextScene.handle})");
|
||||||
@@ -1744,6 +1937,48 @@ namespace Unity.Netcode
|
|||||||
HandleClientSceneEvent(sceneEventId);
|
HandleClientSceneEvent(sceneEventId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Makes sure that client-side instantiated dynamically spawned NetworkObjects are migrated
|
||||||
|
/// into the same scene (if not already) as they are on the server-side during the initial
|
||||||
|
/// client connection synchronization process.
|
||||||
|
/// </summary>
|
||||||
|
private void SynchronizeNetworkObjectScene()
|
||||||
|
{
|
||||||
|
foreach (var networkObject in m_NetworkManager.SpawnManager.SpawnedObjectsList)
|
||||||
|
{
|
||||||
|
// This is only done for dynamically spawned NetworkObjects
|
||||||
|
// Theoretically, a server could have NetworkObjects in a server-side only scene, if the client doesn't have that scene loaded
|
||||||
|
// then skip it (it will reside in the currently active scene in this scenario on the client-side)
|
||||||
|
if (networkObject.IsSceneObject.Value == false && ServerSceneHandleToClientSceneHandle.ContainsKey(networkObject.NetworkSceneHandle))
|
||||||
|
{
|
||||||
|
networkObject.SceneOriginHandle = ServerSceneHandleToClientSceneHandle[networkObject.NetworkSceneHandle];
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// If the NetworkObject does not have a parent and is not in the same scene as it is on the server side, then find the right scene
|
||||||
|
// and move it to that scene.
|
||||||
|
if (networkObject.gameObject.scene.handle != networkObject.SceneOriginHandle && networkObject.transform.parent == null)
|
||||||
|
{
|
||||||
|
if (ScenesLoaded.ContainsKey(networkObject.SceneOriginHandle))
|
||||||
|
{
|
||||||
|
var scene = ScenesLoaded[networkObject.SceneOriginHandle];
|
||||||
|
if (scene == DontDestroyOnLoadScene)
|
||||||
|
{
|
||||||
|
Debug.Log($"{networkObject.gameObject.name} migrating into DDOL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene);
|
||||||
|
}
|
||||||
|
else if (m_NetworkManager.LogLevel <= LogLevel.Normal)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarningServer($"[Client-{m_NetworkManager.LocalClientId}][{networkObject.gameObject.name}] Server - " +
|
||||||
|
$"client scene mismatch detected! Client-side has no scene loaded with handle ({networkObject.SceneOriginHandle})!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client Side:
|
/// Client Side:
|
||||||
/// Handles incoming Scene_Event messages for clients
|
/// Handles incoming Scene_Event messages for clients
|
||||||
@@ -1754,6 +1989,23 @@ namespace Unity.Netcode
|
|||||||
var sceneEventData = SceneEventDataStore[sceneEventId];
|
var sceneEventData = SceneEventDataStore[sceneEventId];
|
||||||
switch (sceneEventData.SceneEventType)
|
switch (sceneEventData.SceneEventType)
|
||||||
{
|
{
|
||||||
|
case SceneEventType.ActiveSceneChanged:
|
||||||
|
{
|
||||||
|
if (HashToBuildIndex.ContainsKey(sceneEventData.ActiveSceneHash))
|
||||||
|
{
|
||||||
|
var scene = SceneManager.GetSceneByBuildIndex(HashToBuildIndex[sceneEventData.ActiveSceneHash]);
|
||||||
|
if (scene.isLoaded)
|
||||||
|
{
|
||||||
|
SceneManager.SetActiveScene(scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SceneEventType.ObjectSceneChanged:
|
||||||
|
{
|
||||||
|
MigrateNetworkObjectsIntoScenes();
|
||||||
|
break;
|
||||||
|
}
|
||||||
case SceneEventType.Load:
|
case SceneEventType.Load:
|
||||||
{
|
{
|
||||||
OnClientSceneLoadingEvent(sceneEventId);
|
OnClientSceneLoadingEvent(sceneEventId);
|
||||||
@@ -1777,6 +2029,19 @@ namespace Unity.Netcode
|
|||||||
// Synchronize the NetworkObjects for this scene
|
// Synchronize the NetworkObjects for this scene
|
||||||
sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager);
|
sceneEventData.SynchronizeSceneNetworkObjects(m_NetworkManager);
|
||||||
|
|
||||||
|
// If needed, set the currently active scene
|
||||||
|
if (HashToBuildIndex.ContainsKey(sceneEventData.ActiveSceneHash))
|
||||||
|
{
|
||||||
|
var targetActiveScene = SceneManager.GetSceneByBuildIndex(HashToBuildIndex[sceneEventData.ActiveSceneHash]);
|
||||||
|
if (targetActiveScene.isLoaded && targetActiveScene.handle != SceneManager.GetActiveScene().handle)
|
||||||
|
{
|
||||||
|
SceneManager.SetActiveScene(targetActiveScene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If needed, migrate dynamically spawned NetworkObjects to the same scene as on the server side
|
||||||
|
SynchronizeNetworkObjectScene();
|
||||||
|
|
||||||
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
sceneEventData.SceneEventType = SceneEventType.SynchronizeComplete;
|
||||||
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
SendSceneEventData(sceneEventId, new ulong[] { NetworkManager.ServerClientId });
|
||||||
|
|
||||||
@@ -1793,6 +2058,19 @@ namespace Unity.Netcode
|
|||||||
ClientId = m_NetworkManager.LocalClientId, // Client sent this to the server
|
ClientId = m_NetworkManager.LocalClientId, // Client sent this to the server
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Process any SceneEventType.ObjectSceneChanged messages that
|
||||||
|
// were deferred while synchronizing and migrate the associated
|
||||||
|
// NetworkObjects to their newly assigned scenes.
|
||||||
|
sceneEventData.ProcessDeferredObjectSceneChangedEvents();
|
||||||
|
|
||||||
|
// Only if PostSynchronizationSceneUnloading is set and we are running in client synchronization
|
||||||
|
// mode additive do we unload any remaining scene that was not synchronized (otherwise any loaded
|
||||||
|
// scene not synchronized by the server will remain loaded)
|
||||||
|
if (PostSynchronizationSceneUnloading && ClientSynchronizationMode == LoadSceneMode.Additive)
|
||||||
|
{
|
||||||
|
SceneManagerHandler.UnloadUnassignedScenes(m_NetworkManager);
|
||||||
|
}
|
||||||
|
|
||||||
OnSynchronizeComplete?.Invoke(m_NetworkManager.LocalClientId);
|
OnSynchronizeComplete?.Invoke(m_NetworkManager.LocalClientId);
|
||||||
|
|
||||||
EndSceneEvent(sceneEventId);
|
EndSceneEvent(sceneEventId);
|
||||||
@@ -1907,9 +2185,12 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
OnSynchronizeComplete?.Invoke(clientId);
|
OnSynchronizeComplete?.Invoke(clientId);
|
||||||
|
|
||||||
// We now can call the client connected callback on the server at this time
|
// At this time the client is fully synchronized with all loaded scenes and
|
||||||
// This assures the client is fully synchronized with all loaded scenes and
|
// NetworkObjects and should be considered "fully connected". Send the
|
||||||
// NetworkObjects
|
// notification that the client is connected.
|
||||||
|
// TODO 2023: We should have a better name for this or have multiple states the
|
||||||
|
// client progresses through (the name and associated legacy behavior/expected state
|
||||||
|
// of the client was persisted since MLAPI)
|
||||||
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
m_NetworkManager.InvokeOnClientConnectedCallback(clientId);
|
||||||
|
|
||||||
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
// Check to see if the client needs to resynchronize and before sending the message make sure the client is still connected to avoid
|
||||||
@@ -1955,6 +2236,23 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (sceneEventData.IsSceneEventClientSide())
|
if (sceneEventData.IsSceneEventClientSide())
|
||||||
{
|
{
|
||||||
|
// If the client is being synchronized for the first time do some initialization
|
||||||
|
if (sceneEventData.SceneEventType == SceneEventType.Synchronize)
|
||||||
|
{
|
||||||
|
ScenePlacedObjects.Clear();
|
||||||
|
// Set the server's configured client synchronization mode on the client side
|
||||||
|
ClientSynchronizationMode = sceneEventData.ClientSynchronizationMode;
|
||||||
|
|
||||||
|
// Only if ClientSynchronizationMode is Additive and the client receives a synchronize scene event
|
||||||
|
if (ClientSynchronizationMode == LoadSceneMode.Additive)
|
||||||
|
{
|
||||||
|
// Check for scenes already loaded and create a table of scenes already loaded (SceneEntries) that will be
|
||||||
|
// used if the server is synchronizing the same scenes (i.e. if a matching scene is already loaded on the
|
||||||
|
// client side, then that scene will be used as opposed to loading another scene). This allows for clients
|
||||||
|
// to reconnect to a network session without having to unload all of the scenes and reload all of the scenes.
|
||||||
|
SceneManagerHandler.PopulateLoadedScenes(ref ScenesLoaded, m_NetworkManager);
|
||||||
|
}
|
||||||
|
}
|
||||||
HandleClientSceneEvent(sceneEventData.SceneEventId);
|
HandleClientSceneEvent(sceneEventData.SceneEventId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1964,7 +2262,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Debug.LogError($"{nameof(NetworkSceneManager.HandleSceneEvent)} was invoked but {nameof(NetworkManager)} reference was null!");
|
Debug.LogError($"{nameof(HandleSceneEvent)} was invoked but {nameof(NetworkManager)} reference was null!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1974,26 +2272,28 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void MoveObjectsToDontDestroyOnLoad()
|
internal void MoveObjectsToDontDestroyOnLoad()
|
||||||
{
|
{
|
||||||
// Move ALL NetworkObjects marked to persist scene transitions into the DDOL scene
|
// Create a local copy of the spawned objects list since the spawn manager will adjust the list as objects
|
||||||
var objectsToKeep = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
|
// are despawned.
|
||||||
foreach (var sobj in objectsToKeep)
|
var localSpawnedObjectsHashSet = new HashSet<NetworkObject>(m_NetworkManager.SpawnManager.SpawnedObjectsList);
|
||||||
|
foreach (var networkObject in localSpawnedObjectsHashSet)
|
||||||
{
|
{
|
||||||
if (sobj == null)
|
if (networkObject == null || (networkObject != null && networkObject.gameObject.scene == DontDestroyOnLoadScene))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sobj.DestroyWithScene || sobj.gameObject.scene == DontDestroyOnLoadScene)
|
// Only NetworkObjects marked to not be destroyed with the scene
|
||||||
|
if (!networkObject.DestroyWithScene)
|
||||||
{
|
{
|
||||||
// Only move dynamically spawned network objects with no parent as child objects will follow
|
// Only move dynamically spawned NetworkObjects with no parent as the children will follow
|
||||||
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
|
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
|
||||||
{
|
{
|
||||||
UnityEngine.Object.DontDestroyOnLoad(sobj.gameObject);
|
UnityEngine.Object.DontDestroyOnLoad(networkObject.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (m_NetworkManager.IsServer)
|
else if (m_NetworkManager.IsServer)
|
||||||
{
|
{
|
||||||
sobj.Despawn();
|
networkObject.Despawn();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2028,9 +2328,10 @@ namespace Unity.Netcode
|
|||||||
foreach (var networkObjectInstance in networkObjects)
|
foreach (var networkObjectInstance in networkObjects)
|
||||||
{
|
{
|
||||||
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
|
||||||
var sceneHandle = networkObjectInstance.GetSceneOriginHandle();
|
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
|
||||||
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
|
||||||
if (networkObjectInstance.IsSceneObject != false && networkObjectInstance.NetworkManager == m_NetworkManager && sceneHandle == sceneToFilterBy.handle)
|
if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == m_NetworkManager ||
|
||||||
|
networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle)
|
||||||
{
|
{
|
||||||
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
|
||||||
{
|
{
|
||||||
@@ -2057,26 +2358,139 @@ namespace Unity.Netcode
|
|||||||
/// <param name="scene">scene to move the NetworkObjects to</param>
|
/// <param name="scene">scene to move the NetworkObjects to</param>
|
||||||
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
|
internal void MoveObjectsFromDontDestroyOnLoadToScene(Scene scene)
|
||||||
{
|
{
|
||||||
// Move ALL NetworkObjects to the temp scene
|
foreach (var networkObject in m_NetworkManager.SpawnManager.SpawnedObjectsList)
|
||||||
var objectsToKeep = m_NetworkManager.SpawnManager.SpawnedObjectsList;
|
|
||||||
|
|
||||||
foreach (var sobj in objectsToKeep)
|
|
||||||
{
|
{
|
||||||
if (sobj == null)
|
if (networkObject == null)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// If it is in the DDOL then
|
// If it is in the DDOL then
|
||||||
if (sobj.gameObject.scene == DontDestroyOnLoadScene)
|
if (networkObject.gameObject.scene == DontDestroyOnLoadScene && !networkObject.DestroyWithScene)
|
||||||
{
|
{
|
||||||
// only move dynamically spawned network objects, with no parent as child objects will follow,
|
// only move dynamically spawned network objects, with no parent as child objects will follow,
|
||||||
// back into the currently active scene
|
// back into the currently active scene
|
||||||
if (sobj.gameObject.transform.parent == null && sobj.IsSceneObject != null && !sobj.IsSceneObject.Value)
|
if (networkObject.gameObject.transform.parent == null && networkObject.IsSceneObject != null && !networkObject.IsSceneObject.Value)
|
||||||
{
|
{
|
||||||
SceneManager.MoveGameObjectToScene(sobj.gameObject, scene);
|
SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Holds a list of scene handles (server-side relative) and NetworkObjects migrated into it
|
||||||
|
/// during the current frame.
|
||||||
|
/// </summary>
|
||||||
|
internal Dictionary<int, List<NetworkObject>> ObjectsMigratedIntoNewScene = new Dictionary<int, List<NetworkObject>>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Handles notifying clients when a NetworkObject has been migrated into a new scene
|
||||||
|
/// </summary>
|
||||||
|
internal void NotifyNetworkObjectSceneChanged(NetworkObject networkObject)
|
||||||
|
{
|
||||||
|
// Really, this should never happen but in case it does
|
||||||
|
if (!m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogErrorServer("[Please Report This Error][NotifyNetworkObjectSceneChanged] A client is trying to notify of an object's scene change!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore in-scene placed NetworkObjects
|
||||||
|
if (networkObject.IsSceneObject != false)
|
||||||
|
{
|
||||||
|
// Really, this should ever happen but in case it does
|
||||||
|
if (m_NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogErrorServer("[Please Report This Error][NotifyNetworkObjectSceneChanged] Trying to notify in-scene placed object scene change!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore if the scene is the currently active scene and the NetworkObject is auto synchronizing/migrating
|
||||||
|
// to the currently active scene.
|
||||||
|
if (networkObject.gameObject.scene == SceneManager.GetActiveScene() && networkObject.ActiveSceneSynchronization)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't notify if a scene event is in progress
|
||||||
|
// Note: This does not apply to SceneEventType.Synchronize since synchronization isn't a global connected client event.
|
||||||
|
foreach (var sceneEventEntry in SceneEventProgressTracking)
|
||||||
|
{
|
||||||
|
if (!sceneEventEntry.Value.HasTimedOut() && sceneEventEntry.Value.Status == SceneEventProgressStatus.Started)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, add the NetworkObject into the list of NetworkObjects who's scene has changed
|
||||||
|
if (!ObjectsMigratedIntoNewScene.ContainsKey(networkObject.gameObject.scene.handle))
|
||||||
|
{
|
||||||
|
ObjectsMigratedIntoNewScene.Add(networkObject.gameObject.scene.handle, new List<NetworkObject>());
|
||||||
|
}
|
||||||
|
ObjectsMigratedIntoNewScene[networkObject.gameObject.scene.handle].Add(networkObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked by clients when processing a <see cref="SceneEventType.ObjectSceneChanged"/> event
|
||||||
|
/// or invoked by <see cref="SceneEventData.ProcessDeferredObjectSceneChangedEvents"/> when a client finishes
|
||||||
|
/// synchronization.
|
||||||
|
/// </summary>
|
||||||
|
internal void MigrateNetworkObjectsIntoScenes()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
foreach (var sceneEntry in ObjectsMigratedIntoNewScene)
|
||||||
|
{
|
||||||
|
if (ServerSceneHandleToClientSceneHandle.ContainsKey(sceneEntry.Key))
|
||||||
|
{
|
||||||
|
var clientSceneHandle = ServerSceneHandleToClientSceneHandle[sceneEntry.Key];
|
||||||
|
if (ScenesLoaded.ContainsKey(ServerSceneHandleToClientSceneHandle[sceneEntry.Key]))
|
||||||
|
{
|
||||||
|
var scene = ScenesLoaded[clientSceneHandle];
|
||||||
|
foreach (var networkObject in sceneEntry.Value)
|
||||||
|
{
|
||||||
|
SceneManager.MoveGameObjectToScene(networkObject.gameObject, scene);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
NetworkLog.LogErrorServer($"{ex.Message}\n Stack Trace:\n {ex.StackTrace}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear out the list once complete
|
||||||
|
ObjectsMigratedIntoNewScene.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Should be invoked during PostLateUpdate just prior to the
|
||||||
|
/// MessagingSystem processes its outbound message queue.
|
||||||
|
/// </summary>
|
||||||
|
internal void CheckForAndSendNetworkObjectSceneChanged()
|
||||||
|
{
|
||||||
|
// Early exit if not the server or there is nothing pending
|
||||||
|
if (!m_NetworkManager.IsServer || ObjectsMigratedIntoNewScene.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var sceneEvent = BeginSceneEvent();
|
||||||
|
sceneEvent.SceneEventType = SceneEventType.ObjectSceneChanged;
|
||||||
|
SendSceneEventData(sceneEvent.SceneEventId, m_NetworkManager.ConnectedClientsIds.Where(c => c != NetworkManager.ServerClientId).ToArray());
|
||||||
|
EndSceneEvent(sceneEvent.SceneEventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Used to handle client-side scene migration messages received while
|
||||||
|
// a client is synchronizing
|
||||||
|
internal struct DeferredObjectsMovedEvent
|
||||||
|
{
|
||||||
|
internal Dictionary<int, List<ulong>> ObjectsMigratedTable;
|
||||||
|
}
|
||||||
|
internal List<DeferredObjectsMovedEvent> DeferredObjectsMovedEvents = new List<DeferredObjectsMovedEvent>();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
@@ -80,6 +80,16 @@ namespace Unity.Netcode
|
|||||||
/// <b>Event Notification:</b> Both server and client receive a local notification.
|
/// <b>Event Notification:</b> Both server and client receive a local notification.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
SynchronizeComplete,
|
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>
|
/// <summary>
|
||||||
@@ -94,7 +104,7 @@ namespace Unity.Netcode
|
|||||||
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
|
internal ForceNetworkSerializeByMemcpy<Guid> SceneEventProgressId;
|
||||||
internal uint SceneEventId;
|
internal uint SceneEventId;
|
||||||
|
|
||||||
|
internal uint ActiveSceneHash;
|
||||||
internal uint SceneHash;
|
internal uint SceneHash;
|
||||||
internal int SceneHandle;
|
internal int SceneHandle;
|
||||||
|
|
||||||
@@ -139,6 +149,8 @@ namespace Unity.Netcode
|
|||||||
internal Queue<uint> ScenesToSynchronize;
|
internal Queue<uint> ScenesToSynchronize;
|
||||||
internal Queue<uint> SceneHandlesToSynchronize;
|
internal Queue<uint> SceneHandlesToSynchronize;
|
||||||
|
|
||||||
|
internal LoadSceneMode ClientSynchronizationMode;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Server Side:
|
/// Server Side:
|
||||||
@@ -315,6 +327,8 @@ namespace Unity.Netcode
|
|||||||
case SceneEventType.ReSynchronize:
|
case SceneEventType.ReSynchronize:
|
||||||
case SceneEventType.LoadEventCompleted:
|
case SceneEventType.LoadEventCompleted:
|
||||||
case SceneEventType.UnloadEventCompleted:
|
case SceneEventType.UnloadEventCompleted:
|
||||||
|
case SceneEventType.ActiveSceneChanged:
|
||||||
|
case SceneEventType.ObjectSceneChanged:
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -384,6 +398,18 @@ namespace Unity.Netcode
|
|||||||
// Write the scene event type
|
// Write the scene event type
|
||||||
writer.WriteValueSafe(SceneEventType);
|
writer.WriteValueSafe(SceneEventType);
|
||||||
|
|
||||||
|
if (SceneEventType == SceneEventType.ActiveSceneChanged)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(ActiveSceneHash);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SceneEventType == SceneEventType.ObjectSceneChanged)
|
||||||
|
{
|
||||||
|
SerializeObjectsMovedIntoNewScene(writer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Write the scene loading mode
|
// Write the scene loading mode
|
||||||
writer.WriteValueSafe((byte)LoadSceneMode);
|
writer.WriteValueSafe((byte)LoadSceneMode);
|
||||||
|
|
||||||
@@ -392,6 +418,10 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
writer.WriteValueSafe(SceneEventProgressId);
|
writer.WriteValueSafe(SceneEventProgressId);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(ClientSynchronizationMode);
|
||||||
|
}
|
||||||
|
|
||||||
// Write the scene index and handle
|
// Write the scene index and handle
|
||||||
writer.WriteValueSafe(SceneHash);
|
writer.WriteValueSafe(SceneHash);
|
||||||
@@ -401,6 +431,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
case SceneEventType.Synchronize:
|
case SceneEventType.Synchronize:
|
||||||
{
|
{
|
||||||
|
writer.WriteValueSafe(ActiveSceneHash);
|
||||||
WriteSceneSynchronizationData(writer);
|
WriteSceneSynchronizationData(writer);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -445,7 +476,7 @@ namespace Unity.Netcode
|
|||||||
// Size Place Holder -- Start
|
// Size Place Holder -- Start
|
||||||
// !!NOTE!!: Since this is a placeholder to be set after we know how much we have written,
|
// !!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!
|
// for stream offset purposes this MUST not be a packed value!
|
||||||
writer.WriteValueSafe((int)0);
|
writer.WriteValueSafe(0);
|
||||||
int totalBytes = 0;
|
int totalBytes = 0;
|
||||||
|
|
||||||
// Write the number of NetworkObjects we are serializing
|
// Write the number of NetworkObjects we are serializing
|
||||||
@@ -458,7 +489,7 @@ namespace Unity.Netcode
|
|||||||
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
var sceneObject = m_NetworkObjectsSync[i].GetMessageSceneObject(TargetClientId);
|
||||||
sceneObject.Serialize(writer);
|
sceneObject.Serialize(writer);
|
||||||
var noStop = writer.Position;
|
var noStop = writer.Position;
|
||||||
totalBytes += (int)(noStop - noStart);
|
totalBytes += noStop - noStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the number of despawned in-scene placed NetworkObjects
|
// 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].GetSceneOriginHandle());
|
||||||
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
writer.WriteValueSafe(m_DespawnedInSceneObjectsSync[i].GlobalObjectIdHash);
|
||||||
var noStop = writer.Position;
|
var noStop = writer.Position;
|
||||||
totalBytes += (int)(noStop - noStart);
|
totalBytes += noStop - noStart;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Size Place Holder -- End
|
// Size Place Holder -- End
|
||||||
@@ -536,6 +567,26 @@ namespace Unity.Netcode
|
|||||||
internal void Deserialize(FastBufferReader reader)
|
internal void Deserialize(FastBufferReader reader)
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out SceneEventType);
|
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);
|
reader.ReadValueSafe(out byte loadSceneMode);
|
||||||
LoadSceneMode = (LoadSceneMode)loadSceneMode;
|
LoadSceneMode = (LoadSceneMode)loadSceneMode;
|
||||||
|
|
||||||
@@ -543,6 +594,10 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
reader.ReadValueSafe(out SceneEventProgressId);
|
reader.ReadValueSafe(out SceneEventProgressId);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out ClientSynchronizationMode);
|
||||||
|
}
|
||||||
|
|
||||||
reader.ReadValueSafe(out SceneHash);
|
reader.ReadValueSafe(out SceneHash);
|
||||||
reader.ReadValueSafe(out SceneHandle);
|
reader.ReadValueSafe(out SceneHandle);
|
||||||
@@ -551,6 +606,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
case SceneEventType.Synchronize:
|
case SceneEventType.Synchronize:
|
||||||
{
|
{
|
||||||
|
reader.ReadValueSafe(out ActiveSceneHash);
|
||||||
CopySceneSynchronizationData(reader);
|
CopySceneSynchronizationData(reader);
|
||||||
break;
|
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>
|
/// <summary>
|
||||||
/// Used to release the pooled network buffer
|
/// Used to release the pooled network buffer
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -83,7 +83,7 @@ namespace Unity.Netcode
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal bool HasTimedOut()
|
internal bool HasTimedOut()
|
||||||
{
|
{
|
||||||
return WhenSceneEventHasTimedOut <= Time.realtimeSinceStartup;
|
return WhenSceneEventHasTimedOut <= m_NetworkManager.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -164,7 +164,7 @@ namespace Unity.Netcode
|
|||||||
ClientsProcessingSceneEvent.Add(connectedClientId, false);
|
ClientsProcessingSceneEvent.Add(connectedClientId, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
WhenSceneEventHasTimedOut = Time.realtimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
|
WhenSceneEventHasTimedOut = networkManager.RealTimeProvider.RealTimeSinceStartup + networkManager.NetworkConfig.LoadSceneTimeOut;
|
||||||
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
m_TimeOutCoroutine = m_NetworkManager.StartCoroutine(TimeOutSceneEventProgress());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1299,8 +1299,10 @@ namespace Unity.Netcode
|
|||||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
{
|
{
|
||||||
ReadUnmanaged(out int length);
|
ReadUnmanaged(out int length);
|
||||||
value = new T();
|
value = new T
|
||||||
value.Length = length;
|
{
|
||||||
|
Length = length
|
||||||
|
};
|
||||||
ReadBytes(value.GetUnsafePtr(), length);
|
ReadBytes(value.GetUnsafePtr(), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1319,8 +1321,10 @@ namespace Unity.Netcode
|
|||||||
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
where T : unmanaged, INativeList<byte>, IUTF8Bytes
|
||||||
{
|
{
|
||||||
ReadUnmanagedSafe(out int length);
|
ReadUnmanagedSafe(out int length);
|
||||||
value = new T();
|
value = new T
|
||||||
value.Length = length;
|
{
|
||||||
|
Length = length
|
||||||
|
};
|
||||||
ReadBytesSafe(value.GetUnsafePtr(), length);
|
ReadBytesSafe(value.GetUnsafePtr(), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -54,13 +54,7 @@ namespace Unity.Netcode
|
|||||||
throw new ArgumentNullException(nameof(gameObject));
|
throw new ArgumentNullException(nameof(gameObject));
|
||||||
}
|
}
|
||||||
|
|
||||||
var networkObject = gameObject.GetComponent<NetworkObject>();
|
var networkObject = gameObject.GetComponent<NetworkObject>() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
|
||||||
|
|
||||||
if (networkObject == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkObject.IsSpawned == false)
|
if (networkObject.IsSpawned == false)
|
||||||
{
|
{
|
||||||
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
|
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
|
||||||
@@ -90,7 +84,7 @@ namespace Unity.Netcode
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
|
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);
|
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);
|
||||||
|
|
||||||
return networkObject;
|
return networkObject;
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal ulong GetNetworkObjectId()
|
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;
|
return ReleasedNetworkObjectIds.Dequeue().NetworkId;
|
||||||
}
|
}
|
||||||
@@ -405,6 +405,9 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (networkObject != null)
|
if (networkObject != null)
|
||||||
{
|
{
|
||||||
|
networkObject.DestroyWithScene = sceneObject.DestroyWithScene;
|
||||||
|
networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle;
|
||||||
|
|
||||||
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
|
// 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
|
// 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
|
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
|
||||||
@@ -610,6 +613,12 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
childObject.IsSceneObject = sceneObject;
|
childObject.IsSceneObject = sceneObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only dynamically spawned NetworkObjects are allowed
|
||||||
|
if (!sceneObject)
|
||||||
|
{
|
||||||
|
networkObject.SubscribeToActiveSceneForSynch();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
internal void SendSpawnCallForObject(ulong clientId, NetworkObject networkObject)
|
||||||
@@ -848,7 +857,7 @@ namespace Unity.Netcode
|
|||||||
ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId()
|
ReleasedNetworkObjectIds.Enqueue(new ReleasedNetworkId()
|
||||||
{
|
{
|
||||||
NetworkId = networkObject.NetworkObjectId,
|
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);
|
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);
|
clientId = GetNetcodeClientId((byte)hostId, (ushort)connectionId, false);
|
||||||
receiveTime = Time.realtimeSinceStartup;
|
receiveTime = NetworkManager.RealTimeProvider.RealTimeSinceStartup;
|
||||||
|
|
||||||
var networkError = (NetworkError)error;
|
var networkError = (NetworkError)error;
|
||||||
if (networkError == NetworkError.MessageToLong)
|
if (networkError == NetworkError.MessageToLong)
|
||||||
@@ -214,7 +214,7 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
{
|
{
|
||||||
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
|
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()
|
public override void DisconnectLocalClient()
|
||||||
@@ -226,7 +226,7 @@ namespace Unity.Netcode.Transports.UNET
|
|||||||
{
|
{
|
||||||
GetUNetConnectionDetails(clientId, out byte hostId, out ushort connectionId);
|
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()
|
public override void Shutdown()
|
||||||
|
|||||||
@@ -450,6 +450,8 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
internal NetworkManager NetworkManager;
|
internal NetworkManager NetworkManager;
|
||||||
|
|
||||||
|
private IRealTimeProvider m_RealTimeProvider;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SendQueue dictionary is used to batch events instead of sending them immediately.
|
/// SendQueue dictionary is used to batch events instead of sending them immediately.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -763,6 +765,10 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
// Send as many batched messages from the queue as possible.
|
// Send as many batched messages from the queue as possible.
|
||||||
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
|
private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue)
|
||||||
{
|
{
|
||||||
|
if (!m_Driver.IsCreated)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
new SendBatchedMessagesJob
|
new SendBatchedMessagesJob
|
||||||
{
|
{
|
||||||
Driver = m_Driver.ToConcurrent(),
|
Driver = m_Driver.ToConcurrent(),
|
||||||
@@ -784,7 +790,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
|
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
|
||||||
ParseClientId(connection),
|
ParseClientId(connection),
|
||||||
default,
|
default,
|
||||||
Time.realtimeSinceStartup);
|
m_RealTimeProvider.RealTimeSinceStartup);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
@@ -819,7 +825,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
break;
|
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,
|
InvokeOnTransportEvent(NetcodeNetworkEvent.Connect,
|
||||||
clientId,
|
clientId,
|
||||||
default,
|
default,
|
||||||
Time.realtimeSinceStartup);
|
m_RealTimeProvider.RealTimeSinceStartup);
|
||||||
|
|
||||||
m_State = State.Connected;
|
m_State = State.Connected;
|
||||||
return true;
|
return true;
|
||||||
@@ -863,7 +869,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
|
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
|
||||||
clientId,
|
clientId,
|
||||||
default,
|
default,
|
||||||
Time.realtimeSinceStartup);
|
m_RealTimeProvider.RealTimeSinceStartup);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -893,7 +899,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
|
Debug.LogError("Transport failure! Relay allocation needs to be recreated, and NetworkManager restarted. " +
|
||||||
"Use NetworkManager.OnTransportFailure to be notified of such events programmatically.");
|
"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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1116,7 +1122,7 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
|
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
|
||||||
m_ServerClientId,
|
m_ServerClientId,
|
||||||
default,
|
default,
|
||||||
Time.realtimeSinceStartup);
|
m_RealTimeProvider.RealTimeSinceStartup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1179,6 +1185,8 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
NetworkManager = networkManager;
|
NetworkManager = networkManager;
|
||||||
|
|
||||||
|
m_RealTimeProvider = NetworkManager ? NetworkManager.RealTimeProvider : new RealTimeProvider();
|
||||||
|
|
||||||
m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
|
m_NetworkSettings = new NetworkSettings(Allocator.Persistent);
|
||||||
|
|
||||||
// If the user sends a message of exactly m_MaxPayloadSize in length, we need to
|
// 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>
|
/// </summary>
|
||||||
/// <param name="clientId">The clientId this event is for</param>
|
/// <param name="clientId">The clientId this event is for</param>
|
||||||
/// <param name="payload">The incoming data payload</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>
|
/// <returns>Returns the event type</returns>
|
||||||
public override NetcodeNetworkEvent PollEvent(out ulong clientId, out ArraySegment<byte> payload, out float receiveTime)
|
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,
|
InvokeOnTransportEvent(NetcodeNetworkEvent.Disconnect,
|
||||||
clientId,
|
clientId,
|
||||||
default(ArraySegment<byte>),
|
default(ArraySegment<byte>),
|
||||||
Time.realtimeSinceStartup);
|
m_RealTimeProvider.RealTimeSinceStartup);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1424,6 +1432,10 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
private string m_ClientCaCertificate;
|
private string m_ClientCaCertificate;
|
||||||
|
|
||||||
/// <summary>Set the server parameters for encryption.</summary>
|
/// <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="serverCertificate">Public certificate for the server (PEM format).</param>
|
||||||
/// <param name="serverPrivateKey">Private key for the server (PEM format).</param>
|
/// <param name="serverPrivateKey">Private key for the server (PEM format).</param>
|
||||||
public void SetServerSecrets(string serverCertificate, string serverPrivateKey)
|
public void SetServerSecrets(string serverCertificate, string serverPrivateKey)
|
||||||
@@ -1434,9 +1446,15 @@ namespace Unity.Netcode.Transports.UTP
|
|||||||
|
|
||||||
/// <summary>Set the client parameters for encryption.</summary>
|
/// <summary>Set the client parameters for encryption.</summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
/// If the CA certificate is not provided, validation will be done against the OS/browser
|
/// 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.
|
/// 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.
|
/// 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>
|
/// </remarks>
|
||||||
/// <param name="serverCommonName">Common name of the server (typically hostname).</param>
|
/// <param name="serverCommonName">Common name of the server (typically hostname).</param>
|
||||||
/// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param>
|
/// <param name="caCertificate">CA certificate used to validate the server's authenticity.</param>
|
||||||
|
|||||||
@@ -12,7 +12,8 @@
|
|||||||
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
|
"Unity.Multiplayer.Tools.NetworkSolutionInterface",
|
||||||
"Unity.Networking.Transport",
|
"Unity.Networking.Transport",
|
||||||
"Unity.Collections",
|
"Unity.Collections",
|
||||||
"Unity.Burst"
|
"Unity.Burst",
|
||||||
|
"Unity.Mathematics"
|
||||||
],
|
],
|
||||||
"allowUnsafeCode": true,
|
"allowUnsafeCode": true,
|
||||||
"versionDefines": [
|
"versionDefines": [
|
||||||
|
|||||||
@@ -15,6 +15,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal class IntegrationTestSceneHandler : ISceneManagerHandler, IDisposable
|
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
|
// All IntegrationTestSceneHandler instances register their associated NetworkManager
|
||||||
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
|
internal static List<NetworkManager> NetworkManagers = new List<NetworkManager>();
|
||||||
|
|
||||||
@@ -96,7 +106,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// Processes scene loading jobs
|
/// Processes scene loading jobs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queuedSceneJob">job to process</param>
|
/// <param name="queuedSceneJob">job to process</param>
|
||||||
static internal IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
|
internal static IEnumerator ProcessLoadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||||
{
|
{
|
||||||
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||||
while (!itegrationTestSceneHandler.OnCanClientsLoad())
|
while (!itegrationTestSceneHandler.OnCanClientsLoad())
|
||||||
@@ -170,7 +180,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// Processes scene unloading jobs
|
/// Processes scene unloading jobs
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="queuedSceneJob">job to process</param>
|
/// <param name="queuedSceneJob">job to process</param>
|
||||||
static internal IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
|
internal static IEnumerator ProcessUnloadingSceneJob(QueuedSceneJob queuedSceneJob)
|
||||||
{
|
{
|
||||||
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
var itegrationTestSceneHandler = queuedSceneJob.IntegrationTestSceneHandler;
|
||||||
while (!itegrationTestSceneHandler.OnCanClientsUnload())
|
while (!itegrationTestSceneHandler.OnCanClientsUnload())
|
||||||
@@ -213,7 +223,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// Processes all jobs within the queue.
|
/// Processes all jobs within the queue.
|
||||||
/// When all jobs are finished, the coroutine stops.
|
/// When all jobs are finished, the coroutine stops.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
static internal IEnumerator JobQueueProcessor()
|
internal static IEnumerator JobQueueProcessor()
|
||||||
{
|
{
|
||||||
while (QueuedSceneJobs.Count != 0)
|
while (QueuedSceneJobs.Count != 0)
|
||||||
{
|
{
|
||||||
@@ -267,8 +277,8 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
if (m_ServerSceneBeingLoaded == scene.name)
|
if (m_ServerSceneBeingLoaded == scene.name)
|
||||||
{
|
{
|
||||||
ProcessInSceneObjects(scene, NetworkManager);
|
|
||||||
SceneManager.sceneLoaded -= Sever_SceneLoaded;
|
SceneManager.sceneLoaded -= Sever_SceneLoaded;
|
||||||
|
ProcessInSceneObjects(scene, NetworkManager);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,6 +340,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
if (networkManager.SceneManager.ScenesLoaded.ContainsKey(sceneLoaded.handle))
|
||||||
{
|
{
|
||||||
if (NetworkManager.LogLevel == LogLevel.Developer)
|
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.");
|
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);
|
NetworkManager.SceneManager.ScenesLoaded.Add(sceneLoaded.handle, sceneLoaded);
|
||||||
|
StartTrackingScene(sceneLoaded, true, NetworkManager);
|
||||||
return sceneLoaded;
|
return sceneLoaded;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -365,6 +381,521 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
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>
|
/// <summary>
|
||||||
/// Constructor now must take NetworkManager
|
/// Constructor now must take NetworkManager
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -410,7 +941,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QueuedSceneJobs.Clear();
|
QueuedSceneJobs.Clear();
|
||||||
|
if (CoroutineRunner != null && CoroutineRunner.gameObject != null)
|
||||||
|
{
|
||||||
Object.Destroy(CoroutineRunner.gameObject);
|
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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.RuntimeTests;
|
||||||
|
using Unity.Netcode.Transports.UTP;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using Unity.Netcode.RuntimeTests;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.TestHelpers.Runtime
|
namespace Unity.Netcode.TestHelpers.Runtime
|
||||||
@@ -22,6 +24,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// determine how clients will load scenes
|
/// determine how clients will load scenes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal static bool IsRunning { get; private set; }
|
internal static bool IsRunning { get; private set; }
|
||||||
|
|
||||||
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
|
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
|
||||||
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
|
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>());
|
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].ContainsKey(networkObject.NetworkObjectId))
|
||||||
{
|
{
|
||||||
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId] == null)
|
if (s_GlobalNetworkObjects[networkObject.NetworkManager.LocalClientId] == null)
|
||||||
@@ -143,6 +147,75 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
protected bool m_BypassConnectionTimeout { get; set; }
|
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>
|
/// <summary>
|
||||||
/// Used to display the various integration test
|
/// Used to display the various integration test
|
||||||
/// stages and can be used to log verbose information
|
/// stages and can be used to log verbose information
|
||||||
@@ -216,20 +289,54 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return null;
|
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]
|
[UnitySetUp]
|
||||||
public IEnumerator SetUp()
|
public IEnumerator SetUp()
|
||||||
{
|
{
|
||||||
VerboseDebug($"Entering {nameof(SetUp)}");
|
VerboseDebug($"Entering {nameof(SetUp)}");
|
||||||
|
|
||||||
NetcodeLogAssert = new NetcodeLogAssert();
|
NetcodeLogAssert = new NetcodeLogAssert();
|
||||||
|
if (m_SetupIsACoroutine)
|
||||||
|
{
|
||||||
yield return OnSetup();
|
yield return OnSetup();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnInlineSetup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_EnableTimeTravel)
|
||||||
|
{
|
||||||
|
MockTimeProvider.Reset();
|
||||||
|
ComponentFactory.Register<IRealTimeProvider>(manager => new MockTimeProvider());
|
||||||
|
}
|
||||||
|
|
||||||
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null ||
|
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests && m_ServerNetworkManager == null ||
|
||||||
m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
|
m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
|
||||||
{
|
{
|
||||||
CreateServerAndClients();
|
CreateServerAndClients();
|
||||||
|
|
||||||
|
if (m_EnableTimeTravel)
|
||||||
|
{
|
||||||
|
StartServerAndClientsWithTimeTravel();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
yield return StartServerAndClients();
|
yield return StartServerAndClients();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VerboseDebug($"Exiting {nameof(SetUp)}");
|
VerboseDebug($"Exiting {nameof(SetUp)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -294,6 +401,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
clientNetworkManagersList.Remove(networkManager);
|
clientNetworkManagersList.Remove(networkManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
|
m_ClientNetworkManagers = clientNetworkManagersList.ToArray();
|
||||||
m_NumberOfClients = clientNetworkManagersList.Count;
|
m_NumberOfClients = clientNetworkManagersList.Count;
|
||||||
}
|
}
|
||||||
@@ -304,7 +412,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void OnNewClientCreated(NetworkManager networkManager)
|
protected virtual void OnNewClientCreated(NetworkManager networkManager)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -322,7 +429,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
|
protected virtual void OnNewClientStartedAndConnected(NetworkManager networkManager)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -331,7 +437,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected IEnumerator CreateAndStartNewClient()
|
protected IEnumerator CreateAndStartNewClient()
|
||||||
{
|
{
|
||||||
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length);
|
var networkManager = NetcodeIntegrationTestHelpers.CreateNewClient(m_ClientNetworkManagers.Length, m_EnableTimeTravel);
|
||||||
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
networkManager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
|
||||||
|
|
||||||
// Notification that the new client (NetworkManager) has been created
|
// Notification that the new client (NetworkManager) has been created
|
||||||
@@ -356,13 +462,53 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
if (s_GlobalTimeoutHelper.TimedOut)
|
if (s_GlobalTimeoutHelper.TimedOut)
|
||||||
{
|
{
|
||||||
AddRemoveNetworkManager(networkManager, false);
|
AddRemoveNetworkManager(networkManager, false);
|
||||||
Object.Destroy(networkManager.gameObject);
|
Object.DestroyImmediate(networkManager.gameObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
|
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for the new client to be connected!");
|
||||||
ClientNetworkManagerPostStart(networkManager);
|
ClientNetworkManagerPostStart(networkManager);
|
||||||
VerboseDebug($"[{networkManager.name}] Created and connected!");
|
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>
|
/// <summary>
|
||||||
/// This will stop a client while in the middle of an integration test
|
/// This will stop a client while in the middle of an integration test
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -373,6 +519,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return WaitForConditionOrTimeOut(() => !networkManager.IsConnectedClient);
|
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>
|
/// <summary>
|
||||||
/// Creates the server and clients
|
/// Creates the server and clients
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -383,8 +539,13 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
|
|
||||||
CreatePlayerPrefab();
|
CreatePlayerPrefab();
|
||||||
|
|
||||||
|
if (m_EnableTimeTravel)
|
||||||
|
{
|
||||||
|
m_TargetFrameRate = -1;
|
||||||
|
}
|
||||||
|
|
||||||
// Create multiple NetworkManager instances
|
// 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");
|
Debug.LogError("Failed to create instances");
|
||||||
Assert.Fail("Failed to create instances");
|
Assert.Fail("Failed to create instances");
|
||||||
@@ -431,6 +592,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return null;
|
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>
|
/// <summary>
|
||||||
/// Invoked after the server and clients have started and verified
|
/// Invoked after the server and clients have started and verified
|
||||||
/// their connections with each other.
|
/// their connections with each other.
|
||||||
@@ -440,6 +609,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return null;
|
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)
|
private void ClientNetworkManagerPostStart(NetworkManager networkManager)
|
||||||
{
|
{
|
||||||
networkManager.name = $"NetworkManager - Client - {networkManager.LocalClientId}";
|
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>());
|
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
|
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(networkManager.LocalClientId))
|
||||||
{
|
{
|
||||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(networkManager.LocalClientId, playerNetworkObject);
|
||||||
@@ -495,6 +673,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
ClientNetworkManagerPostStart(networkManager);
|
ClientNetworkManagerPostStart(networkManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_UseHost)
|
if (m_UseHost)
|
||||||
{
|
{
|
||||||
#if UNITY_2023_1_OR_NEWER
|
#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>());
|
m_PlayerNetworkObjects.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
|
if (!m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].ContainsKey(m_ServerNetworkManager.LocalClientId))
|
||||||
{
|
{
|
||||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
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.Add(playerNetworkObject.NetworkManager.LocalClientId, new Dictionary<ulong, NetworkObject>());
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PlayerNetworkObjects[playerNetworkObject.NetworkManager.LocalClientId].Add(m_ServerNetworkManager.LocalClientId, playerNetworkObject);
|
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>
|
/// <summary>
|
||||||
/// Override this method to control when clients
|
/// Override this method to control when clients
|
||||||
/// can fake-load a scene.
|
/// can fake-load a scene.
|
||||||
@@ -660,12 +908,15 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
m_PlayerNetworkObjects.Clear();
|
m_PlayerNetworkObjects.Clear();
|
||||||
s_GlobalNetworkObjects.Clear();
|
s_GlobalNetworkObjects.Clear();
|
||||||
}
|
}
|
||||||
catch (Exception e) { throw e; }
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (m_PlayerPrefab != null)
|
if (m_PlayerPrefab != null)
|
||||||
{
|
{
|
||||||
Object.Destroy(m_PlayerPrefab);
|
Object.DestroyImmediate(m_PlayerPrefab);
|
||||||
m_PlayerPrefab = null;
|
m_PlayerPrefab = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -689,17 +940,34 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return null;
|
yield return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnInlineTearDown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[UnityTearDown]
|
[UnityTearDown]
|
||||||
public IEnumerator TearDown()
|
public IEnumerator TearDown()
|
||||||
{
|
{
|
||||||
|
IntegrationTestSceneHandler.SceneNameToSceneHandles.Clear();
|
||||||
VerboseDebug($"Entering {nameof(TearDown)}");
|
VerboseDebug($"Entering {nameof(TearDown)}");
|
||||||
|
if (m_TearDownIsACoroutine)
|
||||||
|
{
|
||||||
yield return OnTearDown();
|
yield return OnTearDown();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnInlineTearDown();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
|
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.PerTest)
|
||||||
{
|
{
|
||||||
ShutdownAndCleanUp();
|
ShutdownAndCleanUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_EnableTimeTravel)
|
||||||
|
{
|
||||||
|
ComponentFactory.Deregister<IRealTimeProvider>();
|
||||||
|
}
|
||||||
|
|
||||||
VerboseDebug($"Exiting {nameof(TearDown)}");
|
VerboseDebug($"Exiting {nameof(TearDown)}");
|
||||||
LogWaitForMessages();
|
LogWaitForMessages();
|
||||||
NetcodeLogAssert.Dispose();
|
NetcodeLogAssert.Dispose();
|
||||||
@@ -773,6 +1041,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CanDestroyNetworkObject(networkObject))
|
if (CanDestroyNetworkObject(networkObject))
|
||||||
{
|
{
|
||||||
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
|
networkObject.NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
@@ -831,10 +1100,49 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Otherwise wait for 1 tick interval
|
// Otherwise wait for 1 tick interval
|
||||||
yield return s_DefaultWaitForTick;
|
yield return s_DefaultWaitForTick;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop checking for a timeout
|
// Stop checking for a timeout
|
||||||
timeOutHelper.Stop();
|
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>
|
/// <summary>
|
||||||
/// This version accepts an IConditionalPredicate implementation to provide
|
/// This version accepts an IConditionalPredicate implementation to provide
|
||||||
/// more flexibility for checking complex conditional cases.
|
/// more flexibility for checking complex conditional cases.
|
||||||
@@ -857,6 +1165,29 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
conditionalPredicate.Finished(timeOutHelper.TimedOut);
|
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>
|
/// <summary>
|
||||||
/// Validates that all remote clients (i.e. non-server) detect they are connected
|
/// 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
|
/// to the server and that the server reflects the appropriate number of clients
|
||||||
@@ -872,6 +1203,22 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
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>
|
/// <summary>
|
||||||
/// Overloaded method that just passes in all clients to
|
/// Overloaded method that just passes in all clients to
|
||||||
/// <see cref="WaitForClientsConnectedOrTimeOut(NetworkManager[])"/>
|
/// <see cref="WaitForClientsConnectedOrTimeOut(NetworkManager[])"/>
|
||||||
@@ -881,6 +1228,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
yield return WaitForClientsConnectedOrTimeOut(m_ClientNetworkManagers);
|
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
|
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
|
// 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>();
|
messageHook.AssignMessageType<T>();
|
||||||
messageHookEntriesForSpawn.Add(messageHook);
|
messageHookEntriesForSpawn.Add(messageHook);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to determine if all clients received the CreateObjectMessage
|
// Used to determine if all clients received the CreateObjectMessage
|
||||||
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
||||||
yield return WaitForConditionOrTimeOut(hooks);
|
yield return WaitForConditionOrTimeOut(hooks);
|
||||||
Assert.False(s_GlobalTimeoutHelper.TimedOut);
|
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
|
// Build our message hook entries tables so we can determine if all clients received spawn or ownership messages
|
||||||
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
|
var messageHookEntriesForSpawn = new List<MessageHookEntry>();
|
||||||
foreach (var clientNetworkManager in wiatForReceivedBy)
|
foreach (var clientNetworkManager in waitForReceivedBy)
|
||||||
{
|
{
|
||||||
foreach (var message in messagesInOrder)
|
foreach (var message in messagesInOrder)
|
||||||
{
|
{
|
||||||
@@ -910,12 +1268,49 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
messageHookEntriesForSpawn.Add(messageHook);
|
messageHookEntriesForSpawn.Add(messageHook);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used to determine if all clients received the CreateObjectMessage
|
// Used to determine if all clients received the CreateObjectMessage
|
||||||
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
var hooks = new MessageHooksConditional(messageHookEntriesForSpawn);
|
||||||
yield return WaitForConditionOrTimeOut(hooks);
|
yield return WaitForConditionOrTimeOut(hooks);
|
||||||
Assert.False(s_GlobalTimeoutHelper.TimedOut);
|
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>
|
/// <summary>
|
||||||
/// Creates a basic NetworkObject test prefab, assigns it to a new
|
/// Creates a basic NetworkObject test prefab, assigns it to a new
|
||||||
/// NetworkPrefab entry, and then adds it to the server and client(s)
|
/// NetworkPrefab entry, and then adds it to the server and client(s)
|
||||||
@@ -1000,6 +1395,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
gameObjectsSpawned.Add(SpawnObject(prefabNetworkObject, owner, destroyWithScene));
|
gameObjectsSpawned.Add(SpawnObject(prefabNetworkObject, owner, destroyWithScene));
|
||||||
}
|
}
|
||||||
|
|
||||||
return gameObjectsSpawned;
|
return gameObjectsSpawned;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1008,7 +1404,6 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public NetcodeIntegrationTest()
|
public NetcodeIntegrationTest()
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1038,7 +1433,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected void AssertOnTimeout(string timeOutErrorMessage, TimeoutHelper assignedTimeoutHelper = null)
|
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);
|
Assert.False(timeoutHelper.TimedOut, timeOutErrorMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1054,6 +1449,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
|
VerboseDebug($"Unloading scene {scene.name}-{scene.handle}");
|
||||||
var asyncOperation = SceneManager.UnloadSceneAsync(scene);
|
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");
|
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;
|
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");
|
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);
|
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;
|
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
|
// Create gameObject
|
||||||
var go = new GameObject("NetworkManager - Server");
|
var go = new GameObject("NetworkManager - Server");
|
||||||
@@ -194,7 +203,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
// Create networkManager component
|
// Create networkManager component
|
||||||
var server = go.AddComponent<NetworkManager>();
|
var server = go.AddComponent<NetworkManager>();
|
||||||
NetworkManagerInstances.Insert(0, server);
|
NetworkManagerInstances.Insert(0, server);
|
||||||
|
if (mockTransport)
|
||||||
|
{
|
||||||
|
AddMockTransport(server);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
AddUnityTransport(server);
|
AddUnityTransport(server);
|
||||||
|
}
|
||||||
return server;
|
return server;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,20 +222,20 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// <param name="clients">The clients NetworkManagers</param>
|
/// <param name="clients">The clients NetworkManagers</param>
|
||||||
/// <param name="targetFrameRate">The targetFrameRate of the Unity engine to use while the multi instance helper is running. Will be reset on shutdown.</param>
|
/// <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>
|
/// <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>();
|
s_NetworkManagerInstances = new List<NetworkManager>();
|
||||||
server = null;
|
server = null;
|
||||||
if (serverFirst)
|
if (serverFirst)
|
||||||
{
|
{
|
||||||
server = CreateServer();
|
server = CreateServer(useMockTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
CreateNewClients(clientCount, out clients);
|
CreateNewClients(clientCount, out clients, useMockTransport);
|
||||||
|
|
||||||
if (!serverFirst)
|
if (!serverFirst)
|
||||||
{
|
{
|
||||||
server = CreateServer();
|
server = CreateServer(useMockTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
s_OriginalTargetFrameRate = Application.targetFrameRate;
|
s_OriginalTargetFrameRate = Application.targetFrameRate;
|
||||||
@@ -228,13 +244,20 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static NetworkManager CreateNewClient(int identifier)
|
internal static NetworkManager CreateNewClient(int identifier, bool mockTransport = false)
|
||||||
{
|
{
|
||||||
// Create gameObject
|
// Create gameObject
|
||||||
var go = new GameObject("NetworkManager - Client - " + identifier);
|
var go = new GameObject("NetworkManager - Client - " + identifier);
|
||||||
// Create networkManager component
|
// Create networkManager component
|
||||||
var networkManager = go.AddComponent<NetworkManager>();
|
var networkManager = go.AddComponent<NetworkManager>();
|
||||||
|
if (mockTransport)
|
||||||
|
{
|
||||||
|
AddMockTransport(networkManager);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
AddUnityTransport(networkManager);
|
AddUnityTransport(networkManager);
|
||||||
|
}
|
||||||
return networkManager;
|
return networkManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,13 +267,13 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="clientCount">The amount of clients</param>
|
/// <param name="clientCount">The amount of clients</param>
|
||||||
/// <param name="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];
|
clients = new NetworkManager[clientCount];
|
||||||
for (int i = 0; i < clientCount; i++)
|
for (int i = 0; i < clientCount; i++)
|
||||||
{
|
{
|
||||||
// Create networkManager component
|
// Create networkManager component
|
||||||
clients[i] = CreateNewClient(i);
|
clients[i] = CreateNewClient(i, useMockTransport);
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkManagerInstances.AddRange(clients);
|
NetworkManagerInstances.AddRange(clients);
|
||||||
@@ -314,7 +337,7 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
{
|
{
|
||||||
if (networkManager.gameObject != null)
|
if (networkManager.gameObject != null)
|
||||||
{
|
{
|
||||||
Object.Destroy(networkManager.gameObject);
|
Object.DestroyImmediate(networkManager.gameObject);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,6 +362,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool VerifySceneIsValidForClientsToUnload(Scene scene)
|
||||||
|
{
|
||||||
|
// Unless specifically set, we always return false
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This registers scene validation callback for the server to prevent it from telling connecting
|
/// 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
|
/// 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)
|
if (networkManager.IsServer && networkManager.SceneManager.VerifySceneBeforeLoading == null)
|
||||||
{
|
{
|
||||||
networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad;
|
networkManager.SceneManager.VerifySceneBeforeLoading = VerifySceneIsValidForClientsToLoad;
|
||||||
|
|
||||||
// If a unit/integration test does not handle this on their own, then Ignore the validation warning
|
// If a unit/integration test does not handle this on their own, then Ignore the validation warning
|
||||||
networkManager.SceneManager.DisableValidationWarnings(true);
|
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
|
// Register the test runner scene so it will be able to synchronize NetworkObjects without logging a
|
||||||
// warning about using the currently active scene
|
// warning about using the currently active scene
|
||||||
var scene = SceneManager.GetActiveScene();
|
var scene = SceneManager.GetActiveScene();
|
||||||
@@ -494,8 +534,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
Assert.IsNotNull(server, prefabCreateAssertError);
|
Assert.IsNotNull(server, prefabCreateAssertError);
|
||||||
Assert.IsFalse(server.IsListening, prefabCreateAssertError);
|
Assert.IsFalse(server.IsListening, prefabCreateAssertError);
|
||||||
|
|
||||||
var gameObject = new GameObject();
|
var gameObject = new GameObject
|
||||||
gameObject.name = baseName;
|
{
|
||||||
|
name = baseName
|
||||||
|
};
|
||||||
var networkObject = gameObject.AddComponent<NetworkObject>();
|
var networkObject = gameObject.AddComponent<NetworkObject>();
|
||||||
networkObject.NetworkManagerOwner = server;
|
networkObject.NetworkManagerOwner = server;
|
||||||
MakeNetworkObjectTestPrefab(networkObject);
|
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>
|
/// <summary>
|
||||||
/// Waits for a predicate condition to be met
|
/// Waits for a predicate condition to be met
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.Transports.UTP;
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.TestHelpers.Runtime
|
namespace Unity.Netcode.TestHelpers.Runtime
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,30 +8,67 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class TimeoutHelper
|
public class TimeoutHelper
|
||||||
{
|
{
|
||||||
private const float k_DefaultTimeOutWaitPeriod = 2.0f;
|
protected const float k_DefaultTimeOutWaitPeriod = 2.0f;
|
||||||
|
|
||||||
|
|
||||||
private float m_MaximumTimeBeforeTimeOut;
|
private float m_MaximumTimeBeforeTimeOut;
|
||||||
private float m_TimeOutPeriod;
|
private float m_TimeOutPeriod;
|
||||||
|
|
||||||
private bool m_IsStarted;
|
protected bool m_IsStarted { get; private set; }
|
||||||
public bool TimedOut { get; internal 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()
|
public void Start()
|
||||||
{
|
{
|
||||||
|
m_TimeStopped = 0.0f;
|
||||||
|
m_TimeStarted = Time.realtimeSinceStartup;
|
||||||
m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod;
|
m_MaximumTimeBeforeTimeOut = Time.realtimeSinceStartup + m_TimeOutPeriod;
|
||||||
m_IsStarted = true;
|
m_IsStarted = true;
|
||||||
TimedOut = false;
|
TimedOut = false;
|
||||||
|
OnStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnStop()
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
|
if (m_TimeStopped == 0.0f)
|
||||||
|
{
|
||||||
|
m_TimeStopped = Time.realtimeSinceStartup;
|
||||||
|
}
|
||||||
TimedOut = HasTimedOut();
|
TimedOut = HasTimedOut();
|
||||||
m_IsStarted = false;
|
m_IsStarted = false;
|
||||||
|
OnStop();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual bool OnHasTimedOut()
|
||||||
|
{
|
||||||
|
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasTimedOut()
|
public bool HasTimedOut()
|
||||||
{
|
{
|
||||||
return m_IsStarted ? m_MaximumTimeBeforeTimeOut < Time.realtimeSinceStartup : TimedOut;
|
return OnHasTimedOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TimeoutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod)
|
public TimeoutHelper(float timeOutPeriod = k_DefaultTimeOutWaitPeriod)
|
||||||
@@ -39,4 +76,70 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
m_TimeOutPeriod = timeOutPeriod;
|
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 networkContext = new NetworkContext();
|
||||||
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
||||||
var msg = new DisconnectReasonMessage();
|
var msg = new DisconnectReasonMessage
|
||||||
msg.Reason = string.Empty;
|
{
|
||||||
|
Reason = string.Empty
|
||||||
|
};
|
||||||
msg.Serialize(writer, msg.Version);
|
msg.Serialize(writer, msg.Version);
|
||||||
|
|
||||||
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
||||||
@@ -26,8 +28,10 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
var networkContext = new NetworkContext();
|
var networkContext = new NetworkContext();
|
||||||
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
||||||
var msg = new DisconnectReasonMessage();
|
var msg = new DisconnectReasonMessage
|
||||||
msg.Reason = "Foo";
|
{
|
||||||
|
Reason = "Foo"
|
||||||
|
};
|
||||||
msg.Serialize(writer, msg.Version);
|
msg.Serialize(writer, msg.Version);
|
||||||
|
|
||||||
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
||||||
@@ -42,8 +46,10 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
var networkContext = new NetworkContext();
|
var networkContext = new NetworkContext();
|
||||||
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
var writer = new FastBufferWriter(20, Allocator.Temp, 20);
|
||||||
var msg = new DisconnectReasonMessage();
|
var msg = new DisconnectReasonMessage
|
||||||
msg.Reason = "ThisStringIsWayLongerThanTwentyBytes";
|
{
|
||||||
|
Reason = "ThisStringIsWayLongerThanTwentyBytes"
|
||||||
|
};
|
||||||
msg.Serialize(writer, msg.Version);
|
msg.Serialize(writer, msg.Version);
|
||||||
|
|
||||||
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
var fbr = new FastBufferReader(writer, Allocator.Temp);
|
||||||
|
|||||||
@@ -222,10 +222,11 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
var listMessages = new List<MessagingSystem.MessageWithHandler>();
|
var listMessages = new List<MessagingSystem.MessageWithHandler>();
|
||||||
|
|
||||||
var messageWithHandler = new MessagingSystem.MessageWithHandler();
|
var messageWithHandler = new MessagingSystem.MessageWithHandler
|
||||||
|
{
|
||||||
messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage);
|
MessageType = typeof(zzzLateLexicographicNetworkMessage),
|
||||||
messageWithHandler.GetVersion = MessagingSystem.CreateMessageAndGetVersion<zzzLateLexicographicNetworkMessage>;
|
GetVersion = MessagingSystem.CreateMessageAndGetVersion<zzzLateLexicographicNetworkMessage>
|
||||||
|
};
|
||||||
listMessages.Add(messageWithHandler);
|
listMessages.Add(messageWithHandler);
|
||||||
|
|
||||||
messageWithHandler.MessageType = typeof(ConnectionRequestMessage);
|
messageWithHandler.MessageType = typeof(ConnectionRequestMessage);
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
var v1 = new VersionedTestMessage_v1();
|
var v1 = new VersionedTestMessage_v1();
|
||||||
v1.Deserialize(reader, ref context, receivedMessageVersion);
|
v1.Deserialize(reader, ref context, receivedMessageVersion);
|
||||||
A = v1.A;
|
A = v1.A;
|
||||||
D = (float)v1.D;
|
D = v1.D;
|
||||||
E = k_DefaultE;
|
E = k_DefaultE;
|
||||||
Upgraded = true;
|
Upgraded = true;
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
|
||||||
using Unity.Netcode.Editor;
|
using Unity.Netcode.Editor;
|
||||||
using Unity.Netcode.Transports.UTP;
|
using Unity.Netcode.Transports.UTP;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
@@ -130,9 +130,11 @@ namespace Unity.Netcode.EditorTests
|
|||||||
overridingTargetPrefab.GlobalObjectIdHash = 3;
|
overridingTargetPrefab.GlobalObjectIdHash = 3;
|
||||||
sourcePrefabToOverride.GlobalObjectIdHash = 4;
|
sourcePrefabToOverride.GlobalObjectIdHash = 4;
|
||||||
|
|
||||||
networkConfig.OldPrefabList = new List<NetworkPrefab>();
|
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 });
|
new NetworkPrefab { Prefab = regularPrefab.gameObject },
|
||||||
|
new NetworkPrefab { Prefab = overriddenPrefab.gameObject, Override = NetworkPrefabOverride.Prefab, OverridingTargetPrefab = overridingTargetPrefab.gameObject, SourcePrefabToOverride = sourcePrefabToOverride.gameObject, SourceHashToOverride = 123456 }
|
||||||
|
};
|
||||||
|
|
||||||
networkConfig.InitializePrefabs();
|
networkConfig.InitializePrefabs();
|
||||||
|
|
||||||
@@ -159,15 +161,20 @@ namespace Unity.Netcode.EditorTests
|
|||||||
// Setup
|
// Setup
|
||||||
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||||
networkManager.NetworkConfig = new NetworkConfig();
|
networkManager.NetworkConfig = new NetworkConfig
|
||||||
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
||||||
networkManager2.NetworkConfig = new NetworkConfig();
|
networkManager2.NetworkConfig = new NetworkConfig
|
||||||
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
var object3 = new GameObject("Object 3").AddComponent<NetworkObject>();
|
||||||
|
|
||||||
@@ -205,13 +212,17 @@ namespace Unity.Netcode.EditorTests
|
|||||||
// Setup
|
// Setup
|
||||||
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||||
networkManager.NetworkConfig = new NetworkConfig();
|
networkManager.NetworkConfig = new NetworkConfig
|
||||||
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
||||||
networkManager2.NetworkConfig = new NetworkConfig();
|
networkManager2.NetworkConfig = new NetworkConfig
|
||||||
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
@@ -251,13 +262,17 @@ namespace Unity.Netcode.EditorTests
|
|||||||
// Setup
|
// Setup
|
||||||
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
var networkManager = networkManagerObject.AddComponent<NetworkManager>();
|
||||||
networkManager.NetworkConfig = new NetworkConfig();
|
networkManager.NetworkConfig = new NetworkConfig
|
||||||
networkManager.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
var networkManagerObject2 = new GameObject(nameof(NestedNetworkObjectPrefabCheck));
|
||||||
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
var networkManager2 = networkManagerObject2.AddComponent<NetworkManager>();
|
||||||
networkManager2.NetworkConfig = new NetworkConfig();
|
networkManager2.NetworkConfig = new NetworkConfig
|
||||||
networkManager2.NetworkConfig.NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>();
|
{
|
||||||
|
NetworkTransport = networkManager.gameObject.AddComponent<UnityTransport>()
|
||||||
|
};
|
||||||
|
|
||||||
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
var object1 = new GameObject("Object 1").AddComponent<NetworkObject>();
|
||||||
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
var object2 = new GameObject("Object 2").AddComponent<NetworkObject>();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
|
||||||
using Unity.Netcode.Editor.Configuration;
|
using Unity.Netcode.Editor.Configuration;
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.EditorTests
|
namespace Unity.Netcode.EditorTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,8 +7,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public abstract class BaseFastBufferReaderWriterTest
|
public abstract class BaseFastBufferReaderWriterTest
|
||||||
{
|
{
|
||||||
|
|
||||||
#region Test Types
|
|
||||||
protected enum ByteEnum : byte
|
protected enum ByteEnum : byte
|
||||||
{
|
{
|
||||||
A,
|
A,
|
||||||
@@ -78,7 +76,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
WriteDirect,
|
WriteDirect,
|
||||||
WriteSafe
|
WriteSafe
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
protected abstract void RunTypeTest<T>(T valueToTest) where T : unmanaged;
|
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;
|
protected abstract void RunTypeArrayTestSafe<T>(T[] valueToTest) where T : unmanaged;
|
||||||
|
|
||||||
#region Helpers
|
|
||||||
protected TestStruct GetTestStruct()
|
protected TestStruct GetTestStruct()
|
||||||
{
|
{
|
||||||
var random = new Random();
|
var random = new Random();
|
||||||
@@ -98,7 +94,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
A = (byte)random.Next(),
|
A = (byte)random.Next(),
|
||||||
B = (short)random.Next(),
|
B = (short)random.Next(),
|
||||||
C = (ushort)random.Next(),
|
C = (ushort)random.Next(),
|
||||||
D = (int)random.Next(),
|
D = random.Next(),
|
||||||
E = (uint)random.Next(),
|
E = (uint)random.Next(),
|
||||||
F = ((long)random.Next() << 32) + random.Next(),
|
F = ((long)random.Next() << 32) + random.Next(),
|
||||||
G = ((ulong)random.Next() << 32) + (ulong)random.Next(),
|
G = ((ulong)random.Next() << 32) + (ulong)random.Next(),
|
||||||
@@ -111,9 +107,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
return testStruct;
|
return testStruct;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
private void RunTestWithWriteType<T>(T val, WriteType wt, FastBufferWriter.ForPrimitives _ = default) where T : unmanaged
|
private void RunTestWithWriteType<T>(T val, WriteType wt, FastBufferWriter.ForPrimitives _ = default) where T : unmanaged
|
||||||
{
|
{
|
||||||
switch (wt)
|
switch (wt)
|
||||||
@@ -149,7 +142,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
else if (testType == typeof(int))
|
else if (testType == typeof(int))
|
||||||
{
|
{
|
||||||
RunTestWithWriteType((int)random.Next(), writeType);
|
RunTestWithWriteType(random.Next(), writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(uint))
|
else if (testType == typeof(uint))
|
||||||
{
|
{
|
||||||
@@ -354,10 +347,10 @@ namespace Unity.Netcode.EditorTests
|
|||||||
else if (testType == typeof(long))
|
else if (testType == typeof(long))
|
||||||
{
|
{
|
||||||
RunTypeTestLocal(new[]{
|
RunTypeTestLocal(new[]{
|
||||||
((long)random.Next() << 32) + (long)random.Next(),
|
((long)random.Next() << 32) + random.Next(),
|
||||||
((long)random.Next() << 32) + (long)random.Next(),
|
((long)random.Next() << 32) + random.Next(),
|
||||||
((long)random.Next() << 32) + (long)random.Next(),
|
((long)random.Next() << 32) + random.Next(),
|
||||||
((long)random.Next() << 32) + (long)random.Next()
|
((long)random.Next() << 32) + random.Next()
|
||||||
}, writeType);
|
}, writeType);
|
||||||
}
|
}
|
||||||
else if (testType == typeof(ulong))
|
else if (testType == typeof(ulong))
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class BytePackerTests
|
public class BytePackerTests
|
||||||
{
|
{
|
||||||
#region Test Types
|
|
||||||
|
|
||||||
private enum ByteEnum : byte
|
private enum ByteEnum : byte
|
||||||
{
|
{
|
||||||
A,
|
A,
|
||||||
@@ -74,8 +72,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
WriteAsObject
|
WriteAsObject
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private unsafe void VerifyBytewiseEquality<T>(T value, T otherValue) where T : unmanaged
|
private unsafe void VerifyBytewiseEquality<T>(T value, T otherValue) where T : unmanaged
|
||||||
{
|
{
|
||||||
byte* asBytePointer = (byte*)&value;
|
byte* asBytePointer = (byte*)&value;
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class FastBufferReaderTests : BaseFastBufferReaderWriterTest
|
public class FastBufferReaderTests : BaseFastBufferReaderWriterTest
|
||||||
{
|
{
|
||||||
#region Common Checks
|
|
||||||
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
||||||
{
|
{
|
||||||
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
||||||
@@ -230,9 +229,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
method.Invoke(reader, args);
|
method.Invoke(reader, args);
|
||||||
value = (T[])args[0];
|
value = (T[])args[0];
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Generic Checks
|
|
||||||
protected override unsafe void RunTypeTest<T>(T valueToTest)
|
protected override unsafe void RunTypeTest<T>(T valueToTest)
|
||||||
{
|
{
|
||||||
var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
|
var writeSize = FastBufferWriter.GetWriteSize(valueToTest);
|
||||||
@@ -343,9 +340,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Tests
|
|
||||||
[Test]
|
[Test]
|
||||||
public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_ValueMatchesWhatWasWritten(
|
public void GivenFastBufferWriterContainingValue_WhenReadingUnmanagedType_ValueMatchesWhatWasWritten(
|
||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[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);
|
Assert.AreEqual(reader.Handle->AllowedReadMark, 25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
{
|
{
|
||||||
public class FastBufferWriterTests : BaseFastBufferReaderWriterTest
|
public class FastBufferWriterTests : BaseFastBufferReaderWriterTest
|
||||||
{
|
{
|
||||||
|
|
||||||
#region Common Checks
|
|
||||||
|
|
||||||
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
private void WriteCheckBytes(FastBufferWriter writer, int writeSize, string failMessage = "")
|
||||||
{
|
{
|
||||||
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
Assert.IsTrue(writer.TryBeginWrite(2), "Writer denied write permission");
|
||||||
@@ -66,10 +63,6 @@ namespace Unity.Netcode.EditorTests
|
|||||||
VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
|
VerifyTypedEquality(valueToTest, writer.GetUnsafePtr());
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Generic Checks
|
|
||||||
|
|
||||||
private void RunMethod<T>(string methodName, FastBufferWriter writer, in T value) where T : unmanaged
|
private void RunMethod<T>(string methodName, FastBufferWriter writer, in T value) where T : unmanaged
|
||||||
{
|
{
|
||||||
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
|
MethodInfo method = typeof(FastBufferWriter).GetMethod(methodName, new[] { typeof(T).MakeByRefType() });
|
||||||
@@ -248,10 +241,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
VerifyCheckBytes(underlyingArray, writeSize);
|
VerifyCheckBytes(underlyingArray, writeSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
|
|
||||||
|
|
||||||
#region Tests
|
|
||||||
[Test, Description("Tests")]
|
[Test, Description("Tests")]
|
||||||
public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly(
|
public void WhenWritingUnmanagedType_ValueIsWrittenCorrectly(
|
||||||
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
[Values(typeof(byte), typeof(sbyte), typeof(short), typeof(ushort), typeof(int), typeof(uint),
|
||||||
@@ -1317,6 +1307,5 @@ namespace Unity.Netcode.EditorTests
|
|||||||
Assert.AreEqual(writer.Handle->AllowedWriteMark, 25);
|
Assert.AreEqual(writer.Handle->AllowedWriteMark, 25);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var writer = new DataStreamWriter(data);
|
var writer = new DataStreamWriter(data);
|
||||||
writer.WriteInt(1);
|
writer.WriteInt(1);
|
||||||
writer.WriteByte((byte)42);
|
writer.WriteByte(42);
|
||||||
|
|
||||||
var reader = new DataStreamReader(data);
|
var reader = new DataStreamReader(data);
|
||||||
var q = new BatchedReceiveQueue(reader);
|
var q = new BatchedReceiveQueue(reader);
|
||||||
@@ -52,9 +52,9 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var writer = new DataStreamWriter(data);
|
var writer = new DataStreamWriter(data);
|
||||||
writer.WriteInt(1);
|
writer.WriteInt(1);
|
||||||
writer.WriteByte((byte)42);
|
writer.WriteByte(42);
|
||||||
writer.WriteInt(1);
|
writer.WriteInt(1);
|
||||||
writer.WriteByte((byte)142);
|
writer.WriteByte(142);
|
||||||
|
|
||||||
var reader = new DataStreamReader(data);
|
var reader = new DataStreamReader(data);
|
||||||
var q = new BatchedReceiveQueue(reader);
|
var q = new BatchedReceiveQueue(reader);
|
||||||
@@ -132,7 +132,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var writer = new DataStreamWriter(data);
|
var writer = new DataStreamWriter(data);
|
||||||
writer.WriteInt(1);
|
writer.WriteInt(1);
|
||||||
writer.WriteByte((byte)42);
|
writer.WriteByte(42);
|
||||||
|
|
||||||
var reader = new DataStreamReader(data);
|
var reader = new DataStreamReader(data);
|
||||||
var q = new BatchedReceiveQueue(reader);
|
var q = new BatchedReceiveQueue(reader);
|
||||||
@@ -168,7 +168,7 @@ namespace Unity.Netcode.EditorTests
|
|||||||
|
|
||||||
var writer = new DataStreamWriter(data);
|
var writer = new DataStreamWriter(data);
|
||||||
writer.WriteInt(1);
|
writer.WriteInt(1);
|
||||||
writer.WriteByte((byte)42);
|
writer.WriteByte(42);
|
||||||
|
|
||||||
var reader = new DataStreamReader(data);
|
var reader = new DataStreamReader(data);
|
||||||
var q = new BatchedReceiveQueue(reader);
|
var q = new BatchedReceiveQueue(reader);
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
"Unity.Multiplayer.NetStats",
|
"Unity.Multiplayer.NetStats",
|
||||||
"Unity.Multiplayer.Tools.MetricTypes",
|
"Unity.Multiplayer.Tools.MetricTypes",
|
||||||
"Unity.Multiplayer.Tools.NetStats",
|
"Unity.Multiplayer.Tools.NetStats",
|
||||||
"Unity.Networking.Transport"
|
"Unity.Networking.Transport",
|
||||||
|
"Unity.Mathematics"
|
||||||
],
|
],
|
||||||
"optionalUnityReferences": [
|
"optionalUnityReferences": [
|
||||||
"TestAssemblies"
|
"TestAssemblies"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using Unity.Netcode.Transports.UTP;
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using UnityEngine;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
@@ -255,14 +255,14 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
// NetworkVariable Value Type Constructor Test Coverage
|
// NetworkVariable Value Type Constructor Test Coverage
|
||||||
m_NetworkVariableBool = new NetworkVariable<bool>(true);
|
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_NetworkVariableColor = new NetworkVariable<Color>(new Color(1, 1, 1, 1));
|
||||||
m_NetworkVariableColor32 = new NetworkVariable<Color32>(new Color32(1, 1, 1, 1));
|
m_NetworkVariableColor32 = new NetworkVariable<Color32>(new Color32(1, 1, 1, 1));
|
||||||
m_NetworkVariableDouble = new NetworkVariable<double>(1.0);
|
m_NetworkVariableDouble = new NetworkVariable<double>(1.0);
|
||||||
m_NetworkVariableFloat = new NetworkVariable<float>(1.0f);
|
m_NetworkVariableFloat = new NetworkVariable<float>(1.0f);
|
||||||
m_NetworkVariableInt = new NetworkVariable<int>(1);
|
m_NetworkVariableInt = new NetworkVariable<int>(1);
|
||||||
m_NetworkVariableLong = new NetworkVariable<long>(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_NetworkVariableQuaternion = new NetworkVariable<Quaternion>(Quaternion.identity);
|
||||||
m_NetworkVariableShort = new NetworkVariable<short>(256);
|
m_NetworkVariableShort = new NetworkVariable<short>(256);
|
||||||
m_NetworkVariableVector4 = new NetworkVariable<Vector4>(new Vector4(1, 1, 1, 1));
|
m_NetworkVariableVector4 = new NetworkVariable<Vector4>(new Vector4(1, 1, 1, 1));
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Text;
|
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine;
|
using System.Text;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
@@ -66,9 +66,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void VerifyUniqueNetworkConfigPerRequest()
|
public void VerifyUniqueNetworkConfigPerRequest()
|
||||||
{
|
{
|
||||||
var networkConfig = new NetworkConfig();
|
var networkConfig = new NetworkConfig
|
||||||
networkConfig.EnableSceneManagement = true;
|
{
|
||||||
networkConfig.TickRate = 30;
|
EnableSceneManagement = true,
|
||||||
|
TickRate = 30
|
||||||
|
};
|
||||||
var currentHash = networkConfig.GetConfig();
|
var currentHash = networkConfig.GetConfig();
|
||||||
networkConfig.EnableSceneManagement = false;
|
networkConfig.EnableSceneManagement = false;
|
||||||
networkConfig.TickRate = 60;
|
networkConfig.TickRate = 60;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
@@ -194,7 +193,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
private int m_NumberOfClientsToLateJoin = 2;
|
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();
|
DeferredMessageTestRpcAndNetworkVariableComponent.ClientInstances.Clear();
|
||||||
DeferredMessageTestRpcComponent.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
|
// Replace the IDeferredMessageManager component with our test one in the component factory
|
||||||
ComponentFactory.Register<IDeferredMessageManager>(networkManager => new TestDeferredMessageManager(networkManager));
|
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)
|
// Revert the IDeferredMessageManager component to its default (DeferredMessageManager)
|
||||||
ComponentFactory.Deregister<IDeferredMessageManager>();
|
ComponentFactory.Deregister<IDeferredMessageManager>();
|
||||||
m_ClientSpawnCatchers.Clear();
|
m_ClientSpawnCatchers.Clear();
|
||||||
yield return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnServerAndClientsCreated()
|
protected override void OnServerAndClientsCreated()
|
||||||
@@ -255,12 +256,12 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
base.OnNewClientCreated(networkManager);
|
base.OnNewClientCreated(networkManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)
|
private void SpawnClients(bool clearTestDeferredMessageManagerCallFlags = true)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < m_NumberOfClientsToLateJoin; i++)
|
for (int i = 0; i < m_NumberOfClientsToLateJoin; i++)
|
||||||
{
|
{
|
||||||
// Create and join client
|
// Create and join client
|
||||||
yield return CreateAndStartNewClient();
|
CreateAndStartNewClientWithTimeTravel();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clearTestDeferredMessageManagerCallFlags)
|
if (clearTestDeferredMessageManagerCallFlags)
|
||||||
@@ -308,16 +309,15 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
m_ClientSpawnCatchers.Clear();
|
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.
|
// Clear out these values from whatever might have set them during the initial startup.
|
||||||
ClearTestDeferredMessageManagerCallFlags();
|
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)
|
foreach (var catcher in m_ClientSpawnCatchers)
|
||||||
{
|
{
|
||||||
@@ -328,7 +328,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearTestDeferredMessageManagerCallFlags()
|
private void ClearTestDeferredMessageManagerCallFlags()
|
||||||
@@ -348,28 +348,28 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnAddPrefab));
|
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 TFirstMessage : INetworkMessage
|
||||||
where TSecondMessage : INetworkMessage
|
where TSecondMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
yield return WaitForMessagesReceived(new List<Type>
|
WaitForMessagesReceivedWithTimeTravel(new List<Type>
|
||||||
{
|
{
|
||||||
typeof(TFirstMessage),
|
typeof(TFirstMessage),
|
||||||
typeof(TSecondMessage)
|
typeof(TSecondMessage)
|
||||||
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage>()
|
private void WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage>()
|
||||||
where TFirstMessage : INetworkMessage
|
where TFirstMessage : INetworkMessage
|
||||||
where TSecondMessage : INetworkMessage
|
where TSecondMessage : INetworkMessage
|
||||||
where TThirdMessage : INetworkMessage
|
where TThirdMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
yield return WaitForMessagesReceived(new List<Type>
|
WaitForMessagesReceivedWithTimeTravel(new List<Type>
|
||||||
{
|
{
|
||||||
typeof(TFirstMessage),
|
typeof(TFirstMessage),
|
||||||
typeof(TSecondMessage),
|
typeof(TSecondMessage),
|
||||||
@@ -377,13 +377,13 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerator WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage, TFourthMessage>()
|
private void WaitForAllClientsToReceive<TFirstMessage, TSecondMessage, TThirdMessage, TFourthMessage>()
|
||||||
where TFirstMessage : INetworkMessage
|
where TFirstMessage : INetworkMessage
|
||||||
where TSecondMessage : INetworkMessage
|
where TSecondMessage : INetworkMessage
|
||||||
where TThirdMessage : INetworkMessage
|
where TThirdMessage : INetworkMessage
|
||||||
where TFourthMessage : INetworkMessage
|
where TFourthMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
yield return WaitForMessagesReceived(new List<Type>
|
WaitForMessagesReceivedWithTimeTravel(new List<Type>
|
||||||
{
|
{
|
||||||
typeof(TFirstMessage),
|
typeof(TFirstMessage),
|
||||||
typeof(TSecondMessage),
|
typeof(TSecondMessage),
|
||||||
@@ -392,19 +392,19 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred()
|
public void WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred()
|
||||||
{
|
{
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
serverObject.GetComponent<DeferredMessageTestRpcComponent>().SendTestClientRpc();
|
serverObject.GetComponent<DeferredMessageTestRpcComponent>().SendTestClientRpc();
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<ClientRpcMessage>();
|
WaitForAllClientsToReceive<ClientRpcMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -415,19 +415,19 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred()
|
public void WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred()
|
||||||
{
|
{
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().Despawn(false);
|
serverObject.GetComponent<NetworkObject>().Despawn(false);
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<DestroyObjectMessage>();
|
WaitForAllClientsToReceive<DestroyObjectMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -438,18 +438,18 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred()
|
public void WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred()
|
||||||
{
|
{
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -459,22 +459,22 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred()
|
public void WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred()
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
|
|
||||||
|
|
||||||
var serverObject = Object.Instantiate(m_NetworkVariablePrefab);
|
var serverObject = Object.Instantiate(m_NetworkVariablePrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
serverObject.GetComponent<DeferredMessageTestNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
serverObject.GetComponent<DeferredMessageTestNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<NetworkVariableDeltaMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
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.")]
|
//[Ignore("Disabling this temporarily until it is migrated into new integration test.")]
|
||||||
public IEnumerator WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred()
|
public void WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred()
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<CreateObjectMessage>();
|
WaitForAllClientsToReceive<CreateObjectMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -514,10 +514,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenAnRpcIsDeferred_ItIsProcessedOnSpawn()
|
public void WhenAnRpcIsDeferred_ItIsProcessedOnSpawn()
|
||||||
{
|
{
|
||||||
yield return WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred();
|
WhenAnRpcArrivesBeforeASpawnArrives_ItIsDeferred();
|
||||||
ReleaseSpawns();
|
ReleaseSpawns();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -532,10 +532,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenADespawnIsDeferred_ItIsProcessedOnSpawn()
|
public void WhenADespawnIsDeferred_ItIsProcessedOnSpawn()
|
||||||
{
|
{
|
||||||
yield return WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred();
|
WhenADespawnArrivesBeforeASpawnArrives_ItIsDeferred();
|
||||||
ReleaseSpawns();
|
ReleaseSpawns();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -551,10 +551,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenAChangeOwnershipMessageIsDeferred_ItIsProcessedOnSpawn()
|
public void WhenAChangeOwnershipMessageIsDeferred_ItIsProcessedOnSpawn()
|
||||||
{
|
{
|
||||||
yield return WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred();
|
WhenAChangeOwnershipMessageArrivesBeforeASpawnArrives_ItIsDeferred();
|
||||||
ReleaseSpawns();
|
ReleaseSpawns();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -568,10 +568,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpawn()
|
public void WhenANetworkVariableDeltaMessageIsDeferred_ItIsProcessedOnSpawn()
|
||||||
{
|
{
|
||||||
yield return WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred();
|
WhenANetworkVariableDeltaMessageArrivesBeforeASpawnArrives_ItIsDeferred();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -592,7 +592,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
|
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -605,12 +605,12 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab()
|
public void WhenASpawnMessageIsDeferred_ItIsProcessedOnAddPrefab()
|
||||||
{
|
{
|
||||||
// This will prevent spawned clients from adding prefabs
|
// This will prevent spawned clients from adding prefabs
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred();
|
WhenASpawnMessageArrivesBeforeThePrefabIsAvailable_ItIsDeferred();
|
||||||
|
|
||||||
// Now add the prefabs
|
// Now add the prefabs
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -630,7 +630,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
|
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
|
||||||
|
|
||||||
// Validate this test
|
// Validate this test
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -644,28 +644,26 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool LogAllMessages => true;
|
[Test]
|
||||||
|
public void WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn()
|
||||||
[UnityTest]
|
|
||||||
public IEnumerator WhenMultipleSpawnTriggeredMessagesAreDeferred_TheyAreAllProcessedOnSpawn()
|
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
|
|
||||||
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().SendTestClientRpc();
|
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().SendTestClientRpc();
|
||||||
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<ClientRpcMessage, NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<ClientRpcMessage, NetworkVariableDeltaMessage>();
|
||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -694,8 +692,8 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
|
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
|
||||||
yield return new WaitForSeconds(0.1f);
|
TimeTravel(0.1, 1);
|
||||||
|
|
||||||
// Validate the spawned objects
|
// Validate the spawned objects
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -711,11 +709,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllProcessedOnAddNetworkPrefab()
|
public void WhenMultipleAddPrefabTriggeredMessagesAreDeferred_TheyAreAllProcessedOnAddNetworkPrefab()
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
@@ -724,7 +722,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject2.GetComponent<NetworkObject>().Spawn();
|
serverObject2.GetComponent<NetworkObject>().Spawn();
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<CreateObjectMessage, CreateObjectMessage>();
|
WaitForAllClientsToReceive<CreateObjectMessage, CreateObjectMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -751,7 +749,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
|
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
|
||||||
|
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -789,11 +787,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingThePrefabCausesThemToBeProcessed()
|
public void WhenSpawnTriggeredMessagesAreDeferredBeforeThePrefabIsAdded_AddingThePrefabCausesThemToBeProcessed()
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
|
|
||||||
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
@@ -803,11 +801,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
||||||
// TODO: Remove this if we figure out how to work around the NetworkVariableDeltaMessage.Serialized issue at line 59
|
// 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
|
// 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);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
||||||
|
|
||||||
// Validate messages are deferred and pending
|
// Validate messages are deferred and pending
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -837,9 +835,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsSpawned);
|
WaitForConditionOrTimeOutWithTimeTravel(HaveAllClientsSpawned);
|
||||||
|
|
||||||
yield return new WaitForSeconds(0.1f);
|
TimeTravel(0.1, 1);
|
||||||
|
|
||||||
// Validate the test
|
// Validate the test
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -856,11 +854,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout)
|
public void WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout)
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -869,7 +867,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
var start = 0f;
|
var start = 0f;
|
||||||
|
|
||||||
@@ -879,7 +877,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
{
|
{
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
start = Time.realtimeSinceStartup;
|
start = client.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -888,7 +886,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
|
|
||||||
foreach (var unused in m_ClientNetworkManagers)
|
foreach (var unused in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -901,8 +899,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
||||||
{
|
{
|
||||||
++purgeCount;
|
++purgeCount;
|
||||||
var elapsed = Time.realtimeSinceStartup - start;
|
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
|
||||||
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
|
Debug.Log(client.RealTimeProvider.GetType().FullName);
|
||||||
|
Assert.GreaterOrEqual(elapsed, timeout);
|
||||||
Assert.AreEqual(1, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(1, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
||||||
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
|
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
|
||||||
@@ -912,8 +911,20 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
manager.OnBeforePurge = beforePurge;
|
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()
|
bool HaveAllClientsPurged()
|
||||||
{
|
{
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -927,15 +938,18 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsPurged);
|
Assert.IsFalse(HaveAnyClientsPurged());
|
||||||
AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!");
|
|
||||||
|
TimeTravel(0.02, 1);
|
||||||
|
|
||||||
|
Assert.IsTrue(HaveAllClientsPurged());
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
|
public void WhenMultipleMessagesForTheSameObjectAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -945,7 +959,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
var serverObject = Object.Instantiate(m_RpcAndNetworkVariablePrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
var start = 0f;
|
var start = 0f;
|
||||||
|
|
||||||
@@ -955,7 +969,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
{
|
{
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
start = Time.realtimeSinceStartup;
|
start = client.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -966,7 +980,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
serverObject.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
yield return WaitForMessagesReceived(
|
WaitForMessagesReceivedWithTimeTravel(
|
||||||
new List<Type> {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),
|
new List<Type> {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),
|
||||||
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
||||||
|
|
||||||
@@ -981,8 +995,8 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
||||||
{
|
{
|
||||||
++purgeCount;
|
++purgeCount;
|
||||||
var elapsed = Time.realtimeSinceStartup - start;
|
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
|
||||||
Assert.GreaterOrEqual(elapsed, timeout - 0.25f);
|
Assert.GreaterOrEqual(elapsed, timeout);
|
||||||
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
||||||
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
|
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredMessageManager.TriggerType.OnSpawn, key));
|
||||||
@@ -992,6 +1006,21 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
manager.OnBeforePurge = beforePurge;
|
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()
|
bool HaveAllClientsPurged()
|
||||||
{
|
{
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -1005,15 +1034,18 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return WaitForConditionOrTimeOut(HaveAllClientsPurged);
|
Assert.IsFalse(HaveAnyClientsPurged());
|
||||||
AssertOnTimeout("Timed out waiting for all clients to purge their deferred messages!");
|
|
||||||
|
TimeTravel(0.02 + timePassedSinceFirstStart, 1);
|
||||||
|
|
||||||
|
Assert.IsTrue(HaveAllClientsPurged());
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
|
public void WhenMultipleMessagesForDifferentObjectsAreDeferredForMoreThanTheConfiguredTime_TheyAreAllRemoved([Values(1, 2, 3)] int timeout)
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -1028,7 +1060,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject2.GetComponent<NetworkObject>().Spawn();
|
serverObject2.GetComponent<NetworkObject>().Spawn();
|
||||||
|
|
||||||
yield return WaitForClientsToCatchSpawns(2);
|
WaitForClientsToCatchSpawns(2);
|
||||||
|
|
||||||
var start = 0f;
|
var start = 0f;
|
||||||
|
|
||||||
@@ -1038,7 +1070,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
{
|
{
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
start = Time.realtimeSinceStartup;
|
start = client.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -1053,7 +1085,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverObject2.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
serverObject2.GetComponent<DeferredMessageTestRpcAndNetworkVariableComponent>().TestNetworkVariable.Value = 1;
|
||||||
serverObject2.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
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),
|
new List<Type> {typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),typeof(ClientRpcMessage), typeof(NetworkVariableDeltaMessage), typeof(ChangeOwnershipMessage),
|
||||||
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
}, m_ClientNetworkManagers.ToList(), ReceiptType.Received);
|
||||||
|
|
||||||
@@ -1071,7 +1103,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
||||||
{
|
{
|
||||||
++purgeCount;
|
++purgeCount;
|
||||||
var elapsed = Time.realtimeSinceStartup - start;
|
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
|
||||||
Assert.GreaterOrEqual(elapsed, timeout - 0.25f);
|
Assert.GreaterOrEqual(elapsed, timeout - 0.25f);
|
||||||
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(remainingMessagesTotalThisClient, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
||||||
@@ -1082,7 +1114,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
manager.OnBeforePurge = beforePurge;
|
manager.OnBeforePurge = beforePurge;
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitForSeconds(timeout + 0.1f);
|
TimeTravel(timeout + 0.1f, 1);
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
AddPrefabsToClient(client);
|
AddPrefabsToClient(client);
|
||||||
@@ -1095,11 +1127,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([Values(1, 2, 3)] int timeout)
|
public void WhenADeferredMessageIsRemoved_OtherMessagesForSameObjectAreRemoved([Values(1, 2, 3)] int timeout)
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1108,7 +1140,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var serverObject = Object.Instantiate(m_RpcPrefab);
|
var serverObject = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.GetComponent<NetworkObject>().Spawn();
|
serverObject.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns();
|
WaitForClientsToCatchSpawns();
|
||||||
|
|
||||||
var start = 0f;
|
var start = 0f;
|
||||||
|
|
||||||
@@ -1118,7 +1150,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
{
|
{
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
start = Time.realtimeSinceStartup;
|
start = client.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -1127,9 +1159,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
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)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1140,7 +1172,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1161,7 +1193,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
||||||
{
|
{
|
||||||
++purgeCount;
|
++purgeCount;
|
||||||
var elapsed = Time.realtimeSinceStartup - start;
|
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
|
||||||
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
|
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
|
||||||
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
||||||
@@ -1177,7 +1209,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
AddPrefabsToClient(client);
|
AddPrefabsToClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitForSeconds(0.6f);
|
TimeTravel(0.6f, 1);
|
||||||
|
|
||||||
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
|
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -1187,11 +1219,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[Test]
|
||||||
public IEnumerator WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNotRemoved([Values(1, 2, 3)] int timeout)
|
public void WhenADeferredMessageIsRemoved_OtherMessagesForDifferentObjectsAreNotRemoved([Values(1, 2, 3)] int timeout)
|
||||||
{
|
{
|
||||||
m_SkipAddingPrefabsToClient = true;
|
m_SkipAddingPrefabsToClient = true;
|
||||||
yield return SpawnClients();
|
SpawnClients();
|
||||||
CatchSpawns();
|
CatchSpawns();
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1203,7 +1235,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var serverObject2 = Object.Instantiate(m_RpcPrefab);
|
var serverObject2 = Object.Instantiate(m_RpcPrefab);
|
||||||
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject2.GetComponent<NetworkObject>().NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject2.GetComponent<NetworkObject>().Spawn();
|
serverObject2.GetComponent<NetworkObject>().Spawn();
|
||||||
yield return WaitForClientsToCatchSpawns(2);
|
WaitForClientsToCatchSpawns(2);
|
||||||
|
|
||||||
var start = 0f;
|
var start = 0f;
|
||||||
|
|
||||||
@@ -1213,7 +1245,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
{
|
{
|
||||||
if (start == 0)
|
if (start == 0)
|
||||||
{
|
{
|
||||||
start = Time.realtimeSinceStartup;
|
start = client.RealTimeProvider.RealTimeSinceStartup;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
var manager = (TestDeferredMessageManager)client.DeferredMessageManager;
|
||||||
@@ -1222,9 +1254,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
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)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1236,7 +1268,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
serverObject2.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
|
serverObject2.GetComponent<NetworkObject>().ChangeOwnership(m_ServerNetworkManager.LocalClientId);
|
||||||
yield return WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -1258,7 +1290,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
TestDeferredMessageManager.BeforePurgeDelegate beforePurge = (manager, key) =>
|
||||||
{
|
{
|
||||||
++purgeCount;
|
++purgeCount;
|
||||||
var elapsed = Time.realtimeSinceStartup - start;
|
var elapsed = client.RealTimeProvider.RealTimeSinceStartup - start;
|
||||||
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
|
Assert.GreaterOrEqual(elapsed, timeout - 0.05f);
|
||||||
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(2, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(2, manager.DeferredMessageCountForType(IDeferredMessageManager.TriggerType.OnSpawn));
|
||||||
@@ -1277,7 +1309,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
AddPrefabsToClient(client);
|
AddPrefabsToClient(client);
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return new WaitForSeconds(0.6f);
|
TimeTravel(0.6f, 1);
|
||||||
|
|
||||||
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
|
Assert.AreEqual(m_NumberOfClientsToLateJoin, purgeCount);
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using UnityEngine;
|
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ using System.Linq;
|
|||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
using Debug = UnityEngine.Debug;
|
using Debug = UnityEngine.Debug;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using UnityEngine;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.Transports.UTP;
|
using Unity.Netcode.Transports.UTP;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using UnityEngine;
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using UnityEngine.TestTools;
|
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
using Unity.Netcode.Components;
|
using Unity.Netcode.Components;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
@@ -56,8 +56,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
yield return StartServerAndClients();
|
yield return StartServerAndClients();
|
||||||
|
|
||||||
var parentObject = new GameObject();
|
var parentObject = new GameObject();
|
||||||
var childObject = new GameObject();
|
var childObject = new GameObject
|
||||||
childObject.name = "ChildObject";
|
{
|
||||||
|
name = "ChildObject"
|
||||||
|
};
|
||||||
childObject.transform.parent = parentObject.transform;
|
childObject.transform.parent = parentObject.transform;
|
||||||
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
|
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
|
||||||
var childBehaviour = childObject.AddComponent<NetworkTransform>();
|
var childBehaviour = childObject.AddComponent<NetworkTransform>();
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ using System.Collections;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -12,12 +12,11 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var networkManager = gameObject.AddComponent<NetworkManager>();
|
var networkManager = gameObject.AddComponent<NetworkManager>();
|
||||||
var transport = gameObject.AddComponent<DummyTransport>();
|
var transport = gameObject.AddComponent<DummyTransport>();
|
||||||
|
|
||||||
networkManager.NetworkConfig = new NetworkConfig();
|
networkManager.NetworkConfig = new NetworkConfig
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
// Set dummy transport that does nothing
|
// Set dummy transport that does nothing
|
||||||
networkManager.NetworkConfig.NetworkTransport = transport;
|
NetworkTransport = transport
|
||||||
|
};
|
||||||
|
|
||||||
CustomMessagingManager preManager = networkManager.CustomMessagingManager;
|
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 gameObject = new GameObject(nameof(SceneManagerAssigned));
|
||||||
var networkManager = gameObject.AddComponent<NetworkManager>();
|
var networkManager = gameObject.AddComponent<NetworkManager>();
|
||||||
var transport = gameObject.AddComponent<DummyTransport>();
|
var transport = gameObject.AddComponent<DummyTransport>();
|
||||||
networkManager.NetworkConfig = new NetworkConfig();
|
networkManager.NetworkConfig = new NetworkConfig
|
||||||
|
{
|
||||||
// Set dummy transport that does nothing
|
// Set dummy transport that does nothing
|
||||||
networkManager.NetworkConfig.NetworkTransport = transport;
|
NetworkTransport = transport
|
||||||
|
};
|
||||||
|
|
||||||
NetworkSceneManager preManager = networkManager.SceneManager;
|
NetworkSceneManager preManager = networkManager.SceneManager;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using NUnit.Framework;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
using Object = UnityEngine.Object;
|
using Object = UnityEngine.Object;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
|
||||||
|
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
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