3 Commits

Author SHA1 Message Date
Unity Technologies
c2664dfb7f com.unity.netcode.gameobjects@1.11.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.11.0] - 2024-08-20

### Added

- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)

### Fixed

- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011)
- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)
- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)
- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)
- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)

### Changed

- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
2024-08-20 00:00:00 +00:00
Unity Technologies
896943c8bf com.unity.netcode.gameobjects@1.10.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.10.0] - 2024-07-22

### Added

- Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2906)
- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2906)
- Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2906)

### Fixed

- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2980)
- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded.(#2977)
- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined. (#2953)
- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#2941)
- Fixed issue with the host trying to send itself a message that it has connected when first starting up. (#2941)
- Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2923)
- Fixed issue where `NetworkDeltaPosition` would "jitter" periodically if both unreliable delta state updates and half-floats were used together. (#2922)
- Fixed issue where `NetworkRigidbody2D` would not properly change body type based on the instance's authority when spawned. (#2916)
- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2906)
- Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2895)

### Changed
2024-07-22 00:00:00 +00:00
Unity Technologies
158f26b913 com.unity.netcode.gameobjects@1.9.1
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.9.1] - 2024-04-18

### Added
- Added AnticipatedNetworkVariable<T>, which adds support for client anticipation of NetworkVariable values, allowing for more responsive gameplay (#2820)
- Added AnticipatedNetworkTransform, which adds support for client anticipation of NetworkTransforms (#2820)
- Added NetworkVariableBase.ExceedsDirtinessThreshold to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in NetworkVariable<T> with the callback NetworkVariable<T>.CheckExceedsDirtinessThreshold) (#2820)
- Added NetworkVariableUpdateTraits, which add additional throttling support: MinSecondsBetweenUpdates will prevent the NetworkVariable from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while MaxSecondsBetweenUpdates will force a dirty NetworkVariable to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2820)
- Added virtual method NetworkVariableBase.OnInitialize() which can be used by NetworkVariable subclasses to add initialization code (#2820)
- Added virtual method NetworkVariableBase.Update(), which is called once per frame to support behaviors such as interpolation between an anticipated value and an authoritative one. (#2820)
- Added NetworkTime.TickWithPartial, which represents the current tick as a double that includes the fractional/partial tick value. (#2820)
- Added NetworkTickSystem.AnticipationTick, which can be helpful with implementation of client anticipation. This value represents the tick the current local client was at at the beginning of the most recent network round trip, which enables it to correlate server update ticks with the client tick that may have triggered them. (#2820)
- `NetworkVariable` now includes built-in support for `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, and `Dictionary` (#2813)
- `NetworkVariable` now includes delta compression for collection values (`NativeList`, `NativeArray`, `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, `Dictionary`, and `FixedString` types) to save bandwidth by only sending the values that changed. (Note: For `NativeList`, `NativeArray`, and `List`, this algorithm works differently than that used in `NetworkList`. This algorithm will use less bandwidth for "set" and "add" operations, but `NetworkList` is more bandwidth-efficient if you are performing frequent "insert" operations.) (#2813)
- `UserNetworkVariableSerialization` now has optional callbacks for `WriteDelta` and `ReadDelta`. If both are provided, they will be used for all serialization operations on NetworkVariables of that type except for the first one for each client. If either is missing, the existing `Write` and `Read` will always be used. (#2813)
- Network variables wrapping `INetworkSerializable` types can perform delta serialization by setting `UserNetworkVariableSerialization<T>.WriteDelta` and `UserNetworkVariableSerialization<T>.ReadDelta` for those types. The built-in `INetworkSerializable` serializer will continue to be used for all other serialization operations, but if those callbacks are set, it will call into them on all but the initial serialization to perform delta serialization. (This could be useful if you have a large struct where most values do not change regularly and you want to send only the fields that did change.) (#2813)

### Fixed

- Fixed issue where NetworkTransformEditor would throw and exception if you excluded the physics package. (#2871)
- Fixed issue where `NetworkTransform` could not properly synchronize its base position when using half float precision. (#2845)
- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822)
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)

### Changed

- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874)
- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872)
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
2024-04-18 00:00:00 +00:00
92 changed files with 13836 additions and 1463 deletions

View File

@@ -6,6 +6,80 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.11.0] - 2024-08-20
### Added
- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)
### Fixed
- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011)
- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)
- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)
- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)
- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)
### Changed
- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)
## [1.10.0] - 2024-07-22
### Added
- Added `NetworkBehaviour.OnNetworkPreSpawn` and `NetworkBehaviour.OnNetworkPostSpawn` methods that provide the ability to handle pre and post spawning actions during the `NetworkObject` spawn sequence. (#2906)
- Added a client-side only `NetworkBehaviour.OnNetworkSessionSynchronized` convenience method that is invoked on all `NetworkBehaviour`s after a newly joined client has finished synchronizing with the network session in progress. (#2906)
- Added `NetworkBehaviour.OnInSceneObjectsSpawned` convenience method that is invoked when all in-scene `NetworkObject`s have been spawned after a scene has been loaded or upon a host or server starting. (#2906)
### Fixed
- Fixed issue where the realtime network stats monitor was not able to display RPC traffic in release builds due to those stats being only available in development builds or the editor. (#2980)
- Fixed issue where `NetworkManager.ScenesLoaded` was not being updated if `PostSynchronizationSceneUnloading` was set and any loaded scenes not used during synchronization were unloaded.(#2977)
- Fixed issue where internal delta serialization could not have a byte serializer defined when serializing deltas for other types. Added `[GenerateSerializationForType(typeof(byte))]` to both the `NetworkVariable` and `AnticipatedNetworkVariable` classes to assure a byte serializer is defined. (#2953)
- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#2941)
- Fixed issue with the host trying to send itself a message that it has connected when first starting up. (#2941)
- Fixed issue where in-scene placed NetworkObjects could be destroyed if a client disconnects early and/or before approval. (#2923)
- Fixed issue where `NetworkDeltaPosition` would "jitter" periodically if both unreliable delta state updates and half-floats were used together. (#2922)
- Fixed issue where `NetworkRigidbody2D` would not properly change body type based on the instance's authority when spawned. (#2916)
- Fixed issue where a `NetworkObject` component's associated `NetworkBehaviour` components would not be detected if scene loading is disabled in the editor and the currently loaded scene has in-scene placed `NetworkObject`s. (#2906)
- Fixed issue where an in-scene placed `NetworkObject` with `NetworkTransform` that is also parented under a `GameObject` would not properly synchronize when the parent `GameObject` had a world space position other than 0,0,0. (#2895)
### Changed
## [1.9.1] - 2024-04-18
### Added
- Added AnticipatedNetworkVariable<T>, which adds support for client anticipation of NetworkVariable values, allowing for more responsive gameplay (#2820)
- Added AnticipatedNetworkTransform, which adds support for client anticipation of NetworkTransforms (#2820)
- Added NetworkVariableBase.ExceedsDirtinessThreshold to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in NetworkVariable<T> with the callback NetworkVariable<T>.CheckExceedsDirtinessThreshold) (#2820)
- Added NetworkVariableUpdateTraits, which add additional throttling support: MinSecondsBetweenUpdates will prevent the NetworkVariable from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while MaxSecondsBetweenUpdates will force a dirty NetworkVariable to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2820)
- Added virtual method NetworkVariableBase.OnInitialize() which can be used by NetworkVariable subclasses to add initialization code (#2820)
- Added virtual method NetworkVariableBase.Update(), which is called once per frame to support behaviors such as interpolation between an anticipated value and an authoritative one. (#2820)
- Added NetworkTime.TickWithPartial, which represents the current tick as a double that includes the fractional/partial tick value. (#2820)
- `NetworkVariable` now includes built-in support for `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, and `Dictionary` (#2813)
- `NetworkVariable` now includes delta compression for collection values (`NativeList`, `NativeArray`, `NativeHashSet`, `NativeHashMap`, `List`, `HashSet`, `Dictionary`, and `FixedString` types) to save bandwidth by only sending the values that changed. (Note: For `NativeList`, `NativeArray`, and `List`, this algorithm works differently than that used in `NetworkList`. This algorithm will use less bandwidth for "set" and "add" operations, but `NetworkList` is more bandwidth-efficient if you are performing frequent "insert" operations.) (#2813)
- `UserNetworkVariableSerialization` now has optional callbacks for `WriteDelta` and `ReadDelta`. If both are provided, they will be used for all serialization operations on NetworkVariables of that type except for the first one for each client. If either is missing, the existing `Write` and `Read` will always be used. (#2813)
- Network variables wrapping `INetworkSerializable` types can perform delta serialization by setting `UserNetworkVariableSerialization<T>.WriteDelta` and `UserNetworkVariableSerialization<T>.ReadDelta` for those types. The built-in `INetworkSerializable` serializer will continue to be used for all other serialization operations, but if those callbacks are set, it will call into them on all but the initial serialization to perform delta serialization. (This could be useful if you have a large struct where most values do not change regularly and you want to send only the fields that did change.) (#2813)
### Fixed
- Fixed issue where NetworkTransformEditor would throw and exception if you excluded the physics package. (#2871)
- Fixed issue where `NetworkTransform` could not properly synchronize its base position when using half float precision. (#2845)
- Fixed issue where the host was not invoking `OnClientDisconnectCallback` for its own local client when internally shutting down. (#2822)
- Fixed issue where NetworkTransform could potentially attempt to "unregister" a named message prior to it being registered. (#2807)
- Fixed issue where in-scene placed `NetworkObject`s with complex nested children `NetworkObject`s (more than one child in depth) would not synchronize properly if WorldPositionStays was set to true. (#2796)
### Changed
- Changed `NetworkObjectReference` and `NetworkBehaviourReference` to allow null references when constructing and serializing. (#2874)
- Changed `NetworkAnimator` no longer requires the `Animator` component to exist on the same `GameObject`. (#2872)
- Changed `NetworkTransform` to now use `NetworkTransformMessage` as opposed to named messages for NetworkTransformState updates. (#2810)
- Changed `CustomMessageManager` so it no longer attempts to register or "unregister" a null or empty string and will log an error if this condition occurs. (#2807)
## [1.8.1] - 2024-02-05
### Fixed

View File

@@ -0,0 +1,500 @@
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode.Components
{
#pragma warning disable IDE0001
/// <summary>
/// A subclass of <see cref="NetworkTransform"/> that supports basic client anticipation - the client
/// can set a value on the belief that the server will update it to reflect the same value in a future update
/// (i.e., as the result of an RPC call). This value can then be adjusted as new updates from the server come in,
/// in three basic modes:
///
/// <list type="bullet">
///
/// <item><b>Snap:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="StaleDataHandling.Ignore"/> and no <see cref="NetworkBehaviour.OnReanticipate"/> callback),
/// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value,
/// resulting in a "snap" to the new value if it is different from the anticipated value.</item>
///
/// <item><b>Smooth:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Ignore"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> callback that calls
/// <see cref="Smooth"/> from the anticipated value to the authority value with an appropriate
/// <see cref="Mathf.Lerp"/>-style smooth function), when a more up-to-date value is received from the authority,
/// it will interpolate over time from an incorrect anticipated value to the correct authoritative value.</item>
///
/// <item><b>Constant Reanticipation:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Reanticipate"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> that calculates a
/// new anticipated value based on the current authoritative value), when a more up-to-date value is received from
/// the authority, user code calculates a new anticipated value, possibly calling <see cref="Smooth"/> to interpolate
/// between the previous anticipation and the new anticipation. This is useful for values that change frequently and
/// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply
/// need a one-time anticipation when the user performs that action.</item>
///
/// </list>
///
/// Note that these three modes may be combined. For example, if an <see cref="NetworkBehaviour.OnReanticipate"/> callback
/// does not call either <see cref="Smooth"/> or one of the Anticipate methods, the result will be a snap to the
/// authoritative value, enabling for a callback that may conditionally call <see cref="Smooth"/> when the
/// difference between the anticipated and authoritative values is within some threshold, but fall back to
/// snap behavior if the difference is too large.
/// </summary>
#pragma warning restore IDE0001
[DisallowMultipleComponent]
[AddComponentMenu("Netcode/Anticipated Network Transform")]
[DefaultExecutionOrder(100000)] // this is needed to catch the update time after the transform was updated by user scripts
public class AnticipatedNetworkTransform : NetworkTransform
{
public struct TransformState
{
public Vector3 Position;
public Quaternion Rotation;
public Vector3 Scale;
}
private TransformState m_AuthoritativeTransform = new TransformState();
private TransformState m_AnticipatedTransform = new TransformState();
private TransformState m_PreviousAnticipatedTransform = new TransformState();
private ulong m_LastAnticipaionCounter;
private double m_LastAnticipationTime;
private ulong m_LastAuthorityUpdateCounter;
private TransformState m_SmoothFrom;
private TransformState m_SmoothTo;
private float m_SmoothDuration;
private float m_CurrentSmoothTime;
private bool m_OutstandingAuthorityChange = false;
#if UNITY_EDITOR
private void Reset()
{
// Anticipation + smoothing is a form of interpolation, and adding NetworkTransform's buffered interpolation
// makes the anticipation get weird, so we default it to false.
Interpolate = false;
}
#endif
#pragma warning disable IDE0001
/// <summary>
/// Defines what the behavior should be if we receive a value from the server with an earlier associated
/// time value than the anticipation time value.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Ignore"/>, the stale data will be ignored and the authoritative
/// value will not replace the anticipated value until the anticipation time is reached. <see cref="OnAuthoritativeValueChanged"/>
/// and <see cref="OnReanticipate"/> will also not be invoked for this stale data.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Reanticipate"/>, the stale data will replace the anticipated data and
/// <see cref="OnAuthoritativeValueChanged"/> and <see cref="OnReanticipate"/> will be invoked.
/// In this case, the authoritativeTime value passed to <see cref="OnReanticipate"/> will be lower than
/// the anticipationTime value, and that callback can be used to calculate a new anticipated value.
/// </summary>
#pragma warning restore IDE0001
public StaleDataHandling StaleDataHandling = StaleDataHandling.Reanticipate;
/// <summary>
/// Contains the current state of this transform on the server side.
/// Note that, on the server side, this gets updated at the end of the frame, and will not immediately reflect
/// changes to the transform.
/// </summary>
public TransformState AuthoritativeState => m_AuthoritativeTransform;
/// <summary>
/// Contains the current anticipated state, which will match the values of this object's
/// actual <see cref="MonoBehaviour.transform"/>. When a server
/// update arrives, this value will be overwritten by the new
/// server value (unless stale data handling is set to "Ignore"
/// and the update is determined to be stale). This value will
/// be duplicated in <see cref="PreviousAnticipatedState"/>, which
/// will NOT be overwritten in server updates.
/// </summary>
public TransformState AnticipatedState => m_AnticipatedTransform;
/// <summary>
/// Indicates whether this transform currently needs
/// reanticipation. If this is true, the anticipated value
/// has been overwritten by the authoritative value from the
/// server; the previous anticipated value is stored in <see cref="PreviousAnticipatedState"/>
/// </summary>
public bool ShouldReanticipate
{
get;
private set;
}
/// <summary>
/// Holds the most recent anticipated state, whatever was
/// most recently set using the Anticipate methods. Unlike
/// <see cref="AnticipatedState"/>, this does not get overwritten
/// when a server update arrives.
/// </summary>
public TransformState PreviousAnticipatedState => m_PreviousAnticipatedTransform;
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will be in the given
/// <see cref="newPosition"/>
/// </summary>
/// <param name="newPosition"></param>
public void AnticipateMove(Vector3 newPosition)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.position = newPosition;
m_AnticipatedTransform.Position = newPosition;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Position = newPosition;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_LastAnticipationTime = NetworkManager.LocalTime.Time;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will have the given
/// <see cref="newRotation"/>
/// </summary>
/// <param name="newRotation"></param>
public void AnticipateRotate(Quaternion newRotation)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.rotation = newRotation;
m_AnticipatedTransform.Rotation = newRotation;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Rotation = newRotation;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_LastAnticipationTime = NetworkManager.LocalTime.Time;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, this transform will have the given
/// <see cref="newScale"/>
/// </summary>
/// <param name="newScale"></param>
public void AnticipateScale(Vector3 newScale)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
transform.localScale = newScale;
m_AnticipatedTransform.Scale = newScale;
if (CanCommitToTransform)
{
m_AuthoritativeTransform.Scale = newScale;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_LastAnticipationTime = NetworkManager.LocalTime.Time;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
/// <summary>
/// Anticipate that, at the end of one round trip to the server, the transform will have the given
/// <see cref="newState"/>
/// </summary>
/// <param name="newState"></param>
public void AnticipateState(TransformState newState)
{
if (NetworkManager.ShutdownInProgress || !NetworkManager.IsListening)
{
return;
}
var transform_ = transform;
transform_.position = newState.Position;
transform_.rotation = newState.Rotation;
transform_.localScale = newState.Scale;
m_AnticipatedTransform = newState;
if (CanCommitToTransform)
{
m_AuthoritativeTransform = newState;
}
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_LastAnticipaionCounter = NetworkManager.AnticipationSystem.AnticipationCounter;
m_LastAnticipationTime = NetworkManager.LocalTime.Time;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
protected override void Update()
{
// If not spawned or this instance has authority, exit early
if (!IsSpawned)
{
return;
}
// Do not call the base class implementation...
// AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update
// This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating
// and we will want all reanticipation done before anything else wants to reference the transform in
// Update()
//base.Update();
if (m_CurrentSmoothTime < m_SmoothDuration)
{
m_CurrentSmoothTime += NetworkManager.RealTimeProvider.DeltaTime;
var transform_ = transform;
var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f);
m_AnticipatedTransform = new TransformState
{
Position = Vector3.Lerp(m_SmoothFrom.Position, m_SmoothTo.Position, pct),
Rotation = Quaternion.Slerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct),
Scale = Vector3.Lerp(m_SmoothFrom.Scale, m_SmoothTo.Scale, pct)
};
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
if (!CanCommitToTransform)
{
transform_.position = m_AnticipatedTransform.Position;
transform_.localScale = m_AnticipatedTransform.Scale;
transform_.rotation = m_AnticipatedTransform.Rotation;
}
}
}
internal class AnticipatedObject : IAnticipationEventReceiver, IAnticipatedObject
{
public AnticipatedNetworkTransform Transform;
public void SetupForRender()
{
if (Transform.CanCommitToTransform)
{
var transform_ = Transform.transform;
Transform.m_AuthoritativeTransform = new TransformState
{
Position = transform_.position,
Rotation = transform_.rotation,
Scale = transform_.localScale
};
if (Transform.m_CurrentSmoothTime >= Transform.m_SmoothDuration)
{
// If we've had a call to Smooth() we'll continue interpolating.
// Otherwise we'll go ahead and make the visual and actual locations
// match.
Transform.m_AnticipatedTransform = Transform.m_AuthoritativeTransform;
}
transform_.position = Transform.m_AnticipatedTransform.Position;
transform_.rotation = Transform.m_AnticipatedTransform.Rotation;
transform_.localScale = Transform.m_AnticipatedTransform.Scale;
}
}
public void SetupForUpdate()
{
if (Transform.CanCommitToTransform)
{
var transform_ = Transform.transform;
transform_.position = Transform.m_AuthoritativeTransform.Position;
transform_.rotation = Transform.m_AuthoritativeTransform.Rotation;
transform_.localScale = Transform.m_AuthoritativeTransform.Scale;
}
}
public void Update()
{
// No need to do this, it's handled by NetworkBehaviour.Update
}
public void ResetAnticipation()
{
Transform.ShouldReanticipate = false;
}
public NetworkObject OwnerObject => Transform.NetworkObject;
}
private AnticipatedObject m_AnticipatedObject = null;
private void ResetAnticipatedState()
{
var transform_ = transform;
m_AuthoritativeTransform = new TransformState
{
Position = transform_.position,
Rotation = transform_.rotation,
Scale = transform_.localScale
};
m_AnticipatedTransform = m_AuthoritativeTransform;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
}
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
{
base.OnSynchronize(ref serializer);
if (!CanCommitToTransform)
{
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
}
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
public override void OnNetworkDespawn()
{
if (m_AnticipatedObject != null)
{
NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject);
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject);
m_AnticipatedObject = null;
}
ResetAnticipatedState();
base.OnNetworkDespawn();
}
public override void OnDestroy()
{
if (m_AnticipatedObject != null)
{
NetworkManager.AnticipationSystem.DeregisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject);
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject);
m_AnticipatedObject = null;
}
base.OnDestroy();
}
/// <summary>
/// Interpolate between the transform represented by <see cref="from"/> to the transform represented by
/// <see cref="to"/> over <see cref="durationSeconds"/> of real time. The duration uses
/// <see cref="Time.deltaTime"/>, so it is affected by <see cref="Time.timeScale"/>.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="durationSeconds"></param>
public void Smooth(TransformState from, TransformState to, float durationSeconds)
{
var transform_ = transform;
if (durationSeconds <= 0)
{
m_AnticipatedTransform = to;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
transform_.position = to.Position;
transform_.rotation = to.Rotation;
transform_.localScale = to.Scale;
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
return;
}
m_AnticipatedTransform = from;
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
if (!CanCommitToTransform)
{
transform_.position = from.Position;
transform_.rotation = from.Rotation;
transform_.localScale = from.Scale;
}
m_SmoothFrom = from;
m_SmoothTo = to;
m_SmoothDuration = durationSeconds;
m_CurrentSmoothTime = 0;
}
protected override void OnBeforeUpdateTransformState()
{
// this is called when new data comes from the server
m_LastAuthorityUpdateCounter = NetworkManager.AnticipationSystem.LastAnticipationAck;
m_OutstandingAuthorityChange = true;
}
protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
{
base.OnNetworkTransformStateUpdated(ref oldState, ref newState);
ApplyAuthoritativeState();
}
protected override void OnTransformUpdated()
{
if (CanCommitToTransform || m_AnticipatedObject == null)
{
return;
}
// this is called pretty much every frame and will change the transform
// If we've overridden the transform with an anticipated state, we need to be able to change it back
// to the anticipated state (while updating the authority state accordingly) or else
// mark this transform for reanticipation
var transform_ = transform;
var previousAnticipatedTransform = m_AnticipatedTransform;
// Update authority state to catch any possible interpolation data
m_AuthoritativeTransform.Position = transform_.position;
m_AuthoritativeTransform.Rotation = transform_.rotation;
m_AuthoritativeTransform.Scale = transform_.localScale;
if (!m_OutstandingAuthorityChange)
{
// Keep the anticipated value unchanged, we have no updates from the server at all.
transform_.position = previousAnticipatedTransform.Position;
transform_.localScale = previousAnticipatedTransform.Scale;
transform_.rotation = previousAnticipatedTransform.Rotation;
return;
}
if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipaionCounter > m_LastAuthorityUpdateCounter)
{
// Keep the anticipated value unchanged because it is more recent than the authoritative one.
transform_.position = previousAnticipatedTransform.Position;
transform_.localScale = previousAnticipatedTransform.Scale;
transform_.rotation = previousAnticipatedTransform.Rotation;
return;
}
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
m_OutstandingAuthorityChange = false;
m_AnticipatedTransform = m_AuthoritativeTransform;
ShouldReanticipate = true;
NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 97616b67982a4be48d957d421e422433
timeCreated: 1705597211

8
Components/Messages.meta Normal file
View File

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

View File

@@ -0,0 +1,116 @@
using Unity.Netcode.Components;
using UnityEngine;
namespace Unity.Netcode
{
/// <summary>
/// NetworkTransform State Update Message
/// </summary>
internal struct NetworkTransformMessage : INetworkMessage
{
public int Version => 0;
public ulong NetworkObjectId;
public int NetworkBehaviourId;
public NetworkTransform.NetworkTransformState State;
private NetworkTransform m_ReceiverNetworkTransform;
private FastBufferReader m_CurrentReader;
private unsafe void CopyPayload(ref FastBufferWriter writer)
{
writer.WriteBytesSafe(m_CurrentReader.GetUnsafePtrAtCurrentPosition(), m_CurrentReader.Length - m_CurrentReader.Position);
}
public void Serialize(FastBufferWriter writer, int targetVersion)
{
if (m_CurrentReader.IsInitialized)
{
CopyPayload(ref writer);
}
else
{
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourId);
writer.WriteNetworkSerializable(State);
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = context.SystemOwner as NetworkManager;
if (networkManager == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}] System owner context was not of type {nameof(NetworkManager)}!");
return false;
}
var currentPosition = reader.Position;
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(NetworkObjectId))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnSpawn, NetworkObjectId, reader, ref context);
return false;
}
// Get the behaviour index
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourId);
// Deserialize the state
reader.ReadNetworkSerializable(out State);
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
// Get the target NetworkTransform
m_ReceiverNetworkTransform = networkObject.ChildNetworkBehaviours[NetworkBehaviourId] as NetworkTransform;
var isServerAuthoritative = m_ReceiverNetworkTransform.IsServerAuthoritative();
var ownerAuthoritativeServerSide = !isServerAuthoritative && networkManager.IsServer;
if (ownerAuthoritativeServerSide)
{
var ownerClientId = networkObject.OwnerClientId;
if (ownerClientId == NetworkManager.ServerClientId)
{
// Ownership must have changed, ignore any additional pending messages that might have
// come from a previous owner client.
return true;
}
var networkDelivery = State.IsReliableStateUpdate() ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;
// Forward the state update if there are any remote clients to foward it to
if (networkManager.ConnectionManager.ConnectedClientsList.Count > (networkManager.IsHost ? 2 : 1))
{
// This is only to copy the existing and already serialized struct for forwarding purposes only.
// This will not include any changes made to this struct at this particular stage of processing the message.
var currentMessage = this;
// Create a new reader that replicates this message
currentMessage.m_CurrentReader = new FastBufferReader(reader, Collections.Allocator.None);
// Rewind the new reader to the beginning of the message's payload
currentMessage.m_CurrentReader.Seek(currentPosition);
// Forward the message to all connected clients that are observers of the associated NetworkObject
var clientCount = networkManager.ConnectionManager.ConnectedClientsList.Count;
for (int i = 0; i < clientCount; i++)
{
var clientId = networkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
if (NetworkManager.ServerClientId == clientId || (!isServerAuthoritative && clientId == ownerClientId) || !networkObject.Observers.Contains(clientId))
{
continue;
}
networkManager.MessageManager.SendMessage(ref currentMessage, networkDelivery, clientId);
}
// Dispose of the reader used for forwarding
currentMessage.m_CurrentReader.Dispose();
}
}
return true;
}
public void Handle(ref NetworkContext context)
{
if (m_ReceiverNetworkTransform == null)
{
Debug.LogError($"[{nameof(NetworkTransformMessage)}][Dropped] Reciever {nameof(NetworkTransform)} was not set!");
return;
}
m_ReceiverNetworkTransform.TransformStateUpdate(ref State);
}
}
}

View File

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

View File

@@ -164,7 +164,6 @@ namespace Unity.Netcode.Components
/// NetworkAnimator enables remote synchronization of <see cref="UnityEngine.Animator"/> state for on network objects.
/// </summary>
[AddComponentMenu("Netcode/Network Animator")]
[RequireComponent(typeof(Animator))]
public class NetworkAnimator : NetworkBehaviour, ISerializationCallbackReceiver
{
[Serializable]
@@ -833,7 +832,12 @@ namespace Unity.Netcode.Components
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])
// If we are not transitioned into the "any state" and the animator transition isn't a full path hash (layer to layer) and our pre-built destination state to transition does not contain the
// current layer (i.e. transitioning into a state from another layer) =or= we do contain the layer and the layer contains state to transition to is contained within our pre-built destination
// state then we can handle this transition as a non-cross fade state transition between layers.
// Otherwise, if we don't enter into this then this is a "trigger transition to some state that is now being transitioned back to the Idle state via trigger" or "Dual Triggers" IDLE<-->State.
else if (!tt.anyState && tt.fullPathHash != m_TransitionHash[layer] && (!m_DestinationStateToTransitioninfo.ContainsKey(layer) ||
(m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash))))
{
// first time in this transition for this layer
m_TransitionHash[layer] = tt.fullPathHash;
@@ -842,6 +846,10 @@ namespace Unity.Netcode.Components
animState.CrossFade = false;
animState.Transition = true;
animState.NormalizedTime = tt.normalizedTime;
if (m_DestinationStateToTransitioninfo.ContainsKey(layer) && m_DestinationStateToTransitioninfo[layer].ContainsKey(nt.fullPathHash))
{
animState.DestinationStateHash = nt.fullPathHash;
}
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})");
}

View File

@@ -32,9 +32,13 @@ namespace Unity.Netcode.Components
/// </summary>
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
HalfVector3.NetworkSerialize(serializer);
if (SynchronizeBase)
if (!SynchronizeBase)
{
HalfVector3.NetworkSerialize(serializer);
}
else
{
serializer.SerializeValue(ref DeltaPosition);
serializer.SerializeValue(ref CurrentBasePosition);
}
}

View File

@@ -29,15 +29,25 @@ namespace Unity.Netcode.Components
m_NetworkTransform = GetComponent<NetworkTransform>();
m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative();
SetupRigidBody();
}
/// <summary>
/// If the current <see cref="NetworkTransform"/> has authority,
/// then use the <see cref="RigidBody"/> interpolation strategy,
/// if the <see cref="NetworkTransform"/> is handling interpolation,
/// set interpolation to none on the <see cref="RigidBody"/>
/// <br/>
/// Turn off physics for the rigid body until spawned, otherwise
/// clients can run fixed update before the first
/// full <see cref="NetworkTransform"/> update
/// </summary>
private void SetupRigidBody()
{
m_Rigidbody = GetComponent<Rigidbody>();
m_OriginalInterpolation = m_Rigidbody.interpolation;
// Set interpolation to none if NetworkTransform is handling interpolation, otherwise it sets it to the original value
m_Rigidbody.interpolation = m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation;
// Turn off physics for the rigid body until spawned, otherwise
// clients can run fixed update before the first full
// NetworkTransform update
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation.None : m_OriginalInterpolation);
m_Rigidbody.isKinematic = true;
}

View File

@@ -12,71 +12,101 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
public class NetworkRigidbody2D : NetworkBehaviour
{
/// <summary>
/// Determines if we are server (true) or owner (false) authoritative
/// </summary>
private bool m_IsServerAuthoritative;
private Rigidbody2D m_Rigidbody;
private NetworkTransform m_NetworkTransform;
private bool m_OriginalKinematic;
private RigidbodyInterpolation2D m_OriginalInterpolation;
// Used to cache the authority state of this rigidbody during the last frame
private bool m_IsAuthority;
/// <summary>
/// Gets a bool value indicating whether this <see cref="NetworkRigidbody2D"/> on this peer currently holds authority.
/// </summary>
private bool HasAuthority => m_NetworkTransform.CanCommitToTransform;
private void Awake()
{
m_Rigidbody = GetComponent<Rigidbody2D>();
m_NetworkTransform = GetComponent<NetworkTransform>();
m_IsServerAuthoritative = m_NetworkTransform.IsServerAuthoritative();
SetupRigidBody();
}
private void FixedUpdate()
/// <summary>
/// If the current <see cref="NetworkTransform"/> has authority,
/// then use the <see cref="Rigidbody2D"/> interpolation strategy,
/// if the <see cref="NetworkTransform"/> is handling interpolation,
/// set interpolation to none on the <see cref="Rigidbody2D"/>
/// <br/>
/// Turn off physics for the rigid body until spawned, otherwise
/// clients can run fixed update before the first
/// full <see cref="NetworkTransform"/> update
/// </summary>
private void SetupRigidBody()
{
if (IsSpawned)
{
if (HasAuthority != m_IsAuthority)
{
m_IsAuthority = HasAuthority;
UpdateRigidbodyKinematicMode();
}
}
}
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
private void UpdateRigidbodyKinematicMode()
{
if (m_IsAuthority == false)
{
m_OriginalKinematic = m_Rigidbody.isKinematic;
m_Rigidbody.isKinematic = true;
m_Rigidbody = GetComponent<Rigidbody2D>();
m_OriginalInterpolation = m_Rigidbody.interpolation;
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
m_Rigidbody.interpolation = RigidbodyInterpolation2D.None;
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : (m_NetworkTransform.Interpolate ? RigidbodyInterpolation2D.None : m_OriginalInterpolation);
// Turn off physics for the rigid body until spawned, otherwise
// clients can run fixed update before the first full
// NetworkTransform update
m_Rigidbody.isKinematic = true;
}
/// <summary>
/// For owner authoritative (i.e. ClientNetworkTransform)
/// we adjust our authority when we gain ownership
/// </summary>
public override void OnGainedOwnership()
{
UpdateOwnershipAuthority();
}
/// <summary>
/// For owner authoritative(i.e. ClientNetworkTransform)
/// we adjust our authority when we have lost ownership
/// </summary>
public override void OnLostOwnership()
{
UpdateOwnershipAuthority();
}
/// <summary>
/// Sets the authority differently depending upon
/// whether it is server or owner authoritative
/// </summary>
private void UpdateOwnershipAuthority()
{
if (m_IsServerAuthoritative)
{
m_IsAuthority = NetworkManager.IsServer;
}
else
{
// Resets the rigidbody back to it's non replication only state. Happens on shutdown and when authority is lost
m_Rigidbody.isKinematic = m_OriginalKinematic;
m_Rigidbody.interpolation = m_OriginalInterpolation;
m_IsAuthority = IsOwner;
}
// If you have authority then you are not kinematic
m_Rigidbody.isKinematic = !m_IsAuthority;
// Set interpolation of the Rigidbody2D based on authority
// With authority: let local transform handle interpolation
// Without authority: let the NetworkTransform handle interpolation
m_Rigidbody.interpolation = m_IsAuthority ? m_OriginalInterpolation : RigidbodyInterpolation2D.None;
}
/// <inheritdoc />
public override void OnNetworkSpawn()
{
m_IsAuthority = HasAuthority;
m_OriginalKinematic = m_Rigidbody.isKinematic;
m_OriginalInterpolation = m_Rigidbody.interpolation;
UpdateRigidbodyKinematicMode();
UpdateOwnershipAuthority();
}
/// <inheritdoc />
public override void OnNetworkDespawn()
{
UpdateRigidbodyKinematicMode();
UpdateOwnershipAuthority();
}
}
}

View File

@@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
using Unity.Mathematics;
using UnityEngine;
@@ -1330,9 +1329,6 @@ namespace Unity.Netcode.Components
private Quaternion m_CurrentRotation;
private Vector3 m_TargetRotation;
// Used to for each instance to uniquely identify the named message
private string m_MessageName;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void UpdatePositionInterpolator(Vector3 position, double time, bool resetInterpolator = false)
@@ -1428,6 +1424,7 @@ namespace Unity.Netcode.Components
/// <param name="targetClientId">the clientId being synchronized (both reading and writing)</param>
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
{
m_CachedNetworkManager = NetworkManager;
var targetClientId = m_TargetIdBeingSynchronized;
var synchronizationState = new NetworkTransformState()
{
@@ -1685,6 +1682,15 @@ namespace Unity.Netcode.Components
var scale = transformToUse.localScale;
networkState.IsSynchronizing = isSynchronization;
// All of the checks below, up to the delta position checking portion, are to determine if the
// authority changed a property during runtime that requires a full synchronizing.
if (InLocalSpace != networkState.InLocalSpace)
{
networkState.InLocalSpace = InLocalSpace;
isDirty = true;
networkState.IsTeleportingNextFrame = true;
}
// Check for parenting when synchronizing and/or teleporting
if (isSynchronization || networkState.IsTeleportingNextFrame)
{
@@ -1694,11 +1700,13 @@ namespace Unity.Netcode.Components
// values are applied.
var hasParentNetworkObject = false;
var parentNetworkObject = (NetworkObject)null;
// If the NetworkObject belonging to this NetworkTransform instance has a parent
// (i.e. this handles nested NetworkTransforms under a parent at some layer above)
if (NetworkObject.transform.parent != null)
{
var parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();
parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();
// In-scene placed NetworkObjects parented under a GameObject with no
// NetworkObject preserve their lossyScale when synchronizing.
@@ -1719,28 +1727,25 @@ namespace Unity.Netcode.Components
// the NetworkTransform is using world or local space synchronization.
// WorldPositionStays: (always use world space)
// !WorldPositionStays: (always use local space)
if (isSynchronization)
// Exception: If it is an in-scene placed NetworkObject and it is parented under a GameObject
// then always use local space unless AutoObjectParentSync is disabled and the NetworkTransform
// is synchronizing in world space.
if (isSynchronization && networkState.IsParented)
{
if (NetworkObject.WorldPositionStays())
var parentedUnderGameObject = NetworkObject.transform.parent != null && !parentNetworkObject && NetworkObject.IsSceneObject.Value;
if (NetworkObject.WorldPositionStays() && (!parentedUnderGameObject || (parentedUnderGameObject && !NetworkObject.AutoObjectParentSync && !InLocalSpace)))
{
position = transformToUse.position;
networkState.InLocalSpace = false;
}
else
{
position = transformToUse.localPosition;
networkState.InLocalSpace = true;
}
}
}
// All of the checks below, up to the delta position checking portion, are to determine if the
// authority changed a property during runtime that requires a full synchronizing.
if (InLocalSpace != networkState.InLocalSpace)
{
networkState.InLocalSpace = InLocalSpace;
isDirty = true;
networkState.IsTeleportingNextFrame = true;
}
if (Interpolate != networkState.UseInterpolation)
{
networkState.UseInterpolation = Interpolate;
@@ -2051,10 +2056,15 @@ namespace Unity.Netcode.Components
return isDirty;
}
protected virtual void OnTransformUpdated()
{
}
/// <summary>
/// Applies the authoritative state to the transform
/// </summary>
private void ApplyAuthoritativeState()
protected internal void ApplyAuthoritativeState()
{
var networkState = m_LocalAuthoritativeNetworkState;
// The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated
@@ -2225,6 +2235,7 @@ namespace Unity.Netcode.Components
}
transform.localScale = m_CurrentScale;
}
OnTransformUpdated();
}
/// <summary>
@@ -2422,6 +2433,7 @@ namespace Unity.Netcode.Components
{
AddLogEntry(ref newState, NetworkObject.OwnerClientId);
}
OnTransformUpdated();
}
/// <summary>
@@ -2590,6 +2602,11 @@ namespace Unity.Netcode.Components
}
protected virtual void OnBeforeUpdateTransformState()
{
}
private NetworkTransformState m_OldState = new NetworkTransformState();
/// <summary>
@@ -2613,6 +2630,8 @@ namespace Unity.Netcode.Components
// Get the time when this new state was sent
newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time;
OnBeforeUpdateTransformState();
// Apply the new state
ApplyUpdatedState(newState);
@@ -2753,21 +2772,17 @@ namespace Unity.Netcode.Components
// Started using this again to avoid the getter processing cost of NetworkBehaviour.NetworkManager
m_CachedNetworkManager = NetworkManager;
// Register a custom named message specifically for this instance
m_MessageName = $"NTU_{NetworkObjectId}_{NetworkBehaviourId}";
m_CachedNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(m_MessageName, TransformStateUpdate);
Initialize();
if (CanCommitToTransform && UseHalfFloatPrecision)
{
SetState(GetSpaceRelativePosition(), GetSpaceRelativeRotation(), GetScale(), false);
}
}
/// <inheritdoc/>
public override void OnNetworkDespawn()
{
// During destroy, use NetworkBehaviour.NetworkManager as opposed to m_CachedNetworkManager
if (!NetworkManager.ShutdownInProgress && NetworkManager.CustomMessagingManager != null)
{
NetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(m_MessageName);
}
DeregisterForTickUpdate(this);
CanCommitToTransform = false;
@@ -3156,79 +3171,23 @@ namespace Unity.Netcode.Components
}
/// <summary>
/// Receives the <see cref="NetworkTransformState"/> named message updates
/// Invoked by <see cref="NetworkTransformMessage"/> to update the transform state
/// </summary>
/// <param name="senderId">authority of the transform</param>
/// <param name="messagePayload">serialzied <see cref="NetworkTransformState"/></param>
private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayload)
/// <param name="networkTransformState"></param>
internal void TransformStateUpdate(ref NetworkTransformState networkTransformState)
{
var ownerAuthoritativeServerSide = !OnIsServerAuthoritative() && IsServer;
if (ownerAuthoritativeServerSide && OwnerClientId == NetworkManager.ServerClientId)
{
// Ownership must have changed, ignore any additional pending messages that might have
// come from a previous owner client.
return;
}
// Store the previous/old state
m_OldState = m_LocalAuthoritativeNetworkState;
// Save the current payload stream position
var currentPosition = messagePayload.Position;
// Assign the new incoming state
m_LocalAuthoritativeNetworkState = networkTransformState;
// Deserialize the message (and determine network delivery)
messagePayload.ReadNetworkSerializableInPlace(ref m_LocalAuthoritativeNetworkState);
// Rewind back prior to serialization
messagePayload.Seek(currentPosition);
// Get the network delivery method used to send this state update
var networkDelivery = m_LocalAuthoritativeNetworkState.ReliableSequenced ? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;
// Forward owner authoritative messages before doing anything else
if (ownerAuthoritativeServerSide)
{
// Forward the state update if there are any remote clients to foward it to
if (m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count > (IsHost ? 2 : 1))
{
ForwardStateUpdateMessage(messagePayload, networkDelivery);
}
}
// Apply the message
// Apply the state update
OnNetworkStateChanged(m_OldState, m_LocalAuthoritativeNetworkState);
}
/// <summary>
/// Forwards owner authoritative state updates when received by the server
/// </summary>
/// <param name="messagePayload">the owner state message payload</param>
private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload, NetworkDelivery networkDelivery)
{
var serverAuthoritative = OnIsServerAuthoritative();
var currentPosition = messagePayload.Position;
var messageSize = messagePayload.Length - currentPosition;
var writer = new FastBufferWriter(messageSize, Allocator.Temp);
using (writer)
{
writer.WriteBytesSafe(messagePayload.GetUnsafePtr(), messageSize, currentPosition);
var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count;
for (int i = 0; i < clientCount; i++)
{
var clientId = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
if (NetworkManager.ServerClientId == clientId || (!serverAuthoritative && clientId == OwnerClientId))
{
continue;
}
m_CachedNetworkManager.CustomMessagingManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery);
}
}
messagePayload.Seek(currentPosition);
}
/// <summary>
/// Sends <see cref="NetworkTransformState"/> named message updates by the authority of the transform
/// Invoked by the authoritative instance to sends a <see cref="NetworkTransformMessage"/> containing the <see cref="NetworkTransformState"/>
/// </summary>
private void UpdateTransformState()
{
@@ -3248,7 +3207,12 @@ namespace Unity.Netcode.Components
}
var customMessageManager = m_CachedNetworkManager.CustomMessagingManager;
var writer = new FastBufferWriter(128, Allocator.Temp);
var networkTransformMessage = new NetworkTransformMessage()
{
NetworkObjectId = NetworkObjectId,
NetworkBehaviourId = NetworkBehaviourId,
State = m_LocalAuthoritativeNetworkState
};
// Determine what network delivery method to use:
// When to send reliable packets:
@@ -3259,9 +3223,6 @@ namespace Unity.Netcode.Components
| m_LocalAuthoritativeNetworkState.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat
? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;
using (writer)
{
writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState);
// Server-host always sends updates to all clients (but itself)
if (IsServer)
{
@@ -3273,18 +3234,21 @@ namespace Unity.Netcode.Components
{
continue;
}
customMessageManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery);
if (!NetworkObject.Observers.Contains(clientId))
{
continue;
}
NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, clientId);
}
}
else
{
// Clients (owner authoritative) send messages to the server-host
customMessageManager.SendNamedMessage(m_MessageName, NetworkManager.ServerClientId, writer, networkDelivery);
}
NetworkManager.MessageManager.SendMessage(ref networkTransformMessage, networkDelivery, NetworkManager.ServerClientId);
}
}
#region Network Tick Registration and Handling
private static Dictionary<NetworkManager, NetworkTransformTickRegistration> s_NetworkTickRegistration = new Dictionary<NetworkManager, NetworkTransformTickRegistration>();
private static void RemoveTickUpdate(NetworkManager networkManager)
@@ -3394,6 +3358,8 @@ namespace Unity.Netcode.Components
}
}
}
#endregion
}
internal interface INetworkTransformLogStateEntry

View File

@@ -0,0 +1,14 @@
using Unity.Netcode.Components;
using UnityEditor;
namespace Unity.Netcode.Editor
{
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="AnticipatedNetworkTransform"/>
/// </summary>
[CustomEditor(typeof(AnticipatedNetworkTransform), true)]
public class AnticipatedNetworkTransformEditor : NetworkTransformEditor
{
public override bool HideInterpolateValue => true;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 34bc168605014eeeadf97b12080e11fa
timeCreated: 1707514321

View File

@@ -21,6 +21,7 @@ namespace Unity.Netcode.Editor.CodeGen
public const string NetcodeModuleName = "Unity.Netcode.Runtime.dll";
public const string RuntimeAssemblyName = "Unity.Netcode.Runtime";
public const string ComponentsAssemblyName = "Unity.Netcode.Components";
public static readonly string NetworkBehaviour_FullName = typeof(NetworkBehaviour).FullName;
public static readonly string INetworkMessage_FullName = typeof(INetworkMessage).FullName;

View File

@@ -17,7 +17,7 @@ namespace Unity.Netcode.Editor.CodeGen
{
public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName || compiledAssembly.Name == CodeGenHelpers.ComponentsAssemblyName;
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();

View File

@@ -31,6 +31,43 @@ namespace Unity.Netcode.Editor.CodeGen
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();
public void AddWrappedType(TypeReference wrappedType)
{
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
var resolved = wrappedType.Resolve();
if (resolved != null)
{
if (resolved.FullName == "System.Collections.Generic.List`1")
{
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]);
}
if (resolved.FullName == "System.Collections.Generic.HashSet`1")
{
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]);
}
else if (resolved.FullName == "System.Collections.Generic.Dictionary`2")
{
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]);
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1]);
}
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
else if (resolved.FullName == "Unity.Collections.NativeHashSet`1")
{
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]);
}
else if (resolved.FullName == "Unity.Collections.NativeHashMap`2")
{
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[0]);
AddWrappedType(((GenericInstanceType)wrappedType).GenericArguments[1]);
}
#endif
}
}
}
public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly)
{
if (!WillProcess(compiledAssembly))
@@ -87,10 +124,7 @@ namespace Unity.Netcode.Editor.CodeGen
if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute))
{
var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value);
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
AddWrappedType(wrappedType);
}
}
@@ -101,10 +135,7 @@ namespace Unity.Netcode.Editor.CodeGen
if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute))
{
var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value);
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
AddWrappedType(wrappedType);
}
}
}
@@ -241,6 +272,36 @@ namespace Unity.Netcode.Editor.CodeGen
serializeMethod?.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
else if (type.Resolve().FullName == "System.Collections.Generic.List`1")
{
var wrappedType = ((GenericInstanceType)type).GenericArguments[0];
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_List_MethodRef);
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_List_MethodRef);
serializeMethod.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
else if (type.Resolve().FullName == "System.Collections.Generic.HashSet`1")
{
var wrappedType = ((GenericInstanceType)type).GenericArguments[0];
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_HashSet_MethodRef);
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_HashSet_MethodRef);
serializeMethod.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
else if (type.Resolve().FullName == "System.Collections.Generic.Dictionary`2")
{
var wrappedKeyType = ((GenericInstanceType)type).GenericArguments[0];
var wrappedValType = ((GenericInstanceType)type).GenericArguments[1];
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_Dictionary_MethodRef);
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_Dictionary_MethodRef);
serializeMethod.GenericArguments.Add(wrappedKeyType);
serializeMethod.GenericArguments.Add(wrappedValType);
equalityMethod.GenericArguments.Add(wrappedKeyType);
equalityMethod.GenericArguments.Add(wrappedValType);
}
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
else if (type.Resolve().FullName == "Unity.Collections.NativeList`1")
{
@@ -267,12 +328,30 @@ namespace Unity.Netcode.Editor.CodeGen
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef);
}
if (serializeMethod != null)
{
serializeMethod.GenericArguments.Add(wrappedType);
}
serializeMethod?.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
else if (type.Resolve().FullName == "Unity.Collections.NativeHashSet`1")
{
var wrappedType = ((GenericInstanceType)type).GenericArguments[0];
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashSet_MethodRef);
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashSet_MethodRef);
serializeMethod.GenericArguments.Add(wrappedType);
equalityMethod.GenericArguments.Add(wrappedType);
}
else if (type.Resolve().FullName == "Unity.Collections.NativeHashMap`2")
{
var wrappedKeyType = ((GenericInstanceType)type).GenericArguments[0];
var wrappedValType = ((GenericInstanceType)type).GenericArguments[1];
serializeMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashMap_MethodRef);
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashMap_MethodRef);
serializeMethod.GenericArguments.Add(wrappedKeyType);
serializeMethod.GenericArguments.Add(wrappedValType);
equalityMethod.GenericArguments.Add(wrappedKeyType);
equalityMethod.GenericArguments.Add(wrappedValType);
}
#endif
else if (type.IsValueType)
{
@@ -329,6 +408,7 @@ namespace Unity.Netcode.Editor.CodeGen
}
else
{
m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>");
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
}
@@ -398,7 +478,12 @@ namespace Unity.Netcode.Editor.CodeGen
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableArray_MethodRef;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashSet_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashMap_MethodRef;
#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_List_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_HashSet_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_Dictionary_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedString_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeSerializer_FixedStringArray_MethodRef;
@@ -415,7 +500,12 @@ namespace Unity.Netcode.Editor.CodeGen
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsList_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashSet_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashMap_MethodRef;
#endif
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_List_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_HashSet_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_Dictionary_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
private MethodReference m_RuntimeInitializeOnLoadAttribute_Ctor;
@@ -940,7 +1030,22 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_UnmanagedINetworkSerializableList):
m_NetworkVariableSerializationTypes_InitializeSerializer_UnmanagedINetworkSerializableList_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_NativeHashSet):
m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashSet_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_NativeHashMap):
m_NetworkVariableSerializationTypes_InitializeSerializer_NativeHashMap_MethodRef = method;
break;
#endif
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_List):
m_NetworkVariableSerializationTypes_InitializeSerializer_List_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_HashSet):
m_NetworkVariableSerializationTypes_InitializeSerializer_HashSet_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_Dictionary):
m_NetworkVariableSerializationTypes_InitializeSerializer_Dictionary_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeSerializer_ManagedINetworkSerializable):
m_NetworkVariableSerializationTypes_InitializeSerializer_ManagedINetworkSerializable_MethodRef = method;
break;
@@ -971,7 +1076,22 @@ namespace Unity.Netcode.Editor.CodeGen
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedIEquatableList):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedIEquatableList_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_NativeHashSet):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashSet_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_NativeHashMap):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_NativeHashMap_MethodRef = method;
break;
#endif
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_List):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_List_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_HashSet):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_HashSet_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_Dictionary):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_Dictionary_MethodRef = method;
break;
case nameof(NetworkVariableSerializationTypes.InitializeEqualityChecker_UnmanagedValueEquals):
m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef = method;
break;
@@ -1246,10 +1366,7 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
var wrappedType = genericInstanceType.GenericArguments[idx];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
AddWrappedType(wrappedType);
}
}
}
@@ -1282,10 +1399,7 @@ namespace Unity.Netcode.Editor.CodeGen
continue;
}
var wrappedType = genericInstanceType.GenericArguments[idx];
if (!m_WrappedNetworkVariableTypes.Contains(wrappedType))
{
m_WrappedNetworkVariableTypes.Add(wrappedType);
}
AddWrappedType(wrappedType);
}
}
}

