Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
896943c8bf | ||
|
|
158f26b913 | ||
|
|
f8ebf679ec |
59
CHANGELOG.md
59
CHANGELOG.md
@@ -5,6 +5,65 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
|
||||
## [1.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
|
||||
|
||||
- Fixed a compile error when compiling for IL2CPP targets when using the new `[Rpc]` attribute. (#2824)
|
||||
|
||||
## [1.8.0] - 2023-12-12
|
||||
|
||||
|
||||
500
Components/AnticipatedNetworkTransform.cs
Normal file
500
Components/AnticipatedNetworkTransform.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Components/AnticipatedNetworkTransform.cs.meta
Normal file
3
Components/AnticipatedNetworkTransform.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 97616b67982a4be48d957d421e422433
|
||||
timeCreated: 1705597211
|
||||
8
Components/Messages.meta
Normal file
8
Components/Messages.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a9db1d18fa0117f4da5e8e65386b894a
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
116
Components/Messages/NetworkTransformMessage.cs
Normal file
116
Components/Messages/NetworkTransformMessage.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Components/Messages/NetworkTransformMessage.cs.meta
Normal file
11
Components/Messages/NetworkTransformMessage.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dcfc8ac43fef97e42adb19b998d70c37
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
m_Rigidbody = GetComponent<Rigidbody2D>();
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// Puts the rigidbody in a kinematic non-interpolated mode on everyone but the server.
|
||||
private void UpdateRigidbodyKinematicMode()
|
||||
/// <summary>
|
||||
/// For owner authoritative (i.e. ClientNetworkTransform)
|
||||
/// we adjust our authority when we gain ownership
|
||||
/// </summary>
|
||||
public override void OnGainedOwnership()
|
||||
{
|
||||
if (m_IsAuthority == false)
|
||||
{
|
||||
m_OriginalKinematic = m_Rigidbody.isKinematic;
|
||||
m_Rigidbody.isKinematic = true;
|
||||
UpdateOwnershipAuthority();
|
||||
}
|
||||
|
||||
m_OriginalInterpolation = m_Rigidbody.interpolation;
|
||||
// Set interpolation to none, the NetworkTransform component interpolates the position of the object.
|
||||
m_Rigidbody.interpolation = RigidbodyInterpolation2D.None;
|
||||
/// <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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,32 +3223,32 @@ namespace Unity.Netcode.Components
|
||||
| m_LocalAuthoritativeNetworkState.UnreliableFrameSync | m_LocalAuthoritativeNetworkState.SynchronizeBaseHalfFloat
|
||||
? NetworkDelivery.ReliableSequenced : NetworkDelivery.UnreliableSequenced;
|
||||
|
||||
using (writer)
|
||||
// Server-host always sends updates to all clients (but itself)
|
||||
if (IsServer)
|
||||
{
|
||||
writer.WriteNetworkSerializable(m_LocalAuthoritativeNetworkState);
|
||||
// Server-host always sends updates to all clients (but itself)
|
||||
if (IsServer)
|
||||
var clientCount = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList.Count;
|
||||
for (int i = 0; i < clientCount; i++)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var clientId = m_CachedNetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId;
|
||||
if (NetworkManager.ServerClientId == clientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
customMessageManager.SendNamedMessage(m_MessageName, clientId, writer, networkDelivery);
|
||||
continue;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Clients (owner authoritative) send messages to the server-host
|
||||
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)
|
||||
@@ -3379,7 +3343,7 @@ namespace Unity.Netcode.Components
|
||||
|
||||
/// <summary>
|
||||
/// If a NetworkTransformTickRegistration exists for the NetworkManager instance, then this will
|
||||
/// remove the NetworkTransform instance from the single tick update entry point.
|
||||
/// remove the NetworkTransform instance from the single tick update entry point.
|
||||
/// </summary>
|
||||
/// <param name="networkTransform"></param>
|
||||
private static void DeregisterForTickUpdate(NetworkTransform networkTransform)
|
||||
@@ -3394,6 +3358,8 @@ namespace Unity.Netcode.Components
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
internal interface INetworkTransformLogStateEntry
|
||||
|
||||
14
Editor/AnticipatedNetworkTransformEditor.cs
Normal file
14
Editor/AnticipatedNetworkTransformEditor.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
3
Editor/AnticipatedNetworkTransformEditor.cs.meta
Normal file
3
Editor/AnticipatedNetworkTransformEditor.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 34bc168605014eeeadf97b12080e11fa
|
||||
timeCreated: 1707514321
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
|
||||
@@ -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;
|
||||
@@ -822,7 +912,7 @@ namespace Unity.Netcode.Editor.CodeGen
|
||||
break;
|
||||
}
|
||||
}
|
||||
m_UniversalRpcParams_TypeRef = moduleDefinition.ImportReference(rpcParamsTypeDef);
|
||||
m_UniversalRpcParams_TypeRef = moduleDefinition.ImportReference(universalRpcParamsTypeDef);
|
||||
foreach (var fieldDef in rpcParamsTypeDef.Fields)
|
||||
{
|
||||
switch (fieldDef.Name)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
EditorGUILayout.PropertyField(m_InterpolateProperty);
|
||||
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" +
|
||||
|
||||
@@ -104,9 +104,11 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
peerClientIds[idx] = peerId;
|
||||
++idx;
|
||||
if (peerClientIds.Length > idx)
|
||||
{
|
||||
peerClientIds[idx] = peerId;
|
||||
++idx;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
@@ -491,24 +493,41 @@ 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();
|
||||
|
||||
InvokeOnClientDisconnectCallback(clientId);
|
||||
|
||||
if (LocalClient.IsHost)
|
||||
{
|
||||
InvokeOnPeerDisconnectedCallback(clientId);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
else // As long as we are not in the middle of a shutdown
|
||||
if (!NetworkManager.ShutdownInProgress)
|
||||
else
|
||||
{
|
||||
// We must pass true here and not process any sends messages as we are no longer connected.
|
||||
// Otherwise, attempting to process messages here can cause an exception within UnityTransport
|
||||
// as the client ID is no longer valid.
|
||||
NetworkManager.Shutdown(true);
|
||||
// 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.
|
||||
// Otherwise, attempting to process messages here can cause an exception within UnityTransport
|
||||
// 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();
|
||||
@@ -891,9 +910,14 @@ namespace Unity.Netcode
|
||||
|
||||
ConnectedClients.Add(clientId, networkClient);
|
||||
ConnectedClientsList.Add(networkClient);
|
||||
var message = new ClientConnectedMessage { ClientId = clientId };
|
||||
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ConnectedClientIds);
|
||||
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);
|
||||
}
|
||||
|
||||
return networkClient;
|
||||
}
|
||||
|
||||
|
||||
@@ -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,12 +505,14 @@ namespace Unity.Netcode
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_NetworkObject != null)
|
||||
{
|
||||
return m_NetworkObject;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (m_NetworkObject == null)
|
||||
{
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
m_NetworkObject = GetComponentInParent<NetworkObject>();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
@@ -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;
|
||||
@@ -679,7 +771,7 @@ namespace Unity.Netcode
|
||||
/// <summary>
|
||||
/// Invoked on all clients, override this method to be notified of any
|
||||
/// ownership changes (even if the instance was niether the previous or
|
||||
/// newly assigned current owner).
|
||||
/// newly assigned current owner).
|
||||
/// </summary>
|
||||
/// <param name="previous">the previous owner</param>
|
||||
/// <param name="current">the current owner</param>
|
||||
@@ -740,7 +832,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 +858,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 +905,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 +922,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 +942,6 @@ namespace Unity.Netcode
|
||||
{
|
||||
InitializeVariables();
|
||||
}
|
||||
|
||||
PreNetworkVariableWrite();
|
||||
}
|
||||
|
||||
@@ -861,7 +968,10 @@ namespace Unity.Netcode
|
||||
var networkVariable = NetworkVariableFields[k];
|
||||
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
|
||||
{
|
||||
shouldSend = true;
|
||||
if (networkVariable.CanSend())
|
||||
{
|
||||
shouldSend = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -902,9 +1012,16 @@ 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())
|
||||
{
|
||||
return true;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1061,6 +1178,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 +1313,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()
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
70
Runtime/Messaging/Messages/AnticipationCounterSync.cs
Normal file
70
Runtime/Messaging/Messages/AnticipationCounterSync.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: db5034828a9741ce9bc8ec9a64d5a5b6
|
||||
timeCreated: 1706042908
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -49,6 +49,10 @@ namespace Unity.Netcode
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (clientId == NetworkManager.ServerClientId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
m_GroupSendTarget.Add(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
393
Runtime/NetworkVariable/AnticipatedNetworkVariable.cs
Normal file
393
Runtime/NetworkVariable/AnticipatedNetworkVariable.cs
Normal file
@@ -0,0 +1,393 @@
|
||||
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)]
|
||||
[GenerateSerializationForType(typeof(byte))]
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 10f5188736b742d1993a2aad46a03e78
|
||||
timeCreated: 1705595868
|
||||
746
Runtime/NetworkVariable/CollectionSerializationUtility.cs
Normal file
746
Runtime/NetworkVariable/CollectionSerializationUtility.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c822ece4e24f4676861e07288a7f8526
|
||||
timeCreated: 1705437250
|
||||
@@ -9,6 +9,7 @@ namespace Unity.Netcode
|
||||
/// <typeparam name="T">the unmanaged type for <see cref="NetworkVariable{T}"/> </typeparam>
|
||||
[Serializable]
|
||||
[GenerateSerializationForGenericParameter(0)]
|
||||
[GenerateSerializationForType(typeof(byte))]
|
||||
public class NetworkVariable<T> : NetworkVariableBase
|
||||
{
|
||||
/// <summary>
|
||||
@@ -22,6 +23,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>
|
||||
@@ -142,16 +165,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>
|
||||
@@ -173,7 +196,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>
|
||||
@@ -189,7 +212,7 @@ namespace Unity.Netcode
|
||||
// would be stored in different fields
|
||||
|
||||
T previousValue = m_InternalValue;
|
||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
|
||||
|
||||
if (keepDirtyDelta)
|
||||
{
|
||||
|
||||
@@ -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>
|
||||
@@ -30,6 +45,43 @@ namespace Unity.Netcode
|
||||
public void Initialize(NetworkBehaviour networkBehaviour)
|
||||
{
|
||||
m_NetworkBehaviour = networkBehaviour;
|
||||
if (m_NetworkBehaviour.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 +144,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 +180,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);
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
107
Runtime/NetworkVariable/ResizableBitVector.cs
Normal file
107
Runtime/NetworkVariable/ResizableBitVector.cs
Normal 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<byte> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/NetworkVariable/ResizableBitVector.cs.meta
Normal file
3
Runtime/NetworkVariable/ResizableBitVector.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 664696a622e244dfa43b26628c05e4a6
|
||||
timeCreated: 1705437231
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
100
Runtime/Timing/AnticipationSystem.cs
Normal file
100
Runtime/Timing/AnticipationSystem.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Runtime/Timing/AnticipationSystem.cs.meta
Normal file
3
Runtime/Timing/AnticipationSystem.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21e6ae0a82f945458519aee4ee119cab
|
||||
timeCreated: 1707845625
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -165,13 +165,30 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
||||
|
||||
foreach (var sobj in inSceneNetworkObjects)
|
||||
{
|
||||
if (sobj.NetworkManagerOwner != networkManager)
|
||||
ProcessInSceneObject(sobj, networkManager);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assures to apply an ObjectNameIdentifier to all children
|
||||
/// </summary>
|
||||
private static void ProcessInSceneObject(NetworkObject networkObject, NetworkManager networkManager)
|
||||
{
|
||||
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)
|
||||
{
|
||||
sobj.NetworkManagerOwner = networkManager;
|
||||
}
|
||||
if (sobj.GetComponent<ObjectNameIdentifier>() == null && sobj.GetComponentInChildren<ObjectNameIdentifier>() == null)
|
||||
{
|
||||
sobj.gameObject.AddComponent<ObjectNameIdentifier>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
clientId = data.FromClientId;
|
||||
payload = data.Payload;
|
||||
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;
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)))
|
||||
{
|
||||
NetworkUpdateLoop.RunNetworkUpdateStage(stage);
|
||||
string methodName = string.Empty;
|
||||
switch (stage)
|
||||
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)
|
||||
{
|
||||
case NetworkUpdateStage.FixedUpdate:
|
||||
methodName = "FixedUpdate"; // mapping NetworkUpdateStage.FixedUpdate to MonoBehaviour.FixedUpdate
|
||||
break;
|
||||
case NetworkUpdateStage.Update:
|
||||
methodName = "Update"; // mapping NetworkUpdateStage.Update to MonoBehaviour.Update
|
||||
break;
|
||||
case NetworkUpdateStage.PreLateUpdate:
|
||||
methodName = "LateUpdate"; // mapping NetworkUpdateStage.PreLateUpdate to MonoBehaviour.LateUpdate
|
||||
break;
|
||||
stage = NetworkUpdateStage.PostLateUpdate;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(methodName))
|
||||
else if (stage == NetworkUpdateStage.PostLateUpdate)
|
||||
{
|
||||
#if UNITY_2023_1_OR_NEWER
|
||||
foreach (var behaviour in Object.FindObjectsByType<NetworkBehaviour>(FindObjectsSortMode.InstanceID))
|
||||
#else
|
||||
foreach (var behaviour in Object.FindObjectsOfType<NetworkBehaviour>())
|
||||
#endif
|
||||
stage = NetworkUpdateStage.PostScriptLateUpdate;
|
||||
}
|
||||
NetworkUpdateLoop.RunNetworkUpdateStage(stage);
|
||||
|
||||
if (stage == NetworkUpdateStage.Update || stage == NetworkUpdateStage.FixedUpdate || stage == NetworkUpdateStage.PreLateUpdate)
|
||||
{
|
||||
foreach (var behaviour in Object.FindObjectsByType<NetworkBehaviour>(FindObjectsSortMode.None))
|
||||
{
|
||||
var method = behaviour.GetType().GetMethod(methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
|
||||
method?.Invoke(behaviour, new object[] { });
|
||||
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:
|
||||
updateData.FixedUpdate?.Invoke(behaviour, new object[] { });
|
||||
break;
|
||||
case NetworkUpdateStage.Update:
|
||||
updateData.Update?.Invoke(behaviour, new object[] { });
|
||||
break;
|
||||
case NetworkUpdateStage.PreLateUpdate:
|
||||
updateData.LateUpdate?.Invoke(behaviour, new object[] { });
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,11 +90,16 @@ namespace Unity.Netcode.EditorTests
|
||||
break;
|
||||
}
|
||||
case TypeOfCorruption.CorruptBytes:
|
||||
batchData.Seek(batchData.Length - 2);
|
||||
var currentByte = batchData.GetUnsafePtr()[0];
|
||||
batchData.WriteByteSafe((byte)(currentByte == 0 ? 1 : 0));
|
||||
MessageQueue.Add(batchData.ToArray());
|
||||
break;
|
||||
{
|
||||
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());
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
137
Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs
Normal file
137
Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs
Normal 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}!");
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta
Normal file
11
Tests/Runtime/NetworkBehaviourPrePostSpawnTests.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2263d66f6df15a7428d279dbdaba1519
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
521
Tests/Runtime/NetworkTransformAnticipationTests.cs
Normal file
521
Tests/Runtime/NetworkTransformAnticipationTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Tests/Runtime/NetworkTransformAnticipationTests.cs.meta
Normal file
3
Tests/Runtime/NetworkTransformAnticipationTests.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4f149de86bec4f6eb1a4d62a1b52938a
|
||||
timeCreated: 1706630210
|
||||
420
Tests/Runtime/NetworkVariableAnticipationTests.cs
Normal file
420
Tests/Runtime/NetworkVariableAnticipationTests.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Tests/Runtime/NetworkVariableAnticipationTests.cs.meta
Normal file
3
Tests/Runtime/NetworkVariableAnticipationTests.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 43cd37f850534b7db07e20281442f10d
|
||||
timeCreated: 1706288570
|
||||
File diff suppressed because it is too large
Load Diff
936
Tests/Runtime/NetworkVariableTestsHelperTypes.cs
Normal file
936
Tests/Runtime/NetworkVariableTestsHelperTypes.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
3
Tests/Runtime/NetworkVariableTestsHelperTypes.cs.meta
Normal file
3
Tests/Runtime/NetworkVariableTestsHelperTypes.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a580aaafc247486193f372174a99fae4
|
||||
timeCreated: 1705098783
|
||||
138
Tests/Runtime/NetworkVariableTraitsTests.cs
Normal file
138
Tests/Runtime/NetworkVariableTraitsTests.cs
Normal 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); ;
|
||||
}
|
||||
}
|
||||
}
|
||||
3
Tests/Runtime/NetworkVariableTraitsTests.cs.meta
Normal file
3
Tests/Runtime/NetworkVariableTraitsTests.cs.meta
Normal file
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c4ca75209dbd43f48930e4887392bafd
|
||||
timeCreated: 1706826268
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,27 +1175,40 @@ 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()
|
||||
{
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
|
||||
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);
|
||||
var target = senderObject.RpcTarget.Single(recipient, RpcTargetUse.Temp);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var target = senderObject.RpcTarget.Single(recipient, RpcTargetUse.Temp);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, new[] { recipient }, false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient != c).ToArray());
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, new[] { recipient }, false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient != c).ToArray());
|
||||
|
||||
// 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());
|
||||
// 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,27 +1222,40 @@ 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()
|
||||
{
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
|
||||
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);
|
||||
var target = senderObject.RpcTarget.Not(recipient, RpcTargetUse.Temp);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var target = senderObject.RpcTarget.Not(recipient, RpcTargetUse.Temp);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient != c).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, new[] { recipient });
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient != c).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, new[] { recipient });
|
||||
|
||||
// Pass some time to make sure that no other client ever receives this
|
||||
TimeTravel(1f, 30);
|
||||
VerifyNotReceived(objectOwner, new[] { recipient });
|
||||
// Pass some time to make sure that no other client ever receives this
|
||||
TimeTravel(1f, 30);
|
||||
VerifyNotReceived(objectOwner, new[] { recipient });
|
||||
OnInlineTearDown();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1243,56 +1285,80 @@ 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 sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
|
||||
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
BaseRpcTarget target = null;
|
||||
switch (allocationType)
|
||||
var waitFor = new WaitForFixedUpdate();
|
||||
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
|
||||
{
|
||||
case AllocationType.Array:
|
||||
target = senderObject.RpcTarget.Group(recipient, RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.List:
|
||||
target = senderObject.RpcTarget.Group(recipient.ToList(), RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.NativeArray:
|
||||
var arr = new NativeArray<ulong>(recipient, Allocator.Temp);
|
||||
target = senderObject.RpcTarget.Group(arr, RpcTargetUse.Temp);
|
||||
arr.Dispose();
|
||||
break;
|
||||
case AllocationType.NativeList:
|
||||
// For some reason on 2020.3, calling list.AsArray() and passing that to the next function
|
||||
// causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later
|
||||
// versions of Unity.
|
||||
var list = new NativeList<ulong>(recipient.Length, Allocator.TempJob);
|
||||
foreach (var id in recipient)
|
||||
m_EnableVerboseDebug = true;
|
||||
VerboseDebug($"Processing: {defaultSendTo}");
|
||||
m_EnableVerboseDebug = false;
|
||||
|
||||
foreach (var recipient in RecipientGroups)
|
||||
{
|
||||
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
|
||||
{
|
||||
list.Add(id);
|
||||
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);
|
||||
BaseRpcTarget target = null;
|
||||
switch (allocationType)
|
||||
{
|
||||
case AllocationType.Array:
|
||||
target = senderObject.RpcTarget.Group(recipient, RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.List:
|
||||
target = senderObject.RpcTarget.Group(recipient.ToList(), RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.NativeArray:
|
||||
var arr = new NativeArray<ulong>(recipient, Allocator.Temp);
|
||||
target = senderObject.RpcTarget.Group(arr, RpcTargetUse.Temp);
|
||||
arr.Dispose();
|
||||
break;
|
||||
case AllocationType.NativeList:
|
||||
// For some reason on 2020.3, calling list.AsArray() and passing that to the next function
|
||||
// causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later
|
||||
// versions of Unity.
|
||||
var list = new NativeList<ulong>(recipient.Length, Allocator.TempJob);
|
||||
foreach (var id in recipient)
|
||||
{
|
||||
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 });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient.Contains(c)).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray());
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
target = senderObject.RpcTarget.Group(list, RpcTargetUse.Temp);
|
||||
list.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => recipient.Contains(c)).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray());
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
[TestFixture(HostOrServer.Host)]
|
||||
@@ -1320,54 +1386,78 @@ 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 sendMethodName = $"DefaultTo{defaultSendTo}AllowOverrideRpc";
|
||||
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
BaseRpcTarget target = null;
|
||||
switch (allocationType)
|
||||
var waitFor = new WaitForFixedUpdate();
|
||||
foreach (var defaultSendTo in Enum.GetValues(typeof(SendTo)))
|
||||
{
|
||||
case AllocationType.Array:
|
||||
target = senderObject.RpcTarget.Not(recipient, RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.List:
|
||||
target = senderObject.RpcTarget.Not(recipient.ToList(), RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.NativeArray:
|
||||
var arr = new NativeArray<ulong>(recipient, Allocator.Temp);
|
||||
target = senderObject.RpcTarget.Not(arr, RpcTargetUse.Temp);
|
||||
arr.Dispose();
|
||||
break;
|
||||
case AllocationType.NativeList:
|
||||
// For some reason on 2020.3, calling list.AsArray() and passing that to the next function
|
||||
// causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later
|
||||
// versions of Unity.
|
||||
var list = new NativeList<ulong>(recipient.Length, Allocator.TempJob);
|
||||
foreach (var id in recipient)
|
||||
m_EnableVerboseDebug = true;
|
||||
VerboseDebug($"Processing: {defaultSendTo}");
|
||||
m_EnableVerboseDebug = false;
|
||||
foreach (var recipient in RecipientGroups)
|
||||
{
|
||||
for (ulong objectOwner = 0u; objectOwner <= 2u; ++objectOwner)
|
||||
{
|
||||
list.Add(id);
|
||||
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);
|
||||
BaseRpcTarget target = null;
|
||||
switch (allocationType)
|
||||
{
|
||||
case AllocationType.Array:
|
||||
target = senderObject.RpcTarget.Not(recipient, RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.List:
|
||||
target = senderObject.RpcTarget.Not(recipient.ToList(), RpcTargetUse.Temp);
|
||||
break;
|
||||
case AllocationType.NativeArray:
|
||||
var arr = new NativeArray<ulong>(recipient, Allocator.Temp);
|
||||
target = senderObject.RpcTarget.Not(arr, RpcTargetUse.Temp);
|
||||
arr.Dispose();
|
||||
break;
|
||||
case AllocationType.NativeList:
|
||||
// For some reason on 2020.3, calling list.AsArray() and passing that to the next function
|
||||
// causes Allocator.Temp allocations to become invalid somehow. This is not an issue on later
|
||||
// versions of Unity.
|
||||
var list = new NativeList<ulong>(recipient.Length, Allocator.TempJob);
|
||||
foreach (var id in recipient)
|
||||
{
|
||||
list.Add(id);
|
||||
}
|
||||
target = senderObject.RpcTarget.Not(list, RpcTargetUse.Temp);
|
||||
list.Dispose();
|
||||
break;
|
||||
}
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient.Contains(c)).ToArray());
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
target = senderObject.RpcTarget.Not(list, RpcTargetUse.Temp);
|
||||
list.Dispose();
|
||||
break;
|
||||
}
|
||||
}
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)target });
|
||||
|
||||
VerifyRemoteReceived(objectOwner, sender, sendMethodName, s_ClientIds.Where(c => !recipient.Contains(c)).ToArray(), false);
|
||||
VerifyNotReceived(objectOwner, s_ClientIds.Where(c => recipient.Contains(c)).ToArray());
|
||||
|
||||
// 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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1391,223 +1481,180 @@ 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
|
||||
{
|
||||
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
|
||||
public SendTo SendTo;
|
||||
public ulong ObjectOwner;
|
||||
public ulong Sender;
|
||||
|
||||
public TestData(SendTo sendTo, ulong objectOwner, ulong sender)
|
||||
{
|
||||
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
|
||||
// Just consider this case a success...
|
||||
return;
|
||||
SendTo = sendTo;
|
||||
ObjectOwner = objectOwner;
|
||||
Sender = sender;
|
||||
}
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { new RpcParams() });
|
||||
|
||||
VerifyNotReceived(objectOwner, new[] { sender });
|
||||
// Should be received on the next frame
|
||||
SimulateOneFrame();
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
|
||||
var verifyMethod = GetType().GetMethod(verifyMethodName);
|
||||
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
|
||||
}
|
||||
|
||||
[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
|
||||
)
|
||||
private static TestData[] s_LocalDeliveryTestCases =
|
||||
{
|
||||
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
|
||||
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)
|
||||
{
|
||||
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
|
||||
// Just consider this case a success...
|
||||
return;
|
||||
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...
|
||||
yield break;
|
||||
}
|
||||
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { new RpcParams() });
|
||||
|
||||
VerifyNotReceived(objectOwner, new[] { sender });
|
||||
// Should be received on the next frame
|
||||
SimulateOneFrame();
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
|
||||
var verifyMethod = GetType().GetMethod(verifyMethodName);
|
||||
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
|
||||
OnInlineTearDown();
|
||||
}
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.Defer });
|
||||
|
||||
VerifyNotReceived(objectOwner, new[] { sender });
|
||||
// Should be received on the next frame
|
||||
SimulateOneFrame();
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
|
||||
var verifyMethod = GetType().GetMethod(verifyMethodName);
|
||||
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
|
||||
}
|
||||
|
||||
[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 TestDeferLocalOverrideToTrue()
|
||||
{
|
||||
if (defaultSendTo == SendTo.ClientsAndHost && sender == 0u && !m_ServerNetworkManager.IsHost)
|
||||
foreach (var testCase in s_LocalDeliveryTestCases)
|
||||
{
|
||||
// Not calling Assert.Ignore() because Unity will mark the whole block of tests as ignored
|
||||
// Just consider this case a success...
|
||||
return;
|
||||
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...
|
||||
yield break;
|
||||
}
|
||||
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}WithRpcParamsRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.Defer });
|
||||
|
||||
VerifyNotReceived(objectOwner, new[] { sender });
|
||||
// Should be received on the next frame
|
||||
SimulateOneFrame();
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
|
||||
var verifyMethod = GetType().GetMethod(verifyMethodName);
|
||||
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
|
||||
OnInlineTearDown();
|
||||
}
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.SendImmediate });
|
||||
}
|
||||
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
[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;
|
||||
|
||||
var verifyMethod = GetType().GetMethod(verifyMethodName);
|
||||
verifyMethod.Invoke(this, new object[] { objectOwner, sender, sendMethodName });
|
||||
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...
|
||||
yield break;
|
||||
}
|
||||
|
||||
var sendMethodName = $"DefaultTo{defaultSendTo}DeferLocalRpc";
|
||||
var verifyMethodName = $"VerifySentTo{defaultSendTo}";
|
||||
var senderObject = GetPlayerObject(objectOwner, sender);
|
||||
var sendMethod = senderObject.GetType().GetMethod(sendMethodName);
|
||||
sendMethod.Invoke(senderObject, new object[] { (RpcParams)LocalDeferMode.SendImmediate });
|
||||
|
||||
VerifyLocalReceived(objectOwner, sender, sendMethodName, false);
|
||||
|
||||
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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
10
package.json
10
package.json
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user