View File

@@ -222,7 +222,7 @@ namespace Unity.Netcode.Editor
private void DrawTransportField()
{
#if RELAY_INTEGRATION_AVAILABLE
var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false);
var useRelay = EditorPrefs.GetBool(m_UseEasyRelayIntegrationKey, false);
#else
var useRelay = false;
#endif
@@ -257,7 +257,7 @@ namespace Unity.Netcode.Editor
}
#if RELAY_INTEGRATION_AVAILABLE
private readonly string k_UseEasyRelayIntegrationKey = "NetworkManagerUI_UseRelay_" + Application.dataPath.GetHashCode();
private readonly string m_UseEasyRelayIntegrationKey = "NetworkManagerUI_UseRelay_" + Application.dataPath.GetHashCode();
private string m_JoinCode = "";
private string m_StartConnectionError = null;
private string m_Region = "";
@@ -272,7 +272,7 @@ namespace Unity.Netcode.Editor
#if RELAY_INTEGRATION_AVAILABLE
// use editor prefs to persist the setting when entering / leaving play mode / exiting Unity
var useRelay = EditorPrefs.GetBool(k_UseEasyRelayIntegrationKey, false);
var useRelay = EditorPrefs.GetBool(m_UseEasyRelayIntegrationKey, false);
GUILayout.BeginHorizontal();
useRelay = GUILayout.Toggle(useRelay, "Try Relay in the Editor");
@@ -284,7 +284,7 @@ namespace Unity.Netcode.Editor
}
GUILayout.EndHorizontal();
EditorPrefs.SetBool(k_UseEasyRelayIntegrationKey, useRelay);
EditorPrefs.SetBool(m_UseEasyRelayIntegrationKey, useRelay);
if (useRelay && !Application.isPlaying && !CloudProjectSettings.projectBound)
{
EditorGUILayout.HelpBox("To use relay, you need to setup your project in the Project Settings in the Services section.", MessageType.Warning);

View File

@@ -37,6 +37,8 @@ namespace Unity.Netcode.Editor
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
public virtual bool HideInterpolateValue => false;
/// <inheritdoc/>
public void OnEnable()
{
@@ -137,7 +139,11 @@ namespace Unity.Netcode.Editor
EditorGUILayout.Space();
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
if (!HideInterpolateValue)
{
EditorGUILayout.PropertyField(m_InterpolateProperty);
}
EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
@@ -150,9 +156,10 @@ namespace Unity.Netcode.Editor
}
EditorGUILayout.PropertyField(m_UseHalfFloatPrecision);
#if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present
var go = ((NetworkTransform)target).gameObject;
#if COM_UNITY_MODULES_PHYSICS
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
{
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +

View File

@@ -104,10 +104,12 @@ namespace Unity.Netcode
{
continue;
}
if (peerClientIds.Length > idx)
{
peerClientIds[idx] = peerId;
++idx;
}
}
try
{
@@ -491,18 +493,25 @@ namespace Unity.Netcode
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
MessageManager.ProcessIncomingMessageQueue();
if (LocalClient.IsServer)
{
// We need to process the disconnection before notifying
OnClientDisconnectFromServer(clientId);
// Now notify the client has disconnected
InvokeOnClientDisconnectCallback(clientId);
if (LocalClient.IsHost)
{
InvokeOnPeerDisconnectedCallback(clientId);
}
if (LocalClient.IsServer)
{
OnClientDisconnectFromServer(clientId);
}
else // As long as we are not in the middle of a shutdown
else
{
// Notify local client of disconnection
InvokeOnClientDisconnectCallback(clientId);
// As long as we are not in the middle of a shutdown
if (!NetworkManager.ShutdownInProgress)
{
// We must pass true here and not process any sends messages as we are no longer connected.
@@ -510,6 +519,16 @@ namespace Unity.Netcode
// as the client ID is no longer valid.
NetworkManager.Shutdown(true);
}
}
if (NetworkManager.IsServer)
{
MessageManager.ClientDisconnected(clientId);
}
else
{
MessageManager.ClientDisconnected(NetworkManager.ServerClientId);
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
@@ -891,9 +910,14 @@ namespace Unity.Netcode
ConnectedClients.Add(clientId, networkClient);
ConnectedClientsList.Add(networkClient);
ConnectedClientIds.Add(clientId);
// Host should not send this message to itself
if (clientId != NetworkManager.ServerClientId)
{
var message = new ClientConnectedMessage { ClientId = clientId };
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
ConnectedClientIds.Add(clientId);
}
return networkClient;
}

View File

@@ -27,7 +27,7 @@ namespace Unity.Netcode
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, RpcReceiveHandler>> __rpc_func_table = new Dictionary<Type, Dictionary<uint, RpcReceiveHandler>>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<Type, Dictionary<uint, string>> __rpc_name_table = new Dictionary<Type, Dictionary<uint, string>>();
#endif
@@ -123,7 +123,7 @@ namespace Unity.Netcode
}
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{
NetworkManager.NetworkMetrics.TrackRpcSent(
@@ -254,7 +254,7 @@ namespace Unity.Netcode
}
bufferWriter.Dispose();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName))
{
if (clientRpcParams.Send.TargetClientIds != null)
@@ -505,13 +505,15 @@ namespace Unity.Netcode
{
get
{
if (m_NetworkObject != null)
{
return m_NetworkObject;
}
try
{
if (m_NetworkObject == null)
{
m_NetworkObject = GetComponentInParent<NetworkObject>();
}
}
catch (Exception)
{
return null;
@@ -613,16 +615,70 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Gets called after the <see cref="NetworkObject"/> is spawned. No NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked yet.
/// A reference to <see cref="NetworkManager"/> is passed in as a parameter to determine the context of execution (IsServer/IsClient)
/// </summary>
/// <remarks>
/// <param name="networkManager">a ref to the <see cref="NetworkManager"/> since this is not yet set on the <see cref="NetworkBehaviour"/></param>
/// The <see cref="NetworkBehaviour"/> will not have anything assigned to it at this point in time.
/// Settings like ownership, NetworkBehaviourId, NetworkManager, and most other spawn related properties will not be set.
/// This can be used to handle things like initializing/instantiating a NetworkVariable or the like.
/// </remarks>
protected virtual void OnNetworkPreSpawn(ref NetworkManager networkManager) { }
/// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets spawned, message handlers are ready to be registered and the network is setup.
/// </summary>
public virtual void OnNetworkSpawn() { }
/// <summary>
/// Gets called after the <see cref="NetworkObject"/> is spawned. All NetworkBehaviours associated with the NetworkObject will have had <see cref="OnNetworkSpawn"/> invoked.
/// </summary>
/// <remarks>
/// Will be invoked on each <see cref="NetworkBehaviour"/> associated with the <see cref="NetworkObject"/> being spawned.
/// All associated <see cref="NetworkBehaviour"/> components will have had <see cref="OnNetworkSpawn"/> invoked on the spawned <see cref="NetworkObject"/>.
/// </remarks>
protected virtual void OnNetworkPostSpawn() { }
/// <summary>
/// [Client-Side Only]
/// When a new client joins it is synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all
/// <see cref="NetworkObject"/>s and scenes (if scene management is enabled) have finished synchronizing, all NetworkBehaviour components associated with spawned <see cref="NetworkObject"/>s
/// will have this method invoked.
/// </summary>
/// <remarks>
/// This can be used to handle post synchronization actions where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
/// This is only invoked on clients during a client-server network topology session.
/// </remarks>
protected virtual void OnNetworkSessionSynchronized() { }
/// <summary>
/// [Client & Server Side]
/// When a scene is loaded an in-scene placed NetworkObjects are all spawned, this method is invoked on all of the newly spawned in-scene placed NetworkObjects.
/// </summary>
/// <remarks>
/// This can be used to handle post scene loaded actions for in-scene placed NetworkObjcts where you might need to access a different NetworkObject and/or NetworkBehaviour not local to the current NetworkObject context.
/// </remarks>
protected virtual void OnInSceneObjectsSpawned() { }
/// <summary>
/// Gets called when the <see cref="NetworkObject"/> gets despawned. Is called both on the server and clients.
/// </summary>
public virtual void OnNetworkDespawn() { }
internal void NetworkPreSpawn(ref NetworkManager networkManager)
{
try
{
OnNetworkPreSpawn(ref networkManager);
}
catch (Exception e)
{
Debug.LogException(e);
}
}
internal void InternalOnNetworkSpawn()
{
IsSpawned = true;
@@ -651,6 +707,42 @@ namespace Unity.Netcode
}
}
internal void NetworkPostSpawn()
{
try
{
OnNetworkPostSpawn();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
internal void NetworkSessionSynchronized()
{
try
{
OnNetworkSessionSynchronized();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
internal void InSceneNetworkObjectsSpawned()
{
try
{
OnInSceneObjectsSpawned();
}
catch (Exception e)
{
Debug.LogException(e);
}
}
internal void InternalOnNetworkDespawn()
{
IsSpawned = false;
@@ -666,7 +758,7 @@ namespace Unity.Netcode
}
/// <summary>
/// Gets called when the local client gains ownership of this object
/// Invoked on both the server and the local client of the owner when <see cref="Netcode.NetworkObject"/> ownership is assigned.
/// </summary>
public virtual void OnGainedOwnership() { }
@@ -694,7 +786,8 @@ namespace Unity.Netcode
}
/// <summary>
/// Gets called when we loose ownership of this object
/// Invoked on the local client when it loses ownership of the associated <see cref="Netcode.NetworkObject"/>.
/// This method is also invoked on the server when any client loses ownership.
/// </summary>
public virtual void OnLostOwnership() { }
@@ -740,7 +833,7 @@ namespace Unity.Netcode
#pragma warning restore IDE1006 // restore naming rule violation check
{
__rpc_func_table[GetType()][hash] = handler;
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()][hash] = rpcMethodName;
#endif
}
@@ -766,7 +859,7 @@ namespace Unity.Netcode
if (!__rpc_func_table.ContainsKey(GetType()))
{
__rpc_func_table[GetType()] = new Dictionary<uint, RpcReceiveHandler>();
#if UNITY_EDITOR || DEVELOPMENT_BUILD
#if UNITY_EDITOR || DEVELOPMENT_BUILD || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
__rpc_name_table[GetType()] = new Dictionary<uint, string>();
#endif
__initializeRpcs();
@@ -813,7 +906,16 @@ namespace Unity.Netcode
// during OnNetworkSpawn has been sent and needs to be cleared
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].ResetDirty();
var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
{
networkVariable.UpdateLastSentTime();
networkVariable.ResetDirty();
networkVariable.SetDirty(false);
}
}
}
}
else
@@ -821,11 +923,18 @@ namespace Unity.Netcode
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
var networkVariable = NetworkVariableFields[NetworkVariableIndexesToReset[i]];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
{
networkVariable.UpdateLastSentTime();
networkVariable.ResetDirty();
networkVariable.SetDirty(false);
}
}
}
}
MarkVariablesDirty(false);
}
internal void PreVariableUpdate()
@@ -834,7 +943,6 @@ namespace Unity.Netcode
{
InitializeVariables();
}
PreNetworkVariableWrite();
}
@@ -860,8 +968,11 @@ namespace Unity.Netcode
{
var networkVariable = NetworkVariableFields[k];
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
{
if (networkVariable.CanSend())
{
shouldSend = true;
}
break;
}
}
@@ -902,10 +1013,17 @@ namespace Unity.Netcode
// TODO: There should be a better way by reading one dirty variable vs. 'n'
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
if (NetworkVariableFields[i].IsDirty())
var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
{
return true;
}
// If it's dirty but can't be sent yet, we have to keep monitoring it until one of the
// conditions blocking its send changes.
NetworkManager.BehaviourUpdater.AddForUpdate(NetworkObject);
}
}
return false;
@@ -1061,6 +1179,11 @@ namespace Unity.Netcode
}
public virtual void OnReanticipate(double lastRoundTripTime)
{
}
/// <summary>
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary>
@@ -1191,9 +1314,9 @@ namespace Unity.Netcode
/// <summary>
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to.
/// NOTE: If you override this, you will want to always invoke this base class version of this
/// <see cref="OnDestroy"/> method!!
/// Invoked when the <see cref="GameObject"/> the <see cref="NetworkBehaviour"/> is attached to is destroyed.
/// NOTE: If you override this, you should invoke this base class version of this
/// <see cref="OnDestroy"/> method.
/// </summary>
public virtual void OnDestroy()
{

View File

@@ -11,6 +11,7 @@ namespace Unity.Netcode
private NetworkManager m_NetworkManager;
private NetworkConnectionManager m_ConnectionManager;
private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
private HashSet<NetworkObject> m_PendingDirtyNetworkObjects = new HashSet<NetworkObject>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
@@ -18,7 +19,7 @@ namespace Unity.Netcode
internal void AddForUpdate(NetworkObject networkObject)
{
m_DirtyNetworkObjects.Add(networkObject);
m_PendingDirtyNetworkObjects.Add(networkObject);
}
internal void NetworkBehaviourUpdate()
@@ -28,6 +29,9 @@ namespace Unity.Netcode
#endif
try
{
m_DirtyNetworkObjects.UnionWith(m_PendingDirtyNetworkObjects);
m_PendingDirtyNetworkObjects.Clear();
// NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point
// trying to process them, even if they were previously marked as dirty.
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);

View File

@@ -27,7 +27,7 @@ namespace Unity.Netcode
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, RpcReceiveHandler> __rpc_func_table = new Dictionary<uint, RpcReceiveHandler>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
// RuntimeAccessModifiersILPP will make this `public`
internal static readonly Dictionary<uint, string> __rpc_name_table = new Dictionary<uint, string>();
#endif
@@ -45,15 +45,25 @@ namespace Unity.Netcode
DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0);
AnticipationSystem.SetupForUpdate();
MessageManager.ProcessIncomingMessageQueue();
MessageManager.CleanupDisconnectedClients();
AnticipationSystem.ProcessReanticipation();
}
break;
case NetworkUpdateStage.PreUpdate:
{
NetworkTimeSystem.UpdateTime();
AnticipationSystem.Update();
}
break;
case NetworkUpdateStage.PostScriptLateUpdate:
AnticipationSystem.Sync();
AnticipationSystem.SetupForRender();
break;
case NetworkUpdateStage.PostLateUpdate:
{
// This should be invoked just prior to the MessageManager processes its outbound queue.
@@ -274,6 +284,25 @@ namespace Unity.Netcode
remove => ConnectionManager.OnTransportFailure -= value;
}
public delegate void ReanticipateDelegate(double lastRoundTripTime);
/// <summary>
/// This callback is called after all individual OnReanticipate calls on AnticipatedNetworkVariable
/// and AnticipatedNetworkTransform values have been invoked. The first parameter is a hash set of
/// all the variables that have been changed on this frame (you can detect a particular variable by
/// checking if the set contains it), while the second parameter is a set of all anticipated network
/// transforms that have been changed. Both are passed as their base class type.
///
/// The third parameter is the local time corresponding to the current authoritative server state
/// (i.e., to determine the amount of time that needs to be re-simulated, you will use
/// NetworkManager.LocalTime.Time - authorityTime).
/// </summary>
public event ReanticipateDelegate OnReanticipate
{
add => AnticipationSystem.OnReanticipate += value;
remove => AnticipationSystem.OnReanticipate -= value;
}
/// <summary>
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
/// </summary>
@@ -434,20 +463,21 @@ namespace Unity.Netcode
public event Action OnServerStarted = null;
/// <summary>
/// The callback to invoke once the local client is ready
/// 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>
/// <remarks>The bool parameter will be set to true when stopping a host instance and false when stopping a server instance.</remarks>
/// <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
/// The callback to invoke once the local client stops.
/// </summary>
/// <remarks>The parameter states whether the client was running in host mode</remarks>
/// <remarks>The bool parameter will be set to true when stopping a host client and false when stopping a standard client instance.</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;
@@ -518,6 +548,8 @@ namespace Unity.Netcode
/// </summary>
public NetworkTickSystem NetworkTickSystem { get; private set; }
internal AnticipationSystem AnticipationSystem { get; private set; }
/// <summary>
/// Used for time mocking in tests
/// </summary>
@@ -813,6 +845,7 @@ namespace Unity.Netcode
this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostScriptLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);
// ComponentFactory needs to set its defaults next
@@ -845,6 +878,7 @@ namespace Unity.Netcode
// The remaining systems can then be initialized
NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate);
NetworkTickSystem = NetworkTimeSystem.Initialize(this);
AnticipationSystem = new AnticipationSystem(this);
// Create spawn manager instance
SpawnManager = new NetworkSpawnManager(this);
@@ -1154,7 +1188,6 @@ namespace Unity.Netcode
// Everything is shutdown in the order of their dependencies
DeferredMessageManager?.CleanupAllTriggers();
CustomMessagingManager = null;
RpcTarget?.Dispose();
RpcTarget = null;
@@ -1165,6 +1198,8 @@ namespace Unity.Netcode
// Shutdown connection manager last which shuts down transport
ConnectionManager.Shutdown();
CustomMessagingManager = null;
if (MessageManager != null)
{
MessageManager.Dispose();
@@ -1185,6 +1220,12 @@ namespace Unity.Netcode
IsListening = false;
m_ShuttingDown = false;
// Generate a local notification that the host client is disconnected
if (IsHost)
{
ConnectionManager.InvokeOnClientDisconnectCallback(LocalClientId);
}
if (ConnectionManager.LocalClient.IsClient)
{
// If we were a client, we want to know if we were a host

View File

@@ -512,6 +512,7 @@ namespace Unity.Netcode
private void Awake()
{
m_ChildNetworkBehaviours = null;
SetCachedParent(transform.parent);
SceneOrigin = gameObject.scene;
}
@@ -992,6 +993,11 @@ namespace Unity.Netcode
m_CachedParent = parentTransform;
}
internal Transform GetCachedParent()
{
return m_CachedParent;
}
internal ulong? GetNetworkParenting() => m_LatestParent;
internal void SetNetworkParenting(ulong? latestParent, bool worldPositionStays)
@@ -1236,7 +1242,7 @@ namespace Unity.Netcode
// we call CheckOrphanChildren() method and quickly iterate over OrphanChildren set and see if we can reparent/adopt one.
internal static HashSet<NetworkObject> OrphanChildren = new HashSet<NetworkObject>();
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false)
internal bool ApplyNetworkParenting(bool removeParent = false, bool ignoreNotSpawned = false, bool orphanedChildPass = false)
{
if (!AutoObjectParentSync)
{
@@ -1324,9 +1330,17 @@ namespace Unity.Netcode
// If we made it here, then parent this instance under the parentObject
var parentObject = NetworkManager.SpawnManager.SpawnedObjects[m_LatestParent.Value];
// If we are handling an orphaned child and its parent is orphaned too, then don't parent yet.
if (orphanedChildPass)
{
if (OrphanChildren.Contains(parentObject))
{
return false;
}
}
m_CachedParent = parentObject.transform;
transform.SetParent(parentObject.transform, m_CachedWorldPositionStays);
InvokeBehaviourOnNetworkObjectParentChanged(parentObject);
return true;
}
@@ -1336,7 +1350,7 @@ namespace Unity.Netcode
var objectsToRemove = new List<NetworkObject>();
foreach (var orphanObject in OrphanChildren)
{
if (orphanObject.ApplyNetworkParenting())
if (orphanObject.ApplyNetworkParenting(orphanedChildPass: true))
{
objectsToRemove.Add(orphanObject);
}
@@ -1347,6 +1361,18 @@ namespace Unity.Netcode
}
}
internal void InvokeBehaviourNetworkPreSpawn()
{
var networkManager = NetworkManager;
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].NetworkPreSpawn(ref networkManager);
}
}
}
internal void InvokeBehaviourNetworkSpawn()
{
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
@@ -1371,6 +1397,42 @@ namespace Unity.Netcode
}
}
internal void InvokeBehaviourNetworkPostSpawn()
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].NetworkPostSpawn();
}
}
}
internal void InternalNetworkSessionSynchronized()
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].NetworkSessionSynchronized();
}
}
}
internal void InternalInSceneNetworkObjectsSpawned()
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{
ChildNetworkBehaviours[i].InSceneNetworkObjectsSpawned();
}
}
}
internal void InvokeBehaviourNetworkDespawn()
{
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId, true);
@@ -1797,6 +1859,12 @@ namespace Unity.Netcode
var syncRotationPositionLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
var syncScaleLocalSpaceRelative = obj.HasParent && !m_CachedWorldPositionStays;
// Always synchronize in-scene placed object's scale using local space
if (obj.IsSceneObject)
{
syncScaleLocalSpaceRelative = obj.HasParent;
}
// If auto object synchronization is turned off
if (!AutoObjectParentSync)
{
@@ -1808,7 +1876,6 @@ namespace Unity.Netcode
syncScaleLocalSpaceRelative = obj.HasParent;
}
obj.Transform = new SceneObject.TransformData
{
// If we are parented and we have the m_CachedWorldPositionStays disabled, then use local space
@@ -1868,6 +1935,9 @@ namespace Unity.Netcode
// in order to be able to determine which NetworkVariables the client will be allowed to read.
networkObject.OwnerClientId = sceneObject.OwnerClientId;
// Special Case: Invoke NetworkBehaviour.OnPreSpawn methods here before SynchronizeNetworkBehaviours
networkObject.InvokeBehaviourNetworkPreSpawn();
// Synchronize NetworkBehaviours
var bufferSerializer = new BufferSerializer<BufferSerializerReader>(new BufferSerializerReader(reader));
networkObject.SynchronizeNetworkBehaviours(ref bufferSerializer, networkManager.LocalClientId);

View File

@@ -54,7 +54,14 @@ namespace Unity.Netcode
/// </summary>
PreLateUpdate = 6,
/// <summary>
/// Updated after Monobehaviour.LateUpdate, but BEFORE rendering
/// </summary>
// Yes, these numbers are out of order due to backward compatibility requirements.
// The enum values are listed in the order they will be called.
PostScriptLateUpdate = 8,
/// <summary>
/// Updated after the Monobehaviour.LateUpdate for all components is invoked
/// and all rendering is complete
/// </summary>
PostLateUpdate = 7
}
@@ -258,6 +265,18 @@ namespace Unity.Netcode
}
}
internal struct NetworkPostScriptLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkPostScriptLateUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostScriptLateUpdate)
};
}
}
internal struct NetworkPostLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
@@ -399,6 +418,7 @@ namespace Unity.Netcode
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
TryAddLoopSystem(ref currentSystem, NetworkPostScriptLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.After);
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
@@ -440,6 +460,7 @@ namespace Unity.Netcode
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostScriptLateUpdate));
}
else if (currentSystem.type == typeof(PostLateUpdate))
{

View File

@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode
{
@@ -199,6 +200,14 @@ namespace Unity.Netcode
/// <param name="callback">The callback to run when a named message is received.</param>
public void RegisterNamedMessageHandler(string name, HandleNamedMessageDelegate callback)
{
if (string.IsNullOrEmpty(name))
{
if (m_NetworkManager.LogLevel <= LogLevel.Error)
{
Debug.LogError($"[{nameof(RegisterNamedMessageHandler)}] Cannot register a named message of type null or empty!");
}
return;
}
var hash32 = XXHash.Hash32(name);
var hash64 = XXHash.Hash64(name);
@@ -215,6 +224,15 @@ namespace Unity.Netcode
/// <param name="name">The name of the message.</param>
public void UnregisterNamedMessageHandler(string name)
{
if (string.IsNullOrEmpty(name))
{
if (m_NetworkManager.LogLevel <= LogLevel.Error)
{
Debug.LogError($"[{nameof(UnregisterNamedMessageHandler)}] Cannot unregister a named message of type null or empty!");
}
return;
}
var hash32 = XXHash.Hash32(name);
var hash64 = XXHash.Hash64(name);

View File

@@ -0,0 +1,70 @@
namespace Unity.Netcode
{
internal struct AnticipationCounterSyncPingMessage : INetworkMessage
{
public int Version => 0;
public ulong Counter;
public double Time;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValuePacked(writer, Counter);
writer.WriteValueSafe(Time);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsServer)
{
return false;
}
ByteUnpacker.ReadValuePacked(reader, out Counter);
reader.ReadValueSafe(out Time);
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (networkManager.IsListening && !networkManager.ShutdownInProgress && networkManager.ConnectedClients.ContainsKey(context.SenderId))
{
var message = new AnticipationCounterSyncPongMessage { Counter = Counter, Time = Time };
networkManager.MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, context.SenderId);
}
}
}
internal struct AnticipationCounterSyncPongMessage : INetworkMessage
{
public int Version => 0;
public ulong Counter;
public double Time;
public void Serialize(FastBufferWriter writer, int targetVersion)
{
BytePacker.WriteValuePacked(writer, Counter);
writer.WriteValueSafe(Time);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.IsClient)
{
return false;
}
ByteUnpacker.ReadValuePacked(reader, out Counter);
reader.ReadValueSafe(out Time);
return true;
}
public void Handle(ref NetworkContext context)
{
var networkManager = (NetworkManager)context.SystemOwner;
networkManager.AnticipationSystem.LastAnticipationAck = Counter;
networkManager.AnticipationSystem.LastAnticipationAckTime = Time;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: db5034828a9741ce9bc8ec9a64d5a5b6
timeCreated: 1706042908

View File

@@ -170,6 +170,12 @@ namespace Unity.Netcode
networkManager.IsConnectedClient = true;
// When scene management is disabled we notify after everything is synchronized
networkManager.ConnectionManager.InvokeOnClientConnectedCallback(context.SenderId);
// For convenience, notify all NetworkBehaviours that synchronization is complete.
foreach (var networkObject in networkManager.SpawnManager.SpawnedObjectsList)
{
networkObject.InternalNetworkSessionSynchronized();
}
}
ConnectedClientIds.Dispose();

View File

@@ -30,6 +30,9 @@ namespace Unity.Netcode
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
}
var obj = NetworkBehaviour.NetworkObject;
var networkManager = obj.NetworkManagerOwner;
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
@@ -38,7 +41,7 @@ namespace Unity.Netcode
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
{
// This var does not belong to the currently iterating delivery group.
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
@@ -52,9 +55,11 @@ namespace Unity.Netcode
var startingSize = writer.Length;
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
var shouldWrite = networkVariable.IsDirty() &&
networkVariable.CanClientRead(TargetClientId) &&
(NetworkBehaviour.NetworkManager.IsServer || networkVariable.CanClientWrite(NetworkBehaviour.NetworkManager.LocalClientId));
(networkManager.IsServer || networkVariable.CanClientWrite(networkManager.LocalClientId)) &&
networkVariable.CanSend();
// Prevent the server from writing to the client that owns a given NetworkVariable
// Allowing the write would send an old value to the client and cause jitter
@@ -67,14 +72,14 @@ namespace Unity.Netcode
// The object containing the behaviour we're about to process is about to be shown to this client
// As a result, the client will get the fully serialized NetworkVariable and would be confused by
// an extraneous delta
if (NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
NetworkBehaviour.NetworkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
.Contains(NetworkBehaviour.NetworkObject))
if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(TargetClientId) &&
networkManager.SpawnManager.ObjectsToShowToClient[TargetClientId]
.Contains(obj))
{
shouldWrite = false;
}
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
@@ -88,9 +93,9 @@ namespace Unity.Netcode
if (shouldWrite)
{
if (NetworkBehaviour.NetworkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
var tempWriter = new FastBufferWriter(NetworkBehaviour.NetworkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, NetworkBehaviour.NetworkManager.MessageManager.FragmentedMessageMaxSize);
var tempWriter = new FastBufferWriter(networkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, networkManager.MessageManager.FragmentedMessageMaxSize);
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
@@ -105,9 +110,9 @@ namespace Unity.Netcode
{
networkVariable.WriteDelta(writer);
}
NetworkBehaviour.NetworkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
TargetClientId,
NetworkBehaviour.NetworkObject,
obj,
networkVariable.Name,
NetworkBehaviour.__getTypeName(),
writer.Length - startingSize);

View File

@@ -41,7 +41,7 @@ namespace Unity.Netcode
payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName))
{
networkManager.NetworkMetrics.TrackRpcReceived(

View File

@@ -36,12 +36,12 @@ namespace Unity.Netcode
private protected void SendMessageToClient(NetworkBehaviour behaviour, ulong clientId, ref RpcMessage message, NetworkDelivery delivery)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
var size =
#endif
behaviour.NetworkManager.MessageManager.SendMessage(ref message, delivery, clientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(

View File

@@ -46,7 +46,7 @@ namespace Unity.Netcode
message.Handle(ref context);
length = tempBuffer.Length;
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{
behaviour.NetworkManager.NetworkMetrics.TrackRpcSent(

View File

@@ -49,6 +49,10 @@ namespace Unity.Netcode
{
continue;
}
if (clientId == NetworkManager.ServerClientId)
{
continue;
}
m_GroupSendTarget.Add(clientId);
}
}

View File

@@ -40,6 +40,10 @@ namespace Unity.Netcode
{
continue;
}
if (clientId == NetworkManager.ServerClientId)
{
continue;
}
if (clientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);
@@ -57,6 +61,10 @@ namespace Unity.Netcode
{
continue;
}
if (clientId == NetworkManager.ServerClientId)
{
continue;
}
if (clientId == behaviour.NetworkManager.LocalClientId)
{
m_LocalSendRpcTarget.Send(behaviour, ref message, delivery, rpcParams);

View File

@@ -18,12 +18,12 @@ namespace Unity.Netcode
internal override void Send(NetworkBehaviour behaviour, ref RpcMessage message, NetworkDelivery delivery, RpcParams rpcParams)
{
var proxyMessage = new ProxyMessage { Delivery = delivery, TargetClientIds = TargetClientIds.AsArray(), WrappedMessage = message };
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
var size =
#endif
behaviour.NetworkManager.MessageManager.SendMessage(ref proxyMessage, delivery, NetworkManager.ServerClientId);
#if DEVELOPMENT_BUILD || UNITY_EDITOR
#if DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE
if (NetworkBehaviour.__rpc_name_table[behaviour.GetType()].TryGetValue(message.Metadata.NetworkRpcMethodId, out var rpcMethodName))
{
foreach (var clientId in TargetClientIds)

View File

@@ -0,0 +1,392 @@
using System;
using Unity.Mathematics;
using UnityEngine;
namespace Unity.Netcode
{
public enum StaleDataHandling
{
Ignore,
Reanticipate
}
#pragma warning disable IDE0001
/// <summary>
/// A variable that can be synchronized over the network.
/// This version supports basic client anticipation - the client can set a value on the belief that the server
/// will update it to reflect the same value in a future update (i.e., as the result of an RPC call).
/// This value can then be adjusted as new updates from the server come in, in three basic modes:
///
/// <list type="bullet">
///
/// <item><b>Snap:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Ignore"/> and no <see cref="NetworkBehaviour.OnReanticipate"/> callback),
/// the moment a more up-to-date value is received from the authority, it will simply replace the anticipated value,
/// resulting in a "snap" to the new value if it is different from the anticipated value.</item>
///
/// <item><b>Smooth:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Ignore"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> callback that calls
/// <see cref="Smooth"/> from the anticipated value to the authority value with an appropriate
/// <see cref="Mathf.Lerp"/>-style smooth function), when a more up-to-date value is received from the authority,
/// it will interpolate over time from an incorrect anticipated value to the correct authoritative value.</item>
///
/// <item><b>Constant Reanticipation:</b> In this mode (with <see cref="StaleDataHandling"/> set to
/// <see cref="Netcode.StaleDataHandling.Reanticipate"/> and an <see cref="NetworkBehaviour.OnReanticipate"/> that calculates a
/// new anticipated value based on the current authoritative value), when a more up-to-date value is received from
/// the authority, user code calculates a new anticipated value, possibly calling <see cref="Smooth"/> to interpolate
/// between the previous anticipation and the new anticipation. This is useful for values that change frequently and
/// need to constantly be re-evaluated, as opposed to values that change only in response to user action and simply
/// need a one-time anticipation when the user performs that action.</item>
///
/// </list>
///
/// Note that these three modes may be combined. For example, if an <see cref="NetworkBehaviour.OnReanticipate"/> callback
/// does not call either <see cref="Smooth"/> or <see cref="Anticipate"/>, the result will be a snap to the
/// authoritative value, enabling for a callback that may conditionally call <see cref="Smooth"/> when the
/// difference between the anticipated and authoritative values is within some threshold, but fall back to
/// snap behavior if the difference is too large.
/// </summary>
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
#pragma warning restore IDE0001
[Serializable]
[GenerateSerializationForGenericParameter(0)]
public class AnticipatedNetworkVariable<T> : NetworkVariableBase
{
[SerializeField]
private NetworkVariable<T> m_AuthoritativeValue;
private T m_AnticipatedValue;
private T m_PreviousAnticipatedValue;
private ulong m_LastAuthorityUpdateCounter = 0;
private ulong m_LastAnticipationCounter = 0;
private bool m_IsDisposed = false;
private bool m_SettingAuthoritativeValue = false;
private T m_SmoothFrom;
private T m_SmoothTo;
private float m_SmoothDuration;
private float m_CurrentSmoothTime;
private bool m_HasSmoothValues;
#pragma warning disable IDE0001
/// <summary>
/// Defines what the behavior should be if we receive a value from the server with an earlier associated
/// time value than the anticipation time value.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Ignore"/>, the stale data will be ignored and the authoritative
/// value will not replace the anticipated value until the anticipation time is reached. <see cref="OnAuthoritativeValueChanged"/>
/// and <see cref="NetworkBehaviour.OnReanticipate"/> will also not be invoked for this stale data.
/// <br/><br/>
/// If this is <see cref="Netcode.StaleDataHandling.Reanticipate"/>, the stale data will replace the anticipated data and
/// <see cref="OnAuthoritativeValueChanged"/> and <see cref="NetworkBehaviour.OnReanticipate"/> will be invoked.
/// In this case, the authoritativeTime value passed to <see cref="NetworkBehaviour.OnReanticipate"/> will be lower than
/// the anticipationTime value, and that callback can be used to calculate a new anticipated value.
/// </summary>
#pragma warning restore IDE0001
public StaleDataHandling StaleDataHandling;
public delegate void OnAuthoritativeValueChangedDelegate(AnticipatedNetworkVariable<T> variable, in T previousValue, in T newValue);
/// <summary>
/// Invoked any time the authoritative value changes, even when the data is stale or has been changed locally.
/// </summary>
public OnAuthoritativeValueChangedDelegate OnAuthoritativeValueChanged = null;
/// <summary>
/// Determines if the difference between the last serialized value and the current value is large enough
/// to serialize it again.
/// </summary>
public event NetworkVariable<T>.CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold
{
add => m_AuthoritativeValue.CheckExceedsDirtinessThreshold += value;
remove => m_AuthoritativeValue.CheckExceedsDirtinessThreshold -= value;
}
private class AnticipatedObject : IAnticipatedObject
{
public AnticipatedNetworkVariable<T> Variable;
public void Update()
{
Variable.Update();
}
public void ResetAnticipation()
{
Variable.ShouldReanticipate = false;
}
public NetworkObject OwnerObject => Variable.m_NetworkBehaviour.NetworkObject;
}
private AnticipatedObject m_AnticipatedObject;
public override void OnInitialize()
{
m_AuthoritativeValue.Initialize(m_NetworkBehaviour);
NetworkVariableSerialization<T>.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue);
NetworkVariableSerialization<T>.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue);
if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null)
{
m_AnticipatedObject = new AnticipatedObject { Variable = this };
m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
}
public override bool ExceedsDirtinessThreshold()
{
return m_AuthoritativeValue.ExceedsDirtinessThreshold();
}
/// <summary>
/// Retrieves the current value for the variable.
/// This is the "display value" for this variable, and is affected by <see cref="Anticipate"/> and
/// <see cref="Smooth"/>, as well as by updates from the authority, depending on <see cref="StaleDataHandling"/>
/// and the behavior of any <see cref="NetworkBehaviour.OnReanticipate"/> callbacks.
/// <br /><br />
/// When a server update arrives, this value will be overwritten
/// by the new server value (unless stale data handling is set
/// to "Ignore" and the update is determined to be stale).
/// This value will be duplicated in
/// <see cref="PreviousAnticipatedValue"/>, which
/// will NOT be overwritten in server updates.
/// </summary>
public T Value => m_AnticipatedValue;
/// <summary>
/// Indicates whether this variable currently needs
/// reanticipation. If this is true, the anticipated value
/// has been overwritten by the authoritative value from the
/// server; the previous anticipated value is stored in <see cref="PreviousAnticipatedState"/>
/// </summary>
public bool ShouldReanticipate
{
get;
private set;
}
/// <summary>
/// Holds the most recent anticipated value, whatever was
/// most recently set using <see cref="Anticipate"/>. Unlike
/// <see cref="Value"/>, this does not get overwritten
/// when a server update arrives.
/// </summary>
public T PreviousAnticipatedValue => m_PreviousAnticipatedValue;
/// <summary>
/// Sets the current value of the variable on the expectation that the authority will set the variable
/// to the same value within one network round trip (i.e., in response to an RPC).
/// </summary>
/// <param name="value"></param>
public void Anticipate(T value)
{
if (m_NetworkBehaviour.NetworkManager.ShutdownInProgress || !m_NetworkBehaviour.NetworkManager.IsListening)
{
return;
}
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
m_LastAnticipationCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.AnticipationCounter;
m_AnticipatedValue = value;
NetworkVariableSerialization<T>.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue);
if (CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
AuthoritativeValue = value;
}
}
#pragma warning disable IDE0001
/// <summary>
/// Retrieves or sets the underlying authoritative value.
/// Note that only a client or server with write permissions to this variable may set this value.
/// When this variable has been anticipated, this value will alawys return the most recent authoritative
/// state, which is updated even if <see cref="StaleDataHandling"/> is <see cref="Netcode.StaleDataHandling.Ignore"/>.
/// </summary>
#pragma warning restore IDE0001
public T AuthoritativeValue
{
get => m_AuthoritativeValue.Value;
set
{
m_SettingAuthoritativeValue = true;
try
{
m_AuthoritativeValue.Value = value;
m_AnticipatedValue = value;
NetworkVariableSerialization<T>.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue);
}
finally
{
m_SettingAuthoritativeValue = false;
}
}
}
/// <summary>
/// A function to interpolate between two values based on a percentage.
/// See <see cref="Mathf.Lerp"/>, <see cref="Vector3.Lerp"/>, <see cref="Vector3.Slerp"/>, and so on
/// for examples.
/// </summary>
public delegate T SmoothDelegate(T authoritativeValue, T anticipatedValue, float amount);
private SmoothDelegate m_SmoothDelegate = null;
public AnticipatedNetworkVariable(T value = default,
StaleDataHandling staleDataHandling = StaleDataHandling.Ignore)
: base()
{
StaleDataHandling = staleDataHandling;
m_AuthoritativeValue = new NetworkVariable<T>(value)
{
OnValueChanged = OnValueChangedInternal
};
}
public void Update()
{
if (m_CurrentSmoothTime < m_SmoothDuration)
{
m_CurrentSmoothTime += m_NetworkBehaviour.NetworkManager.RealTimeProvider.DeltaTime;
var pct = math.min(m_CurrentSmoothTime / m_SmoothDuration, 1f);
m_AnticipatedValue = m_SmoothDelegate(m_SmoothFrom, m_SmoothTo, pct);
NetworkVariableSerialization<T>.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue);
}
}
public override void Dispose()
{
if (m_IsDisposed)
{
return;
}
if (m_NetworkBehaviour != null && m_NetworkBehaviour.NetworkManager != null && m_NetworkBehaviour.NetworkManager.AnticipationSystem != null)
{
if (m_AnticipatedObject != null)
{
m_NetworkBehaviour.NetworkManager.AnticipationSystem.AllAnticipatedObjects.Remove(m_AnticipatedObject);
m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Remove(m_AnticipatedObject);
m_AnticipatedObject = null;
}
}
m_IsDisposed = true;
m_AuthoritativeValue.Dispose();
if (m_AnticipatedValue is IDisposable anticipatedValueDisposable)
{
anticipatedValueDisposable.Dispose();
}
m_AnticipatedValue = default;
if (m_PreviousAnticipatedValue is IDisposable previousValueDisposable)
{
previousValueDisposable.Dispose();
m_PreviousAnticipatedValue = default;
}
if (m_HasSmoothValues)
{
if (m_SmoothFrom is IDisposable smoothFromDisposable)
{
smoothFromDisposable.Dispose();
m_SmoothFrom = default;
}
if (m_SmoothTo is IDisposable smoothToDisposable)
{
smoothToDisposable.Dispose();
m_SmoothTo = default;
}
m_HasSmoothValues = false;
}
}
~AnticipatedNetworkVariable()
{
Dispose();
}
private void OnValueChangedInternal(T previousValue, T newValue)
{
if (!m_SettingAuthoritativeValue)
{
m_LastAuthorityUpdateCounter = m_NetworkBehaviour.NetworkManager.AnticipationSystem.LastAnticipationAck;
if (StaleDataHandling == StaleDataHandling.Ignore && m_LastAnticipationCounter > m_LastAuthorityUpdateCounter)
{
// Keep the anticipated value unchanged because it is more recent than the authoritative one.
return;
}
ShouldReanticipate = true;
m_NetworkBehaviour.NetworkManager.AnticipationSystem.ObjectsToReanticipate.Add(m_AnticipatedObject);
}
NetworkVariableSerialization<T>.Duplicate(AuthoritativeValue, ref m_AnticipatedValue);
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
OnAuthoritativeValueChanged?.Invoke(this, previousValue, newValue);
}
/// <summary>
/// Interpolate this variable from <see cref="from"/> to <see cref="to"/> over <see cref="durationSeconds"/> of
/// real time. The duration uses <see cref="Time.deltaTime"/>, so it is affected by <see cref="Time.timeScale"/>.
/// </summary>
/// <param name="from"></param>
/// <param name="to"></param>
/// <param name="durationSeconds"></param>
/// <param name="how"></param>
public void Smooth(in T from, in T to, float durationSeconds, SmoothDelegate how)
{
if (durationSeconds <= 0)
{
NetworkVariableSerialization<T>.Duplicate(to, ref m_AnticipatedValue);
m_SmoothDuration = 0;
m_CurrentSmoothTime = 0;
m_SmoothDelegate = null;
return;
}
NetworkVariableSerialization<T>.Duplicate(from, ref m_AnticipatedValue);
NetworkVariableSerialization<T>.Duplicate(from, ref m_SmoothFrom);
NetworkVariableSerialization<T>.Duplicate(to, ref m_SmoothTo);
m_SmoothDuration = durationSeconds;
m_CurrentSmoothTime = 0;
m_SmoothDelegate = how;
m_HasSmoothValues = true;
}
public override bool IsDirty()
{
return m_AuthoritativeValue.IsDirty();
}
public override void ResetDirty()
{
m_AuthoritativeValue.ResetDirty();
}
public override void WriteDelta(FastBufferWriter writer)
{
m_AuthoritativeValue.WriteDelta(writer);
}
public override void WriteField(FastBufferWriter writer)
{
m_AuthoritativeValue.WriteField(writer);
}
public override void ReadField(FastBufferReader reader)
{
m_AuthoritativeValue.ReadField(reader);
NetworkVariableSerialization<T>.Duplicate(m_AuthoritativeValue.Value, ref m_AnticipatedValue);
NetworkVariableSerialization<T>.Duplicate(m_AnticipatedValue, ref m_PreviousAnticipatedValue);
}
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
m_AuthoritativeValue.ReadDelta(reader, keepDirtyDelta);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 10f5188736b742d1993a2aad46a03e78
timeCreated: 1705595868

View File

@@ -0,0 +1,746 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
using Unity.Mathematics;
namespace Unity.Netcode
{
internal static class CollectionSerializationUtility
{
public static void WriteNativeArrayDelta<T>(FastBufferWriter writer, ref NativeArray<T> value, ref NativeArray<T> previousValue) where T : unmanaged
{
// This bit vector serializes the list of which fields have changed using 1 bit per field.
// This will always be 1 bit per field of the whole array (rounded up to the nearest 8 bits)
// even if there is only one change, so as compared to serializing the index with each item,
// this will use more bandwidth when the overall bandwidth usage is small and the array is large,
// but less when the overall bandwidth usage is large. So it optimizes for the worst case while accepting
// some reduction in efficiency in the best case.
using var changes = new ResizableBitVector(Allocator.Temp);
int minLength = math.min(value.Length, previousValue.Length);
var numChanges = 0;
// Iterate the array, checking which values have changed and marking that in the bit vector
for (var i = 0; i < minLength; ++i)
{
var val = value[i];
var prevVal = previousValue[i];
if (!NetworkVariableSerialization<T>.AreEqual(ref val, ref prevVal))
{
++numChanges;
changes.Set(i);
}
}
// Mark any newly added items as well
// We don't need to mark removed items because they are captured by serializing the length
for (var i = previousValue.Length; i < value.Length; ++i)
{
++numChanges;
changes.Set(i);
}
// If the size of serializing the dela is greater than the size of serializing the whole array (i.e.,
// because almost the entire array has changed and the overhead of the change set increases bandwidth),
// then we just do a normal full serialization instead of a delta.
if (changes.GetSerializedSize() + FastBufferWriter.GetWriteSize<T>() * numChanges > FastBufferWriter.GetWriteSize<T>() * value.Length)
{
// 1 = full serialization
writer.WriteByteSafe(1);
writer.WriteValueSafe(value);
return;
}
// 0 = delta serialization
writer.WriteByte(0);
// Write the length, which will be used on the read side to resize the array
BytePacker.WriteValuePacked(writer, value.Length);
writer.WriteValueSafe(changes);
unsafe
{
var ptr = (T*)value.GetUnsafePtr();
var prevPtr = (T*)previousValue.GetUnsafePtr();
for (int i = 0; i < value.Length; ++i)
{
if (changes.IsSet(i))
{
if (i < previousValue.Length)
{
// If we have an item in the previous array for this index, we can do nested deltas!
NetworkVariableSerialization<T>.WriteDelta(writer, ref ptr[i], ref prevPtr[i]);
}
else
{
// If not, just write it normally
NetworkVariableSerialization<T>.Write(writer, ref ptr[i]);
}
}
}
}
}
public static void ReadNativeArrayDelta<T>(FastBufferReader reader, ref NativeArray<T> value) where T : unmanaged
{
// 1 = full serialization, 0 = delta serialization
reader.ReadByteSafe(out byte full);
if (full == 1)
{
// If we're doing full serialization, we fall back on reading the whole array.
value.Dispose();
reader.ReadValueSafe(out value, Allocator.Persistent);
return;
}
// If not, first read the length and the change bits
ByteUnpacker.ReadValuePacked(reader, out int length);
var changes = new ResizableBitVector(Allocator.Temp);
using var toDispose = changes;
{
reader.ReadNetworkSerializableInPlace(ref changes);
// If the length has changed, we need to resize.
// NativeArray is not resizeable, so we have to dispose and allocate a new one.
var previousLength = value.Length;
if (length != value.Length)
{
var newArray = new NativeArray<T>(length, Allocator.Persistent);
unsafe
{
UnsafeUtility.MemCpy(newArray.GetUnsafePtr(), value.GetUnsafePtr(), math.min(newArray.Length * sizeof(T), value.Length * sizeof(T)));
}
value.Dispose();
value = newArray;
}
unsafe
{
var ptr = (T*)value.GetUnsafePtr();
for (var i = 0; i < value.Length; ++i)
{
if (changes.IsSet(i))
{
if (i < previousLength)
{
// If we have an item to read a delta into, read it as a delta
NetworkVariableSerialization<T>.ReadDelta(reader, ref ptr[i]);
}
else
{
// If not, read as a standard element
NetworkVariableSerialization<T>.Read(reader, ref ptr[i]);
}
}
}
}
}
}
public static void WriteListDelta<T>(FastBufferWriter writer, ref List<T> value, ref List<T> previousValue)
{
// Lists can be null, so we have to handle that case.
// We do that by marking this as a full serialization and using the existing null handling logic
// in NetworkVariableSerialization<List<T>>
if (value == null || previousValue == null)
{
writer.WriteByteSafe(1);
NetworkVariableSerialization<List<T>>.Write(writer, ref value);
return;
}
// This bit vector serializes the list of which fields have changed using 1 bit per field.
// This will always be 1 bit per field of the whole array (rounded up to the nearest 8 bits)
// even if there is only one change, so as compared to serializing the index with each item,
// this will use more bandwidth when the overall bandwidth usage is small and the array is large,
// but less when the overall bandwidth usage is large. So it optimizes for the worst case while accepting
// some reduction in efficiency in the best case.
using var changes = new ResizableBitVector(Allocator.Temp);
int minLength = math.min(value.Count, previousValue.Count);
var numChanges = 0;
// Iterate the list, checking which values have changed and marking that in the bit vector
for (var i = 0; i < minLength; ++i)
{
var val = value[i];
var prevVal = previousValue[i];
if (!NetworkVariableSerialization<T>.AreEqual(ref val, ref prevVal))
{
++numChanges;
changes.Set(i);
}
}
// Mark any newly added items as well
// We don't need to mark removed items because they are captured by serializing the length
for (var i = previousValue.Count; i < value.Count; ++i)
{
++numChanges;
changes.Set(i);
}
// If the size of serializing the dela is greater than the size of serializing the whole array (i.e.,
// because almost the entire array has changed and the overhead of the change set increases bandwidth),
// then we just do a normal full serialization instead of a delta.
// In the case of List<T>, it's difficult to know exactly what the serialized size is going to be before
// we serialize it, so we fudge it.
if (numChanges >= value.Count * 0.9)
{
// 1 = full serialization
writer.WriteByteSafe(1);
NetworkVariableSerialization<List<T>>.Write(writer, ref value);
return;
}
// 0 = delta serialization
writer.WriteByteSafe(0);
// Write the length, which will be used on the read side to resize the list
BytePacker.WriteValuePacked(writer, value.Count);
writer.WriteValueSafe(changes);
for (int i = 0; i < value.Count; ++i)
{
if (changes.IsSet(i))
{
var reffable = value[i];
if (i < previousValue.Count)
{
// If we have an item in the previous array for this index, we can do nested deltas!
var prevReffable = previousValue[i];
NetworkVariableSerialization<T>.WriteDelta(writer, ref reffable, ref prevReffable);
}
else
{
// If not, just write it normally.
NetworkVariableSerialization<T>.Write(writer, ref reffable);
}
}
}
}
public static void ReadListDelta<T>(FastBufferReader reader, ref List<T> value)
{
// 1 = full serialization, 0 = delta serialization
reader.ReadByteSafe(out byte full);
if (full == 1)
{
// If we're doing full serialization, we fall back on reading the whole list.
NetworkVariableSerialization<List<T>>.Read(reader, ref value);
return;
}
// If not, first read the length and the change bits
ByteUnpacker.ReadValuePacked(reader, out int length);
var changes = new ResizableBitVector(Allocator.Temp);
using var toDispose = changes;
{
reader.ReadNetworkSerializableInPlace(ref changes);
// If the list shrank, we need to resize it down.
// List<T> has no method to reserve space for future elements,
// so if we have to grow it, we just do that using Add() below.
if (length < value.Count)
{
value.RemoveRange(length, value.Count - length);
}
for (var i = 0; i < length; ++i)
{
if (changes.IsSet(i))
{
if (i < value.Count)
{
// If we have an item to read a delta into, read it as a delta
T item = value[i];
NetworkVariableSerialization<T>.ReadDelta(reader, ref item);
value[i] = item;
}
else
{
// If not, just read it as a standard item.
T item = default;
NetworkVariableSerialization<T>.Read(reader, ref item);
value.Add(item);
}
}
}
}
}
// For HashSet and Dictionary, we need to have some local space to hold lists we need to serialize.
// We don't want to do allocations all the time and we know each one needs a maximum of three lists,
// so we're going to keep static lists that we can reuse in these methods.
private static class ListCache<T>
{
private static List<T> s_AddedList = new List<T>();
private static List<T> s_RemovedList = new List<T>();
private static List<T> s_ChangedList = new List<T>();
public static List<T> GetAddedList()
{
s_AddedList.Clear();
return s_AddedList;
}
public static List<T> GetRemovedList()
{
s_RemovedList.Clear();
return s_RemovedList;
}
public static List<T> GetChangedList()
{
s_ChangedList.Clear();
return s_ChangedList;
}
}
public static void WriteHashSetDelta<T>(FastBufferWriter writer, ref HashSet<T> value, ref HashSet<T> previousValue) where T : IEquatable<T>
{
// HashSets can be null, so we have to handle that case.
// We do that by marking this as a full serialization and using the existing null handling logic
// in NetworkVariableSerialization<HashSet<T>>
if (value == null || previousValue == null)
{
writer.WriteByteSafe(1);
NetworkVariableSerialization<HashSet<T>>.Write(writer, ref value);
return;
}
// No changed array because a set can't have a "changed" element, only added and removed.
var added = ListCache<T>.GetAddedList();
var removed = ListCache<T>.GetRemovedList();
// collect the new elements
foreach (var item in value)
{
if (!previousValue.Contains(item))
{
added.Add(item);
}
}
// collect the removed elements
foreach (var item in previousValue)
{
if (!value.Contains(item))
{
removed.Add(item);
}
}
// If we've got more changes than total items, we just do a full serialization
if (added.Count + removed.Count >= value.Count)
{
writer.WriteByteSafe(1);
NetworkVariableSerialization<HashSet<T>>.Write(writer, ref value);
return;
}
writer.WriteByteSafe(0);
// Write out the added and removed arrays.
writer.WriteValueSafe(added.Count);
for (var i = 0; i < added.Count; ++i)
{
var item = added[i];
NetworkVariableSerialization<T>.Write(writer, ref item);
}
writer.WriteValueSafe(removed.Count);
for (var i = 0; i < removed.Count; ++i)
{
var item = removed[i];
NetworkVariableSerialization<T>.Write(writer, ref item);
}
}
public static void ReadHashSetDelta<T>(FastBufferReader reader, ref HashSet<T> value) where T : IEquatable<T>
{
// 1 = full serialization, 0 = delta serialization
reader.ReadByteSafe(out byte full);
if (full != 0)
{
NetworkVariableSerialization<HashSet<T>>.Read(reader, ref value);
return;
}
// Read in the added and removed values
reader.ReadValueSafe(out int addedCount);
for (var i = 0; i < addedCount; ++i)
{
T item = default;
NetworkVariableSerialization<T>.Read(reader, ref item);
value.Add(item);
}
reader.ReadValueSafe(out int removedCount);
for (var i = 0; i < removedCount; ++i)
{
T item = default;
NetworkVariableSerialization<T>.Read(reader, ref item);
value.Remove(item);
}
}
public static void WriteDictionaryDelta<TKey, TVal>(FastBufferWriter writer, ref Dictionary<TKey, TVal> value, ref Dictionary<TKey, TVal> previousValue)
where TKey : IEquatable<TKey>
{
if (value == null || previousValue == null)
{
writer.WriteByteSafe(1);
NetworkVariableSerialization<Dictionary<TKey, TVal>>.Write(writer, ref value);
return;
}
var added = ListCache<KeyValuePair<TKey, TVal>>.GetAddedList();
var changed = ListCache<KeyValuePair<TKey, TVal>>.GetRemovedList();
var removed = ListCache<KeyValuePair<TKey, TVal>>.GetChangedList();
// Collect items that have been added or have changed
foreach (var item in value)
{
var val = item.Value;
var hasPrevVal = previousValue.TryGetValue(item.Key, out var prevVal);
if (!hasPrevVal)
{
added.Add(item);
}
else if (!NetworkVariableSerialization<TVal>.AreEqual(ref val, ref prevVal))
{
changed.Add(item);
}
}
// collect the items that have been removed
foreach (var item in previousValue)
{
if (!value.ContainsKey(item.Key))
{
removed.Add(item);
}
}
// If there are more changes than total values, just do a full serialization
if (added.Count + removed.Count + changed.Count >= value.Count)
{
writer.WriteByteSafe(1);
NetworkVariableSerialization<Dictionary<TKey, TVal>>.Write(writer, ref value);
return;
}
writer.WriteByteSafe(0);
// Else, write out the added, removed, and changed arrays
writer.WriteValueSafe(added.Count);
for (var i = 0; i < added.Count; ++i)
{
(var key, var val) = (added[i].Key, added[i].Value);
NetworkVariableSerialization<TKey>.Write(writer, ref key);
NetworkVariableSerialization<TVal>.Write(writer, ref val);
}
writer.WriteValueSafe(removed.Count);
for (var i = 0; i < removed.Count; ++i)
{
var key = removed[i].Key;
NetworkVariableSerialization<TKey>.Write(writer, ref key);
}
writer.WriteValueSafe(changed.Count);
for (var i = 0; i < changed.Count; ++i)
{
(var key, var val) = (changed[i].Key, changed[i].Value);
NetworkVariableSerialization<TKey>.Write(writer, ref key);
NetworkVariableSerialization<TVal>.Write(writer, ref val);
}
}
public static void ReadDictionaryDelta<TKey, TVal>(FastBufferReader reader, ref Dictionary<TKey, TVal> value)
where TKey : IEquatable<TKey>
{
// 1 = full serialization, 0 = delta serialization
reader.ReadByteSafe(out byte full);
if (full != 0)
{
NetworkVariableSerialization<Dictionary<TKey, TVal>>.Read(reader, ref value);
return;
}
// Added
reader.ReadValueSafe(out int length);
for (var i = 0; i < length; ++i)
{
(TKey key, TVal val) = (default, default);
NetworkVariableSerialization<TKey>.Read(reader, ref key);
NetworkVariableSerialization<TVal>.Read(reader, ref val);
value.Add(key, val);
}
// Removed
reader.ReadValueSafe(out length);
for (var i = 0; i < length; ++i)
{
TKey key = default;
NetworkVariableSerialization<TKey>.Read(reader, ref key);
value.Remove(key);
}
// Changed
reader.ReadValueSafe(out length);
for (var i = 0; i < length; ++i)
{
(TKey key, TVal val) = (default, default);
NetworkVariableSerialization<TKey>.Read(reader, ref key);
NetworkVariableSerialization<TVal>.Read(reader, ref val);
value[key] = val;
}
}
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public static void WriteNativeListDelta<T>(FastBufferWriter writer, ref NativeList<T> value, ref NativeList<T> previousValue) where T : unmanaged
{
// See WriteListDelta and WriteNativeArrayDelta to understand most of this. It's basically the same,
// just adjusted for the NativeList API
using var changes = new ResizableBitVector(Allocator.Temp);
int minLength = math.min(value.Length, previousValue.Length);
var numChanges = 0;
for (var i = 0; i < minLength; ++i)
{
var val = value[i];
var prevVal = previousValue[i];
if (!NetworkVariableSerialization<T>.AreEqual(ref val, ref prevVal))
{
++numChanges;
changes.Set(i);
}
}
for (var i = previousValue.Length; i < value.Length; ++i)
{
++numChanges;
changes.Set(i);
}
if (changes.GetSerializedSize() + FastBufferWriter.GetWriteSize<T>() * numChanges > FastBufferWriter.GetWriteSize<T>() * value.Length)
{
writer.WriteByteSafe(1);
writer.WriteValueSafe(value);
return;
}
writer.WriteByte(0);
BytePacker.WriteValuePacked(writer, value.Length);
writer.WriteValueSafe(changes);
unsafe
{
var ptr = (T*)value.GetUnsafePtr();
var prevPtr = (T*)previousValue.GetUnsafePtr();
for (int i = 0; i < value.Length; ++i)
{
if (changes.IsSet(i))
{
if (i < previousValue.Length)
{
NetworkVariableSerialization<T>.WriteDelta(writer, ref ptr[i], ref prevPtr[i]);
}
else
{
NetworkVariableSerialization<T>.Write(writer, ref ptr[i]);
}
}
}
}
}
public static void ReadNativeListDelta<T>(FastBufferReader reader, ref NativeList<T> value) where T : unmanaged
{
// See ReadListDelta and ReadNativeArrayDelta to understand most of this. It's basically the same,
// just adjusted for the NativeList API
reader.ReadByteSafe(out byte full);
if (full == 1)
{
reader.ReadValueSafeInPlace(ref value);
return;
}
ByteUnpacker.ReadValuePacked(reader, out int length);
var changes = new ResizableBitVector(Allocator.Temp);
using var toDispose = changes;
{
reader.ReadNetworkSerializableInPlace(ref changes);
var previousLength = value.Length;
// The one big difference between this and NativeArray/List is that NativeList supports
// easy and fast resizing and reserving space.
if (length != value.Length)
{
value.Resize(length, NativeArrayOptions.UninitializedMemory);
}
unsafe
{
var ptr = (T*)value.GetUnsafePtr();
for (var i = 0; i < value.Length; ++i)
{
if (changes.IsSet(i))
{
if (i < previousLength)
{
NetworkVariableSerialization<T>.ReadDelta(reader, ref ptr[i]);
}
else
{
NetworkVariableSerialization<T>.Read(reader, ref ptr[i]);
}
}
}
}
}
}
public static unsafe void WriteNativeHashSetDelta<T>(FastBufferWriter writer, ref NativeHashSet<T> value, ref NativeHashSet<T> previousValue) where T : unmanaged, IEquatable<T>
{
// See WriteHashSet; this is the same algorithm, adjusted for the NativeHashSet API
var added = stackalloc T[value.Count()];
var removed = stackalloc T[previousValue.Count()];
var addedCount = 0;
var removedCount = 0;
foreach (var item in value)
{
if (!previousValue.Contains(item))
{
added[addedCount] = item;
++addedCount;
}
}
foreach (var item in previousValue)
{
if (!value.Contains(item))
{
removed[removedCount] = item;
++removedCount;
}
}
if (addedCount + removedCount >= value.Count())
{
writer.WriteByteSafe(1);
writer.WriteValueSafe(value);
return;
}
writer.WriteByteSafe(0);
writer.WriteValueSafe(addedCount);
for (var i = 0; i < addedCount; ++i)
{
NetworkVariableSerialization<T>.Write(writer, ref added[i]);
}
writer.WriteValueSafe(removedCount);
for (var i = 0; i < removedCount; ++i)
{
NetworkVariableSerialization<T>.Write(writer, ref removed[i]);
}
}
public static void ReadNativeHashSetDelta<T>(FastBufferReader reader, ref NativeHashSet<T> value) where T : unmanaged, IEquatable<T>
{
// See ReadHashSet; this is the same algorithm, adjusted for the NativeHashSet API
reader.ReadByteSafe(out byte full);
if (full != 0)
{
reader.ReadValueSafeInPlace(ref value);
return;
}
reader.ReadValueSafe(out int addedCount);
for (var i = 0; i < addedCount; ++i)
{
T item = default;
NetworkVariableSerialization<T>.Read(reader, ref item);
value.Add(item);
}
reader.ReadValueSafe(out int removedCount);
for (var i = 0; i < removedCount; ++i)
{
T item = default;
NetworkVariableSerialization<T>.Read(reader, ref item);
value.Remove(item);
}
}
public static unsafe void WriteNativeHashMapDelta<TKey, TVal>(FastBufferWriter writer, ref NativeHashMap<TKey, TVal> value, ref NativeHashMap<TKey, TVal> previousValue)
where TKey : unmanaged, IEquatable<TKey>
where TVal : unmanaged
{
// See WriteDictionary; this is the same algorithm, adjusted for the NativeHashMap API
var added = stackalloc KeyValue<TKey, TVal>[value.Count()];
var changed = stackalloc KeyValue<TKey, TVal>[value.Count()];
var removed = stackalloc KeyValue<TKey, TVal>[previousValue.Count()];
var addedCount = 0;
var changedCount = 0;
var removedCount = 0;
foreach (var item in value)
{
var hasPrevVal = previousValue.TryGetValue(item.Key, out var prevVal);
if (!hasPrevVal)
{
added[addedCount] = item;
++addedCount;
}
else if (!NetworkVariableSerialization<TVal>.AreEqual(ref item.Value, ref prevVal))
{
changed[changedCount] = item;
++changedCount;
}
}
foreach (var item in previousValue)
{
if (!value.ContainsKey(item.Key))
{
removed[removedCount] = item;
++removedCount;
}
}
if (addedCount + removedCount + changedCount >= value.Count())
{
writer.WriteByteSafe(1);
writer.WriteValueSafe(value);
return;
}
writer.WriteByteSafe(0);
writer.WriteValueSafe(addedCount);
for (var i = 0; i < addedCount; ++i)
{
(var key, var val) = (added[i].Key, added[i].Value);
NetworkVariableSerialization<TKey>.Write(writer, ref key);
NetworkVariableSerialization<TVal>.Write(writer, ref val);
}
writer.WriteValueSafe(removedCount);
for (var i = 0; i < removedCount; ++i)
{
var key = removed[i].Key;
NetworkVariableSerialization<TKey>.Write(writer, ref key);
}
writer.WriteValueSafe(changedCount);
for (var i = 0; i < changedCount; ++i)
{
(var key, var val) = (changed[i].Key, changed[i].Value);
NetworkVariableSerialization<TKey>.Write(writer, ref key);
NetworkVariableSerialization<TVal>.Write(writer, ref val);
}
}
public static void ReadNativeHashMapDelta<TKey, TVal>(FastBufferReader reader, ref NativeHashMap<TKey, TVal> value)
where TKey : unmanaged, IEquatable<TKey>
where TVal : unmanaged
{
// See ReadDictionary; this is the same algorithm, adjusted for the NativeHashMap API
reader.ReadByteSafe(out byte full);
if (full != 0)
{
reader.ReadValueSafeInPlace(ref value);
return;
}
// Added
reader.ReadValueSafe(out int length);
for (var i = 0; i < length; ++i)
{
(TKey key, TVal val) = (default, default);
NetworkVariableSerialization<TKey>.Read(reader, ref key);
NetworkVariableSerialization<TVal>.Read(reader, ref val);
value.Add(key, val);
}
// Removed
reader.ReadValueSafe(out length);
for (var i = 0; i < length; ++i)
{
TKey key = default;
NetworkVariableSerialization<TKey>.Read(reader, ref key);
value.Remove(key);
}
// Changed
reader.ReadValueSafe(out length);
for (var i = 0; i < length; ++i)
{
(TKey key, TVal val) = (default, default);
NetworkVariableSerialization<TKey>.Read(reader, ref key);
NetworkVariableSerialization<TVal>.Read(reader, ref val);
value[key] = val;
}
}
#endif
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c822ece4e24f4676861e07288a7f8526
timeCreated: 1705437250

View File

@@ -369,7 +369,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return;
}
m_List.Add(item);
@@ -390,7 +391,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return;
}
m_List.Clear();
@@ -416,7 +418,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return false;
}
int index = m_List.IndexOf(item);
@@ -451,7 +454,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return;
}
if (index < m_List.Length)
@@ -480,7 +484,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return;
}
var value = m_List[index];
@@ -505,7 +510,8 @@ namespace Unity.Netcode
// check write permissions
if (!CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkList");
LogWritePermissionError();
return;
}
var previousValue = m_List[index];

View File

@@ -22,6 +22,28 @@ namespace Unity.Netcode
/// </summary>
public OnValueChangedDelegate OnValueChanged;
public delegate bool CheckExceedsDirtinessThresholdDelegate(in T previousValue, in T newValue);
public CheckExceedsDirtinessThresholdDelegate CheckExceedsDirtinessThreshold;
public override bool ExceedsDirtinessThreshold()
{
if (CheckExceedsDirtinessThreshold != null && m_HasPreviousValue)
{
return CheckExceedsDirtinessThreshold(m_PreviousValue, m_InternalValue);
}
return true;
}
public override void OnInitialize()
{
base.OnInitialize();
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
/// <summary>
/// Constructor for <see cref="NetworkVariable{T}"/>
/// </summary>
@@ -57,25 +79,57 @@ namespace Unity.Netcode
/// <summary>
/// The value of the NetworkVariable container
/// </summary>
/// <remarks>
/// When assigning collections to <see cref="Value"/>, unless it is a completely new collection this will not
/// detect any deltas with most managed collection classes since assignment of one collection value to another
/// is actually just a reference to the collection itself. <br />
/// To detect deltas in a collection, you should invoke <see cref="CheckDirtyState"/> after making modifications to the collection.
/// </remarks>
public virtual T Value
{
get => m_InternalValue;
set
{
// Compare bitwise
if (NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId))
{
LogWritePermissionError();
return;
}
if (m_NetworkBehaviour && !CanClientWrite(m_NetworkBehaviour.NetworkManager.LocalClientId))
// Compare the Value being applied to the current value
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref value))
{
throw new InvalidOperationException("Client is not allowed to write to this NetworkVariable");
T previousValue = m_InternalValue;
m_InternalValue = value;
SetDirty(true);
m_IsDisposed = false;
OnValueChanged?.Invoke(previousValue, m_InternalValue);
}
}
}
Set(value);
/// <summary>
/// Invoke this method to check if a collection's items are dirty.
/// The default behavior is to exit early if the <see cref="NetworkVariable{T}"/> is already dirty.
/// </summary>
/// <param name="forceCheck"> when true, this check will force a full item collection check even if the NetworkVariable is already dirty</param>
/// <remarks>
/// This is to be used as a way to check if a <see cref="NetworkVariable{T}"/> containing a managed collection has any changees to the collection items.<br />
/// If you invoked this when a collection is dirty, it will not trigger the <see cref="OnValueChanged"/> unless you set <param name="forceCheck"/> to true. <br />
/// </remarks>
public bool CheckDirtyState(bool forceCheck = false)
{
var isDirty = base.IsDirty();
// Compare the previous with the current if not dirty or forcing a check.
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
{
SetDirty(true);
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
m_IsDisposed = false;
isDirty = true;
}
return isDirty;
}
internal ref T RefValue()
@@ -142,16 +196,16 @@ namespace Unity.Netcode
/// </summary>
public override void ResetDirty()
{
base.ResetDirty();
// Resetting the dirty value declares that the current value is not dirty
// Therefore, we set the m_PreviousValue field to a duplicate of the current
// field, so that our next dirty check is made against the current "not dirty"
// value.
if (!m_HasPreviousValue || !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_PreviousValue))
if (IsDirty())
{
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
base.ResetDirty();
}
/// <summary>
@@ -162,9 +216,8 @@ namespace Unity.Netcode
private protected void Set(T value)
{
SetDirty(true);
T previousValue = m_InternalValue;
m_InternalValue = value;
OnValueChanged?.Invoke(previousValue, m_InternalValue);
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
}
/// <summary>
@@ -173,7 +226,7 @@ namespace Unity.Netcode
/// <param name="writer">The stream to write the value to</param>
public override void WriteDelta(FastBufferWriter writer)
{
WriteField(writer);
NetworkVariableSerialization<T>.WriteDelta(writer, ref m_InternalValue, ref m_PreviousValue);
}
/// <summary>
@@ -183,20 +236,22 @@ namespace Unity.Netcode
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
// In order to get managed collections to properly have a previous and current value, we have to
// duplicate the collection at this point before making any modifications to the current.
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
// todo:
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
// would be stored in different fields
T previousValue = m_InternalValue;
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
if (keepDirtyDelta)
{
SetDirty(true);
}
OnValueChanged?.Invoke(previousValue, m_InternalValue);
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
}
/// <inheritdoc />

View File

@@ -3,11 +3,26 @@ using UnityEngine;
namespace Unity.Netcode
{
public struct NetworkVariableUpdateTraits
{
[Tooltip("The minimum amount of time that must pass between sending updates. If this amount of time has not passed since the last update, dirtiness will be ignored.")]
public float MinSecondsBetweenUpdates;
[Tooltip("The maximum amount of time that a variable can be dirty without sending an update. If this amount of time has passed since the last update, an update will be sent even if the dirtiness threshold has not been met.")]
public float MaxSecondsBetweenUpdates;
}
/// <summary>
/// Interface for network value containers
/// </summary>
public abstract class NetworkVariableBase : IDisposable
{
[SerializeField]
internal NetworkVariableUpdateTraits UpdateTraits = default;
[NonSerialized]
internal double LastUpdateSent;
/// <summary>
/// The delivery type (QoS) to send data with
/// </summary>
@@ -17,19 +32,82 @@ namespace Unity.Netcode
/// Maintains a link to the associated NetworkBehaviour
/// </summary>
private protected NetworkBehaviour m_NetworkBehaviour;
private NetworkManager m_InternalNetworkManager;
public NetworkBehaviour GetBehaviour()
{
return m_NetworkBehaviour;
}
internal string GetWritePermissionError()
{
return $"|Client-{m_NetworkManager.LocalClientId}|{m_NetworkBehaviour.name}|{Name}| Write permissions ({WritePerm}) for this client instance is not allowed!";
}
internal void LogWritePermissionError()
{
Debug.LogError(GetWritePermissionError());
}
private protected NetworkManager m_NetworkManager
{
get
{
if (m_InternalNetworkManager == null && m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
{
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
}
return m_InternalNetworkManager;
}
}
/// <summary>
/// Initializes the NetworkVariable
/// </summary>
/// <param name="networkBehaviour">The NetworkBehaviour the NetworkVariable belongs to</param>
public void Initialize(NetworkBehaviour networkBehaviour)
{
m_InternalNetworkManager = null;
m_NetworkBehaviour = networkBehaviour;
if (m_NetworkBehaviour && m_NetworkBehaviour.NetworkObject?.NetworkManager)
{
m_InternalNetworkManager = m_NetworkBehaviour.NetworkObject?.NetworkManager;
if (m_NetworkBehaviour.NetworkManager.NetworkTimeSystem != null)
{
UpdateLastSentTime();
}
}
OnInitialize();
}
/// <summary>
/// Called on initialization
/// </summary>
public virtual void OnInitialize()
{
}
/// <summary>
/// Sets the update traits for this network variable to determine how frequently it will send updates.
/// </summary>
/// <param name="traits"></param>
public void SetUpdateTraits(NetworkVariableUpdateTraits traits)
{
UpdateTraits = traits;
}
/// <summary>
/// Check whether or not this variable has changed significantly enough to send an update.
/// If not, no update will be sent even if the variable is dirty, unless the time since last update exceeds
/// the <see cref="UpdateTraits"/>' <see cref="NetworkVariableUpdateTraits.MaxSecondsBetweenUpdates"/>.
/// </summary>
/// <returns></returns>
public virtual bool ExceedsDirtinessThreshold()
{
return true;
}
/// <summary>
@@ -92,6 +170,25 @@ namespace Unity.Netcode
}
}
internal bool CanSend()
{
var timeSinceLastUpdate = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime - LastUpdateSent;
return
(
UpdateTraits.MaxSecondsBetweenUpdates > 0 &&
timeSinceLastUpdate >= UpdateTraits.MaxSecondsBetweenUpdates
) ||
(
timeSinceLastUpdate >= UpdateTraits.MinSecondsBetweenUpdates &&
ExceedsDirtinessThreshold()
);
}
internal void UpdateLastSentTime()
{
LastUpdateSent = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime;
}
protected void MarkNetworkBehaviourDirty()
{
if (m_NetworkBehaviour == null)
@@ -109,6 +206,16 @@ namespace Unity.Netcode
}
return;
}
if (!m_NetworkBehaviour.NetworkManager.IsListening)
{
if (m_NetworkBehaviour.NetworkManager.LogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"NetworkVariable is written to after the NetworkManager has already shutdown! " +
"Are you modifying a NetworkVariable within a NetworkBehaviour.OnDestroy or NetworkBehaviour.OnDespawn method?");
}
return;
}
m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject);
}
@@ -136,6 +243,11 @@ namespace Unity.Netcode
/// <returns>Whether or not the client has permission to read</returns>
public bool CanClientRead(ulong clientId)
{
if (!m_NetworkBehaviour)
{
return false;
}
switch (ReadPerm)
{
default:
@@ -153,6 +265,11 @@ namespace Unity.Netcode
/// <returns>Whether or not the client has permission to write</returns>
public bool CanClientWrite(ulong clientId)
{
if (!m_NetworkBehaviour)
{
return false;
}
switch (WritePerm)
{
default:

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,107 @@
using System;
using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe;
namespace Unity.Netcode
{
/// <summary>
/// This is a simple resizable bit vector - i.e., a list of flags that use 1 bit each and can
/// grow to an indefinite size. This is backed by a NativeList&lt;byte&gt; instead of a single
/// integer value, allowing it to contain any size of memory. Contains built-in serialization support.
/// </summary>
internal struct ResizableBitVector : INetworkSerializable, IDisposable
{
private NativeList<byte> m_Bits;
private const int k_Divisor = sizeof(byte) * 8;
public ResizableBitVector(Allocator allocator)
{
m_Bits = new NativeList<byte>(allocator);
}
public void Dispose()
{
m_Bits.Dispose();
}
public int GetSerializedSize()
{
return sizeof(int) + m_Bits.Length;
}
private (int, int) GetBitData(int i)
{
var index = i / k_Divisor;
var bitWithinIndex = i % k_Divisor;
return (index, bitWithinIndex);
}
/// <summary>
/// Set bit 'i' - i.e., bit 0 is 00000001, bit 1 is 00000010, and so on.
/// There is no upper bound on i except for the memory available in the system.
/// </summary>
/// <param name="i"></param>
public void Set(int i)
{
var (index, bitWithinIndex) = GetBitData(i);
if (index >= m_Bits.Length)
{
m_Bits.Resize(index + 1, NativeArrayOptions.ClearMemory);
}
m_Bits[index] |= (byte)(1 << bitWithinIndex);
}
/// <summary>
/// Unset bit 'i' - i.e., bit 0 is 00000001, bit 1 is 00000010, and so on.
/// There is no upper bound on i except for the memory available in the system.
/// Note that once a BitVector has grown to a certain size, it will not shrink back down,
/// so if you set and unset every bit, it will still serialize at its high watermark size.
/// </summary>
/// <param name="i"></param>
public void Unset(int i)
{
var (index, bitWithinIndex) = GetBitData(i);
if (index >= m_Bits.Length)
{
return;
}
m_Bits[index] &= (byte)~(1 << bitWithinIndex);
}
/// <summary>
/// Check if bit 'i' is set - i.e., bit 0 is 00000001, bit 1 is 00000010, and so on.
/// There is no upper bound on i except for the memory available in the system.
/// </summary>
/// <param name="i"></param>
public bool IsSet(int i)
{
var (index, bitWithinIndex) = GetBitData(i);
if (index >= m_Bits.Length)
{
return false;
}
return (m_Bits[index] & (byte)(1 << bitWithinIndex)) != 0;
}
public unsafe void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
var length = m_Bits.Length;
serializer.SerializeValue(ref length);
m_Bits.ResizeUninitialized(length);
var ptr = m_Bits.GetUnsafePtr();
{
if (serializer.IsReader)
{
serializer.GetFastBufferReader().ReadBytesSafe((byte*)ptr, length);
}
else
{
serializer.GetFastBufferWriter().WriteBytesSafe((byte*)ptr, length);
}
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 664696a622e244dfa43b26628c05e4a6
timeCreated: 1705437231

View File

@@ -226,6 +226,11 @@ namespace Unity.Netcode
foreach (var sceneToUnload in m_ScenesToUnload)
{
SceneManager.UnloadSceneAsync(sceneToUnload);
// Update the ScenesLoaded when we unload scenes
if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.handle))
{
sceneManager.ScenesLoaded.Remove(sceneToUnload.handle);
}
}
}

View File

@@ -1688,6 +1688,17 @@ namespace Unity.Netcode
}
}
foreach (var keyValuePairByGlobalObjectIdHash in ScenePlacedObjects)
{
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
{
if (!keyValuePairBySceneHandle.Value.IsPlayerObject)
{
keyValuePairBySceneHandle.Value.InternalInSceneNetworkObjectsSpawned();
}
}
}
// Add any despawned when spawned in-scene placed NetworkObjects to the scene event data
sceneEventData.AddDespawnedInSceneNetworkObjects();
@@ -2142,6 +2153,12 @@ namespace Unity.Netcode
OnSynchronizeComplete?.Invoke(NetworkManager.LocalClientId);
// For convenience, notify all NetworkBehaviours that synchronization is complete.
foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList)
{
networkObject.InternalNetworkSessionSynchronized();
}
EndSceneEvent(sceneEventId);
}
break;

View File

@@ -245,6 +245,73 @@ namespace Unity.Netcode
}
}
/// <summary>
/// Used with SortParentedNetworkObjects to sort the children of the root parent NetworkObject
/// </summary>
/// <param name="first">object to be sorted</param>
/// <param name="second">object to be compared to for sorting the first object</param>
/// <returns></returns>
private int SortChildrenNetworkObjects(NetworkObject first, NetworkObject second)
{
var firstParent = first.GetCachedParent()?.GetComponent<NetworkObject>();
// If the second is the first's parent then move the first down
if (firstParent != null && firstParent == second)
{
return 1;
}
var secondParent = second.GetCachedParent()?.GetComponent<NetworkObject>();
// If the first is the second's parent then move the first up
if (secondParent != null && secondParent == first)
{
return -1;
}
// Otherwise, don't move the first at all
return 0;
}
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children order
/// </summary>
private void SortParentedNetworkObjects()
{
var networkObjectList = m_NetworkObjectsSync.ToList();
foreach (var networkObject in networkObjectList)
{
// Find only the root parent NetworkObjects
if (networkObject.transform.childCount > 0 && networkObject.transform.parent == null)
{
// Get all child NetworkObjects of the root
var childNetworkObjects = networkObject.GetComponentsInChildren<NetworkObject>().ToList();
childNetworkObjects.Sort(SortChildrenNetworkObjects);
// Remove the root from the children list
childNetworkObjects.Remove(networkObject);
// Remove the root's children from the primary list
foreach (var childObject in childNetworkObjects)
{
m_NetworkObjectsSync.Remove(childObject);
}
// Insert or Add the sorted children list
var nextIndex = m_NetworkObjectsSync.IndexOf(networkObject) + 1;
if (nextIndex == m_NetworkObjectsSync.Count)
{
m_NetworkObjectsSync.AddRange(childNetworkObjects);
}
else
{
m_NetworkObjectsSync.InsertRange(nextIndex, childNetworkObjects);
}
}
}
}
internal static bool LogSerializationOrder = false;
internal void AddSpawnedNetworkObjects()
{
m_NetworkObjectsSync.Clear();
@@ -256,22 +323,22 @@ namespace Unity.Netcode
}
}
// Sort by parents before children
m_NetworkObjectsSync.Sort(SortParentedNetworkObjects);
// Sort by INetworkPrefabInstanceHandler implementation before the
// NetworkObjects spawned by the implementation
m_NetworkObjectsSync.Sort(SortNetworkObjects);
// The last thing we sort is parents before children
SortParentedNetworkObjects();
// This is useful to know what NetworkObjects a client is going to be synchronized with
// as well as the order in which they will be deserialized
if (m_NetworkManager.LogLevel == LogLevel.Developer)
if (LogSerializationOrder && m_NetworkManager.LogLevel == LogLevel.Developer)
{
var messageBuilder = new System.Text.StringBuilder(0xFFFF);
messageBuilder.Append("[Server-Side Client-Synchronization] NetworkObject serialization order:");
messageBuilder.AppendLine("[Server-Side Client-Synchronization] NetworkObject serialization order:");
foreach (var networkObject in m_NetworkObjectsSync)
{
messageBuilder.Append($"{networkObject.name}");
messageBuilder.AppendLine($"{networkObject.name}");
}
NetworkLog.LogInfo(messageBuilder.ToString());
}
@@ -362,32 +429,6 @@ namespace Unity.Netcode
return 0;
}
/// <summary>
/// Sorts the synchronization order of the NetworkObjects to be serialized
/// by parents before children.
/// </summary>
/// <remarks>
/// This only handles late joining players. Spawning and nesting several children
/// dynamically is still handled by the orphaned child list when deserialized out of
/// hierarchical order (i.e. Spawn parent and child dynamically, parent message is
/// dropped and re-sent but child object is received and processed)
/// </remarks>
private int SortParentedNetworkObjects(NetworkObject first, NetworkObject second)
{
// If the first has a parent, move the first down
if (first.transform.parent != null)
{
return 1;
}
else // If the second has a parent and the first does not, then move the first up
if (second.transform.parent != null)
{
return -1;
}
return 0;
}
/// <summary>
/// Client and Server Side:
/// Serializes data based on the SceneEvent type (<see cref="SceneEventType"/>)
@@ -681,7 +722,7 @@ namespace Unity.Netcode
{
// is not packed!
InternalBuffer.ReadValueSafe(out ushort newObjectsCount);
var sceneObjects = new List<NetworkObject>();
for (ushort i = 0; i < newObjectsCount; i++)
{
var sceneObject = new NetworkObject.SceneObject();
@@ -693,10 +734,22 @@ namespace Unity.Netcode
m_NetworkManager.SceneManager.SetTheSceneBeingSynchronized(sceneObject.NetworkSceneHandle);
}
NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
var networkObject = NetworkObject.AddSceneObject(sceneObject, InternalBuffer, m_NetworkManager);
if (sceneObject.IsSceneObject)
{
sceneObjects.Add(networkObject);
}
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();
// Notify all newly spawned in-scene placed NetworkObjects that all in-scene placed
// NetworkObjects have been spawned.
foreach (var networkObject in sceneObjects)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
}
finally
{
@@ -942,6 +995,15 @@ namespace Unity.Netcode
}
}
// Notify that all in-scene placed NetworkObjects have been spawned
foreach (var networkObject in m_NetworkObjectsSync)
{
if (networkObject.IsSceneObject.HasValue && networkObject.IsSceneObject.Value)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
}
// Now deserialize the despawned in-scene placed NetworkObjects list (if any)
DeserializeDespawnedInScenePlacedNetworkObjects();

View File

@@ -1069,6 +1069,36 @@ namespace Unity.Netcode
ReadUnmanagedSafeInPlace(ref value);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReadValueSafeInPlace<T>(ref NativeHashSet<T> value) where T : unmanaged, IEquatable<T>
{
ReadUnmanagedSafe(out int length);
value.Clear();
for (var i = 0; i < length; ++i)
{
T val = default;
NetworkVariableSerialization<T>.Read(this, ref val);
value.Add(val);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void ReadValueSafeInPlace<TKey, TVal>(ref NativeHashMap<TKey, TVal> value)
where TKey : unmanaged, IEquatable<TKey>
where TVal : unmanaged
{
ReadUnmanagedSafe(out int length);
value.Clear();
for (var i = 0; i < length; ++i)
{
TKey key = default;
TVal val = default;
NetworkVariableSerialization<TKey>.Read(this, ref key);
NetworkVariableSerialization<TVal>.Read(this, ref val);
value[key] = val;
}
}
#endif
/// <summary>

View File

@@ -1189,6 +1189,31 @@ namespace Unity.Netcode
WriteUnmanaged(value);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteValueSafe<T>(NativeHashSet<T> value) where T : unmanaged, IEquatable<T>
{
WriteUnmanagedSafe(value.Count());
foreach (var item in value)
{
var iReffable = item;
NetworkVariableSerialization<T>.Write(this, ref iReffable);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void WriteValueSafe<TKey, TVal>(NativeHashMap<TKey, TVal> value)
where TKey : unmanaged, IEquatable<TKey>
where TVal : unmanaged
{
WriteUnmanagedSafe(value.Count());
foreach (var item in value)
{
(var key, var val) = (item.Key, item.Value);
NetworkVariableSerialization<TKey>.Write(this, ref key);
NetworkVariableSerialization<TVal>.Write(this, ref val);
}
}
#endif
/// <summary>

View File

@@ -5,12 +5,15 @@ namespace Unity.Netcode
{
/// <summary>
/// A helper struct for serializing <see cref="NetworkBehaviour"/>s over the network. Can be used in RPCs and <see cref="NetworkVariable{T}"/>.
/// Note: network ids get recycled by the NetworkManager after a while. So a reference pointing to
/// Network IDs get recycled by the NetworkManager after a while, so using a NetworkBehaviourReference for too long may result in a
/// different NetworkBehaviour being returned for the assigned NetworkBehaviourId. NetworkBehaviourReferences are best for short-term
/// use when receieved via RPC or custom message, rather than for long-term references to NetworkBehaviours.
/// </summary>
public struct NetworkBehaviourReference : INetworkSerializable, IEquatable<NetworkBehaviourReference>
{
private NetworkObjectReference m_NetworkObjectReference;
private ushort m_NetworkBehaviourId;
private static ushort s_NullId = ushort.MaxValue;
/// <summary>
/// Creates a new instance of the <see cref="NetworkBehaviourReference{T}"/> struct.
@@ -21,7 +24,9 @@ namespace Unity.Netcode
{
if (networkBehaviour == null)
{
throw new ArgumentNullException(nameof(networkBehaviour));
m_NetworkObjectReference = new NetworkObjectReference((NetworkObject)null);
m_NetworkBehaviourId = s_NullId;
return;
}
if (networkBehaviour.NetworkObject == null)
{
@@ -40,7 +45,7 @@ namespace Unity.Netcode
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet(out NetworkBehaviour networkBehaviour, NetworkManager networkManager = null)
{
networkBehaviour = GetInternal(this, null);
networkBehaviour = GetInternal(this, networkManager);
return networkBehaviour != null;
}
@@ -53,13 +58,17 @@ namespace Unity.Netcode
/// <returns>True if the <see cref="NetworkBehaviour"/> was found; False if the <see cref="NetworkBehaviour"/> was not found. This can happen if the corresponding <see cref="NetworkObject"/> has not been spawned yet. you can try getting the reference at a later point in time.</returns>
public bool TryGet<T>(out T networkBehaviour, NetworkManager networkManager = null) where T : NetworkBehaviour
{
networkBehaviour = GetInternal(this, null) as T;
networkBehaviour = GetInternal(this, networkManager) as T;
return networkBehaviour != null;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkBehaviour GetInternal(NetworkBehaviourReference networkBehaviourRef, NetworkManager networkManager = null)
{
if (networkBehaviourRef.m_NetworkBehaviourId == s_NullId)
{
return null;
}
if (networkBehaviourRef.m_NetworkObjectReference.TryGet(out NetworkObject networkObject, networkManager))
{
return networkObject.GetNetworkBehaviourAtOrderIndex(networkBehaviourRef.m_NetworkBehaviourId);

View File

@@ -10,6 +10,7 @@ namespace Unity.Netcode
public struct NetworkObjectReference : INetworkSerializable, IEquatable<NetworkObjectReference>
{
private ulong m_NetworkObjectId;
private static ulong s_NullId = ulong.MaxValue;
/// <summary>
/// The <see cref="NetworkObject.NetworkObjectId"/> of the referenced <see cref="NetworkObject"/>.
@@ -24,13 +25,13 @@ namespace Unity.Netcode
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="networkObject">The <see cref="NetworkObject"/> to reference.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(NetworkObject networkObject)
{
if (networkObject == null)
{
throw new ArgumentNullException(nameof(networkObject));
m_NetworkObjectId = s_NullId;
return;
}
if (networkObject.IsSpawned == false)
@@ -45,16 +46,20 @@ namespace Unity.Netcode
/// Creates a new instance of the <see cref="NetworkObjectReference"/> struct.
/// </summary>
/// <param name="gameObject">The GameObject from which the <see cref="NetworkObject"/> component will be referenced.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <exception cref="ArgumentException"></exception>
public NetworkObjectReference(GameObject gameObject)
{
if (gameObject == null)
{
throw new ArgumentNullException(nameof(gameObject));
m_NetworkObjectId = s_NullId;
return;
}
var networkObject = gameObject.GetComponent<NetworkObject>() ?? throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
var networkObject = gameObject.GetComponent<NetworkObject>();
if (!networkObject)
{
throw new ArgumentException($"Cannot create {nameof(NetworkObjectReference)} from {nameof(GameObject)} without a {nameof(NetworkObject)} component.");
}
if (networkObject.IsSpawned == false)
{
throw new ArgumentException($"{nameof(NetworkObjectReference)} can only be created from spawned {nameof(NetworkObject)}s.");
@@ -80,10 +85,14 @@ namespace Unity.Netcode
/// </summary>
/// <param name="networkObjectRef">The reference.</param>
/// <param name="networkManager">The networkmanager. Uses <see cref="NetworkManager.Singleton"/> to resolve if null.</param>
/// <returns>The resolves <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns>
/// <returns>The resolved <see cref="NetworkObject"/>. Returns null if the networkobject was not found</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static NetworkObject Resolve(NetworkObjectReference networkObjectRef, NetworkManager networkManager = null)
{
if (networkObjectRef.m_NetworkObjectId == s_NullId)
{
return null;
}
networkManager = networkManager ?? NetworkManager.Singleton;
networkManager.SpawnManager.SpawnedObjects.TryGetValue(networkObjectRef.m_NetworkObjectId, out NetworkObject networkObject);

View File

@@ -531,20 +531,27 @@ namespace Unity.Netcode
networkObject.DestroyWithScene = sceneObject.DestroyWithScene;
networkObject.NetworkSceneHandle = sceneObject.NetworkSceneHandle;
var nonNetworkObjectParent = false;
// SPECIAL CASE FOR IN-SCENE PLACED: (only when the parent has a NetworkObject)
// This is a special case scenario where a late joining client has joined and loaded one or
// more scenes that contain nested in-scene placed NetworkObject children yet the server's
// synchronization information does not indicate the NetworkObject in question has a parent.
// Under this scenario, we want to remove the parent before spawning and setting the transform values.
if (sceneObject.IsSceneObject && !sceneObject.HasParent && networkObject.transform.parent != null)
if (sceneObject.IsSceneObject && networkObject.transform.parent != null)
{
var parentNetworkObject = networkObject.transform.parent.GetComponent<NetworkObject>();
// if the in-scene placed NetworkObject has a parent NetworkObject but the synchronization information does not
// include parenting, then we need to force the removal of that parent
if (networkObject.transform.parent.GetComponent<NetworkObject>() != null)
if (!sceneObject.HasParent && parentNetworkObject)
{
// remove the parent
networkObject.ApplyNetworkParenting(true, true);
}
else if (sceneObject.HasParent && !parentNetworkObject)
{
nonNetworkObjectParent = true;
}
}
// Set the transform unless we were spawned by a prefab handler
@@ -554,7 +561,7 @@ namespace Unity.Netcode
{
// If world position stays is true or we have auto object parent synchronization disabled
// then we want to apply the position and rotation values world space relative
if (worldPositionStays || !networkObject.AutoObjectParentSync)
if ((worldPositionStays && !nonNetworkObjectParent) || !networkObject.AutoObjectParentSync)
{
networkObject.transform.position = position;
networkObject.transform.rotation = rotation;
@@ -604,7 +611,12 @@ namespace Unity.Netcode
return networkObject;
}
// Ran on both server and client
/// <summary>
/// Invoked when spawning locally
/// </summary>
/// <remarks>
/// Pre and Post spawn methods *CAN* be invoked prior to invoking <see cref="SpawnNetworkObjectLocallyCommon"/>
/// </remarks>
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
{
if (networkObject == null)
@@ -625,11 +637,21 @@ namespace Unity.Netcode
Debug.LogError("Spawning NetworkObjects with nested NetworkObjects is only supported for scene objects. Child NetworkObjects will not be spawned over the network!");
}
}
// Invoke NetworkBehaviour.OnPreSpawn methods
networkObject.InvokeBehaviourNetworkPreSpawn();
SpawnNetworkObjectLocallyCommon(networkObject, networkId, sceneObject, playerObject, ownerClientId, destroyWithScene);
// Invoke NetworkBehaviour.OnPostSpawn methods
networkObject.InvokeBehaviourNetworkPostSpawn();
}
// Ran on both server and client
/// <summary>
/// Invoked from AddSceneObject
/// </summary>
/// <remarks>
/// IMPORTANT: Pre spawn methods need to be invoked from within <see cref="NetworkObject.AddSceneObject"/>.
/// </remarks>
internal void SpawnNetworkObjectLocally(NetworkObject networkObject, in NetworkObject.SceneObject sceneObject, bool destroyWithScene)
{
if (networkObject == null)
@@ -642,7 +664,11 @@ namespace Unity.Netcode
throw new SpawnStateException("Object is already spawned");
}
// Do not invoke Pre spawn here (SynchronizeNetworkBehaviours needs to be invoked prior to this)
SpawnNetworkObjectLocallyCommon(networkObject, sceneObject.NetworkObjectId, sceneObject.IsSceneObject, sceneObject.IsPlayerObject, sceneObject.OwnerClientId, destroyWithScene);
// It is ok to invoke NetworkBehaviour.OnPostSpawn methods
networkObject.InvokeBehaviourNetworkPostSpawn();
}
private void SpawnNetworkObjectLocallyCommon(NetworkObject networkObject, ulong networkId, bool sceneObject, bool playerObject, ulong ownerClientId, bool destroyWithScene)
@@ -859,7 +885,7 @@ namespace Unity.Netcode
{
// If it is an in-scene placed NetworkObject then just despawn and let it be destroyed when the scene
// is unloaded. Otherwise, despawn and destroy it.
var shouldDestroy = !(networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value);
var shouldDestroy = !(networkObjects[i].IsSceneObject == null || (networkObjects[i].IsSceneObject != null && networkObjects[i].IsSceneObject.Value));
// If we are going to destroy this NetworkObject, check for any in-scene placed children that need to be removed
if (shouldDestroy)
@@ -948,11 +974,16 @@ namespace Unity.Netcode
}
}
foreach (var networkObject in networkObjectsToSpawn)
{
SpawnNetworkObjectLocally(networkObject, GetNetworkObjectId(), true, false, networkObject.OwnerClientId, true);
}
// Notify all in-scene placed NetworkObjects have been spawned
foreach (var networkObject in networkObjectsToSpawn)
{
networkObject.InternalInSceneNetworkObjectsSpawned();
}
}
internal void OnDespawnObject(NetworkObject networkObject, bool destroyGameObject)

View File

@@ -0,0 +1,100 @@
using System.Collections.Generic;
namespace Unity.Netcode
{
internal interface IAnticipationEventReceiver
{
public void SetupForUpdate();
public void SetupForRender();
}
internal interface IAnticipatedObject
{
public void Update();
public void ResetAnticipation();
public NetworkObject OwnerObject { get; }
}
internal class AnticipationSystem
{
internal ulong LastAnticipationAck;
internal double LastAnticipationAckTime;
internal HashSet<IAnticipatedObject> AllAnticipatedObjects = new HashSet<IAnticipatedObject>();
internal ulong AnticipationCounter;
private NetworkManager m_NetworkManager;
public HashSet<IAnticipatedObject> ObjectsToReanticipate = new HashSet<IAnticipatedObject>();
public AnticipationSystem(NetworkManager manager)
{
m_NetworkManager = manager;
}
public event NetworkManager.ReanticipateDelegate OnReanticipate;
private HashSet<IAnticipationEventReceiver> m_AnticipationEventReceivers = new HashSet<IAnticipationEventReceiver>();
public void RegisterForAnticipationEvents(IAnticipationEventReceiver receiver)
{
m_AnticipationEventReceivers.Add(receiver);
}
public void DeregisterForAnticipationEvents(IAnticipationEventReceiver receiver)
{
m_AnticipationEventReceivers.Remove(receiver);
}
public void SetupForUpdate()
{
foreach (var receiver in m_AnticipationEventReceivers)
{
receiver.SetupForUpdate();
}
}
public void SetupForRender()
{
foreach (var receiver in m_AnticipationEventReceivers)
{
receiver.SetupForRender();
}
}
public void ProcessReanticipation()
{
var lastRoundTripTime = m_NetworkManager.LocalTime.Time - LastAnticipationAckTime;
foreach (var item in ObjectsToReanticipate)
{
foreach (var behaviour in item.OwnerObject.ChildNetworkBehaviours)
{
behaviour.OnReanticipate(lastRoundTripTime);
}
item.ResetAnticipation();
}
ObjectsToReanticipate.Clear();
OnReanticipate?.Invoke(lastRoundTripTime);
}
public void Update()
{
foreach (var item in AllAnticipatedObjects)
{
item.Update();
}
}
public void Sync()
{
if (AllAnticipatedObjects.Count != 0 && !m_NetworkManager.ShutdownInProgress && !m_NetworkManager.ConnectionManager.LocalClient.IsServer && m_NetworkManager.ConnectionManager.LocalClient.IsConnected)
{
var message = new AnticipationCounterSyncPingMessage { Counter = AnticipationCounter, Time = m_NetworkManager.LocalTime.Time };
m_NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, NetworkManager.ServerClientId);
}
++AnticipationCounter;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 21e6ae0a82f945458519aee4ee119cab
timeCreated: 1707845625

View File

@@ -24,6 +24,11 @@ namespace Unity.Netcode
/// </summary>
public double TickOffset => m_CachedTickOffset;
/// <summary>
/// Gets the tick, including partial tick value passed since it started.
/// </summary>
public double TickWithPartial => Tick + (TickOffset / m_TickInterval);
/// <summary>
/// Gets the current time. This is a non fixed time value and similar to <see cref="Time.time"/>.
/// </summary>

View File

@@ -5,7 +5,9 @@ namespace Unity.Netcode
{
/// <summary>
/// <see cref="NetworkTimeSystem"/> is a standalone system which can be used to run a network time simulation.
/// The network time system maintains both a local and a server time. The local time is based on
/// The network time system maintains both a local and a server time. The local time is based on the server time
/// as last received from the server plus an offset based on the current RTT - in other words, it is a best-guess
/// effort at predicting what the server tick will be when a given network action is processed on the server.
/// </summary>
public class NetworkTimeSystem
{

View File

@@ -202,11 +202,12 @@ namespace Unity.Netcode.Transports.UTP
set => m_MaxPacketQueueSize = value;
}
[Tooltip("The maximum size of an unreliable payload that can be handled by the transport.")]
[Tooltip("The maximum size of an unreliable payload that can be handled by the transport. The memory for MaxPayloadSize is allocated once per connection and is released when the connection is closed.")]
[SerializeField]
private int m_MaxPayloadSize = InitialMaxPayloadSize;
/// <summary>The maximum size of an unreliable payload that can be handled by the transport.</summary>
/// <remarks>The memory for MaxPayloadSize is allocated once per connection and is released when the connection is closed.</remarks>
public int MaxPayloadSize
{
get => m_MaxPayloadSize;

View File

@@ -165,13 +165,30 @@ namespace Unity.Netcode.TestHelpers.Runtime
foreach (var sobj in inSceneNetworkObjects)
{
if (sobj.NetworkManagerOwner != networkManager)
{
sobj.NetworkManagerOwner = networkManager;
ProcessInSceneObject(sobj, networkManager);
}
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
}
/// <summary>
/// Assures to apply an ObjectNameIdentifier to all children
/// </summary>
private static void ProcessInSceneObject(NetworkObject networkObject, NetworkManager networkManager)
{
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
if (networkObject.NetworkManagerOwner != networkManager)
{
networkObject.NetworkManagerOwner = networkManager;
}
if (networkObject.GetComponent<ObjectNameIdentifier>() == null)
{
networkObject.gameObject.AddComponent<ObjectNameIdentifier>();
var networkObjects = networkObject.gameObject.GetComponentsInChildren<NetworkObject>();
foreach (var child in networkObjects)
{
if (child == networkObject)
{
continue;
}
ProcessInSceneObject(child, networkManager);
}
}
}
@@ -658,6 +675,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
foreach (var sceneToUnload in m_ScenesToUnload)
{
SceneManager.UnloadSceneAsync(sceneToUnload.Key);
// Update the ScenesLoaded when we unload scenes
if (sceneManager.ScenesLoaded.ContainsKey(sceneToUnload.Key.handle))
{
sceneManager.ScenesLoaded.Remove(sceneToUnload.Key.handle);
}
}
}

View File

@@ -1,5 +1,7 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using Random = UnityEngine.Random;
namespace Unity.Netcode.TestHelpers.Runtime
{
@@ -8,37 +10,107 @@ namespace Unity.Netcode.TestHelpers.Runtime
private struct MessageData
{
public ulong FromClientId;
public ArraySegment<byte> Payload;
public FastBufferReader Payload;
public NetworkEvent Event;
public float AvailableTime;
public int Sequence;
public NetworkDelivery Delivery;
}
private static Dictionary<ulong, Queue<MessageData>> s_MessageQueue = new Dictionary<ulong, Queue<MessageData>>();
private static Dictionary<ulong, List<MessageData>> s_MessageQueue = new Dictionary<ulong, List<MessageData>>();
public override ulong ServerClientId { get; } = 0;
public static ulong HighTransportId = 0;
public ulong TransportId = 0;
public float SimulatedLatencySeconds;
public float PacketDropRate;
public float LatencyJitter;
public Dictionary<ulong, int> LastSentSequence = new Dictionary<ulong, int>();
public Dictionary<ulong, int> LastReceivedSequence = new Dictionary<ulong, int>();
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 });
if ((networkDelivery == NetworkDelivery.Unreliable || networkDelivery == NetworkDelivery.UnreliableSequenced) && Random.Range(0, 1) < PacketDropRate)
{
return;
}
if (!LastSentSequence.ContainsKey(clientId))
{
LastSentSequence[clientId] = 1;
}
var reader = new FastBufferReader(payload, Allocator.TempJob);
s_MessageQueue[clientId].Add(new MessageData
{
FromClientId = TransportId,
Payload = reader,
Event = NetworkEvent.Data,
AvailableTime = NetworkManager.RealTimeProvider.UnscaledTime + SimulatedLatencySeconds + Random.Range(-LatencyJitter, LatencyJitter),
Sequence = ++LastSentSequence[clientId],
Delivery = networkDelivery
});
s_MessageQueue[clientId].Sort(((a, b) => a.AvailableTime.CompareTo(b.AvailableTime)));
}
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();
MessageData data;
for (; ; )
{
data = s_MessageQueue[TransportId][0];
if (data.AvailableTime > NetworkManager.RealTimeProvider.UnscaledTime)
{
clientId = 0;
payload = new ArraySegment<byte>();
receiveTime = 0;
return NetworkEvent.Nothing;
}
s_MessageQueue[TransportId].RemoveAt(0);
clientId = data.FromClientId;
payload = data.Payload;
if (data.Event == NetworkEvent.Data && data.Delivery == NetworkDelivery.UnreliableSequenced && LastReceivedSequence.ContainsKey(clientId) && data.Sequence <= LastReceivedSequence[clientId])
{
continue;
}
break;
}
if (data.Delivery == NetworkDelivery.UnreliableSequenced)
{
LastReceivedSequence[clientId] = data.Sequence;
}
payload = new ArraySegment<byte>();
if (data.Event == NetworkEvent.Data)
{
payload = data.Payload.ToArray();
data.Payload.Dispose();
}
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>() });
if (!LastSentSequence.ContainsKey(data.FromClientId))
{
LastSentSequence[data.FromClientId] = 1;
}
s_MessageQueue[data.FromClientId].Add(
new MessageData
{
Event = NetworkEvent.Connect,
FromClientId = ServerClientId,
AvailableTime = NetworkManager.RealTimeProvider.UnscaledTime + SimulatedLatencySeconds + Random.Range(-LatencyJitter, LatencyJitter),
Sequence = ++LastSentSequence[data.FromClientId]
});
}
return data.Event;
}
@@ -51,30 +123,45 @@ namespace Unity.Netcode.TestHelpers.Runtime
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>() });
s_MessageQueue[TransportId] = new List<MessageData>();
s_MessageQueue[ServerClientId].Add(
new MessageData
{
Event = NetworkEvent.Connect,
FromClientId = TransportId,
});
return true;
}
public override bool StartServer()
{
s_MessageQueue[ServerClientId] = new Queue<MessageData>();
s_MessageQueue[ServerClientId] = new List<MessageData>();
return true;
}
public override void DisconnectRemoteClient(ulong clientId)
{
s_MessageQueue[clientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = TransportId, Payload = new ArraySegment<byte>() });
s_MessageQueue[clientId].Add(
new MessageData
{
Event = NetworkEvent.Disconnect,
FromClientId = TransportId,
});
}
public override void DisconnectLocalClient()
{
s_MessageQueue[ServerClientId].Enqueue(new MessageData { Event = NetworkEvent.Disconnect, FromClientId = TransportId, Payload = new ArraySegment<byte>() });
s_MessageQueue[ServerClientId].Add(
new MessageData
{
Event = NetworkEvent.Disconnect,
FromClientId = TransportId,
});
}
public override ulong GetCurrentRtt(ulong clientId)
{
return 0;
return (ulong)(SimulatedLatencySeconds * 1000);
}
public override void Shutdown()
@@ -85,5 +172,35 @@ namespace Unity.Netcode.TestHelpers.Runtime
{
NetworkManager = networkManager;
}
protected static void DisposeQueueItems()
{
foreach (var kvp in s_MessageQueue)
{
foreach (var value in kvp.Value)
{
if (value.Event == NetworkEvent.Data)
{
value.Payload.Dispose();
}
}
}
}
public static void Reset()
{
DisposeQueueItems();
s_MessageQueue.Clear();
HighTransportId = 0;
}
public static void ClearQueues()
{
DisposeQueueItems();
foreach (var kvp in s_MessageQueue)
{
kvp.Value.Clear();
}
}
}
}

View File

@@ -308,6 +308,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
NetcodeLogAssert = new NetcodeLogAssert();
if (m_EnableTimeTravel)
{
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests)
{
MockTransport.ClearQueues();
}
else
{
MockTransport.Reset();
}
// Setup the frames per tick for time travel advance to next tick
ConfigureFramesPerTick();
}
@@ -548,6 +556,33 @@ namespace Unity.Netcode.TestHelpers.Runtime
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(() => !networkManager.IsConnectedClient));
}
protected void SetTimeTravelSimulatedLatency(float latencySeconds)
{
((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).SimulatedLatencySeconds = latencySeconds;
foreach (var client in m_ClientNetworkManagers)
{
((MockTransport)client.NetworkConfig.NetworkTransport).SimulatedLatencySeconds = latencySeconds;
}
}
protected void SetTimeTravelSimulatedDropRate(float dropRatePercent)
{
((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).PacketDropRate = dropRatePercent;
foreach (var client in m_ClientNetworkManagers)
{
((MockTransport)client.NetworkConfig.NetworkTransport).PacketDropRate = dropRatePercent;
}
}
protected void SetTimeTravelSimulatedLatencyJitter(float jitterSeconds)
{
((MockTransport)m_ServerNetworkManager.NetworkConfig.NetworkTransport).LatencyJitter = jitterSeconds;
foreach (var client in m_ClientNetworkManagers)
{
((MockTransport)client.NetworkConfig.NetworkTransport).LatencyJitter = jitterSeconds;
}
}
/// <summary>
/// Creates the server and clients
/// </summary>
@@ -1005,6 +1040,17 @@ namespace Unity.Netcode.TestHelpers.Runtime
VerboseDebug($"Exiting {nameof(TearDown)}");
LogWaitForMessages();
NetcodeLogAssert.Dispose();
if (m_EnableTimeTravel)
{
if (m_NetworkManagerInstatiationMode == NetworkManagerInstatiationMode.AllTests)
{
MockTransport.ClearQueues();
}
else
{
MockTransport.Reset();
}
}
}
/// <summary>
@@ -1554,8 +1600,17 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
/// <param name="amountOfTimeInSeconds"></param>
/// <param name="numFramesToSimulate"></param>
protected static void TimeTravel(double amountOfTimeInSeconds, int numFramesToSimulate)
protected static void TimeTravel(double amountOfTimeInSeconds, int numFramesToSimulate = -1)
{
if (numFramesToSimulate < 0)
{
var frameRate = Application.targetFrameRate;
if (frameRate <= 0)
{
frameRate = 60;
}
numFramesToSimulate = Math.Max((int)(amountOfTimeInSeconds / frameRate), 1);
}
var interval = amountOfTimeInSeconds / numFramesToSimulate;
for (var i = 0; i < numFramesToSimulate; ++i)
{
@@ -1613,6 +1668,16 @@ namespace Unity.Netcode.TestHelpers.Runtime
TimeTravel(timePassed, frames);
}
private struct UpdateData
{
public MethodInfo Update;
public MethodInfo FixedUpdate;
public MethodInfo LateUpdate;
}
private static object[] s_EmptyObjectArray = { };
private static Dictionary<Type, UpdateData> s_UpdateFunctionCache = new Dictionary<Type, UpdateData>();
/// <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
@@ -1620,33 +1685,50 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// </summary>
public static void SimulateOneFrame()
{
foreach (NetworkUpdateStage stage in Enum.GetValues(typeof(NetworkUpdateStage)))
foreach (NetworkUpdateStage updateStage in Enum.GetValues(typeof(NetworkUpdateStage)))
{
var stage = updateStage;
// These two are out of order numerically due to backward compatibility
// requirements. We have to swap them to maintain correct execution
// order.
if (stage == NetworkUpdateStage.PostScriptLateUpdate)
{
stage = NetworkUpdateStage.PostLateUpdate;
}
else if (stage == NetworkUpdateStage.PostLateUpdate)
{
stage = NetworkUpdateStage.PostScriptLateUpdate;
}
NetworkUpdateLoop.RunNetworkUpdateStage(stage);
string methodName = string.Empty;
if (stage == NetworkUpdateStage.Update || stage == NetworkUpdateStage.FixedUpdate || stage == NetworkUpdateStage.PreLateUpdate)
{
foreach (var behaviour in Object.FindObjectsByType<NetworkBehaviour>(FindObjectsSortMode.None))
{
var type = behaviour.GetType();
if (!s_UpdateFunctionCache.TryGetValue(type, out var updateData))
{
updateData = new UpdateData
{
Update = type.GetMethod("Update", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
FixedUpdate = type.GetMethod("FixedUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
LateUpdate = type.GetMethod("LateUpdate", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance),
};
s_UpdateFunctionCache[type] = updateData;
}
switch (stage)
{
case NetworkUpdateStage.FixedUpdate:
methodName = "FixedUpdate"; // mapping NetworkUpdateStage.FixedUpdate to MonoBehaviour.FixedUpdate
updateData.FixedUpdate?.Invoke(behaviour, new object[] { });
break;
case NetworkUpdateStage.Update:
methodName = "Update"; // mapping NetworkUpdateStage.Update to MonoBehaviour.Update
updateData.Update?.Invoke(behaviour, new object[] { });
break;
case NetworkUpdateStage.PreLateUpdate:
methodName = "LateUpdate"; // mapping NetworkUpdateStage.PreLateUpdate to MonoBehaviour.LateUpdate
updateData.LateUpdate?.Invoke(behaviour, new object[] { });
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.Public | BindingFlags.NonPublic | BindingFlags.Instance);
method?.Invoke(behaviour, new object[] { });
}
}
}

View File

@@ -90,11 +90,16 @@ namespace Unity.Netcode.EditorTests
break;
}
case TypeOfCorruption.CorruptBytes:
batchData.Seek(batchData.Length - 2);
var currentByte = batchData.GetUnsafePtr()[0];
{
batchData.Seek(batchData.Length - 4);
for (int i = 0; i < 4; i++)
{
var currentByte = batchData.GetUnsafePtr()[i];
batchData.WriteByteSafe((byte)(currentByte == 0 ? 1 : 0));
MessageQueue.Add(batchData.ToArray());
}
break;
}
case TypeOfCorruption.Truncated:
batchData.Truncate(batchData.Length - 1);
MessageQueue.Add(batchData.ToArray());

View File

@@ -81,7 +81,7 @@ namespace Unity.Netcode.RuntimeTests
public IEnumerator ValidateApprovalTimeout()
{
// Delay for half of the wait period
yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.5f);
yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.25f);
// Verify we haven't received the time out message yet
NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage);

View File

@@ -1,4 +1,5 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
@@ -37,7 +38,10 @@ namespace Unity.Netcode.RuntimeTests
protected override int NumberOfClients => 1;
private OwnerPersistence m_OwnerPersistence;
private ClientDisconnectType m_ClientDisconnectType;
private bool m_ClientDisconnected;
private Dictionary<NetworkManager, ConnectionEventData> m_DisconnectedEvent = new Dictionary<NetworkManager, ConnectionEventData>();
private ulong m_DisconnectEventClientId;
private ulong m_TransportClientId;
private ulong m_ClientId;
@@ -89,6 +93,16 @@ namespace Unity.Netcode.RuntimeTests
m_ClientDisconnected = true;
}
private void OnConnectionEvent(NetworkManager networkManager, ConnectionEventData connectionEventData)
{
if (connectionEventData.EventType != ConnectionEvent.ClientDisconnected)
{
return;
}
m_DisconnectedEvent.Add(networkManager, connectionEventData);
}
/// <summary>
/// Conditional check to assure the transport to client (and vice versa) mappings are cleaned up
/// </summary>
@@ -126,19 +140,26 @@ namespace Unity.Netcode.RuntimeTests
public IEnumerator ClientPlayerDisconnected([Values] ClientDisconnectType clientDisconnectType)
{
m_ClientId = m_ClientNetworkManagers[0].LocalClientId;
m_ClientDisconnectType = clientDisconnectType;
var serverSideClientPlayer = m_ServerNetworkManager.ConnectionManager.ConnectedClients[m_ClientId].PlayerObject;
m_TransportClientId = m_ServerNetworkManager.ConnectionManager.ClientIdToTransportId(m_ClientId);
var clientManager = m_ClientNetworkManagers[0];
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
{
m_ClientNetworkManagers[0].OnClientDisconnectCallback += OnClientDisconnectCallback;
m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
m_ServerNetworkManager.DisconnectClient(m_ClientId);
}
else
{
m_ServerNetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback;
m_ServerNetworkManager.OnConnectionEvent += OnConnectionEvent;
m_ClientNetworkManagers[0].OnConnectionEvent += OnConnectionEvent;
yield return StopOneClient(m_ClientNetworkManagers[0]);
}
@@ -146,6 +167,26 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected);
AssertOnTimeout("Timed out waiting for client to disconnect!");
if (clientDisconnectType == ClientDisconnectType.ServerDisconnectsClient)
{
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(clientManager), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[clientManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
// Unregister for this event otherwise it will be invoked during teardown
m_ServerNetworkManager.OnConnectionEvent -= OnConnectionEvent;
}
else
{
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(clientManager), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[clientManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!");
Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClients.Count}!");
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!");
}
if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner)
{
// When we are destroying with the owner, validate the player object is destroyed on the server side
@@ -161,6 +202,21 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForConditionOrTimeOut(TransportIdCleanedUp);
AssertOnTimeout("Timed out waiting for transport and client id mappings to be cleaned up!");
// Validate the host-client generates a OnClientDisconnected event when it shutsdown.
// Only test when the test run is the client disconnecting from the server (otherwise the server will be shutdown already)
if (clientDisconnectType == ClientDisconnectType.ClientDisconnectsFromServer)
{
m_DisconnectedEvent.Clear();
m_ClientDisconnected = false;
m_ServerNetworkManager.Shutdown();
yield return WaitForConditionOrTimeOut(() => m_ClientDisconnected);
AssertOnTimeout("Timed out waiting for host-client to generate disconnect message!");
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ServerNetworkManager), $"Could not find the server {nameof(NetworkManager)} disconnect event entry!");
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == NetworkManager.ServerClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
}
}
}
}

View File

@@ -86,6 +86,24 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(m_ServerNetworkManager.LocalClientId, receivedMessageSender);
}
private void MockNamedMessageCallback(ulong sender, FastBufferReader reader)
{
}
[Test]
public void NullOrEmptyNamedMessageDoesNotThrowException()
{
LogAssert.Expect(UnityEngine.LogType.Error, $"[{nameof(CustomMessagingManager.RegisterNamedMessageHandler)}] Cannot register a named message of type null or empty!");
m_ServerNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(string.Empty, MockNamedMessageCallback);
LogAssert.Expect(UnityEngine.LogType.Error, $"[{nameof(CustomMessagingManager.RegisterNamedMessageHandler)}] Cannot register a named message of type null or empty!");
m_ServerNetworkManager.CustomMessagingManager.RegisterNamedMessageHandler(null, MockNamedMessageCallback);
LogAssert.Expect(UnityEngine.LogType.Error, $"[{nameof(CustomMessagingManager.UnregisterNamedMessageHandler)}] Cannot unregister a named message of type null or empty!");
m_ServerNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(string.Empty);
LogAssert.Expect(UnityEngine.LogType.Error, $"[{nameof(CustomMessagingManager.UnregisterNamedMessageHandler)}] Cannot unregister a named message of type null or empty!");
m_ServerNetworkManager.CustomMessagingManager.UnregisterNamedMessageHandler(null);
}
[UnityTest]
public IEnumerator NamedMessageIsReceivedOnMultipleClientsWithContent()
{

View File

@@ -0,0 +1,137 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkBehaviourPrePostSpawnTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 0;
private bool m_AllowServerToStart;
private GameObject m_PrePostSpawnObject;
protected override void OnServerAndClientsCreated()
{
m_PrePostSpawnObject = CreateNetworkObjectPrefab("PrePostSpawn");
// Reverse the order of the components to get inverted spawn sequence
m_PrePostSpawnObject.AddComponent<NetworkBehaviourPostSpawn>();
m_PrePostSpawnObject.AddComponent<NetworkBehaviourPreSpawn>();
base.OnServerAndClientsCreated();
}
public class NetworkBehaviourPreSpawn : NetworkBehaviour
{
public static int ValueToSet;
public bool OnNetworkPreSpawnCalled;
public bool NetworkVarValueMatches;
public NetworkVariable<int> TestNetworkVariable;
protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
{
OnNetworkPreSpawnCalled = true;
// If we are the server, then set the randomly generated value (1-200).
// Otherwise, just set the value to 0.
var val = networkManager.IsServer ? ValueToSet : 0;
// Instantiate the NetworkVariable as everyone read & owner write while also setting the value
TestNetworkVariable = new NetworkVariable<int>(val, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
base.OnNetworkPreSpawn(ref networkManager);
}
public override void OnNetworkSpawn()
{
// For both client and server this should match at this point
NetworkVarValueMatches = TestNetworkVariable.Value == ValueToSet;
base.OnNetworkSpawn();
}
}
public class NetworkBehaviourPostSpawn : NetworkBehaviour
{
public bool OnNetworkPostSpawnCalled;
private NetworkBehaviourPreSpawn m_NetworkBehaviourPreSpawn;
public int ValueSet;
public override void OnNetworkSpawn()
{
// Obtain the NetworkBehaviourPreSpawn component
// (could also do this during OnNetworkPreSpawn if we wanted)
m_NetworkBehaviourPreSpawn = GetComponent<NetworkBehaviourPreSpawn>();
base.OnNetworkSpawn();
}
protected override void OnNetworkPostSpawn()
{
OnNetworkPostSpawnCalled = true;
// We should be able to access the component we got during OnNetworkSpawn and all values should be set
// (i.e. OnNetworkSpawn run on all NetworkObject relative NetworkBehaviours)
ValueSet = m_NetworkBehaviourPreSpawn.TestNetworkVariable.Value;
base.OnNetworkPostSpawn();
}
}
protected override bool CanStartServerAndClients()
{
return m_AllowServerToStart;
}
protected override IEnumerator OnSetup()
{
m_AllowServerToStart = false;
return base.OnSetup();
}
protected override void OnNewClientCreated(NetworkManager networkManager)
{
networkManager.NetworkConfig.Prefabs = m_ServerNetworkManager.NetworkConfig.Prefabs;
base.OnNewClientCreated(networkManager);
}
/// <summary>
/// This validates that pre spawn can be used to instantiate and assign a NetworkVariable (or other prespawn tasks)
/// which can be useful for assigning a NetworkVariable value on the server side when the NetworkVariable has owner write permissions.
/// This also assures that duruing post spawn all associated NetworkBehaviours have run through the OnNetworkSpawn pass (i.e. OnNetworkSpawn order is not an issue)
/// </summary>
[UnityTest]
public IEnumerator OnNetworkPreAndPostSpawn()
{
m_AllowServerToStart = true;
NetworkBehaviourPreSpawn.ValueToSet = Random.Range(1, 200);
yield return StartServerAndClients();
yield return CreateAndStartNewClient();
// Spawn the object with the newly joined client as the owner
var serverInstance = SpawnObject(m_PrePostSpawnObject, m_ClientNetworkManagers[0]);
var serverNetworkObject = serverInstance.GetComponent<NetworkObject>();
var serverPreSpawn = serverInstance.GetComponent<NetworkBehaviourPreSpawn>();
var serverPostSpawn = serverInstance.GetComponent<NetworkBehaviourPostSpawn>();
yield return WaitForConditionOrTimeOut(() => s_GlobalNetworkObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClientId)
&& s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId].ContainsKey(serverNetworkObject.NetworkObjectId));
AssertOnTimeout($"Client-{m_ClientNetworkManagers[0].LocalClientId} failed to spawn {nameof(NetworkObject)} id-{serverNetworkObject.NetworkObjectId}!");
var clientNetworkObject = s_GlobalNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][serverNetworkObject.NetworkObjectId];
var clientPreSpawn = clientNetworkObject.GetComponent<NetworkBehaviourPreSpawn>();
var clientPostSpawn = clientNetworkObject.GetComponent<NetworkBehaviourPostSpawn>();
Assert.IsTrue(serverPreSpawn.OnNetworkPreSpawnCalled, $"[Server-side] OnNetworkPreSpawn not invoked!");
Assert.IsTrue(clientPreSpawn.OnNetworkPreSpawnCalled, $"[Client-side] OnNetworkPreSpawn not invoked!");
Assert.IsTrue(serverPostSpawn.OnNetworkPostSpawnCalled, $"[Server-side] OnNetworkPostSpawn not invoked!");
Assert.IsTrue(clientPostSpawn.OnNetworkPostSpawnCalled, $"[Client-side] OnNetworkPostSpawn not invoked!");
Assert.IsTrue(serverPreSpawn.NetworkVarValueMatches, $"[Server-side][PreSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {serverPreSpawn.TestNetworkVariable.Value}!");
Assert.IsTrue(clientPreSpawn.NetworkVarValueMatches, $"[Client-side][PreSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {clientPreSpawn.TestNetworkVariable.Value}!");
Assert.IsTrue(serverPostSpawn.ValueSet == NetworkBehaviourPreSpawn.ValueToSet, $"[Server-side][PostSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {serverPostSpawn.ValueSet}!");
Assert.IsTrue(clientPostSpawn.ValueSet == NetworkBehaviourPreSpawn.ValueToSet, $"[Client-side][PostSpawn] Value {NetworkBehaviourPreSpawn.ValueToSet} does not match {clientPostSpawn.ValueSet}!");
}
}
}

View File

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

View File

@@ -1,5 +1,9 @@
using System.Collections;
using NUnit.Framework;
using Unity.Collections;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
@@ -30,5 +34,87 @@ namespace Unity.Netcode.RuntimeTests
networkManager.Shutdown();
Object.DestroyImmediate(gameObject);
}
[UnityTest]
public IEnumerator VerifyCustomMessageShutdownOrder()
{
Assert.True(NetcodeIntegrationTestHelpers.Create(1, out NetworkManager server, out NetworkManager[] clients));
bool isHost = false;
// Start server to cause initialization
NetcodeIntegrationTestHelpers.Start(isHost, server, clients);
// [Client-Side] Wait for a connection to the server
yield return NetcodeIntegrationTestHelpers.WaitForClientsConnected(clients, null, 512);
// [Host-Side] Check to make sure all clients are connected
yield return NetcodeIntegrationTestHelpers.WaitForClientsConnectedToServer(server, isHost ? (clients.Length + 1) : clients.Length, null, 512);
// Create a message to pass directly to the message handler. If we send the message its processed before we get a chance to shutdown
var dummySendData = new FastBufferWriter(128, Allocator.Temp);
dummySendData.WriteValueSafe("Dummy Data");
// make the message
var unnamedMessage = new UnnamedMessage
{
SendData = dummySendData
};
// make the message
using var serializedMessage = new FastBufferWriter(128, Allocator.Temp);
unnamedMessage.Serialize(serializedMessage, 0);
// Generate the full message
var messageHeader = new NetworkMessageHeader
{
MessageSize = (uint)serializedMessage.Length,
MessageType = server.MessageManager.GetMessageType(typeof(UnnamedMessage)),
};
var fullMessage = new FastBufferWriter(512, Allocator.Temp);
BytePacker.WriteValueBitPacked(fullMessage, messageHeader.MessageType);
BytePacker.WriteValueBitPacked(fullMessage, messageHeader.MessageSize);
fullMessage.WriteBytesSafe(serializedMessage.ToArray());
// Pack the message into a batch
var batchHeader = new NetworkBatchHeader
{
BatchCount = 1
};
var batchedMessage = new FastBufferWriter(1100, Allocator.Temp);
using (batchedMessage)
{
batchedMessage.TryBeginWrite(FastBufferWriter.GetWriteSize(batchHeader) +
FastBufferWriter.GetWriteSize(fullMessage));
batchedMessage.WriteValue(batchHeader);
batchedMessage.WriteBytesSafe(fullMessage.ToArray());
// Fill out the rest of the batch header
batchedMessage.Seek(0);
batchHeader = new NetworkBatchHeader
{
Magic = NetworkBatchHeader.MagicValue,
BatchSize = batchedMessage.Length,
BatchHash = XXHash.Hash64(fullMessage.ToArray()),
BatchCount = 1
};
batchedMessage.WriteValue(batchHeader);
// Handle the message as if it came from the server/client
server.MessageManager.HandleIncomingData(clients[0].LocalClientId, batchedMessage.ToArray(), 0);
foreach (var c in clients)
{
c.MessageManager.HandleIncomingData(server.LocalClientId, batchedMessage.ToArray(), 0);
}
}
// shutdown the network managher
NetcodeIntegrationTestHelpers.Destroy();
}
}
}

View File

@@ -4,6 +4,7 @@ using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
@@ -35,7 +36,7 @@ namespace Unity.Netcode.RuntimeTests
m_ServerManager.OnServerStopped += onServerStopped;
m_ServerManager.Shutdown();
UnityEngine.Object.DestroyImmediate(gameObject);
Object.DestroyImmediate(gameObject);
yield return WaitUntilManagerShutsdown();
@@ -92,7 +93,7 @@ namespace Unity.Netcode.RuntimeTests
m_ServerManager.OnServerStopped += onServerStopped;
m_ServerManager.OnClientStopped += onClientStopped;
m_ServerManager.Shutdown();
UnityEngine.Object.DestroyImmediate(gameObject);
Object.DestroyImmediate(gameObject);
yield return WaitUntilManagerShutsdown();
@@ -228,6 +229,18 @@ namespace Unity.Netcode.RuntimeTests
public virtual IEnumerator Teardown()
{
NetcodeIntegrationTestHelpers.Destroy();
if (m_ServerManager != null)
{
m_ServerManager.ShutdownInternal();
Object.DestroyImmediate(m_ServerManager);
m_ServerManager = null;
}
if (m_ClientManager != null)
{
m_ClientManager.ShutdownInternal();
Object.DestroyImmediate(m_ClientManager);
m_ClientManager = null;
}
yield return null;
}
}

View File

@@ -751,11 +751,29 @@ namespace Unity.Netcode.RuntimeTests
base.OnAuthorityPushTransformState(ref networkTransformState);
}
public bool AuthorityMove;
public Vector3 DirectionToMove;
public float MoveSpeed;
protected override void Update()
{
if (CanCommitToTransform && AuthorityMove)
{
transform.position += DirectionToMove * MoveSpeed * Time.deltaTime;
}
base.Update();
}
public delegate void NonAuthorityReceivedTransformStateDelegateHandler(ref NetworkTransformState networkTransformState);
public event NonAuthorityReceivedTransformStateDelegateHandler NonAuthorityReceivedTransformState;
public bool StateUpdated { get; internal set; }
protected override void OnNetworkTransformStateUpdated(ref NetworkTransformState oldState, ref NetworkTransformState newState)
{
StateUpdated = true;
NonAuthorityReceivedTransformState?.Invoke(ref newState);
base.OnNetworkTransformStateUpdated(ref oldState, ref newState);
}

View File

@@ -310,5 +310,64 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(Approximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!");
Assert.True(Approximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!");
}
/// <summary>
/// Validates that the unreliable frame synchronization is correct on the
/// non-authority side when using half float precision.
/// </summary>
[Test]
public void UnreliableHalfPrecisionTest([Values] Interpolation interpolation)
{
var interpolate = interpolation != Interpolation.EnableInterpolate;
m_AuthoritativeTransform.Interpolate = interpolate;
m_NonAuthoritativeTransform.Interpolate = interpolate;
m_AuthoritativeTransform.UseHalfFloatPrecision = true;
m_NonAuthoritativeTransform.UseHalfFloatPrecision = true;
m_AuthoritativeTransform.UseUnreliableDeltas = true;
m_NonAuthoritativeTransform.UseUnreliableDeltas = true;
m_AuthoritativeTransform.AuthorityPushedTransformState += AuthorityPushedTransformState;
m_NonAuthoritativeTransform.NonAuthorityReceivedTransformState += NonAuthorityReceivedTransformState;
m_AuthoritativeTransform.MoveSpeed = 6.325f;
m_AuthoritativeTransform.AuthorityMove = true;
m_AuthoritativeTransform.DirectionToMove = GetRandomVector3(-1.0f, 1.0f);
// Iterate several times so the authority moves around enough where we get 10 frame synchs to compare against.
for (int i = 0; i < 10; i++)
{
m_AuthorityFrameSync = false;
m_NonAuthorityFrameSync = false;
VerboseDebug($"Starting with authority ({m_AuthoritativeTransform.transform.position}) and nonauthority({m_NonAuthoritativeTransform.transform.position})");
var success = WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthorityFrameSync && m_NonAuthorityFrameSync, 320);
Assert.True(success, $"Timed out waiting for authority or nonauthority frame state synchronization!");
VerboseDebug($"Comparing authority ({m_AuthorityPosition}) with nonauthority({m_NonAuthorityPosition})");
Assert.True(Approximately(m_AuthorityPosition, m_NonAuthorityPosition), $"Non-Authoritative position {m_AuthorityPosition} does not match authortative position {m_NonAuthorityPosition}!");
}
m_AuthoritativeTransform.AuthorityMove = false;
m_AuthoritativeTransform.AuthorityPushedTransformState -= AuthorityPushedTransformState;
m_NonAuthoritativeTransform.NonAuthorityReceivedTransformState -= NonAuthorityReceivedTransformState;
}
private bool m_AuthorityFrameSync;
private Vector3 m_AuthorityPosition;
private bool m_NonAuthorityFrameSync;
private Vector3 m_NonAuthorityPosition;
private void AuthorityPushedTransformState(ref NetworkTransform.NetworkTransformState networkTransformState)
{
if (networkTransformState.UnreliableFrameSync)
{
m_AuthorityPosition = m_AuthoritativeTransform.GetSpaceRelativePosition();
m_AuthorityFrameSync = true;
}
}
private void NonAuthorityReceivedTransformState(ref NetworkTransform.NetworkTransformState networkTransformState)
{
if (networkTransformState.UnreliableFrameSync)
{
m_NonAuthorityPosition = networkTransformState.NetworkDeltaPosition.GetFullPosition();
m_NonAuthorityFrameSync = true;
}
}
}
}

View File

@@ -1,10 +1,8 @@
// TODO: Rewrite test to use the tools package. Debug simulator not available in UTP 2.X.
#if !UTP_TRANSPORT_2_0_ABOVE
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
@@ -38,32 +36,38 @@ namespace Unity.Netcode.RuntimeTests
base(testWithHost, authority, rotationCompression, rotation, precision)
{ }
protected override void OnServerAndClientsCreated()
{
base.OnServerAndClientsCreated();
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => true;
protected override bool m_TearDownIsACoroutine => true;
var unityTransport = m_ServerNetworkManager.NetworkConfig.NetworkTransport as Transports.UTP.UnityTransport;
unityTransport.SetDebugSimulatorParameters(k_Latency, 0, k_PacketLoss);
protected override void OnTimeTravelServerAndClientsConnected()
{
base.OnTimeTravelServerAndClientsConnected();
SetTimeTravelSimulatedLatency(k_Latency * 0.001f);
SetTimeTravelSimulatedDropRate(k_PacketLoss * 0.01f);
}
/// <summary>
/// Handles validating all children of the test objects have matching local and global space vaues.
/// </summary>
private IEnumerator AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTransformCheckType checkType)
private void AllChildrenLocalTransformValuesMatch(bool useSubChild, ChildrenTransformCheckType checkType)
{
// We don't assert on timeout here because we want to log this information during PostAllChildrenLocalTransformValuesMatch
yield return WaitForConditionOrTimeOut(() => AllInstancesKeptLocalTransformValues(useSubChild));
WaitForConditionOrTimeOutWithTimeTravel(() => AllInstancesKeptLocalTransformValues(useSubChild));
var success = true;
m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n");
if (s_GlobalTimeoutHelper.TimedOut)
{
var waitForMs = new WaitForSeconds(0.001f);
//var waitForMs = new WaitForSeconds(0.001f);
// If we timed out, then wait for a full range of ticks to assure all data has been synchronized before declaring this a failed test.
for (int j = 0; j < m_ServerNetworkManager.NetworkConfig.TickRate; j++)
{
m_InfoMessage.Clear();
m_InfoMessage.AppendLine($"[{checkType}][{useSubChild}] Timed out waiting for all children to have the correct local space values:\n");
var instances = useSubChild ? ChildObjectComponent.SubInstances : ChildObjectComponent.Instances;
success = PostAllChildrenLocalTransformValuesMatch(useSubChild);
yield return waitForMs;
TimeTravel(0.001f);
//yield return waitForMs;
}
}
@@ -78,8 +82,8 @@ namespace Unity.Netcode.RuntimeTests
/// parented under another NetworkTransform under all of the possible axial conditions
/// as well as when the parent has a varying scale.
/// </summary>
[UnityTest]
public IEnumerator ParentedNetworkTransformTest([Values] Interpolation interpolation, [Values] bool worldPositionStays, [Values(0.5f, 1.0f, 5.0f)] float scale)
[Test]
public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [Values] bool worldPositionStays, [Values(0.5f, 1.0f, 5.0f)] float scale)
{
ChildObjectComponent.EnableChildLog = m_EnableVerboseDebug;
if (m_EnableVerboseDebug)
@@ -101,7 +105,7 @@ namespace Unity.Netcode.RuntimeTests
var serverSideSubChild = SpawnObject(m_SubChildObject.gameObject, authorityNetworkManager).GetComponent<NetworkObject>();
// Assure all of the child object instances are spawned before proceeding to parenting
yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned);
WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned);
AssertOnTimeout("Timed out waiting for all child instances to be spawned!");
// Get the authority parent and child instances
@@ -139,7 +143,7 @@ namespace Unity.Netcode.RuntimeTests
// Allow one tick for authority to update these changes
yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches);
WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
AssertOnTimeout("All transform values did not match prior to parenting!");
@@ -150,37 +154,37 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(serverSideSubChild.TrySetParent(serverSideChild.transform, worldPositionStays), "[Server-Side SubChild] Failed to set sub-child's parent!");
// This waits for all child instances to be parented
yield return WaitForConditionOrTimeOut(AllChildObjectInstancesHaveChild);
WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild);
AssertOnTimeout("Timed out waiting for all instances to have parented a child!");
var latencyWait = new WaitForSeconds(k_Latency * 0.003f);
var latencyWait = k_Latency * 0.003f;
// Wait for at least 3x designated latency period
yield return latencyWait;
TimeTravel(latencyWait);
// This validates each child instance has preserved their local space values
yield return AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Connected_Clients);
AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Connected_Clients);
// This validates each sub-child instance has preserved their local space values
yield return AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Connected_Clients);
AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Connected_Clients);
// Verify that a late joining client will synchronize to the parented NetworkObjects properly
yield return CreateAndStartNewClient();
CreateAndStartNewClientWithTimeTravel();
// Assure all of the child object instances are spawned (basically for the newly connected client)
yield return WaitForConditionOrTimeOut(AllChildObjectInstancesAreSpawned);
WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned);
AssertOnTimeout("Timed out waiting for all child instances to be spawned!");
// This waits for all child instances to be parented
yield return WaitForConditionOrTimeOut(AllChildObjectInstancesHaveChild);
WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild);
AssertOnTimeout("Timed out waiting for all instances to have parented a child!");
// Wait for at least 3x designated latency period
yield return latencyWait;
TimeTravel(latencyWait);
// This validates each child instance has preserved their local space values
yield return AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Late_Join_Client);
AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Late_Join_Client);
// This validates each sub-child instance has preserved their local space values
yield return AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Late_Join_Client);
AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Late_Join_Client);
}
/// <summary>
@@ -192,10 +196,10 @@ namespace Unity.Netcode.RuntimeTests
/// When testing 3 axis: Interpolation is enabled, sometimes an axis is intentionally excluded during a
/// delta update, and it runs through 8 delta updates per unique test.
/// </remarks>
[UnityTest]
public IEnumerator NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] Axis axis)
[Test]
public void NetworkTransformMultipleChangesOverTime([Values] TransformSpace testLocalTransform, [Values] Axis axis)
{
yield return s_DefaultWaitForTick;
TimeTravelAdvanceTick();
// Just test for OverrideState.Update (they are already being tested for functionality in normal NetworkTransformTests)
var overideState = OverrideState.Update;
var tickRelativeTime = new WaitForSeconds(1.0f / m_ServerNetworkManager.NetworkConfig.TickRate);
@@ -255,7 +259,7 @@ namespace Unity.Netcode.RuntimeTests
// Wait for the deltas to be pushed
yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed);
WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed);
// Just in case we drop the first few state updates
if (s_GlobalTimeoutHelper.TimedOut)
@@ -266,17 +270,17 @@ namespace Unity.Netcode.RuntimeTests
state.InLocalSpace = !m_AuthoritativeTransform.InLocalSpace;
m_AuthoritativeTransform.LocalAuthoritativeNetworkState = state;
// Wait for the deltas to be pushed
yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed);
WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed);
}
AssertOnTimeout("State was never pushed!");
// Allow the precision settings to propagate first as changing precision
// causes a teleport event to occur
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
yield return s_DefaultWaitForTick;
TimeTravelAdvanceTick();
TimeTravelAdvanceTick();
TimeTravelAdvanceTick();
TimeTravelAdvanceTick();
TimeTravelAdvanceTick();
var iterations = axisCount == 3 ? k_PositionRotationScaleIterations3Axis : k_PositionRotationScaleIterations;
// Move and rotate within the same tick, validate the non-authoritative instance updates
@@ -311,7 +315,7 @@ namespace Unity.Netcode.RuntimeTests
MoveRotateAndScaleAuthority(position, rotation, scale, overideState);
// Wait for the deltas to be pushed (unlike the original test, we don't wait for state to be updated as that could be dropped here)
yield return WaitForConditionOrTimeOut(() => m_AuthoritativeTransform.StatePushed);
WaitForConditionOrTimeOutWithTimeTravel(() => m_AuthoritativeTransform.StatePushed);
AssertOnTimeout($"[Non-Interpolate {i}] Timed out waiting for state to be pushed ({m_AuthoritativeTransform.StatePushed})!");
// For 3 axis, we will skip validating that the non-authority interpolates to its target point at least once.
@@ -321,7 +325,7 @@ namespace Unity.Netcode.RuntimeTests
if (m_AxisExcluded || axisCount < 3)
{
// Wait for deltas to synchronize on non-authoritative side
yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches);
WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (s_GlobalTimeoutHelper.TimedOut)
{
@@ -335,7 +339,7 @@ namespace Unity.Netcode.RuntimeTests
// If we matched, then something was dropped and recovered when synchronized
break;
}
yield return s_DefaultWaitForTick;
TimeTravelAdvanceTick();
}
// Only if we still didn't match
@@ -354,7 +358,7 @@ namespace Unity.Netcode.RuntimeTests
if (axisCount == 3)
{
// As a final test, wait for deltas to synchronize on non-authoritative side to assure it interpolates to the correct values
yield return WaitForConditionOrTimeOut(PositionRotationScaleMatches);
WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
// Provide additional debug info about what failed (if it fails)
if (s_GlobalTimeoutHelper.TimedOut)
{
@@ -368,7 +372,7 @@ namespace Unity.Netcode.RuntimeTests
// If we matched, then something was dropped and recovered when synchronized
break;
}
yield return s_DefaultWaitForTick;
TimeTravelAdvanceTick();
}
// Only if we still didn't match
@@ -392,8 +396,8 @@ namespace Unity.Netcode.RuntimeTests
/// - While in local space and world space
/// - While interpolation is enabled and disabled
/// </summary>
[UnityTest]
public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation)
[Test]
public void TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation)
{
// Just test for OverrideState.Update (they are already being tested for functionality in normal NetworkTransformTests)
m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
@@ -411,7 +415,7 @@ namespace Unity.Netcode.RuntimeTests
m_AuthoritativeTransform.transform.position = GetRandomVector3(2f, 30f);
yield return WaitForConditionOrTimeOut(() => PositionsMatch());
WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatch());
AssertOnTimeout($"Timed out waiting for positions to match {m_AuthoritativeTransform.transform.position} | {m_NonAuthoritativeTransform.transform.position}");
// test rotation
@@ -420,19 +424,19 @@ namespace Unity.Netcode.RuntimeTests
m_AuthoritativeTransform.transform.rotation = Quaternion.Euler(GetRandomVector3(5, 60)); // using euler angles instead of quaternions directly to really see issues users might encounter
// Make sure the values match
yield return WaitForConditionOrTimeOut(() => RotationsMatch());
WaitForConditionOrTimeOutWithTimeTravel(() => RotationsMatch());
AssertOnTimeout($"Timed out waiting for rotations to match");
m_AuthoritativeTransform.StatePushed = false;
m_AuthoritativeTransform.transform.localScale = GetRandomVector3(1, 6);
// Make sure the scale values match
yield return WaitForConditionOrTimeOut(() => ScaleValuesMatch());
WaitForConditionOrTimeOutWithTimeTravel(() => ScaleValuesMatch());
AssertOnTimeout($"Timed out waiting for scale values to match");
}
[UnityTest]
public IEnumerator TestSameFrameDeltaStateAndTeleport([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation)
[Test]
public void TestSameFrameDeltaStateAndTeleport([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation)
{
m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
@@ -449,10 +453,10 @@ namespace Unity.Netcode.RuntimeTests
m_RandomPosition = GetRandomVector3(2f, 30f);
m_AuthoritativeTransform.transform.position = m_RandomPosition;
m_Teleported = false;
yield return WaitForConditionOrTimeOut(() => m_Teleported);
WaitForConditionOrTimeOutWithTimeTravel(() => m_Teleported);
AssertOnTimeout($"Timed out waiting for random position to be pushed!");
yield return WaitForConditionOrTimeOut(() => PositionsMatch());
WaitForConditionOrTimeOutWithTimeTravel(() => PositionsMatch());
AssertOnTimeout($"Timed out waiting for positions to match {m_AuthoritativeTransform.transform.position} | {m_NonAuthoritativeTransform.transform.position}");
var authPosition = m_AuthoritativeTransform.GetSpaceRelativePosition();

View File

@@ -1,3 +1,4 @@
using System.Collections;
using NUnit.Framework;
using UnityEngine;
@@ -41,6 +42,24 @@ namespace Unity.Netcode.RuntimeTests
return k_TickRate;
}
private bool m_UseParentingThreshold;
private const float k_ParentingThreshold = 0.25f;
protected override float GetDeltaVarianceThreshold()
{
if (m_UseParentingThreshold)
{
return k_ParentingThreshold;
}
return base.GetDeltaVarianceThreshold();
}
protected override IEnumerator OnSetup()
{
m_UseParentingThreshold = false;
return base.OnSetup();
}
/// <summary>
/// Handles validating the local space values match the original local space values.
/// If not, it generates a message containing the axial values that did not match
@@ -77,6 +96,7 @@ namespace Unity.Netcode.RuntimeTests
[Test]
public void ParentedNetworkTransformTest([Values] Interpolation interpolation, [Values] bool worldPositionStays, [Values(0.5f, 1.0f, 5.0f)] float scale)
{
m_UseParentingThreshold = true;
// Get the NetworkManager that will have authority in order to spawn with the correct authority
var isServerAuthority = m_Authority == Authority.ServerAuthority;
var authorityNetworkManager = m_ServerNetworkManager;

View File

@@ -0,0 +1,521 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.Components;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkTransformAnticipationComponent : NetworkBehaviour
{
[Rpc(SendTo.Server)]
public void MoveRpc(Vector3 newPosition)
{
transform.position = newPosition;
}
[Rpc(SendTo.Server)]
public void ScaleRpc(Vector3 newScale)
{
transform.localScale = newScale;
}
[Rpc(SendTo.Server)]
public void RotateRpc(Quaternion newRotation)
{
transform.rotation = newRotation;
}
public bool ShouldSmooth = false;
public bool ShouldMove = false;
public override void OnReanticipate(double lastRoundTripTime)
{
var transform_ = GetComponent<AnticipatedNetworkTransform>();
if (transform_.ShouldReanticipate)
{
if (ShouldSmooth)
{
transform_.Smooth(transform_.PreviousAnticipatedState, transform_.AuthoritativeState, 1);
}
if (ShouldMove)
{
transform_.AnticipateMove(transform_.AuthoritativeState.Position + new Vector3(0, 5, 0));
}
}
}
}
public class NetworkTransformAnticipationTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => false;
protected override bool m_TearDownIsACoroutine => false;
protected override void OnPlayerPrefabGameObjectCreated()
{
m_PlayerPrefab.AddComponent<AnticipatedNetworkTransform>();
m_PlayerPrefab.AddComponent<NetworkTransformAnticipationComponent>();
}
protected override void OnTimeTravelServerAndClientsConnected()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
var otherClientComponent = GetOtherClientComponent();
serverComponent.transform.position = Vector3.zero;
serverComponent.transform.localScale = Vector3.one;
serverComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward);
testComponent.transform.position = Vector3.zero;
testComponent.transform.localScale = Vector3.one;
testComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward);
otherClientComponent.transform.position = Vector3.zero;
otherClientComponent.transform.localScale = Vector3.one;
otherClientComponent.transform.rotation = Quaternion.LookRotation(Vector3.forward);
}
public AnticipatedNetworkTransform GetTestComponent()
{
return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<AnticipatedNetworkTransform>();
}
public AnticipatedNetworkTransform GetServerComponent()
{
foreach (var obj in Object.FindObjectsByType<AnticipatedNetworkTransform>(FindObjectsSortMode.None))
{
if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId)
{
return obj;
}
}
return null;
}
public AnticipatedNetworkTransform GetOtherClientComponent()
{
foreach (var obj in Object.FindObjectsByType<AnticipatedNetworkTransform>(FindObjectsSortMode.None))
{
if (obj.NetworkManager == m_ClientNetworkManagers[1] && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId)
{
return obj;
}
}
return null;
}
[Test]
public void WhenAnticipating_ValueChangesImmediately()
{
var testComponent = GetTestComponent();
testComponent.AnticipateMove(new Vector3(0, 1, 2));
testComponent.AnticipateScale(new Vector3(1, 2, 3));
testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4)));
Assert.AreEqual(new Vector3(0, 1, 2), testComponent.transform.position);
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.localScale);
Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.transform.rotation);
Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Scale);
Assert.AreEqual(Quaternion.LookRotation(new Vector3(2, 3, 4)), testComponent.AnticipatedState.Rotation);
}
[Test]
public void WhenAnticipating_AuthoritativeValueDoesNotChange()
{
var testComponent = GetTestComponent();
var startPosition = testComponent.transform.position;
var startScale = testComponent.transform.localScale;
var startRotation = testComponent.transform.rotation;
testComponent.AnticipateMove(new Vector3(0, 1, 2));
testComponent.AnticipateScale(new Vector3(1, 2, 3));
testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4)));
Assert.AreEqual(startPosition, testComponent.AuthoritativeState.Position);
Assert.AreEqual(startScale, testComponent.AuthoritativeState.Scale);
Assert.AreEqual(startRotation, testComponent.AuthoritativeState.Rotation);
}
[Test]
public void WhenAnticipating_ServerDoesNotChange()
{
var testComponent = GetTestComponent();
var startPosition = testComponent.transform.position;
var startScale = testComponent.transform.localScale;
var startRotation = testComponent.transform.rotation;
testComponent.AnticipateMove(new Vector3(0, 1, 2));
testComponent.AnticipateScale(new Vector3(1, 2, 3));
testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4)));
var serverComponent = GetServerComponent();
Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position);
Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale);
Assert.AreEqual(startRotation, serverComponent.AuthoritativeState.Rotation);
Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position);
Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale);
Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation);
TimeTravel(2, 120);
Assert.AreEqual(startPosition, serverComponent.AuthoritativeState.Position);
Assert.AreEqual(startScale, serverComponent.AuthoritativeState.Scale);
Assert.AreEqual(startRotation, serverComponent.AuthoritativeState.Rotation);
Assert.AreEqual(startPosition, serverComponent.AnticipatedState.Position);
Assert.AreEqual(startScale, serverComponent.AnticipatedState.Scale);
Assert.AreEqual(startRotation, serverComponent.AnticipatedState.Rotation);
}
[Test]
public void WhenAnticipating_OtherClientDoesNotChange()
{
var testComponent = GetTestComponent();
var startPosition = testComponent.transform.position;
var startScale = testComponent.transform.localScale;
var startRotation = testComponent.transform.rotation;
testComponent.AnticipateMove(new Vector3(0, 1, 2));
testComponent.AnticipateScale(new Vector3(1, 2, 3));
testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4)));
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position);
Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale);
Assert.AreEqual(startRotation, otherClientComponent.AuthoritativeState.Rotation);
Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale);
Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation);
TimeTravel(2, 120);
Assert.AreEqual(startPosition, otherClientComponent.AuthoritativeState.Position);
Assert.AreEqual(startScale, otherClientComponent.AuthoritativeState.Scale);
Assert.AreEqual(startRotation, otherClientComponent.AuthoritativeState.Rotation);
Assert.AreEqual(startPosition, otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(startScale, otherClientComponent.AnticipatedState.Scale);
Assert.AreEqual(startRotation, otherClientComponent.AnticipatedState.Rotation);
}
[Test]
public void WhenServerChangesSnapValue_ValuesAreUpdated()
{
var testComponent = GetTestComponent();
var serverComponent = GetServerComponent();
serverComponent.Interpolate = false;
testComponent.AnticipateMove(new Vector3(0, 1, 2));
testComponent.AnticipateScale(new Vector3(1, 2, 3));
testComponent.AnticipateRotate(Quaternion.LookRotation(new Vector3(2, 3, 4)));
var rpcComponent = testComponent.GetComponent<NetworkTransformAnticipationComponent>();
rpcComponent.MoveRpc(new Vector3(2, 3, 4));
WaitForMessageReceivedWithTimeTravel<RpcMessage>(new List<NetworkManager> { m_ServerNetworkManager });
var otherClientComponent = GetOtherClientComponent();
WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position);
Assert.AreEqual(serverComponent.transform.position, testComponent.transform.position);
Assert.AreEqual(serverComponent.transform.position, testComponent.AnticipatedState.Position);
Assert.AreEqual(serverComponent.transform.position, testComponent.AuthoritativeState.Position);
Assert.AreEqual(serverComponent.transform.position, otherClientComponent.transform.position);
Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(serverComponent.transform.position, otherClientComponent.AuthoritativeState.Position);
}
public void AssertQuaternionsAreEquivalent(Quaternion a, Quaternion b)
{
var aAngles = a.eulerAngles;
var bAngles = b.eulerAngles;
Assert.AreEqual(aAngles.x, bAngles.x, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}");
Assert.AreEqual(aAngles.y, bAngles.y, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}");
Assert.AreEqual(aAngles.z, bAngles.z, 0.001, $"Quaternions were not equal. Expected: {a}, but was {b}");
}
public void AssertVectorsAreEquivalent(Vector3 a, Vector3 b)
{
Assert.AreEqual(a.x, b.x, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}");
Assert.AreEqual(a.y, b.y, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}");
Assert.AreEqual(a.z, b.z, 0.001, $"Vectors were not equal. Expected: {a}, but was {b}");
}
[Test]
public void WhenServerChangesSmoothValue_ValuesAreLerped()
{
var testComponent = GetTestComponent();
var otherClientComponent = GetOtherClientComponent();
testComponent.StaleDataHandling = StaleDataHandling.Ignore;
otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore;
var serverComponent = GetServerComponent();
serverComponent.Interpolate = false;
testComponent.GetComponent<NetworkTransformAnticipationComponent>().ShouldSmooth = true;
otherClientComponent.GetComponent<NetworkTransformAnticipationComponent>().ShouldSmooth = true;
var startPosition = testComponent.transform.position;
var startScale = testComponent.transform.localScale;
var startRotation = testComponent.transform.rotation;
var anticipePosition = new Vector3(0, 1, 2);
var anticipeScale = new Vector3(1, 2, 3);
var anticipeRotation = Quaternion.LookRotation(new Vector3(2, 3, 4));
var serverSetPosition = new Vector3(3, 4, 5);
var serverSetScale = new Vector3(4, 5, 6);
var serverSetRotation = Quaternion.LookRotation(new Vector3(5, 6, 7));
testComponent.AnticipateMove(anticipePosition);
testComponent.AnticipateScale(anticipeScale);
testComponent.AnticipateRotate(anticipeRotation);
var rpcComponent = testComponent.GetComponent<NetworkTransformAnticipationComponent>();
rpcComponent.MoveRpc(serverSetPosition);
rpcComponent.RotateRpc(serverSetRotation);
rpcComponent.ScaleRpc(serverSetScale);
WaitForMessagesReceivedWithTimeTravel(new List<Type>
{
typeof(RpcMessage),
typeof(RpcMessage),
typeof(RpcMessage),
}, new List<NetworkManager> { m_ServerNetworkManager });
WaitForMessageReceivedWithTimeTravel<NetworkTransformMessage>(m_ClientNetworkManagers.ToList());
var percentChanged = 1f / 60f;
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation);
for (var i = 1; i < 60; ++i)
{
TimeTravel(1f / 60f, 1);
percentChanged = 1f / 60f * (i + 1);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation);
}
TimeTravel(1f / 60f, 1);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.transform.position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.transform.localScale);
AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.transform.rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, testComponent.AuthoritativeState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.transform.position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.transform.localScale);
AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.transform.rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale);
AssertQuaternionsAreEquivalent(serverSetRotation, otherClientComponent.AuthoritativeState.Rotation);
}
[Test]
public void WhenServerChangesReanticipeValue_ValuesAreReanticiped()
{
var testComponent = GetTestComponent();
var otherClientComponent = GetOtherClientComponent();
testComponent.GetComponent<NetworkTransformAnticipationComponent>().ShouldMove = true;
otherClientComponent.GetComponent<NetworkTransformAnticipationComponent>().ShouldMove = true;
var serverComponent = GetServerComponent();
serverComponent.Interpolate = false;
serverComponent.transform.position = new Vector3(0, 1, 2);
var rpcComponent = testComponent.GetComponent<NetworkTransformAnticipationComponent>();
rpcComponent.MoveRpc(new Vector3(0, 1, 2));
WaitForMessageReceivedWithTimeTravel<RpcMessage>(new List<NetworkManager> { m_ServerNetworkManager });
WaitForMessageReceivedWithTimeTravel<NetworkTransformMessage>(m_ClientNetworkManagers.ToList());
Assert.AreEqual(new Vector3(0, 6, 2), testComponent.transform.position);
Assert.AreEqual(new Vector3(0, 6, 2), testComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(0, 1, 2), testComponent.AuthoritativeState.Position);
Assert.AreEqual(new Vector3(0, 6, 2), otherClientComponent.transform.position);
Assert.AreEqual(new Vector3(0, 6, 2), otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(0, 1, 2), otherClientComponent.AuthoritativeState.Position);
}
[Test]
public void WhenStaleDataArrivesToIgnoreVariable_ItIsIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames)
{
m_ServerNetworkManager.NetworkConfig.TickRate = tickRate;
m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate;
for (var i = 0; i < skipFrames; ++i)
{
TimeTravel(1 / 60f, 1);
}
var serverComponent = GetServerComponent();
serverComponent.Interpolate = false;
var testComponent = GetTestComponent();
testComponent.StaleDataHandling = StaleDataHandling.Ignore;
testComponent.Interpolate = false;
var otherClientComponent = GetOtherClientComponent();
otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore;
otherClientComponent.Interpolate = false;
var rpcComponent = testComponent.GetComponent<NetworkTransformAnticipationComponent>();
rpcComponent.MoveRpc(new Vector3(1, 2, 3));
WaitForMessageReceivedWithTimeTravel<RpcMessage>(new List<NetworkManager> { m_ServerNetworkManager });
testComponent.AnticipateMove(new Vector3(0, 5, 0));
rpcComponent.MoveRpc(new Vector3(4, 5, 6));
// Depending on tick rate, one of these two things will happen.
// The assertions are different based on this... either the tick rate is slow enough that the second RPC is received
// before the next update and we move to 4, 5, 6, or the tick rate is fast enough that the next update is sent out
// before the RPC is received and we get the update for the move to 1, 2, 3. Both are valid, what we want to assert
// here is that the anticipated state never becomes 1, 2, 3.
WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == new Vector3(1, 2, 3) || testComponent.AuthoritativeState.Position == new Vector3(4, 5, 6));
if (testComponent.AnticipatedState.Position == new Vector3(4, 5, 6))
{
// Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value
Assert.AreEqual(new Vector3(4, 5, 6), testComponent.transform.position);
Assert.AreEqual(new Vector3(4, 5, 6), testComponent.AnticipatedState.Position);
// However, the authoritative value still gets updated
Assert.AreEqual(new Vector3(4, 5, 6), testComponent.AuthoritativeState.Position);
// Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well.
Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.transform.position);
Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(4, 5, 6), otherClientComponent.AuthoritativeState.Position);
}
else
{
// Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value
Assert.AreEqual(new Vector3(0, 5, 0), testComponent.transform.position);
Assert.AreEqual(new Vector3(0, 5, 0), testComponent.AnticipatedState.Position);
// However, the authoritative value still gets updated
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AuthoritativeState.Position);
// Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well.
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.transform.position);
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AuthoritativeState.Position);
}
}
[Test]
public void WhenNonStaleDataArrivesToIgnoreVariable_ItIsNotIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames)
{
m_ServerNetworkManager.NetworkConfig.TickRate = tickRate;
m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate;
for (var i = 0; i < skipFrames; ++i)
{
TimeTravel(1 / 60f, 1);
}
var serverComponent = GetServerComponent();
serverComponent.Interpolate = false;
var testComponent = GetTestComponent();
testComponent.StaleDataHandling = StaleDataHandling.Ignore;
testComponent.Interpolate = false;
var otherClientComponent = GetOtherClientComponent();
otherClientComponent.StaleDataHandling = StaleDataHandling.Ignore;
otherClientComponent.Interpolate = false;
testComponent.AnticipateMove(new Vector3(0, 5, 0));
var rpcComponent = testComponent.GetComponent<NetworkTransformAnticipationComponent>();
rpcComponent.MoveRpc(new Vector3(1, 2, 3));
WaitForMessageReceivedWithTimeTravel<RpcMessage>(new List<NetworkManager> { m_ServerNetworkManager });
WaitForConditionOrTimeOutWithTimeTravel(() => testComponent.AuthoritativeState.Position == serverComponent.transform.position && otherClientComponent.AuthoritativeState.Position == serverComponent.transform.position);
// Anticiped client received this data for a time earlier than its anticipation, and should have prioritized the anticiped value
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.transform.position);
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AnticipatedState.Position);
// However, the authoritative value still gets updated
Assert.AreEqual(new Vector3(1, 2, 3), testComponent.AuthoritativeState.Position);
// Other client got the server value and had made no anticipation, so it applies it to the anticiped value as well.
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.transform.position);
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AnticipatedState.Position);
Assert.AreEqual(new Vector3(1, 2, 3), otherClientComponent.AuthoritativeState.Position);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 4f149de86bec4f6eb1a4d62a1b52938a
timeCreated: 1706630210

View File

@@ -0,0 +1,420 @@
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkVariableAnticipationComponent : NetworkBehaviour
{
public AnticipatedNetworkVariable<int> SnapOnAnticipationFailVariable = new AnticipatedNetworkVariable<int>(0, StaleDataHandling.Ignore);
public AnticipatedNetworkVariable<float> SmoothOnAnticipationFailVariable = new AnticipatedNetworkVariable<float>(0, StaleDataHandling.Reanticipate);
public AnticipatedNetworkVariable<float> ReanticipateOnAnticipationFailVariable = new AnticipatedNetworkVariable<float>(0, StaleDataHandling.Reanticipate);
public override void OnReanticipate(double lastRoundTripTime)
{
if (SmoothOnAnticipationFailVariable.ShouldReanticipate)
{
if (Mathf.Abs(SmoothOnAnticipationFailVariable.AuthoritativeValue - SmoothOnAnticipationFailVariable.PreviousAnticipatedValue) > Mathf.Epsilon)
{
SmoothOnAnticipationFailVariable.Smooth(SmoothOnAnticipationFailVariable.PreviousAnticipatedValue, SmoothOnAnticipationFailVariable.AuthoritativeValue, 1, Mathf.Lerp);
}
}
if (ReanticipateOnAnticipationFailVariable.ShouldReanticipate)
{
// Would love to test some stuff about anticipation based on time, but that is difficult to test accurately.
// This reanticipating variable will just always anticipate a value 5 higher than the server value.
ReanticipateOnAnticipationFailVariable.Anticipate(ReanticipateOnAnticipationFailVariable.AuthoritativeValue + 5);
}
}
public bool SnapRpcResponseReceived = false;
[Rpc(SendTo.Server)]
public void SetSnapValueRpc(int i, RpcParams rpcParams = default)
{
SnapOnAnticipationFailVariable.AuthoritativeValue = i;
SetSnapValueResponseRpc(RpcTarget.Single(rpcParams.Receive.SenderClientId, RpcTargetUse.Temp));
}
[Rpc(SendTo.SpecifiedInParams)]
public void SetSnapValueResponseRpc(RpcParams rpcParams)
{
SnapRpcResponseReceived = true;
}
[Rpc(SendTo.Server)]
public void SetSmoothValueRpc(float f)
{
SmoothOnAnticipationFailVariable.AuthoritativeValue = f;
}
[Rpc(SendTo.Server)]
public void SetReanticipateValueRpc(float f)
{
ReanticipateOnAnticipationFailVariable.AuthoritativeValue = f;
}
}
public class NetworkVariableAnticipationTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => false;
protected override bool m_TearDownIsACoroutine => false;
protected override void OnPlayerPrefabGameObjectCreated()
{
m_PlayerPrefab.AddComponent<NetworkVariableAnticipationComponent>();
}
public NetworkVariableAnticipationComponent GetTestComponent()
{
return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<NetworkVariableAnticipationComponent>();
}
public NetworkVariableAnticipationComponent GetServerComponent()
{
foreach (var obj in Object.FindObjectsByType<NetworkVariableAnticipationComponent>(FindObjectsSortMode.None))
{
if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId)
{
return obj;
}
}
return null;
}
public NetworkVariableAnticipationComponent GetOtherClientComponent()
{
foreach (var obj in Object.FindObjectsByType<NetworkVariableAnticipationComponent>(FindObjectsSortMode.None))
{
if (obj.NetworkManager == m_ClientNetworkManagers[1] && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId)
{
return obj;
}
}
return null;
}
[Test]
public void WhenAnticipating_ValueChangesImmediately()
{
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
testComponent.SmoothOnAnticipationFailVariable.Anticipate(15);
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20);
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(15, testComponent.SmoothOnAnticipationFailVariable.Value);
Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.Value);
}
[Test]
public void WhenAnticipating_AuthoritativeValueDoesNotChange()
{
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
testComponent.SmoothOnAnticipationFailVariable.Anticipate(15);
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20);
Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
}
[Test]
public void WhenAnticipating_ServerDoesNotChange()
{
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
testComponent.SmoothOnAnticipationFailVariable.Anticipate(15);
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20);
var serverComponent = GetServerComponent();
Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.Value);
Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.Value);
TimeTravel(2, 120);
Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, serverComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, serverComponent.SmoothOnAnticipationFailVariable.Value);
Assert.AreEqual(0, serverComponent.ReanticipateOnAnticipationFailVariable.Value);
}
[Test]
public void WhenAnticipating_OtherClientDoesNotChange()
{
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
testComponent.SmoothOnAnticipationFailVariable.Anticipate(15);
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(20);
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value);
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value);
TimeTravel(2, 120);
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value);
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value);
}
[Test]
public void WhenServerChangesSnapValue_ValuesAreUpdated()
{
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
testComponent.SetSnapValueRpc(10);
WaitForMessageReceivedWithTimeTravel<RpcMessage>(
new List<NetworkManager> { m_ServerNetworkManager }
);
var serverComponent = GetServerComponent();
Assert.AreEqual(10, serverComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(10, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
Assert.AreEqual(10, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(10, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
}
[Test]
public void WhenServerChangesSmoothValue_ValuesAreLerped()
{
var testComponent = GetTestComponent();
testComponent.SmoothOnAnticipationFailVariable.Anticipate(15);
Assert.AreEqual(15, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(0, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
// Set to a different value to simulate a anticipation failure - will lerp between the anticipated value
// and the actual one
testComponent.SetSmoothValueRpc(20);
WaitForMessageReceivedWithTimeTravel<RpcMessage>(
new List<NetworkManager> { m_ServerNetworkManager }
);
var serverComponent = GetServerComponent();
Assert.AreEqual(20, serverComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, serverComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(0, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
Assert.AreEqual(15 + 1f / 60f * 5, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
Assert.AreEqual(0 + 1f / 60f * 20, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
for (var i = 1; i < 60; ++i)
{
TimeTravel(1f / 60f, 1);
Assert.AreEqual(15 + 1f / 60f * 5 * (i + 1), testComponent.SmoothOnAnticipationFailVariable.Value, 0.00001);
Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
Assert.AreEqual(0 + 1f / 60f * 20 * (i + 1), otherClientComponent.SmoothOnAnticipationFailVariable.Value, 0.00001);
Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
}
TimeTravel(1f / 60f, 1);
Assert.AreEqual(20, testComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, otherClientComponent.SmoothOnAnticipationFailVariable.Value, Mathf.Epsilon);
}
[Test]
public void WhenServerChangesReanticipateValue_ValuesAreReanticipated()
{
var testComponent = GetTestComponent();
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(15);
Assert.AreEqual(15, testComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
// Set to a different value to simulate a anticipation failure - will lerp between the anticipated value
// and the actual one
testComponent.SetReanticipateValueRpc(20);
WaitForMessageReceivedWithTimeTravel<RpcMessage>(
new List<NetworkManager> { m_ServerNetworkManager }
);
var serverComponent = GetServerComponent();
Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(0, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
Assert.AreEqual(25, testComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
Assert.AreEqual(25, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value, Mathf.Epsilon);
Assert.AreEqual(20, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue, Mathf.Epsilon);
}
[Test]
public void WhenNonStaleDataArrivesToIgnoreVariable_ItIsNotIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames)
{
m_ServerNetworkManager.NetworkConfig.TickRate = tickRate;
m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate;
for (var i = 0; i < skipFrames; ++i)
{
TimeTravel(1 / 60f, 1);
}
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
testComponent.SetSnapValueRpc(20);
WaitForMessageReceivedWithTimeTravel<RpcMessage>(new List<NetworkManager> { m_ServerNetworkManager });
var serverComponent = GetServerComponent();
Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
// Both values get updated
Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
// Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well.
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
}
[Test]
public void WhenStaleDataArrivesToIgnoreVariable_ItIsIgnored([Values(10u, 30u, 60u)] uint tickRate, [Values(0u, 1u, 2u)] uint skipFrames)
{
m_ServerNetworkManager.NetworkConfig.TickRate = tickRate;
m_ServerNetworkManager.NetworkTickSystem.TickRate = tickRate;
for (var i = 0; i < skipFrames; ++i)
{
TimeTravel(1 / 60f, 1);
}
var testComponent = GetTestComponent();
testComponent.SnapOnAnticipationFailVariable.Anticipate(10);
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(0, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
testComponent.SetSnapValueRpc(30);
var serverComponent = GetServerComponent();
serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue = 20;
Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(20, serverComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
if (testComponent.SnapRpcResponseReceived)
{
// In this case the tick rate is slow enough that the RPC was received and processed, so we check that.
Assert.AreEqual(30, testComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(30, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(30, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(30, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
}
else
{
// In this case, we got an update before the RPC was processed, so we should have ignored it.
// Anticipated client received this data for a tick earlier than its anticipation, and should have prioritized the anticipated value
Assert.AreEqual(10, testComponent.SnapOnAnticipationFailVariable.Value);
// However, the authoritative value still gets updated
Assert.AreEqual(20, testComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
// Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well.
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.Value);
Assert.AreEqual(20, otherClientComponent.SnapOnAnticipationFailVariable.AuthoritativeValue);
}
}
[Test]
public void WhenStaleDataArrivesToReanticipatedVariable_ItIsAppliedAndReanticipated()
{
var testComponent = GetTestComponent();
testComponent.ReanticipateOnAnticipationFailVariable.Anticipate(10);
Assert.AreEqual(10, testComponent.ReanticipateOnAnticipationFailVariable.Value);
Assert.AreEqual(0, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
var serverComponent = GetServerComponent();
serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue = 20;
Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.Value);
Assert.AreEqual(20, serverComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
WaitForMessageReceivedWithTimeTravel<NetworkVariableDeltaMessage>(m_ClientNetworkManagers.ToList());
Assert.AreEqual(25, testComponent.ReanticipateOnAnticipationFailVariable.Value);
// However, the authoritative value still gets updated
Assert.AreEqual(20, testComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
// Other client got the server value and had made no anticipation, so it applies it to the anticipated value as well.
var otherClientComponent = GetOtherClientComponent();
Assert.AreEqual(25, otherClientComponent.ReanticipateOnAnticipationFailVariable.Value);
Assert.AreEqual(20, otherClientComponent.ReanticipateOnAnticipationFailVariable.AuthoritativeValue);
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 43cd37f850534b7db07e20281442f10d
timeCreated: 1706288570

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,936 @@
using System;
using System.Collections.Generic;
using Unity.Collections;
using UnityEngine;
namespace Unity.Netcode.RuntimeTests
{
public class NetVarPermTestComp : NetworkBehaviour
{
public NetworkVariable<Vector3> OwnerWritable_Position = new NetworkVariable<Vector3>(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Owner);
public NetworkVariable<Vector3> ServerWritable_Position = new NetworkVariable<Vector3>(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Server);
public NetworkVariable<Vector3> OwnerReadWrite_Position = new NetworkVariable<Vector3>(Vector3.one, NetworkVariableReadPermission.Owner, NetworkVariableWritePermission.Owner);
}
public class NetworkVariableMiddleclass<TMiddleclassName> : NetworkVariable<TMiddleclassName>
{
}
public class NetworkVariableSubclass<TSubclassName> : NetworkVariableMiddleclass<TSubclassName>
{
}
public class NetworkBehaviourWithNetVarArray : NetworkBehaviour
{
public NetworkVariable<int> Int0 = new NetworkVariable<int>();
public NetworkVariable<int> Int1 = new NetworkVariable<int>();
public NetworkVariable<int> Int2 = new NetworkVariable<int>();
public NetworkVariable<int> Int3 = new NetworkVariable<int>();
public NetworkVariable<int> Int4 = new NetworkVariable<int>();
public NetworkVariable<int>[] AllInts = new NetworkVariable<int>[5];
public int InitializedFieldCount => NetworkVariableFields.Count;
private void Awake()
{
AllInts[0] = Int0;
AllInts[1] = Int1;
AllInts[2] = Int2;
AllInts[3] = Int3;
AllInts[4] = Int4;
}
}
internal struct TypeReferencedOnlyInCustomSerialization1 : INetworkSerializeByMemcpy
{
public int I;
}
internal struct TypeReferencedOnlyInCustomSerialization2 : INetworkSerializeByMemcpy
{
public int I;
}
internal struct TypeReferencedOnlyInCustomSerialization3 : INetworkSerializeByMemcpy
{
public int I;
}
internal struct TypeReferencedOnlyInCustomSerialization4 : INetworkSerializeByMemcpy
{
public int I;
}
internal struct TypeReferencedOnlyInCustomSerialization5 : INetworkSerializeByMemcpy
{
public int I;
}
internal struct TypeReferencedOnlyInCustomSerialization6 : INetworkSerializeByMemcpy
{
public int I;
}
// Both T and U are serializable
[GenerateSerializationForGenericParameter(0)]
[GenerateSerializationForGenericParameter(1)]
internal class CustomSerializableClass<TSerializableType1, TSerializableType2>
{
}
// Only U is serializable
[GenerateSerializationForGenericParameter(1)]
internal class CustomSerializableBaseClass<TUnserializableType, TSerializableType>
{
}
// T is serializable, passes TypeReferencedOnlyInCustomSerialization3 as U to the subclass, making it serializable
[GenerateSerializationForGenericParameter(0)]
internal class CustomSerializableSubclass<TSerializableType> : CustomSerializableBaseClass<TSerializableType, TypeReferencedOnlyInCustomSerialization3>
{
}
// T is serializable, passes TypeReferencedOnlyInCustomSerialization3 as U to the subclass, making it serializable
[GenerateSerializationForGenericParameter(0)]
internal class CustomSerializableSubclassWithNativeArray<TSerializableType> : CustomSerializableBaseClass<TSerializableType, NativeArray<TypeReferencedOnlyInCustomSerialization3>>
{
}
internal class CustomGenericSerializationTestBehaviour : NetworkBehaviour
{
public CustomSerializableClass<TypeReferencedOnlyInCustomSerialization1, TypeReferencedOnlyInCustomSerialization2> Value1;
public CustomSerializableClass<NativeArray<TypeReferencedOnlyInCustomSerialization1>, NativeArray<TypeReferencedOnlyInCustomSerialization2>> Value2;
public CustomSerializableSubclass<TypeReferencedOnlyInCustomSerialization4> Value3;
public CustomSerializableSubclassWithNativeArray<NativeArray<TypeReferencedOnlyInCustomSerialization4>> Value4;
}
[GenerateSerializationForType(typeof(TypeReferencedOnlyInCustomSerialization5))]
[GenerateSerializationForType(typeof(NativeArray<TypeReferencedOnlyInCustomSerialization5>))]
internal struct SomeRandomStruct
{
[GenerateSerializationForType(typeof(TypeReferencedOnlyInCustomSerialization6))]
[GenerateSerializationForType(typeof(NativeArray<TypeReferencedOnlyInCustomSerialization6>))]
public void Foo()
{
}
}
public struct TemplatedValueOnlyReferencedByNetworkVariableSubclass<T> : INetworkSerializeByMemcpy
where T : unmanaged
{
public T Value;
}
public enum ByteEnum : byte
{
A,
B,
C = byte.MaxValue
}
public enum SByteEnum : sbyte
{
A,
B,
C = sbyte.MaxValue
}
public enum ShortEnum : short
{
A,
B,
C = short.MaxValue
}
public enum UShortEnum : ushort
{
A,
B,
C = ushort.MaxValue
}
public enum IntEnum : int
{
A,
B,
C = int.MaxValue
}
public enum UIntEnum : uint
{
A,
B,
C = uint.MaxValue
}
public enum LongEnum : long
{
A,
B,
C = long.MaxValue
}
public enum ULongEnum : ulong
{
A,
B,
C = ulong.MaxValue
}
public struct HashableNetworkVariableTestStruct : INetworkSerializeByMemcpy, IEquatable<HashableNetworkVariableTestStruct>
{
public byte A;
public short B;
public ushort C;
public int D;
public uint E;
public long F;
public ulong G;
public bool H;
public char I;
public float J;
public double K;
public bool Equals(HashableNetworkVariableTestStruct other)
{
return A == other.A && B == other.B && C == other.C && D == other.D && E == other.E && F == other.F && G == other.G && H == other.H && I == other.I && J.Equals(other.J) && K.Equals(other.K);
}
public override bool Equals(object obj)
{
return obj is HashableNetworkVariableTestStruct other && Equals(other);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(A);
hashCode.Add(B);
hashCode.Add(C);
hashCode.Add(D);
hashCode.Add(E);
hashCode.Add(F);
hashCode.Add(G);
hashCode.Add(H);
hashCode.Add(I);
hashCode.Add(J);
hashCode.Add(K);
return hashCode.ToHashCode();
}
}
public struct HashMapKeyStruct : INetworkSerializeByMemcpy, IEquatable<HashMapKeyStruct>
{
public byte A;
public short B;
public ushort C;
public int D;
public uint E;
public long F;
public ulong G;
public bool H;
public char I;
public float J;
public double K;
public bool Equals(HashMapKeyStruct other)
{
return A == other.A && B == other.B && C == other.C && D == other.D && E == other.E && F == other.F && G == other.G && H == other.H && I == other.I && J.Equals(other.J) && K.Equals(other.K);
}
public override bool Equals(object obj)
{
return obj is HashMapKeyStruct other && Equals(other);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(A);
hashCode.Add(B);
hashCode.Add(C);
hashCode.Add(D);
hashCode.Add(E);
hashCode.Add(F);
hashCode.Add(G);
hashCode.Add(H);
hashCode.Add(I);
hashCode.Add(J);
hashCode.Add(K);
return hashCode.ToHashCode();
}
}
public struct HashMapValStruct : INetworkSerializeByMemcpy, IEquatable<HashMapValStruct>
{
public byte A;
public short B;
public ushort C;
public int D;
public uint E;
public long F;
public ulong G;
public bool H;
public char I;
public float J;
public double K;
public bool Equals(HashMapValStruct other)
{
return A == other.A && B == other.B && C == other.C && D == other.D && E == other.E && F == other.F && G == other.G && H == other.H && I == other.I && J.Equals(other.J) && K.Equals(other.K);
}
public override bool Equals(object obj)
{
return obj is HashMapValStruct other && Equals(other);
}
public override int GetHashCode()
{
var hashCode = new HashCode();
hashCode.Add(A);
hashCode.Add(B);
hashCode.Add(C);
hashCode.Add(D);
hashCode.Add(E);
hashCode.Add(F);
hashCode.Add(G);
hashCode.Add(H);
hashCode.Add(I);
hashCode.Add(J);
hashCode.Add(K);
return hashCode.ToHashCode();
}
}
public struct NetworkVariableTestStruct : INetworkSerializeByMemcpy
{
public byte A;
public short B;
public ushort C;
public int D;
public uint E;
public long F;
public ulong G;
public bool H;
public char I;
public float J;
public double K;
private static System.Random s_Random = new System.Random();
public static NetworkVariableTestStruct GetTestStruct()
{
var testStruct = new NetworkVariableTestStruct
{
A = (byte)s_Random.Next(),
B = (short)s_Random.Next(),
C = (ushort)s_Random.Next(),
D = s_Random.Next(),
E = (uint)s_Random.Next(),
F = ((long)s_Random.Next() << 32) + s_Random.Next(),
G = ((ulong)s_Random.Next() << 32) + (ulong)s_Random.Next(),
H = true,
I = '\u263a',
J = (float)s_Random.NextDouble(),
K = s_Random.NextDouble(),
};
return testStruct;
}
}
public class HashableNetworkVariableTestClass : INetworkSerializable, IEquatable<HashableNetworkVariableTestClass>
{
public HashableNetworkVariableTestStruct Data;
public bool Equals(HashableNetworkVariableTestClass other)
{
return Data.Equals(other.Data);
}
public override bool Equals(object obj)
{
return obj is HashableNetworkVariableTestClass other && Equals(other);
}
public override int GetHashCode()
{
return Data.GetHashCode();
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Data);
}
}
public class HashMapKeyClass : INetworkSerializable, IEquatable<HashMapKeyClass>
{
public HashMapKeyStruct Data;
public bool Equals(HashMapKeyClass other)
{
return Data.Equals(other.Data);
}
public override bool Equals(object obj)
{
return obj is HashMapKeyClass other && Equals(other);
}
public override int GetHashCode()
{
return Data.GetHashCode();
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Data);
}
}
public class HashMapValClass : INetworkSerializable, IEquatable<HashMapValClass>
{
public HashMapValStruct Data;
public bool Equals(HashMapValClass other)
{
return Data.Equals(other.Data);
}
public override bool Equals(object obj)
{
return obj is HashMapValClass other && Equals(other);
}
public override int GetHashCode()
{
return Data.GetHashCode();
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Data);
}
}
public class NetworkVariableTestClass : INetworkSerializable, IEquatable<NetworkVariableTestClass>
{
public NetworkVariableTestStruct Data;
public bool Equals(NetworkVariableTestClass other)
{
return NetworkVariableSerialization<NetworkVariableTestStruct>.AreEqual(ref Data, ref other.Data);
}
public override bool Equals(object obj)
{
return obj is NetworkVariableTestClass other && Equals(other);
}
// This type is not used for hashing, we just need to implement IEquatable to verify lists match.
public override int GetHashCode()
{
return 0;
}
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
{
serializer.SerializeValue(ref Data);
}
}
// The ILPP code for NetworkVariables to determine how to serialize them relies on them existing as fields of a NetworkBehaviour to find them.
// Some of the tests below create NetworkVariables on the stack, so this class is here just to make sure the relevant types are all accounted for.
public class NetVarILPPClassForTests : NetworkBehaviour
{
public NetworkVariable<byte> ByteVar;
public NetworkVariable<NativeArray<byte>> ByteArrayVar;
public NetworkVariable<List<byte>> ByteManagedListVar;
public NetworkVariable<HashSet<byte>> ByteManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<byte>> ByteListVar;
public NetworkVariable<NativeHashSet<byte>> ByteHashSetVar;
public NetworkVariable<NativeHashMap<byte, byte>> ByteByteHashMapVar;
public NetworkVariable<NativeHashMap<ulong, byte>> ULongByteHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, byte>> Vector2ByteHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, byte>> HashMapKeyStructByteHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, byte>> ByteByteDictionaryVar;
public NetworkVariable<Dictionary<ulong, byte>> ULongByteDictionaryVar;
public NetworkVariable<Dictionary<Vector2, byte>> Vector2ByteDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, byte>> HashMapKeyClassByteDictionaryVar;
public NetworkVariable<sbyte> SbyteVar;
public NetworkVariable<NativeArray<sbyte>> SbyteArrayVar;
public NetworkVariable<List<sbyte>> SbyteManagedListVar;
public NetworkVariable<HashSet<sbyte>> SbyteManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<sbyte>> SbyteListVar;
public NetworkVariable<NativeHashSet<sbyte>> SbyteHashSetVar;
public NetworkVariable<NativeHashMap<byte, sbyte>> ByteSbyteHashMapVar;
public NetworkVariable<NativeHashMap<ulong, sbyte>> ULongSbyteHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, sbyte>> Vector2SbyteHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, sbyte>> HashMapKeyStructSbyteHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, sbyte>> ByteSbyteDictionaryVar;
public NetworkVariable<Dictionary<ulong, sbyte>> ULongSbyteDictionaryVar;
public NetworkVariable<Dictionary<Vector2, sbyte>> Vector2SbyteDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, sbyte>> HashMapKeyClassSbyteDictionaryVar;
public NetworkVariable<short> ShortVar;
public NetworkVariable<NativeArray<short>> ShortArrayVar;
public NetworkVariable<List<short>> ShortManagedListVar;
public NetworkVariable<HashSet<short>> ShortManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<short>> ShortListVar;
public NetworkVariable<NativeHashSet<short>> ShortHashSetVar;
public NetworkVariable<NativeHashMap<byte, short>> ByteShortHashMapVar;
public NetworkVariable<NativeHashMap<ulong, short>> ULongShortHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, short>> Vector2ShortHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, short>> HashMapKeyStructShortHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, short>> ByteShortDictionaryVar;
public NetworkVariable<Dictionary<ulong, short>> ULongShortDictionaryVar;
public NetworkVariable<Dictionary<Vector2, short>> Vector2ShortDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, short>> HashMapKeyClassShortDictionaryVar;
public NetworkVariable<ushort> UshortVar;
public NetworkVariable<NativeArray<ushort>> UshortArrayVar;
public NetworkVariable<List<ushort>> UshortManagedListVar;
public NetworkVariable<HashSet<ushort>> UshortManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<ushort>> UshortListVar;
public NetworkVariable<NativeHashSet<ushort>> UshortHashSetVar;
public NetworkVariable<NativeHashMap<byte, ushort>> ByteUshortHashMapVar;
public NetworkVariable<NativeHashMap<ulong, ushort>> ULongUshortHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, ushort>> Vector2UshortHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, ushort>> HashMapKeyStructUshortHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, ushort>> ByteUshortDictionaryVar;
public NetworkVariable<Dictionary<ulong, ushort>> ULongUshortDictionaryVar;
public NetworkVariable<Dictionary<Vector2, ushort>> Vector2UshortDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, ushort>> HashMapKeyClassUshortDictionaryVar;
public NetworkVariable<int> IntVar;
public NetworkVariable<NativeArray<int>> IntArrayVar;
public NetworkVariable<List<int>> IntManagedListVar;
public NetworkVariable<HashSet<int>> IntManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<int>> IntListVar;
public NetworkVariable<NativeHashSet<int>> IntHashSetVar;
public NetworkVariable<NativeHashMap<byte, int>> ByteIntHashMapVar;
public NetworkVariable<NativeHashMap<ulong, int>> ULongIntHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, int>> Vector2IntHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, int>> HashMapKeyStructIntHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, int>> ByteIntDictionaryVar;
public NetworkVariable<Dictionary<ulong, int>> ULongIntDictionaryVar;
public NetworkVariable<Dictionary<Vector2, int>> Vector2IntDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, int>> HashMapKeyClassIntDictionaryVar;
public NetworkVariable<uint> UintVar;
public NetworkVariable<NativeArray<uint>> UintArrayVar;
public NetworkVariable<List<uint>> UintManagedListVar;
public NetworkVariable<HashSet<uint>> UintManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<uint>> UintListVar;
public NetworkVariable<NativeHashSet<uint>> UintHashSetVar;
public NetworkVariable<NativeHashMap<byte, uint>> ByteUintHashMapVar;
public NetworkVariable<NativeHashMap<ulong, uint>> ULongUintHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, uint>> Vector2UintHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, uint>> HashMapKeyStructUintHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, uint>> ByteUintDictionaryVar;
public NetworkVariable<Dictionary<ulong, uint>> ULongUintDictionaryVar;
public NetworkVariable<Dictionary<Vector2, uint>> Vector2UintDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, uint>> HashMapKeyClassUintDictionaryVar;
public NetworkVariable<long> LongVar;
public NetworkVariable<NativeArray<long>> LongArrayVar;
public NetworkVariable<List<long>> LongManagedListVar;
public NetworkVariable<HashSet<long>> LongManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<long>> LongListVar;
public NetworkVariable<NativeHashSet<long>> LongHashSetVar;
public NetworkVariable<NativeHashMap<byte, long>> ByteLongHashMapVar;
public NetworkVariable<NativeHashMap<ulong, long>> ULongLongHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, long>> Vector2LongHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, long>> HashMapKeyStructLongHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, long>> ByteLongDictionaryVar;
public NetworkVariable<Dictionary<ulong, long>> ULongLongDictionaryVar;
public NetworkVariable<Dictionary<Vector2, long>> Vector2LongDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, long>> HashMapKeyClassLongDictionaryVar;
public NetworkVariable<ulong> UlongVar;
public NetworkVariable<NativeArray<ulong>> UlongArrayVar;
public NetworkVariable<List<ulong>> UlongManagedListVar;
public NetworkVariable<HashSet<ulong>> UlongManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<ulong>> UlongListVar;
public NetworkVariable<NativeHashSet<ulong>> UlongHashSetVar;
public NetworkVariable<NativeHashMap<byte, ulong>> ByteUlongHashMapVar;
public NetworkVariable<NativeHashMap<ulong, ulong>> ULongUlongHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, ulong>> Vector2UlongHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, ulong>> HashMapKeyStructUlongHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, ulong>> ByteUlongDictionaryVar;
public NetworkVariable<Dictionary<ulong, ulong>> ULongUlongDictionaryVar;
public NetworkVariable<Dictionary<Vector2, ulong>> Vector2UlongDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, ulong>> HashMapKeyClassUlongDictionaryVar;
public NetworkVariable<bool> BoolVar;
public NetworkVariable<NativeArray<bool>> BoolArrayVar;
public NetworkVariable<List<bool>> BoolManagedListVar;
public NetworkVariable<HashSet<bool>> BoolManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<bool>> BoolListVar;
public NetworkVariable<NativeHashSet<bool>> BoolHashSetVar;
public NetworkVariable<NativeHashMap<byte, bool>> ByteBoolHashMapVar;
public NetworkVariable<NativeHashMap<ulong, bool>> ULongBoolHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, bool>> Vector2BoolHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, bool>> HashMapKeyStructBoolHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, bool>> ByteBoolDictionaryVar;
public NetworkVariable<Dictionary<ulong, bool>> ULongBoolDictionaryVar;
public NetworkVariable<Dictionary<Vector2, bool>> Vector2BoolDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, bool>> HashMapKeyClassBoolDictionaryVar;
public NetworkVariable<char> CharVar;
public NetworkVariable<NativeArray<char>> CharArrayVar;
public NetworkVariable<List<char>> CharManagedListVar;
public NetworkVariable<HashSet<char>> CharManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<char>> CharListVar;
public NetworkVariable<NativeHashSet<char>> CharHashSetVar;
public NetworkVariable<NativeHashMap<byte, char>> ByteCharHashMapVar;
public NetworkVariable<NativeHashMap<ulong, char>> ULongCharHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, char>> Vector2CharHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, char>> HashMapKeyStructCharHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, char>> ByteCharDictionaryVar;
public NetworkVariable<Dictionary<ulong, char>> ULongCharDictionaryVar;
public NetworkVariable<Dictionary<Vector2, char>> Vector2CharDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, char>> HashMapKeyClassCharDictionaryVar;
public NetworkVariable<float> FloatVar;
public NetworkVariable<NativeArray<float>> FloatArrayVar;
public NetworkVariable<List<float>> FloatManagedListVar;
public NetworkVariable<HashSet<float>> FloatManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<float>> FloatListVar;
public NetworkVariable<NativeHashSet<float>> FloatHashSetVar;
public NetworkVariable<NativeHashMap<byte, float>> ByteFloatHashMapVar;
public NetworkVariable<NativeHashMap<ulong, float>> ULongFloatHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, float>> Vector2FloatHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, float>> HashMapKeyStructFloatHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, float>> ByteFloatDictionaryVar;
public NetworkVariable<Dictionary<ulong, float>> ULongFloatDictionaryVar;
public NetworkVariable<Dictionary<Vector2, float>> Vector2FloatDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, float>> HashMapKeyClassFloatDictionaryVar;
public NetworkVariable<double> DoubleVar;
public NetworkVariable<NativeArray<double>> DoubleArrayVar;
public NetworkVariable<List<double>> DoubleManagedListVar;
public NetworkVariable<HashSet<double>> DoubleManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<double>> DoubleListVar;
public NetworkVariable<NativeHashSet<double>> DoubleHashSetVar;
public NetworkVariable<NativeHashMap<byte, double>> ByteDoubleHashMapVar;
public NetworkVariable<NativeHashMap<ulong, double>> ULongDoubleHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, double>> Vector2DoubleHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, double>> HashMapKeyStructDoubleHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, double>> ByteDoubleDictionaryVar;
public NetworkVariable<Dictionary<ulong, double>> ULongDoubleDictionaryVar;
public NetworkVariable<Dictionary<Vector2, double>> Vector2DoubleDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, double>> HashMapKeyClassDoubleDictionaryVar;
public NetworkVariable<ByteEnum> ByteEnumVar;
public NetworkVariable<NativeArray<ByteEnum>> ByteEnumArrayVar;
public NetworkVariable<List<ByteEnum>> ByteEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<ByteEnum>> ByteEnumListVar;
#endif
public NetworkVariable<SByteEnum> SByteEnumVar;
public NetworkVariable<NativeArray<SByteEnum>> SByteEnumArrayVar;
public NetworkVariable<List<SByteEnum>> SByteEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<SByteEnum>> SByteEnumListVar;
#endif
public NetworkVariable<ShortEnum> ShortEnumVar;
public NetworkVariable<NativeArray<ShortEnum>> ShortEnumArrayVar;
public NetworkVariable<List<ShortEnum>> ShortEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<ShortEnum>> ShortEnumListVar;
#endif
public NetworkVariable<UShortEnum> UShortEnumVar;
public NetworkVariable<NativeArray<UShortEnum>> UShortEnumArrayVar;
public NetworkVariable<List<UShortEnum>> UShortEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<UShortEnum>> UShortEnumListVar;
#endif
public NetworkVariable<IntEnum> IntEnumVar;
public NetworkVariable<NativeArray<IntEnum>> IntEnumArrayVar;
public NetworkVariable<List<IntEnum>> IntEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<IntEnum>> IntEnumListVar;
#endif
public NetworkVariable<UIntEnum> UIntEnumVar;
public NetworkVariable<NativeArray<UIntEnum>> UIntEnumArrayVar;
public NetworkVariable<List<UIntEnum>> UIntEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<UIntEnum>> UIntEnumListVar;
#endif
public NetworkVariable<LongEnum> LongEnumVar;
public NetworkVariable<NativeArray<LongEnum>> LongEnumArrayVar;
public NetworkVariable<List<LongEnum>> LongEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<LongEnum>> LongEnumListVar;
#endif
public NetworkVariable<ULongEnum> ULongEnumVar;
public NetworkVariable<NativeArray<ULongEnum>> ULongEnumArrayVar;
public NetworkVariable<List<ULongEnum>> ULongEnumManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<ULongEnum>> ULongEnumListVar;
#endif
public NetworkVariable<Vector2> Vector2Var;
public NetworkVariable<NativeArray<Vector2>> Vector2ArrayVar;
public NetworkVariable<List<Vector2>> Vector2ManagedListVar;
public NetworkVariable<HashSet<Vector2>> Vector2ManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Vector2>> Vector2ListVar;
public NetworkVariable<NativeHashSet<Vector2>> Vector2HashSetVar;
public NetworkVariable<NativeHashMap<byte, Vector2>> ByteVector2HashMapVar;
public NetworkVariable<NativeHashMap<ulong, Vector2>> ULongVector2HashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Vector2>> Vector2Vector2HashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Vector2>> HashMapKeyStructVector2HashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Vector2>> ByteVector2DictionaryVar;
public NetworkVariable<Dictionary<ulong, Vector2>> ULongVector2DictionaryVar;
public NetworkVariable<Dictionary<Vector2, Vector2>> Vector2Vector2DictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Vector2>> HashMapKeyClassVector2DictionaryVar;
public NetworkVariable<Vector3> Vector3Var;
public NetworkVariable<NativeArray<Vector3>> Vector3ArrayVar;
public NetworkVariable<List<Vector3>> Vector3ManagedListVar;
public NetworkVariable<HashSet<Vector3>> Vector3ManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Vector3>> Vector3ListVar;
public NetworkVariable<NativeHashSet<Vector3>> Vector3HashSetVar;
public NetworkVariable<NativeHashMap<byte, Vector3>> ByteVector3HashMapVar;
public NetworkVariable<NativeHashMap<ulong, Vector3>> ULongVector3HashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Vector3>> Vector2Vector3HashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Vector3>> HashMapKeyStructVector3HashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Vector3>> ByteVector3DictionaryVar;
public NetworkVariable<Dictionary<ulong, Vector3>> ULongVector3DictionaryVar;
public NetworkVariable<Dictionary<Vector2, Vector3>> Vector2Vector3DictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Vector3>> HashMapKeyClassVector3DictionaryVar;
public NetworkVariable<Vector2Int> Vector2IntVar;
public NetworkVariable<NativeArray<Vector2Int>> Vector2IntArrayVar;
public NetworkVariable<List<Vector2Int>> Vector2IntManagedListVar;
public NetworkVariable<HashSet<Vector2Int>> Vector2IntManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Vector2Int>> Vector2IntListVar;
public NetworkVariable<NativeHashSet<Vector2Int>> Vector2IntHashSetVar;
public NetworkVariable<NativeHashMap<byte, Vector2Int>> ByteVector2IntHashMapVar;
public NetworkVariable<NativeHashMap<ulong, Vector2Int>> ULongVector2IntHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Vector2Int>> Vector2Vector2IntHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Vector2Int>> HashMapKeyStructVector2IntHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Vector2Int>> ByteVector2IntDictionaryVar;
public NetworkVariable<Dictionary<ulong, Vector2Int>> ULongVector2IntDictionaryVar;
public NetworkVariable<Dictionary<Vector2, Vector2Int>> Vector2Vector2IntDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Vector2Int>> HashMapKeyClassVector2IntDictionaryVar;
public NetworkVariable<Vector3Int> Vector3IntVar;
public NetworkVariable<NativeArray<Vector3Int>> Vector3IntArrayVar;
public NetworkVariable<List<Vector3Int>> Vector3IntManagedListVar;
public NetworkVariable<HashSet<Vector3Int>> Vector3IntManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Vector3Int>> Vector3IntListVar;
public NetworkVariable<NativeHashSet<Vector3Int>> Vector3IntHashSetVar;
public NetworkVariable<NativeHashMap<byte, Vector3Int>> ByteVector3IntHashMapVar;
public NetworkVariable<NativeHashMap<ulong, Vector3Int>> ULongVector3IntHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Vector3Int>> Vector2Vector3IntHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Vector3Int>> HashMapKeyStructVector3IntHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Vector3Int>> ByteVector3IntDictionaryVar;
public NetworkVariable<Dictionary<ulong, Vector3Int>> ULongVector3IntDictionaryVar;
public NetworkVariable<Dictionary<Vector2, Vector3Int>> Vector2Vector3IntDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Vector3Int>> HashMapKeyClassVector3IntDictionaryVar;
public NetworkVariable<Vector4> Vector4Var;
public NetworkVariable<NativeArray<Vector4>> Vector4ArrayVar;
public NetworkVariable<List<Vector4>> Vector4ManagedListVar;
public NetworkVariable<HashSet<Vector4>> Vector4ManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Vector4>> Vector4ListVar;
public NetworkVariable<NativeHashSet<Vector4>> Vector4HashSetVar;
public NetworkVariable<NativeHashMap<byte, Vector4>> ByteVector4HashMapVar;
public NetworkVariable<NativeHashMap<ulong, Vector4>> ULongVector4HashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Vector4>> Vector2Vector4HashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Vector4>> HashMapKeyStructVector4HashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Vector4>> ByteVector4DictionaryVar;
public NetworkVariable<Dictionary<ulong, Vector4>> ULongVector4DictionaryVar;
public NetworkVariable<Dictionary<Vector2, Vector4>> Vector2Vector4DictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Vector4>> HashMapKeyClassVector4DictionaryVar;
public NetworkVariable<Quaternion> QuaternionVar;
public NetworkVariable<NativeArray<Quaternion>> QuaternionArrayVar;
public NetworkVariable<List<Quaternion>> QuaternionManagedListVar;
public NetworkVariable<HashSet<Quaternion>> QuaternionManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Quaternion>> QuaternionListVar;
public NetworkVariable<NativeHashSet<Quaternion>> QuaternionHashSetVar;
public NetworkVariable<NativeHashMap<byte, Quaternion>> ByteQuaternionHashMapVar;
public NetworkVariable<NativeHashMap<ulong, Quaternion>> ULongQuaternionHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, Quaternion>> Vector2QuaternionHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, Quaternion>> HashMapKeyStructQuaternionHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, Quaternion>> ByteQuaternionDictionaryVar;
public NetworkVariable<Dictionary<ulong, Quaternion>> ULongQuaternionDictionaryVar;
public NetworkVariable<Dictionary<Vector2, Quaternion>> Vector2QuaternionDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, Quaternion>> HashMapKeyClassQuaternionDictionaryVar;
public NetworkVariable<Color> ColorVar;
public NetworkVariable<NativeArray<Color>> ColorArrayVar;
public NetworkVariable<List<Color>> ColorManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Color>> ColorListVar;
#endif
public NetworkVariable<Color32> Color32Var;
public NetworkVariable<NativeArray<Color32>> Color32ArrayVar;
public NetworkVariable<List<Color32>> Color32ManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Color32>> Color32ListVar;
#endif
public NetworkVariable<Ray> RayVar;
public NetworkVariable<NativeArray<Ray>> RayArrayVar;
public NetworkVariable<List<Ray>> RayManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Ray>> RayListVar;
#endif
public NetworkVariable<Ray2D> Ray2DVar;
public NetworkVariable<NativeArray<Ray2D>> Ray2DArrayVar;
public NetworkVariable<List<Ray2D>> Ray2DManagedListVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<Ray2D>> Ray2DListVar;
#endif
public NetworkVariable<NetworkVariableTestStruct> TestStructVar;
public NetworkVariable<NativeArray<NetworkVariableTestStruct>> TestStructArrayVar;
public NetworkVariable<List<NetworkVariableTestClass>> TestStructManagedListVar;
public NetworkVariable<HashSet<HashableNetworkVariableTestClass>> TestStructManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<NetworkVariableTestStruct>> TestStructListVar;
public NetworkVariable<NativeHashSet<HashableNetworkVariableTestStruct>> TestStructHashSetVar;
public NetworkVariable<NativeHashMap<byte, HashMapValStruct>> ByteTestStructHashMapVar;
public NetworkVariable<NativeHashMap<ulong, HashMapValStruct>> ULongTestStructHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, HashMapValStruct>> Vector2TestStructHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, HashMapValStruct>> HashMapKeyStructTestStructHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, HashMapValClass>> ByteTestStructDictionaryVar;
public NetworkVariable<Dictionary<ulong, HashMapValClass>> ULongTestStructDictionaryVar;
public NetworkVariable<Dictionary<Vector2, HashMapValClass>> Vector2TestStructDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, HashMapValClass>> HashMapKeyClassTestStructDictionaryVar;
public NetworkVariable<FixedString32Bytes> FixedStringVar;
public NetworkVariable<NativeArray<FixedString32Bytes>> FixedStringArrayVar;
public NetworkVariable<List<FixedString32Bytes>> FixedStringManagedListVar;
public NetworkVariable<HashSet<FixedString32Bytes>> FixedStringManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<FixedString32Bytes>> FixedStringListVar;
public NetworkVariable<NativeHashSet<FixedString32Bytes>> FixedStringHashSetVar;
public NetworkVariable<NativeHashMap<byte, FixedString32Bytes>> ByteFixedStringHashMapVar;
public NetworkVariable<NativeHashMap<ulong, FixedString32Bytes>> ULongFixedStringHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, FixedString32Bytes>> Vector2FixedStringHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, FixedString32Bytes>> HashMapKeyStructFixedStringHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, FixedString32Bytes>> ByteFixedStringDictionaryVar;
public NetworkVariable<Dictionary<ulong, FixedString32Bytes>> ULongFixedStringDictionaryVar;
public NetworkVariable<Dictionary<Vector2, FixedString32Bytes>> Vector2FixedStringDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, FixedString32Bytes>> HashMapKeyClassFixedStringDictionaryVar;
public NetworkVariable<UnmanagedNetworkSerializableType> UnmanagedNetworkSerializableTypeVar;
public NetworkVariable<HashSet<UnmanagedNetworkSerializableType>> UnmanagedNetworkSerializableManagedHashSetVar;
#if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
public NetworkVariable<NativeList<UnmanagedNetworkSerializableType>> UnmanagedNetworkSerializableListVar;
public NetworkVariable<NativeHashSet<UnmanagedNetworkSerializableType>> UnmanagedNetworkSerializableHashSetVar;
public NetworkVariable<NativeHashMap<byte, UnmanagedNetworkSerializableType>> ByteUnmanagedNetworkSerializableHashMapVar;
public NetworkVariable<NativeHashMap<ulong, UnmanagedNetworkSerializableType>> ULongUnmanagedNetworkSerializableHashMapVar;
public NetworkVariable<NativeHashMap<Vector2, UnmanagedNetworkSerializableType>> Vector2UnmanagedNetworkSerializableHashMapVar;
public NetworkVariable<NativeHashMap<HashMapKeyStruct, UnmanagedNetworkSerializableType>> HashMapKeyStructUnmanagedNetworkSerializableHashMapVar;
#endif
public NetworkVariable<Dictionary<byte, UnmanagedNetworkSerializableType>> ByteUnmanagedNetworkSerializableDictionaryVar;
public NetworkVariable<Dictionary<ulong, UnmanagedNetworkSerializableType>> ULongUnmanagedNetworkSerializableDictionaryVar;
public NetworkVariable<Dictionary<Vector2, UnmanagedNetworkSerializableType>> Vector2UnmanagedNetworkSerializableDictionaryVar;
public NetworkVariable<Dictionary<HashMapKeyClass, UnmanagedNetworkSerializableType>> HashMapKeyClassUnmanagedNetworkSerializableDictionaryVar;
public NetworkVariable<NativeArray<UnmanagedNetworkSerializableType>> UnmanagedNetworkSerializableArrayVar;
public NetworkVariable<List<UnmanagedNetworkSerializableType>> UnmanagedNetworkSerializableManagedListVar;
public NetworkVariable<ManagedNetworkSerializableType> ManagedNetworkSerializableTypeVar;
public NetworkVariable<string> StringVar;
public NetworkVariable<Guid> GuidVar;
public NetworkVariableSubclass<TemplatedValueOnlyReferencedByNetworkVariableSubclass<int>> SubclassVar;
}
public class TemplateNetworkBehaviourType<T> : NetworkBehaviour
{
public NetworkVariable<T> TheVar;
}
public class IntermediateNetworkBehavior<T> : TemplateNetworkBehaviourType<T>
{
public NetworkVariable<T> TheVar2;
}
public class ClassHavingNetworkBehaviour : IntermediateNetworkBehavior<TestClass>
{
}
// Please do not reference TestClass_ReferencedOnlyByTemplateNetworkBehavourType anywhere other than here!
public class ClassHavingNetworkBehaviour2 : TemplateNetworkBehaviourType<TestClass_ReferencedOnlyByTemplateNetworkBehavourType>
{
}
public class StructHavingNetworkBehaviour : TemplateNetworkBehaviourType<TestStruct>
{
}
public struct StructUsedOnlyInNetworkList : IEquatable<StructUsedOnlyInNetworkList>, INetworkSerializeByMemcpy
{
public int Value;
public bool Equals(StructUsedOnlyInNetworkList other)
{
return Value == other.Value;
}
public override bool Equals(object obj)
{
return obj is StructUsedOnlyInNetworkList other && Equals(other);
}
public override int GetHashCode()
{
return Value;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a580aaafc247486193f372174a99fae4
timeCreated: 1705098783

View File

@@ -0,0 +1,138 @@
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using Object = UnityEngine.Object;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkVariableTraitsComponent : NetworkBehaviour
{
public NetworkVariable<float> TheVariable = new NetworkVariable<float>();
}
public class NetworkVariableTraitsTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
protected override bool m_EnableTimeTravel => true;
protected override bool m_SetupIsACoroutine => false;
protected override bool m_TearDownIsACoroutine => false;
protected override void OnPlayerPrefabGameObjectCreated()
{
m_PlayerPrefab.AddComponent<NetworkVariableTraitsComponent>();
}
public NetworkVariableTraitsComponent GetTestComponent()
{
return m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<NetworkVariableTraitsComponent>();
}
public NetworkVariableTraitsComponent GetServerComponent()
{
foreach (var obj in Object.FindObjectsByType<NetworkVariableTraitsComponent>(FindObjectsSortMode.None))
{
if (obj.NetworkManager == m_ServerNetworkManager && obj.OwnerClientId == m_ClientNetworkManagers[0].LocalClientId)
{
return obj;
}
}
return null;
}
[Test]
public void WhenNewValueIsLessThanThreshold_VariableIsNotSerialized()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1;
serverComponent.TheVariable.Value = 0.05f;
TimeTravel(2, 120);
Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0, testComponent.TheVariable.Value); ;
}
[Test]
public void WhenNewValueIsGreaterThanThreshold_VariableIsSerialized()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1;
serverComponent.TheVariable.Value = 0.15f;
TimeTravel(2, 120);
Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ;
}
[Test]
public void WhenNewValueIsLessThanThresholdButMaxTimeHasPassed_VariableIsSerialized()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1;
serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MaxSecondsBetweenUpdates = 2 });
serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime;
serverComponent.TheVariable.Value = 0.05f;
TimeTravel(1 / 60f * 119, 119);
Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0, testComponent.TheVariable.Value); ;
TimeTravel(1 / 60f * 4, 4);
Assert.AreEqual(0.05f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0.05f, testComponent.TheVariable.Value); ;
}
[Test]
public void WhenNewValueIsGreaterThanThresholdButMinTimeHasNotPassed_VariableIsNotSerialized()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
serverComponent.TheVariable.CheckExceedsDirtinessThreshold = (in float value, in float newValue) => Mathf.Abs(newValue - value) >= 0.1;
serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MinSecondsBetweenUpdates = 2 });
serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime;
serverComponent.TheVariable.Value = 0.15f;
TimeTravel(1 / 60f * 119, 119);
Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0, testComponent.TheVariable.Value); ;
TimeTravel(1 / 60f * 4, 4);
Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ;
}
[Test]
public void WhenNoThresholdIsSetButMinTimeHasNotPassed_VariableIsNotSerialized()
{
var serverComponent = GetServerComponent();
var testComponent = GetTestComponent();
serverComponent.TheVariable.SetUpdateTraits(new NetworkVariableUpdateTraits { MinSecondsBetweenUpdates = 2 });
serverComponent.TheVariable.LastUpdateSent = m_ServerNetworkManager.NetworkTimeSystem.LocalTime;
serverComponent.TheVariable.Value = 0.15f;
TimeTravel(1 / 60f * 119, 119);
Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0, testComponent.TheVariable.Value); ;
TimeTravel(1 / 60f * 4, 4);
Assert.AreEqual(0.15f, serverComponent.TheVariable.Value); ;
Assert.AreEqual(0.15f, testComponent.TheVariable.Value); ;
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: c4ca75209dbd43f48930e4887392bafd
timeCreated: 1706826268

View File

@@ -112,6 +112,16 @@ namespace Unity.Netcode.RuntimeTests
protected override IEnumerator OnSetup()
{
WorkingUserNetworkVariableComponentBase.Reset();
UserNetworkVariableSerialization<MyTypeOne>.WriteValue = null;
UserNetworkVariableSerialization<MyTypeOne>.ReadValue = null;
UserNetworkVariableSerialization<MyTypeOne>.DuplicateValue = null;
UserNetworkVariableSerialization<MyTypeTwo>.WriteValue = null;
UserNetworkVariableSerialization<MyTypeTwo>.ReadValue = null;
UserNetworkVariableSerialization<MyTypeTwo>.DuplicateValue = null;
UserNetworkVariableSerialization<MyTypeThree>.WriteValue = null;
UserNetworkVariableSerialization<MyTypeThree>.ReadValue = null;
UserNetworkVariableSerialization<MyTypeThree>.DuplicateValue = null;
return base.OnSetup();
}
@@ -217,5 +227,36 @@ namespace Unity.Netcode.RuntimeTests
}
);
}
protected override IEnumerator OnTearDown()
{
// These have to get set to SOMETHING, otherwise we will get an exception thrown because Object.Destroy()
// calls __initializeNetworkVariables, and the network variable initialization attempts to call FallbackSerializer<T>,
// which throws an exception if any of these values are null. They don't have to DO anything, they just have to
// be non-null to keep the test from failing during teardown.
// None of this is related to what's being tested above, and in reality, these values being null is an invalid
// use case. But one of the tests is explicitly testing that invalid use case, and the values are being set
// to null in OnSetup to ensure test isolation. This wouldn't be a situation a user would have to think about
// in a real world use case.
UserNetworkVariableSerialization<MyTypeOne>.WriteValue = (FastBufferWriter writer, in MyTypeOne value) => { };
UserNetworkVariableSerialization<MyTypeOne>.ReadValue = (FastBufferReader reader, out MyTypeOne value) => { value = new MyTypeOne(); };
UserNetworkVariableSerialization<MyTypeOne>.DuplicateValue = (in MyTypeOne value, ref MyTypeOne duplicatedValue) =>
{
duplicatedValue = value;
};
UserNetworkVariableSerialization<MyTypeTwo>.WriteValue = (FastBufferWriter writer, in MyTypeTwo value) => { };
UserNetworkVariableSerialization<MyTypeTwo>.ReadValue = (FastBufferReader reader, out MyTypeTwo value) => { value = new MyTypeTwo(); };
UserNetworkVariableSerialization<MyTypeTwo>.DuplicateValue = (in MyTypeTwo value, ref MyTypeTwo duplicatedValue) =>
{
duplicatedValue = value;
};
UserNetworkVariableSerialization<MyTypeThree>.WriteValue = (FastBufferWriter writer, in MyTypeThree value) => { };
UserNetworkVariableSerialization<MyTypeThree>.ReadValue = (FastBufferReader reader, out MyTypeThree value) => { value = new MyTypeThree(); };
UserNetworkVariableSerialization<MyTypeThree>.DuplicateValue = (in MyTypeThree value, ref MyTypeThree duplicatedValue) =>
{
duplicatedValue = value;
};
return base.OnTearDown();
}
}
}

View File

@@ -1,4 +1,3 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Unity.Netcode.TestHelpers.Runtime;
@@ -130,72 +129,44 @@ namespace Unity.Netcode.RuntimeTests
for (var clientWriting = 0; clientWriting < 3; clientWriting++)
{
// ==== Server-writable NetworkVariable ====
var gotException = false;
Debug.Log($"Writing to server-write variable on object {objectIndex} on client {clientWriting}");
VerboseDebug($"Writing to server-write variable on object {objectIndex} on client {clientWriting}");
try
{
nextValueToWrite++;
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite;
}
catch (Exception)
if (clientWriting != serverIndex)
{
gotException = true;
LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.GetWritePermissionError());
}
// Verify server-owned netvar can only be written by server
Debug.Assert(gotException == (clientWriting != serverIndex));
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableServer.Value = nextValueToWrite;
// ==== Owner-writable NetworkVariable ====
gotException = false;
Debug.Log($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}");
VerboseDebug($"Writing to owner-write variable on object {objectIndex} on client {clientWriting}");
try
{
nextValueToWrite++;
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite;
}
catch (Exception)
if (clientWriting != objectIndex)
{
gotException = true;
LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.GetWritePermissionError());
}
// Verify client-owned netvar can only be written by owner
Debug.Assert(gotException == (clientWriting != objectIndex));
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkVariableOwner.Value = nextValueToWrite;
// ==== Server-writable NetworkList ====
gotException = false;
Debug.Log($"Writing to server-write list on object {objectIndex} on client {clientWriting}");
VerboseDebug($"Writing to [Add] server-write NetworkList on object {objectIndex} on client {clientWriting}");
try
{
nextValueToWrite++;
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite);
}
catch (Exception)
if (clientWriting != serverIndex)
{
gotException = true;
LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.GetWritePermissionError());
}
// Verify server-owned networkList can only be written by server
Debug.Assert(gotException == (clientWriting != serverIndex));
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListServer.Add(nextValueToWrite);
// ==== Owner-writable NetworkList ====
gotException = false;
Debug.Log($"Writing to owner-write list on object {objectIndex} on client {clientWriting}");
VerboseDebug($"Writing to [Add] owner-write NetworkList on object {objectIndex} on client {clientWriting}");
try
{
nextValueToWrite++;
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.Add(nextValueToWrite);
}
catch (Exception)
if (clientWriting != objectIndex)
{
gotException = true;
LogAssert.Expect(LogType.Error, OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.GetWritePermissionError());
}
// Verify client-owned networkList can only be written by owner
Debug.Assert(gotException == (clientWriting != objectIndex));
OwnerPermissionObject.Objects[objectIndex, clientWriting].MyNetworkListOwner.Add(nextValueToWrite);
yield return WaitForTicks(m_ServerNetworkManager, 5);
yield return WaitForTicks(m_ClientNetworkManagers[0], 5);

File diff suppressed because it is too large Load Diff

View File

@@ -17,6 +17,8 @@ namespace Unity.Netcode.RuntimeTests
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;
public NetworkVariable<NetworkBehaviourReference> TestVariable = new NetworkVariable<NetworkBehaviourReference>();
public TestNetworkBehaviour RpcReceivedBehaviour;
@@ -25,6 +27,7 @@ namespace Unity.Netcode.RuntimeTests
public void SendReferenceServerRpc(NetworkBehaviourReference value)
{
RpcReceivedBehaviour = (TestNetworkBehaviour)value;
ReceivedRPC = true;
}
}
@@ -57,8 +60,43 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[UnityTest]
public IEnumerator TestSerializeNull([Values] bool initializeWithNull)
{
TestNetworkBehaviour.ReceivedRPC = false;
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
using var otherObjectContext = UnityObjectContext.CreateNetworkObject();
otherObjectContext.Object.Spawn();
// If not initializing with null, then use the default constructor with no assigned NetworkBehaviour
if (!initializeWithNull)
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference());
}
else // Otherwise, initialize and pass in null as the reference
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkBehaviourReference(null));
}
// wait for rpc completion
float t = 0;
while (!TestNetworkBehaviour.ReceivedRPC)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedBehaviour);
}
[UnityTest]
public IEnumerator TestRpcImplicitNetworkBehaviour()
@@ -89,6 +127,7 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(testNetworkBehaviour, testNetworkBehaviour.RpcReceivedBehaviour);
}
[Test]
public void TestNetworkVariable()
{
@@ -131,15 +170,6 @@ namespace Unity.Netcode.RuntimeTests
});
}
[Test]
public void FailSerializeNullBehaviour()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkBehaviourReference outReference = null;
});
}
public void Dispose()
{
//Stop, shutdown, and destroy

View File

@@ -19,6 +19,7 @@ namespace Unity.Netcode.RuntimeTests
{
private class TestNetworkBehaviour : NetworkBehaviour
{
public static bool ReceivedRPC;
public NetworkVariable<NetworkObjectReference> TestVariable = new NetworkVariable<NetworkObjectReference>();
public NetworkObject RpcReceivedNetworkObject;
@@ -28,6 +29,7 @@ namespace Unity.Netcode.RuntimeTests
[ServerRpc]
public void SendReferenceServerRpc(NetworkObjectReference value)
{
ReceivedRPC = true;
RpcReceivedGameObject = value;
RpcReceivedNetworkObject = value;
}
@@ -150,6 +152,60 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(networkObject, result);
}
public enum NetworkObjectConstructorTypes
{
None,
NullNetworkObject,
NullGameObject
}
[UnityTest]
public IEnumerator TestSerializeNull([Values] NetworkObjectConstructorTypes networkObjectConstructorTypes)
{
TestNetworkBehaviour.ReceivedRPC = false;
using var networkObjectContext = UnityObjectContext.CreateNetworkObject();
var testNetworkBehaviour = networkObjectContext.Object.gameObject.AddComponent<TestNetworkBehaviour>();
networkObjectContext.Object.Spawn();
switch (networkObjectConstructorTypes)
{
case NetworkObjectConstructorTypes.None:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference());
break;
}
case NetworkObjectConstructorTypes.NullNetworkObject:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((NetworkObject)null));
break;
}
case NetworkObjectConstructorTypes.NullGameObject:
{
testNetworkBehaviour.SendReferenceServerRpc(new NetworkObjectReference((GameObject)null));
break;
}
}
// wait for rpc completion
float t = 0;
while (!TestNetworkBehaviour.ReceivedRPC)
{
t += Time.deltaTime;
if (t > 5f)
{
new AssertionException("RPC with NetworkBehaviour reference hasn't been received");
}
yield return null;
}
// validate
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedNetworkObject);
Assert.AreEqual(null, testNetworkBehaviour.RpcReceivedGameObject);
}
[UnityTest]
public IEnumerator TestRpc()
{
@@ -305,24 +361,6 @@ namespace Unity.Netcode.RuntimeTests
});
}
[Test]
public void FailSerializeNullNetworkObject()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkObjectReference outReference = (NetworkObject)null;
});
}
[Test]
public void FailSerializeNullGameObject()
{
Assert.Throws<ArgumentNullException>(() =>
{
NetworkObjectReference outReference = (GameObject)null;
});
}
public void Dispose()
{
//Stop, shutdown, and destroy

View File

@@ -1,4 +1,6 @@
#if !MULTIPLAYER_TOOLS && !NGO_MINIMALPROJECT
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -7,6 +9,7 @@ using NUnit.Framework;
using Unity.Collections;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
using Object = UnityEngine.Object;
using Random = System.Random;
@@ -24,6 +27,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
public string Received = string.Empty;
public Tuple<int, bool, float, string> ReceivedParams = null;
public ulong ReceivedFrom = ulong.MaxValue;
public int ReceivedCount;
public void OnRpcReceived()
{
@@ -32,6 +36,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
var currentMethod = sf.GetMethod();
Received = currentMethod.Name;
ReceivedCount++;
}
public void OnRpcReceivedWithParams(int a, bool b, float f, string s)
{
@@ -40,6 +45,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
var currentMethod = sf.GetMethod();
Received = currentMethod.Name;
ReceivedCount++;
ReceivedParams = new Tuple<int, bool, float, string>(a, b, f, s);
}
@@ -448,6 +454,9 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
public class UniversalRpcTestsBase : NetcodeIntegrationTest
{
public static int YieldCheck = 0;
public const int YieldCycleCount = 10;
protected override int NumberOfClients => 2;
public UniversalRpcTestsBase(HostOrServer hostOrServer) : base(hostOrServer)
@@ -488,6 +497,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
protected override void OnInlineTearDown()
{
MockTransport.ClearQueues();
Clear();
}
@@ -496,6 +506,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
foreach (var obj in Object.FindObjectsByType<UniversalRpcNetworkBehaviour>(FindObjectsSortMode.None))
{
obj.Received = string.Empty;
obj.ReceivedCount = 0;
obj.ReceivedParams = null;
obj.ReceivedFrom = ulong.MaxValue;
}
@@ -528,10 +539,11 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
return m_PlayerNetworkObjects[onClient][ownerClientId].GetComponent<UniversalRpcNetworkBehaviour>();
}
protected void VerifyLocalReceived(ulong objectOwner, ulong sender, string name, bool verifyReceivedFrom)
protected void VerifyLocalReceived(ulong objectOwner, ulong sender, string name, bool verifyReceivedFrom, int expectedReceived = 1)
{
var obj = GetPlayerObject(objectOwner, sender);
Assert.AreEqual(name, obj.Received);
Assert.That(obj.ReceivedCount, Is.EqualTo(expectedReceived));
Assert.IsNull(obj.ReceivedParams);
if (verifyReceivedFrom)
{
@@ -543,6 +555,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
{
var obj = GetPlayerObject(objectOwner, sender);
Assert.AreEqual(name, obj.Received);
Assert.That(obj.ReceivedCount, Is.EqualTo(1));
Assert.IsNotNull(obj.ReceivedParams);
Assert.AreEqual(i, obj.ReceivedParams.Item1);
Assert.AreEqual(b, obj.ReceivedParams.Item2);
@@ -556,17 +569,18 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
{
UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client);
Assert.AreEqual(string.Empty, playerObject.Received);
Assert.That(playerObject.ReceivedCount, Is.EqualTo(0));
Assert.IsNull(playerObject.ReceivedParams);
}
}
protected void VerifyRemoteReceived(ulong objectOwner, ulong sender, string message, ulong[] receivedBy, bool verifyReceivedFrom, bool waitForMessages = true)
protected void VerifyRemoteReceived(ulong objectOwner, ulong sender, string message, ulong[] receivedBy, bool verifyReceivedFrom, bool waitForMessages = true, int expectedReceived = 1)
{
foreach (var client in receivedBy)
{
if (client == sender)
{
VerifyLocalReceived(objectOwner, sender, message, verifyReceivedFrom);
VerifyLocalReceived(objectOwner, sender, message, verifyReceivedFrom, expectedReceived);
break;
}
@@ -628,6 +642,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
{
UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client);
Assert.AreEqual(message, playerObject.Received);
Assert.That(playerObject.ReceivedCount, Is.EqualTo(expectedReceived));
Assert.IsNull(playerObject.ReceivedParams);
if (verifyReceivedFrom)
{
@@ -701,6 +716,7 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
{
UniversalRpcNetworkBehaviour playerObject = GetPlayerObject(objectOwner, client);
Assert.AreEqual(message, playerObject.Received);
Assert.That(playerObject.ReceivedCount, Is.EqualTo(1));
Assert.IsNotNull(playerObject.ReceivedParams);
Assert.AreEqual(i, playerObject.ReceivedParams.Item1);
@@ -1159,14 +1175,22 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
}
[Test]
public void TestSendingWithSingleOverride(
[Values] SendTo defaultSendTo,
[Values(0u, 1u, 2u)] ulong recipient,
[Values(0u, 1u, 2u)] ulong objectOwner,
[Values(0u, 1u, 2u)] ulong sender
)
[UnityTest]
public IEnumerator TestSendingWithSingleOverride()
{
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
{
for (ulong recipient = 0u; recipient <= 2u; ++recipient)
{
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
{
for (ulong sender = 0u; sender <= 2u; ++sender)
{
if (++YieldCheck % YieldCycleCount == 0)
{
yield return null;
}
OnInlineSetup();
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1180,6 +1204,11 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
// Pass some time to make sure that no other client ever receives this
TimeTravel(1f, 30);
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient != c).ToArray());
OnInlineTearDown();
}
}
}
}
}
}
@@ -1193,14 +1222,22 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
}
[Test]
public void TestSendingWithSingleNotOverride(
[Values] SendTo defaultSendTo,
[Values(0u, 1u, 2u)] ulong recipient,
[Values(0u, 1u, 2u)] ulong objectOwner,
[Values(0u, 1u, 2u)] ulong sender
)
[UnityTest]
public IEnumerator TestSendingWithSingleNotOverride()
{
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
{
for (ulong recipient = 0u; recipient <= 2u; ++recipient)
{
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
{
for (ulong sender = 0u; sender <= 2u; ++sender)
{
if (++YieldCheck % YieldCycleCount == 0)
{
yield return null;
}
OnInlineSetup();
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1214,6 +1251,11 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
// Pass some time to make sure that no other client ever receives this
TimeTravel(1f, 30);
VerifyNotReceived(objectOwner, new[] { recipient });
OnInlineTearDown();
}
}
}
}
}
}
@@ -1243,15 +1285,32 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
List
}
[Test]
public void TestSendingWithGroupOverride(
[Values] SendTo defaultSendTo,
[ValueSource(nameof(RecipientGroups))] ulong[] recipient,
[Values(0u, 1u, 2u)] ulong objectOwner,
[Values(0u, 1u, 2u)] ulong sender,
[Values] AllocationType allocationType
)
// Extending timeout since the added yield return causes this test to commonly timeout
[Timeout(600000)]
[UnityTest]
public IEnumerator TestSendingWithGroupOverride()
{
var waitFor = new WaitForFixedUpdate();
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
{
m_EnableVerboseDebug = true;
VerboseDebug($"Processing: {defaultSendTo}");
m_EnableVerboseDebug = false;
foreach (var recipient in RecipientGroups)
{
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
{
for (ulong sender = 0u; sender <= 2u; ++sender)
{
yield return waitFor;
foreach (var allocationType in Enum.GetValues(typeof(AllocationType)))
{
//if (++YieldCheck % YieldCycleCount == 0)
//{
// yield return null;
//}
OnInlineSetup();
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1278,10 +1337,12 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
{
list.Add(id);
}
target = senderObject.RpcTarget.Group(list, RpcTargetUse.Temp);
list.Dispose();
break;
}
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
@@ -1291,8 +1352,13 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
// Pass some time to make sure that no other client ever receives this
TimeTravel(1f, 30);
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray());
OnInlineTearDown();
}
}
}
}
}
}
}
[TestFixture(HostOrServer.Host)]
@@ -1320,15 +1386,33 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
List
}
[Test]
public void TestSendingWithGroupNotOverride(
[Values] SendTo defaultSendTo,
[ValueSource(nameof(RecipientGroups))] ulong[] recipient,
[Values(0u, 1u, 2u)] ulong objectOwner,
[Values(0u, 1u, 2u)] ulong sender,
[Values] AllocationType allocationType
)
// Extending timeout since the added yield return causes this test to commonly timeout
[Timeout(600000)]
[UnityTest]
public IEnumerator TestSendingWithGroupNotOverride()
{
var waitFor = new WaitForFixedUpdate();
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
{
m_EnableVerboseDebug = true;
VerboseDebug($"Processing: {defaultSendTo}");
m_EnableVerboseDebug = false;
foreach (var recipient in RecipientGroups)
{
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
{
for (ulong sender = 0u; sender <= 2u; ++sender)
{
yield return waitFor;
foreach (var allocationType in Enum.GetValues(typeof(AllocationType)))
{
//if (++YieldCheck % YieldCycleCount == 0)
//{
// yield return waitFor;
//}
OnInlineSetup();
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1368,6 +1452,12 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
// Pass some time to make sure that no other client ever receives this
TimeTravel(1f, 30);
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient.Contains(c)).ToArray());
OnInlineTearDown();
}
}
}
}
}
}
}
@@ -1391,65 +1481,92 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
}
[Test]
// All the test cases that involve sends that will be delivered locally
[TestCase(SendTo.Everyone, 0u, 0u)]
[TestCase(SendTo.Everyone, 0u, 1u)]
[TestCase(SendTo.Everyone, 0u, 2u)]
[TestCase(SendTo.Everyone, 1u, 0u)]
[TestCase(SendTo.Everyone, 1u, 1u)]
[TestCase(SendTo.Everyone, 1u, 2u)]
[TestCase(SendTo.Everyone, 2u, 0u)]
[TestCase(SendTo.Everyone, 2u, 1u)]
[TestCase(SendTo.Everyone, 2u, 2u)]
[TestCase(SendTo.Me, 0u, 0u)]
[TestCase(SendTo.Me, 0u, 1u)]
[TestCase(SendTo.Me, 0u, 2u)]
[TestCase(SendTo.Me, 1u, 0u)]
[TestCase(SendTo.Me, 1u, 1u)]
[TestCase(SendTo.Me, 1u, 2u)]
[TestCase(SendTo.Me, 2u, 0u)]
[TestCase(SendTo.Me, 2u, 1u)]
[TestCase(SendTo.Me, 2u, 2u)]
[TestCase(SendTo.Owner, 0u, 0u)]
[TestCase(SendTo.Owner, 1u, 1u)]
[TestCase(SendTo.Owner, 2u, 2u)]
[TestCase(SendTo.Server, 0u, 0u)]
[TestCase(SendTo.Server, 1u, 0u)]
[TestCase(SendTo.Server, 2u, 0u)]
[TestCase(SendTo.NotOwner, 0u, 1u)]
[TestCase(SendTo.NotOwner, 0u, 2u)]
[TestCase(SendTo.NotOwner, 1u, 0u)]
[TestCase(SendTo.NotOwner, 1u, 2u)]
[TestCase(SendTo.NotOwner, 2u, 0u)]
[TestCase(SendTo.NotOwner, 2u, 1u)]
[TestCase(SendTo.NotServer, 0u, 1u)]
[TestCase(SendTo.NotServer, 0u, 2u)]
[TestCase(SendTo.NotServer, 1u, 1u)]
[TestCase(SendTo.NotServer, 1u, 2u)]
[TestCase(SendTo.NotServer, 2u, 1u)]
[TestCase(SendTo.NotServer, 2u, 2u)]
[TestCase(SendTo.ClientsAndHost, 0u, 0u)]
[TestCase(SendTo.ClientsAndHost, 0u, 1u)]
[TestCase(SendTo.ClientsAndHost, 0u, 2u)]
[TestCase(SendTo.ClientsAndHost, 1u, 0u)]
[TestCase(SendTo.ClientsAndHost, 1u, 1u)]
[TestCase(SendTo.ClientsAndHost, 1u, 2u)]
[TestCase(SendTo.ClientsAndHost, 2u, 0u)]
[TestCase(SendTo.ClientsAndHost, 2u, 1u)]
[TestCase(SendTo.ClientsAndHost, 2u, 2u)]
public void TestDeferLocal(
SendTo defaultSendTo,
ulong objectOwner,
ulong sender
)
private struct TestData
{
public SendTo SendTo;
public ulong ObjectOwner;
public ulong Sender;
public TestData(SendTo sendTo, ulong objectOwner, ulong sender)
{
SendTo = sendTo;
ObjectOwner = objectOwner;
Sender = sender;
}
}
// All the test cases that involve sends that will be delivered locally
private static TestData[] s_LocalDeliveryTestCases =
{
new TestData(SendTo.Everyone, 0u, 0u),
new TestData(SendTo.Everyone, 0u, 1u),
new TestData(SendTo.Everyone, 0u, 2u),
new TestData(SendTo.Everyone, 1u, 0u),
new TestData(SendTo.Everyone, 1u, 1u),
new TestData(SendTo.Everyone, 1u, 2u),
new TestData(SendTo.Everyone, 2u, 0u),
new TestData(SendTo.Everyone, 2u, 1u),
new TestData(SendTo.Everyone, 2u, 2u),
new TestData(SendTo.Me, 0u, 0u),
new TestData(SendTo.Me, 0u, 1u),
new TestData(SendTo.Me, 0u, 2u),
new TestData(SendTo.Me, 1u, 0u),
new TestData(SendTo.Me, 1u, 1u),
new TestData(SendTo.Me, 1u, 2u),
new TestData(SendTo.Me, 2u, 0u),
new TestData(SendTo.Me, 2u, 1u),
new TestData(SendTo.Me, 2u, 2u),
new TestData(SendTo.Owner, 0u, 0u),
new TestData(SendTo.Owner, 1u, 1u),
new TestData(SendTo.Owner, 2u, 2u),
new TestData(SendTo.Server, 0u, 0u),
new TestData(SendTo.Server, 1u, 0u),
new TestData(SendTo.Server, 2u, 0u),
new TestData(SendTo.NotOwner, 0u, 1u),
new TestData(SendTo.NotOwner, 0u, 2u),
new TestData(SendTo.NotOwner, 1u, 0u),
new TestData(SendTo.NotOwner, 1u, 2u),
new TestData(SendTo.NotOwner, 2u, 0u),
new TestData(SendTo.NotOwner, 2u, 1u),
new TestData(SendTo.NotServer, 0u, 1u),
new TestData(SendTo.NotServer, 0u, 2u),
new TestData(SendTo.NotServer, 1u, 1u),
new TestData(SendTo.NotServer, 1u, 2u),
new TestData(SendTo.NotServer, 2u, 1u),
new TestData(SendTo.NotServer, 2u, 2u),
new TestData(SendTo.ClientsAndHost, 0u, 0u),
new TestData(SendTo.ClientsAndHost, 0u, 1u),
new TestData(SendTo.ClientsAndHost, 0u, 2u),
new TestData(SendTo.ClientsAndHost, 1u, 0u),
new TestData(SendTo.ClientsAndHost, 1u, 1u),
new TestData(SendTo.ClientsAndHost, 1u, 2u),
new TestData(SendTo.ClientsAndHost, 2u, 0u),
new TestData(SendTo.ClientsAndHost, 2u, 1u),
new TestData(SendTo.ClientsAndHost, 2u, 2u),
};
[UnityTest]
public IEnumerator TestDeferLocal()
{
foreach (var testCase in s_LocalDeliveryTestCases)
{
if (++YieldCheck % YieldCycleCount == 0)
{
yield return null;
}
OnInlineSetup();
var defaultSendTo = testCase.SendTo;
var sender = testCase.Sender;
var objectOwner = testCase.ObjectOwner;
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
{
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
// Just consider this case a success...
return;
yield break;
}
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1463,67 +1580,31 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
var verifyMethod = GetType().GetMethod(verifyMethodName);
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
OnInlineTearDown();
}
}
[Test]
// All the test cases that involve sends that will be delivered locally
[TestCase(SendTo.Everyone, 0u, 0u)]
[TestCase(SendTo.Everyone, 0u, 1u)]
[TestCase(SendTo.Everyone, 0u, 2u)]
[TestCase(SendTo.Everyone, 1u, 0u)]
[TestCase(SendTo.Everyone, 1u, 1u)]
[TestCase(SendTo.Everyone, 1u, 2u)]
[TestCase(SendTo.Everyone, 2u, 0u)]
[TestCase(SendTo.Everyone, 2u, 1u)]
[TestCase(SendTo.Everyone, 2u, 2u)]
[TestCase(SendTo.Me, 0u, 0u)]
[TestCase(SendTo.Me, 0u, 1u)]
[TestCase(SendTo.Me, 0u, 2u)]
[TestCase(SendTo.Me, 1u, 0u)]
[TestCase(SendTo.Me, 1u, 1u)]
[TestCase(SendTo.Me, 1u, 2u)]
[TestCase(SendTo.Me, 2u, 0u)]
[TestCase(SendTo.Me, 2u, 1u)]
[TestCase(SendTo.Me, 2u, 2u)]
[TestCase(SendTo.Owner, 0u, 0u)]
[TestCase(SendTo.Owner, 1u, 1u)]
[TestCase(SendTo.Owner, 2u, 2u)]
[TestCase(SendTo.Server, 0u, 0u)]
[TestCase(SendTo.Server, 1u, 0u)]
[TestCase(SendTo.Server, 2u, 0u)]
[TestCase(SendTo.NotOwner, 0u, 1u)]
[TestCase(SendTo.NotOwner, 0u, 2u)]
[TestCase(SendTo.NotOwner, 1u, 0u)]
[TestCase(SendTo.NotOwner, 1u, 2u)]
[TestCase(SendTo.NotOwner, 2u, 0u)]
[TestCase(SendTo.NotOwner, 2u, 1u)]
[TestCase(SendTo.NotServer, 0u, 1u)]
[TestCase(SendTo.NotServer, 0u, 2u)]
[TestCase(SendTo.NotServer, 1u, 1u)]
[TestCase(SendTo.NotServer, 1u, 2u)]
[TestCase(SendTo.NotServer, 2u, 1u)]
[TestCase(SendTo.NotServer, 2u, 2u)]
[TestCase(SendTo.ClientsAndHost, 0u, 0u)]
[TestCase(SendTo.ClientsAndHost, 0u, 1u)]
[TestCase(SendTo.ClientsAndHost, 0u, 2u)]
[TestCase(SendTo.ClientsAndHost, 1u, 0u)]
[TestCase(SendTo.ClientsAndHost, 1u, 1u)]
[TestCase(SendTo.ClientsAndHost, 1u, 2u)]
[TestCase(SendTo.ClientsAndHost, 2u, 0u)]
[TestCase(SendTo.ClientsAndHost, 2u, 1u)]
[TestCase(SendTo.ClientsAndHost, 2u, 2u)]
public void TestDeferLocalOverrideToTrue(
SendTo defaultSendTo,
ulong objectOwner,
ulong sender
)
[UnityTest]
public IEnumerator TestDeferLocalOverrideToTrue()
{
foreach (var testCase in s_LocalDeliveryTestCases)
{
if (++YieldCheck % YieldCycleCount == 0)
{
yield return null;
}
OnInlineSetup();
var defaultSendTo = testCase.SendTo;
var sender = testCase.Sender;
var objectOwner = testCase.ObjectOwner;
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
{
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
// Just consider this case a success...
return;
yield break;
}
var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc";
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1537,67 +1618,31 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
var verifyMethod = GetType().GetMethod(verifyMethodName);
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
OnInlineTearDown();
}
}
[Test]
// All the test cases that involve sends that will be delivered locally
[TestCase(SendTo.Everyone, 0u, 0u)]
[TestCase(SendTo.Everyone, 0u, 1u)]
[TestCase(SendTo.Everyone, 0u, 2u)]
[TestCase(SendTo.Everyone, 1u, 0u)]
[TestCase(SendTo.Everyone, 1u, 1u)]
[TestCase(SendTo.Everyone, 1u, 2u)]
[TestCase(SendTo.Everyone, 2u, 0u)]
[TestCase(SendTo.Everyone, 2u, 1u)]
[TestCase(SendTo.Everyone, 2u, 2u)]
[TestCase(SendTo.Me, 0u, 0u)]
[TestCase(SendTo.Me, 0u, 1u)]
[TestCase(SendTo.Me, 0u, 2u)]
[TestCase(SendTo.Me, 1u, 0u)]
[TestCase(SendTo.Me, 1u, 1u)]
[TestCase(SendTo.Me, 1u, 2u)]
[TestCase(SendTo.Me, 2u, 0u)]
[TestCase(SendTo.Me, 2u, 1u)]
[TestCase(SendTo.Me, 2u, 2u)]
[TestCase(SendTo.Owner, 0u, 0u)]
[TestCase(SendTo.Owner, 1u, 1u)]
[TestCase(SendTo.Owner, 2u, 2u)]
[TestCase(SendTo.Server, 0u, 0u)]
[TestCase(SendTo.Server, 1u, 0u)]
[TestCase(SendTo.Server, 2u, 0u)]
[TestCase(SendTo.NotOwner, 0u, 1u)]
[TestCase(SendTo.NotOwner, 0u, 2u)]
[TestCase(SendTo.NotOwner, 1u, 0u)]
[TestCase(SendTo.NotOwner, 1u, 2u)]
[TestCase(SendTo.NotOwner, 2u, 0u)]
[TestCase(SendTo.NotOwner, 2u, 1u)]
[TestCase(SendTo.NotServer, 0u, 1u)]
[TestCase(SendTo.NotServer, 0u, 2u)]
[TestCase(SendTo.NotServer, 1u, 1u)]
[TestCase(SendTo.NotServer, 1u, 2u)]
[TestCase(SendTo.NotServer, 2u, 1u)]
[TestCase(SendTo.NotServer, 2u, 2u)]
[TestCase(SendTo.ClientsAndHost, 0u, 0u)]
[TestCase(SendTo.ClientsAndHost, 0u, 1u)]
[TestCase(SendTo.ClientsAndHost, 0u, 2u)]
[TestCase(SendTo.ClientsAndHost, 1u, 0u)]
[TestCase(SendTo.ClientsAndHost, 1u, 1u)]
[TestCase(SendTo.ClientsAndHost, 1u, 2u)]
[TestCase(SendTo.ClientsAndHost, 2u, 0u)]
[TestCase(SendTo.ClientsAndHost, 2u, 1u)]
[TestCase(SendTo.ClientsAndHost, 2u, 2u)]
public void TestDeferLocalOverrideToFalse(
SendTo defaultSendTo,
ulong objectOwner,
ulong sender
)
[UnityTest]
public IEnumerator TestDeferLocalOverrideToFalse()
{
foreach (var testCase in s_LocalDeliveryTestCases)
{
if (++YieldCheck % YieldCycleCount == 0)
{
yield return null;
}
OnInlineSetup();
var defaultSendTo = testCase.SendTo;
var sender = testCase.Sender;
var objectOwner = testCase.ObjectOwner;
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
{
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
// Just consider this case a success...
return;
yield break;
}
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
var senderObject = GetPlayerObject(objectOwner, sender);
@@ -1608,6 +1653,8 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
var verifyMethod = GetType().GetMethod(verifyMethodName);
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
OnInlineTearDown();
}
}
}
@@ -1636,17 +1683,20 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
VerifyNotReceived(NetworkManager.ServerClientId, s_ClientIds);
for (var i = 0; i < 10; ++i)
var clientListExpected = 1;
var serverListExpected = 2;
for (var i = 1; i <= 10; ++i)
{
WaitForMessageReceivedWithTimeTravel<RpcMessage>(clientList);
VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionClientRpc), clientIdArray, false, false);
VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionClientRpc), clientIdArray, false, false, clientListExpected);
VerifyNotReceived(NetworkManager.ServerClientId, serverIdArray);
clientListExpected *= 2;
Clear();
WaitForMessageReceivedWithTimeTravel<RpcMessage>(serverList);
VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionServerRpc), serverIdArray, false, false);
VerifyRemoteReceived(NetworkManager.ServerClientId, NetworkManager.ServerClientId, nameof(UniversalRpcNetworkBehaviour.MutualRecursionServerRpc), serverIdArray, false, false, serverListExpected);
VerifyNotReceived(NetworkManager.ServerClientId, clientIdArray);
serverListExpected *= 2;
Clear();
}
@@ -1915,3 +1965,4 @@ namespace Unity.Netcode.RuntimeTests.UniversalRpcTests
}
}
#endif

View File

@@ -12,12 +12,20 @@
"Unity.Networking.Transport",
"ClientNetworkTransform",
"Unity.Netcode.TestHelpers.Runtime",
"Unity.Mathematics"
"Unity.Mathematics",
"UnityEngine.TestRunner",
"UnityEditor.TestRunner"
],
"optionalUnityReferences": [
"TestAssemblies"
"includePlatforms": [],
"excludePlatforms": [],
"allowUnsafeCode": true,
"overrideReferences": true,
"precompiledReferences": [
"nunit.framework.dll"
],
"autoReferenced": false,
"defineConstraints": [
"UNITY_INCLUDE_TESTS",
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [
@@ -46,5 +54,6 @@
"expression": "2.0.0-exp",
"define": "UTP_TRANSPORT_2_0_ABOVE"
}
]
],
"noEngineReferences": false
}

View File

@@ -2,23 +2,23 @@
"name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
"version": "1.8.1",
"version": "1.11.0",
"unity": "2021.3",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.10.1",
"com.unity.transport": "1.4.0"
},
"_upm": {
"changelog": "### Fixed\n\n- Fixed a compile error when compiling for IL2CPP targets when using the new `[Rpc]` attribute. (#2824)"
"changelog": "### Added\n\n- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3005)\n\n### Fixed\n\n- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3011)\n- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3005)\n- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3005)\n- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#2999)\n- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#2992)\n\n### Changed\n\n- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)\n- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3005)"
},
"upmCi": {
"footprint": "5e57664d4f43bf176189c5ec8fd10f595b668e7d"
"footprint": "aa624034952045f7f2399c1f99fd31b764234959"
},
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.8/manual/index.html",
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.11/manual/index.html",
"repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git",
"revision": "eb213838233cbee2030891203bcb72d2354a5a7d"
"revision": "e3303ba66b4a642ccf0bc72104107e1b8e1ebe1c"
},
"samples": [
{