com.unity.netcode.gameobjects@1.0.1

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).

## [1.0.1] - 2022-08-23

### Changed

- Changed version to 1.0.1. (#2131)
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects

### Fixed

- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
This commit is contained in:
Unity Technologies
2022-08-23 00:00:00 +00:00
parent 18ffd5fdc8
commit e15bd056c5
58 changed files with 2192 additions and 1676 deletions

View File

@@ -1,3 +1,4 @@
# Changelog # Changelog
All notable changes to this project will be documented in this file. All notable changes to this project will be documented in this file.
@@ -6,6 +7,34 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com).
## [1.0.1] - 2022-08-23
### Changed
- Changed version to 1.0.1. (#2131)
- Updated dependency on `com.unity.transport` to 1.2.0. (#2129)
- When using `UnityTransport`, _reliable_ payloads are now allowed to exceed the configured 'Max Payload Size'. Unreliable payloads remain bounded by this setting. (#2081)
- Preformance improvements for cases with large number of NetworkObjects, by not iterating over all unchanged NetworkObjects
### Fixed
- Fixed an issue where reading/writing more than 8 bits at a time with BitReader/BitWriter would write/read from the wrong place, returning and incorrect result. (#2130)
- Fixed issue with the internal `NetworkTransformState.m_Bitset` flag not getting cleared upon the next tick advancement. (#2110)
- Fixed interpolation issue with `NetworkTransform.Teleport`. (#2110)
- Fixed issue where the authoritative side was interpolating its transform. (#2110)
- Fixed Owner-written NetworkVariable infinitely write themselves (#2109)
- Fixed NetworkList issue that showed when inserting at the very end of a NetworkList (#2099)
- Fixed issue where a client owner of a `NetworkVariable` with both owner read and write permissions would not update the server side when changed. (#2097)
- Fixed issue when attempting to spawn a parent `GameObject`, with `NetworkObject` component attached, that has one or more child `GameObject`s, that are inactive in the hierarchy, with `NetworkBehaviour` components it will no longer attempt to spawn the associated `NetworkBehaviour`(s) or invoke ownership changed notifications but will log a warning message. (#2096)
- Fixed an issue where destroying a NetworkBehaviour would not deregister it from the parent NetworkObject, leading to exceptions when the parent was later destroyed. (#2091)
- Fixed issue where `NetworkObject.NetworkHide` was despawning and destroying, as opposed to only despawning, in-scene placed `NetworkObject`s. (#2086)
- Fixed `NetworkAnimator` synchronizing transitions twice due to it detecting the change in animation state once a transition is started by a trigger. (#2084)
- Fixed issue where `NetworkAnimator` would not synchronize a looping animation for late joining clients if it was at the very end of its loop. (#2076)
- Fixed issue where `NetworkAnimator` was not removing its subscription from `OnClientConnectedCallback` when despawned during the shutdown sequence. (#2074)
- Fixed IsServer and IsClient being set to false before object despawn during the shutdown sequence. (#2074)
- Fixed NetworkList Value event on the server. PreviousValue is now set correctly when a new value is set through property setter. (#2067)
- Fixed NetworkLists not populating on client. NetworkList now uses the most recent list as opposed to the list at the end of previous frame, when sending full updates to dynamically spawned NetworkObject. The difference in behaviour is required as scene management spawns those objects at a different time in the frame, relative to updates. (#2062)
## [1.0.0] - 2022-06-27 ## [1.0.0] - 2022-06-27
### Changed ### Changed

View File

@@ -163,6 +163,7 @@ namespace Unity.Netcode.Components
internal struct AnimationMessage : INetworkSerializable internal struct AnimationMessage : INetworkSerializable
{ {
// state hash per layer. if non-zero, then Play() this animation, skipping transitions // state hash per layer. if non-zero, then Play() this animation, skipping transitions
internal bool Transition;
internal int StateHash; internal int StateHash;
internal float NormalizedTime; internal float NormalizedTime;
internal int Layer; internal int Layer;
@@ -424,14 +425,10 @@ namespace Unity.Netcode.Components
stateHash = nextState.fullPathHash; stateHash = nextState.fullPathHash;
} }
else
if (st.normalizedTime >= adjustedNormalizedMaxTime)
{
continue;
}
var animMsg = new AnimationMessage var animMsg = new AnimationMessage
{ {
Transition = m_Animator.IsInTransition(layer),
StateHash = stateHash, StateHash = stateHash,
NormalizedTime = normalizedTime, NormalizedTime = normalizedTime,
Layer = layer, Layer = layer,
@@ -447,6 +444,9 @@ namespace Unity.Netcode.Components
m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId); m_NetworkAnimatorStateChangeHandler.SynchronizeClient(playerId);
} }
/// <summary>
/// Checks for changes in both Animator parameters and state.
/// </summary>
internal void CheckForAnimatorChanges() internal void CheckForAnimatorChanges()
{ {
if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer) if (!IsOwner && !IsServerAuthoritative() || IsServerAuthoritative() && !IsServer)
@@ -487,6 +487,7 @@ namespace Unity.Netcode.Components
var animMsg = new AnimationMessage var animMsg = new AnimationMessage
{ {
Transition = m_Animator.IsInTransition(layer),
StateHash = stateHash, StateHash = stateHash,
NormalizedTime = normalizedTime, NormalizedTime = normalizedTime,
Layer = layer, Layer = layer,
@@ -749,7 +750,13 @@ namespace Unity.Netcode.Components
/// </summary> /// </summary>
private unsafe void UpdateAnimationState(AnimationMessage animationState) private unsafe void UpdateAnimationState(AnimationMessage animationState)
{ {
if (animationState.StateHash != 0) if (animationState.StateHash == 0)
{
return;
}
var currentState = m_Animator.GetCurrentAnimatorStateInfo(animationState.Layer);
if (currentState.fullPathHash != animationState.StateHash || m_Animator.IsInTransition(animationState.Layer) != animationState.Transition)
{ {
m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime); m_Animator.Play(animationState.StateHash, animationState.Layer, animationState.NormalizedTime);
} }
@@ -835,6 +842,10 @@ namespace Unity.Netcode.Components
[ClientRpc] [ClientRpc]
private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default) private unsafe void SendAnimStateClientRpc(AnimationMessage animSnapshot, ClientRpcParams clientRpcParams = default)
{ {
if (IsServer)
{
return;
}
var isServerAuthoritative = IsServerAuthoritative(); var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative) if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{ {
@@ -883,13 +894,9 @@ namespace Unity.Netcode.Components
/// <param name="clientRpcParams">unused</param> /// <param name="clientRpcParams">unused</param>
[ClientRpc] [ClientRpc]
internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default) internal void SendAnimTriggerClientRpc(AnimationTriggerMessage animationTriggerMessage, ClientRpcParams clientRpcParams = default)
{
var isServerAuthoritative = IsServerAuthoritative();
if (!isServerAuthoritative && !IsOwner || isServerAuthoritative)
{ {
m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet); m_Animator.SetBool(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
} }
}
/// <summary> /// <summary>
/// Sets the trigger for the associated animation /// Sets the trigger for the associated animation
@@ -905,8 +912,13 @@ namespace Unity.Netcode.Components
/// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param> /// <param name="setTrigger">sets (true) or resets (false) the trigger. The default is to set it (true).</param>
public void SetTrigger(int hash, bool setTrigger = true) public void SetTrigger(int hash, bool setTrigger = true)
{ {
var isServerAuthoritative = IsServerAuthoritative(); // MTT-3564:
if (IsOwner && !isServerAuthoritative || IsServer && isServerAuthoritative) // After fixing the issue with trigger controlled Transitions being synchronized twice,
// it exposed additional issues with this logic. Now, either the owner or the server can
// update triggers. Since server-side RPCs are immediately invoked, for a host a trigger
// will happen when SendAnimTriggerClientRpc is called. For a client owner, we call the
// SendAnimTriggerServerRpc and then trigger locally when running in owner authority mode.
if (IsOwner || IsServer)
{ {
var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger }; var animTriggerMessage = new AnimationTriggerMessage() { Hash = hash, IsTriggerSet = setTrigger };
if (IsServer) if (IsServer)
@@ -916,9 +928,11 @@ namespace Unity.Netcode.Components
else else
{ {
SendAnimTriggerServerRpc(animTriggerMessage); SendAnimTriggerServerRpc(animTriggerMessage);
if (!IsServerAuthoritative())
{
m_Animator.SetTrigger(hash);
}
} }
// trigger the animation locally on the server...
m_Animator.SetBool(hash, setTrigger);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,8 +18,7 @@ namespace Unity.Netcode.Editor.CodeGen
public override ILPPInterface GetInstance() => this; public override ILPPInterface GetInstance() => this;
public override bool WillProcess(ICompiledAssembly compiledAssembly) => public override bool WillProcess(ICompiledAssembly compiledAssembly) =>
compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName || compiledAssembly.Name == CodeGenHelpers.RuntimeAssemblyName;
compiledAssembly.References.Any(filePath => Path.GetFileNameWithoutExtension(filePath) == CodeGenHelpers.RuntimeAssemblyName);
private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>(); private readonly List<DiagnosticMessage> m_Diagnostics = new List<DiagnosticMessage>();

View File

@@ -331,7 +331,8 @@ namespace Unity.Netcode
// in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?) // in Update and/or in FixedUpdate could still be checking NetworkBehaviour.NetworkObject directly (i.e. does it exist?)
// or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages // or NetworkBehaviour.IsSpawned (i.e. to early exit if not spawned) which, in turn, could generate several Warning messages
// per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages. // per spawned NetworkObject. Checking for ShutdownInProgress prevents these unnecessary LogWarning messages.
if (m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress)) // We must check IsSpawned, otherwise a warning will be logged under certain valid conditions (see OnDestroy)
if (IsSpawned && m_NetworkObject == null && (NetworkManager.Singleton == null || !NetworkManager.Singleton.ShutdownInProgress))
{ {
if (NetworkLog.CurrentLogLevel <= LogLevel.Normal) if (NetworkLog.CurrentLogLevel <= LogLevel.Normal)
{ {
@@ -582,9 +583,11 @@ namespace Unity.Netcode
{ {
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty(); NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
} }
MarkVariablesDirty(false);
} }
internal void VariableUpdate(ulong targetClientId) internal void PreVariableUpdate()
{ {
if (!m_VarInit) if (!m_VarInit)
{ {
@@ -592,6 +595,10 @@ namespace Unity.Netcode
} }
PreNetworkVariableWrite(); PreNetworkVariableWrite();
}
internal void VariableUpdate(ulong targetClientId)
{
NetworkVariableUpdate(targetClientId, NetworkBehaviourId); NetworkVariableUpdate(targetClientId, NetworkBehaviourId);
} }
@@ -663,11 +670,11 @@ namespace Unity.Netcode
return false; return false;
} }
internal void MarkVariablesDirty() internal void MarkVariablesDirty(bool dirty)
{ {
for (int j = 0; j < NetworkVariableFields.Count; j++) for (int j = 0; j < NetworkVariableFields.Count; j++)
{ {
NetworkVariableFields[j].SetDirty(true); NetworkVariableFields[j].SetDirty(dirty);
} }
} }
@@ -759,6 +766,14 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public virtual void OnDestroy() public virtual void OnDestroy()
{ {
if (NetworkObject != null && NetworkObject.IsSpawned && IsSpawned)
{
// If the associated NetworkObject is still spawned then this
// NetworkBehaviour will be removed from the NetworkObject's
// ChildNetworkBehaviours list.
NetworkObject.OnNetworkBehaviourDestroyed(this);
}
// this seems odd to do here, but in fact especially in tests we can find ourselves // this seems odd to do here, but in fact especially in tests we can find ourselves
// here without having called InitializedVariables, which causes problems if any // here without having called InitializedVariables, which causes problems if any
// of those variables use native containers (e.g. NetworkList) as they won't be // of those variables use native containers (e.g. NetworkList) as they won't be
@@ -770,6 +785,7 @@ namespace Unity.Netcode
InitializeVariables(); InitializeVariables();
} }
for (int i = 0; i < NetworkVariableFields.Count; i++) for (int i = 0; i < NetworkVariableFields.Count; i++)
{ {
NetworkVariableFields[i].Dispose(); NetworkVariableFields[i].Dispose();

View File

@@ -8,12 +8,17 @@ namespace Unity.Netcode
/// </summary> /// </summary>
public class NetworkBehaviourUpdater public class NetworkBehaviourUpdater
{ {
private HashSet<NetworkObject> m_Touched = new HashSet<NetworkObject>(); private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}"); private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
#endif #endif
internal void AddForUpdate(NetworkObject networkObject)
{
m_DirtyNetworkObjects.Add(networkObject);
}
internal void NetworkBehaviourUpdate(NetworkManager networkManager) internal void NetworkBehaviourUpdate(NetworkManager networkManager)
{ {
#if DEVELOPMENT_BUILD || UNITY_EDITOR #if DEVELOPMENT_BUILD || UNITY_EDITOR
@@ -23,57 +28,59 @@ namespace Unity.Netcode
{ {
if (networkManager.IsServer) if (networkManager.IsServer)
{ {
m_Touched.Clear(); foreach (var dirtyObj in m_DirtyNetworkObjects)
{
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{
dirtyObj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++) for (int i = 0; i < networkManager.ConnectedClientsList.Count; i++)
{ {
var client = networkManager.ConnectedClientsList[i]; var client = networkManager.ConnectedClientsList[i];
var spawnedObjs = networkManager.SpawnManager.SpawnedObjectsList; if (networkManager.IsHost && client.ClientId == networkManager.LocalClientId)
m_Touched.UnionWith(spawnedObjs);
foreach (var sobj in spawnedObjs)
{ {
if (sobj.IsNetworkVisibleTo(client.ClientId)) continue;
{
// Sync just the variables for just the objects this client sees
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
}
}
}
} }
// Now, reset all the no-longer-dirty variables if (dirtyObj.IsNetworkVisibleTo(client.ClientId))
foreach (var sobj in m_Touched)
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) // Sync just the variables for just the objects this client sees
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); dirtyObj.ChildNetworkBehaviours[k].VariableUpdate(client.ClientId);
}
}
} }
} }
} }
else else
{ {
// when client updates the server, it tells it about all its objects // when client updates the server, it tells it about all its objects
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) foreach (var sobj in m_DirtyNetworkObjects)
{ {
if (sobj.IsOwner) if (sobj.IsOwner)
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{
sobj.ChildNetworkBehaviours[k].PreVariableUpdate();
}
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId); sobj.ChildNetworkBehaviours[k].VariableUpdate(NetworkManager.ServerClientId);
} }
} }
} }
}
// Now, reset all the no-longer-dirty variables // Now, reset all the no-longer-dirty variables
foreach (var sobj in networkManager.SpawnManager.SpawnedObjectsList) foreach (var dirtyobj in m_DirtyNetworkObjects)
{ {
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++) for (int k = 0; k < dirtyobj.ChildNetworkBehaviours.Count; k++)
{ {
sobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite(); dirtyobj.ChildNetworkBehaviours[k].PostNetworkVariableWrite();
}
} }
} }
m_DirtyNetworkObjects.Clear();
} }
finally finally
{ {

View File

@@ -54,7 +54,12 @@ namespace Unity.Netcode
return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\""; return $"{nameof(NetworkPrefab)} \"{networkPrefab.Prefab.gameObject.name}\"";
} }
internal NetworkBehaviourUpdater BehaviourUpdater { get; private set; } internal NetworkBehaviourUpdater BehaviourUpdater { get; set; }
internal void MarkNetworkObjectDirty(NetworkObject networkObject)
{
BehaviourUpdater.AddForUpdate(networkObject);
}
internal MessagingSystem MessagingSystem { get; private set; } internal MessagingSystem MessagingSystem { get; private set; }
@@ -1384,6 +1389,19 @@ namespace Unity.Netcode
} }
IsConnectedClient = false; IsConnectedClient = false;
// We need to clean up NetworkObjects before we reset the IsServer
// and IsClient properties. This provides consistency of these two
// property values for NetworkObjects that are still spawned when
// the shutdown cycle begins.
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
IsServer = false; IsServer = false;
IsClient = false; IsClient = false;
@@ -1406,14 +1424,6 @@ namespace Unity.Netcode
NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll; NetworkConfig.NetworkTransport.OnTransportEvent -= HandleRawTransportPoll;
} }
if (SpawnManager != null)
{
SpawnManager.DespawnAndDestroyNetworkObjects();
SpawnManager.ServerResetShudownStateForSceneObjects();
SpawnManager = null;
}
if (DeferredMessageManager != null) if (DeferredMessageManager != null)
{ {
DeferredMessageManager.CleanupAllTriggers(); DeferredMessageManager.CleanupAllTriggers();
@@ -2060,6 +2070,20 @@ namespace Unity.Netcode
SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId); SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
for (int index = 0; index < MessagingSystem.MessageHandlers.Length; index++)
{
if (MessagingSystem.MessageTypes[index] != null)
{
var orderingMessage = new OrderingMessage
{
Order = index,
Hash = XXHash.Hash32(MessagingSystem.MessageTypes[index].FullName)
};
SendMessage(ref orderingMessage, NetworkDelivery.ReliableFragmentedSequenced, ownerClientId);
}
}
// If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization // If scene management is enabled, then let NetworkSceneManager handle the initial scene and NetworkObject synchronization
if (!NetworkConfig.EnableSceneManagement) if (!NetworkConfig.EnableSceneManagement)
{ {

View File

@@ -235,9 +235,19 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Shows a previously hidden <see cref="NetworkObject"/> to a client /// Makes the previously hidden <see cref="NetworkObject"/> "netcode visible" to the targeted client.
/// </summary> /// </summary>
/// <param name="clientId">The client to show the <see cref="NetworkObject"/> to</param> /// <remarks>
/// Usage: Use to start sending updates for a previously hidden <see cref="NetworkObject"/> to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client side.<br />
/// In-Scene Placed: The instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkShow(ulong clientId) public void NetworkShow(ulong clientId)
{ {
if (!IsSpawned) if (!IsSpawned)
@@ -260,11 +270,22 @@ namespace Unity.Netcode
NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this); NetworkManager.SpawnManager.SendSpawnCallForObject(clientId, this);
} }
/// <summary> /// <summary>
/// Shows a list of previously hidden <see cref="NetworkObject"/>s to a client /// Makes a list of previously hidden <see cref="NetworkObject"/>s "netcode visible" for the client specified.
/// </summary> /// </summary>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s to show</param> /// <remarks>
/// <param name="clientId">The client to show the objects to</param> /// Usage: Use to start sending updates for previously hidden <see cref="NetworkObject"/>s to the targeted client.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be instantiated and spawned on the targeted client's side.<br />
/// In-Scene Placed: Already instantiated but despawned <see cref="NetworkObject"/>s will be spawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkShow(ulong)"/><br />
/// <see cref="NetworkHide(ulong)"/> or <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The objects to become "netcode visible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId) public static void NetworkShow(List<NetworkObject> networkObjects, ulong clientId)
{ {
if (networkObjects == null || networkObjects.Count == 0) if (networkObjects == null || networkObjects.Count == 0)
@@ -305,9 +326,19 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Hides a object from a specific client /// Hides the <see cref="NetworkObject"/> from the targeted client.
/// </summary> /// </summary>
/// <param name="clientId">The client to hide the object for</param> /// <remarks>
/// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for a currently visible <see cref="NetworkObject"/>.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(List{NetworkObject}, ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="clientId">The targeted client</param>
public void NetworkHide(ulong clientId) public void NetworkHide(ulong clientId)
{ {
if (!IsSpawned) if (!IsSpawned)
@@ -335,7 +366,7 @@ namespace Unity.Netcode
var message = new DestroyObjectMessage var message = new DestroyObjectMessage
{ {
NetworkObjectId = NetworkObjectId, NetworkObjectId = NetworkObjectId,
DestroyGameObject = true DestroyGameObject = !IsSceneObject.Value
}; };
// Send destroy call // Send destroy call
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId); var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableSequenced, clientId);
@@ -343,10 +374,20 @@ namespace Unity.Netcode
} }
/// <summary> /// <summary>
/// Hides a list of objects from a client /// Hides a list of <see cref="NetworkObject"/>s from the targeted client.
/// </summary> /// </summary>
/// <param name="networkObjects">The objects to hide</param> /// <remarks>
/// <param name="clientId">The client to hide the objects from</param> /// Usage: Use to stop sending updates to the targeted client, "netcode invisible", for the currently visible <see cref="NetworkObject"/>s.<br />
/// <br />
/// Dynamically Spawned: <see cref="NetworkObject"/>s will be despawned and destroyed on the targeted client's side.<br />
/// In-Scene Placed: <see cref="NetworkObject"/>s will only be despawned on the targeted client's side.<br />
/// <br />
/// See Also:<br />
/// <see cref="NetworkHide(ulong)"/><br />
/// <see cref="NetworkShow(ulong)"/> or <see cref="NetworkShow(List{NetworkObject}, ulong)"/><br />
/// </remarks>
/// <param name="networkObjects">The <see cref="NetworkObject"/>s that will become "netcode invisible" to the targeted client</param>
/// <param name="clientId">The targeted client</param>
public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId) public static void NetworkHide(List<NetworkObject> networkObjects, ulong clientId)
{ {
if (networkObjects == null || networkObjects.Count == 0) if (networkObjects == null || networkObjects.Count == 0)
@@ -455,8 +496,8 @@ namespace Unity.Netcode
/// <summary> /// <summary>
/// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client /// Spawns a <see cref="NetworkObject"/> across the network and makes it the player object for the given client
/// </summary> /// </summary>
/// <param name="clientId">The clientId whos player object this is</param> /// <param name="clientId">The clientId who's player object this is</param>
/// <param name="destroyWithScene">Should the object be destroyd when the scene is changed</param> /// <param name="destroyWithScene">Should the object be destroyed when the scene is changed</param>
public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false) public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
{ {
SpawnInternal(destroyWithScene, clientId, true); SpawnInternal(destroyWithScene, clientId, true);
@@ -511,9 +552,16 @@ namespace Unity.Netcode
} }
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{ {
ChildNetworkBehaviours[i].InternalOnGainedOwnership(); ChildNetworkBehaviours[i].InternalOnGainedOwnership();
} }
else
{
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!");
}
}
} }
internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject)
@@ -763,9 +811,16 @@ namespace Unity.Netcode
NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId); NetworkManager.SpawnManager.UpdateOwnershipTable(this, OwnerClientId);
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy)
{ {
ChildNetworkBehaviours[i].InternalOnNetworkSpawn(); ChildNetworkBehaviours[i].InternalOnNetworkSpawn();
} }
else
{
Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during spawn!");
}
}
} }
internal void InvokeBehaviourNetworkDespawn() internal void InvokeBehaviourNetworkDespawn()
@@ -813,12 +868,12 @@ namespace Unity.Netcode
} }
} }
internal void MarkVariablesDirty() internal void MarkVariablesDirty(bool dirty)
{ {
for (int i = 0; i < ChildNetworkBehaviours.Count; i++) for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{ {
var behavior = ChildNetworkBehaviours[i]; var behavior = ChildNetworkBehaviours[i];
behavior.MarkVariablesDirty(); behavior.MarkVariablesDirty(dirty);
} }
} }
@@ -1164,5 +1219,21 @@ namespace Unity.Netcode
return GlobalObjectIdHash; return GlobalObjectIdHash;
} }
/// <summary>
/// Removes a NetworkBehaviour from the ChildNetworkBehaviours list when destroyed
/// while the NetworkObject is still spawned.
/// </summary>
internal void OnNetworkBehaviourDestroyed(NetworkBehaviour networkBehaviour)
{
if (networkBehaviour.IsSpawned && IsSpawned)
{
if (NetworkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"{nameof(NetworkBehaviour)}-{networkBehaviour.name} is being destroyed while {nameof(NetworkObject)}-{name} is still spawned! (could break state synchronization)");
}
ChildNetworkBehaviours.Remove(networkBehaviour);
}
}
} }
} }

View File

@@ -0,0 +1,50 @@
using System;
namespace Unity.Netcode
{
/// <summary>
/// Upon connecting, the host sends a series of OrderingMessage to the client so that it can make sure both sides
/// have the same message types in the same positions in
/// - MessagingSystem.m_MessageHandlers
/// - MessagingSystem.m_ReverseTypeMap
/// even if one side has extra messages (compilation, version, patch, or platform differences, etc...)
///
/// The ConnectionRequestedMessage, ConnectionApprovedMessage and OrderingMessage are prioritized at the beginning
/// of the mapping, to guarantee they can be exchanged before the two sides share their ordering
/// The sorting used in also stable so that even if MessageType names share hashes, it will work most of the time
/// </summary>
internal struct OrderingMessage : INetworkMessage
{
public int Order;
public uint Hash;
public void Serialize(FastBufferWriter writer)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough space in the buffer to write {nameof(OrderingMessage)}");
}
writer.WriteValue(Order);
writer.WriteValue(Hash);
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
if (!reader.TryBeginRead(FastBufferWriter.GetWriteSize(Order) + FastBufferWriter.GetWriteSize(Hash)))
{
throw new OverflowException($"Not enough data in the buffer to read {nameof(OrderingMessage)}");
}
reader.ReadValue(out Order);
reader.ReadValue(out Hash);
return true;
}
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).MessagingSystem.ReorderMessage(Order, Hash);
}
}
}

View File

@@ -1,5 +1,5 @@
fileFormatVersion: 2 fileFormatVersion: 2
guid: c2e5a740c1abd4315801e3f26ecf8adb guid: 3ada9e8fd5bf94b1f9a6a21531c8a3ee
MonoImporter: MonoImporter:
externalObjects: {} externalObjects: {}
serializedVersion: 2 serializedVersion: 2

View File

@@ -8,6 +8,11 @@ using UnityEngine;
namespace Unity.Netcode namespace Unity.Netcode
{ {
internal class HandlerNotRegisteredException : SystemException
{
public HandlerNotRegisteredException() { }
public HandlerNotRegisteredException(string issue) : base(issue) { }
}
internal class InvalidMessageStructureException : SystemException internal class InvalidMessageStructureException : SystemException
{ {
@@ -44,8 +49,9 @@ namespace Unity.Netcode
private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent); private NativeList<ReceiveQueueItem> m_IncomingMessageQueue = new NativeList<ReceiveQueueItem>(16, Allocator.Persistent);
private MessageHandler[] m_MessageHandlers = new MessageHandler[255]; // These array will grow as we need more message handlers. 4 is just a starting size.
private Type[] m_ReverseTypeMap = new Type[255]; private MessageHandler[] m_MessageHandlers = new MessageHandler[4];
private Type[] m_ReverseTypeMap = new Type[4];
private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>(); private Dictionary<Type, uint> m_MessageTypes = new Dictionary<Type, uint>();
private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>(); private Dictionary<ulong, NativeList<SendQueueItem>> m_SendQueues = new Dictionary<ulong, NativeList<SendQueueItem>>();
@@ -59,6 +65,7 @@ namespace Unity.Netcode
internal Type[] MessageTypes => m_ReverseTypeMap; internal Type[] MessageTypes => m_ReverseTypeMap;
internal MessageHandler[] MessageHandlers => m_MessageHandlers; internal MessageHandler[] MessageHandlers => m_MessageHandlers;
internal uint MessageHandlerCount => m_HighMessageType; internal uint MessageHandlerCount => m_HighMessageType;
internal uint GetMessageType(Type t) internal uint GetMessageType(Type t)
@@ -75,6 +82,35 @@ namespace Unity.Netcode
public MessageHandler Handler; public MessageHandler Handler;
} }
internal List<MessageWithHandler> PrioritizeMessageOrder(List<MessageWithHandler> allowedTypes)
{
var prioritizedTypes = new List<MessageWithHandler>();
// first pass puts the priority message in the first indices
// Those are the messages that must be delivered in order to allow re-ordering the others later
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName == "Unity.Netcode.ConnectionRequestMessage" ||
t.MessageType.FullName == "Unity.Netcode.ConnectionApprovedMessage" ||
t.MessageType.FullName == "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}
foreach (var t in allowedTypes)
{
if (t.MessageType.FullName != "Unity.Netcode.ConnectionRequestMessage" &&
t.MessageType.FullName != "Unity.Netcode.ConnectionApprovedMessage" &&
t.MessageType.FullName != "Unity.Netcode.OrderingMessage")
{
prioritizedTypes.Add(t);
}
}
return prioritizedTypes;
}
public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null) public MessagingSystem(IMessageSender messageSender, object owner, IMessageProvider provider = null)
{ {
try try
@@ -89,6 +125,7 @@ namespace Unity.Netcode
var allowedTypes = provider.GetMessages(); var allowedTypes = provider.GetMessages();
allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName)); allowedTypes.Sort((a, b) => string.CompareOrdinal(a.MessageType.FullName, b.MessageType.FullName));
allowedTypes = PrioritizeMessageOrder(allowedTypes);
foreach (var type in allowedTypes) foreach (var type in allowedTypes)
{ {
RegisterMessageType(type); RegisterMessageType(type);
@@ -143,6 +180,13 @@ namespace Unity.Netcode
private void RegisterMessageType(MessageWithHandler messageWithHandler) private void RegisterMessageType(MessageWithHandler messageWithHandler)
{ {
// if we are out of space, perform amortized linear growth
if (m_HighMessageType == m_MessageHandlers.Length)
{
Array.Resize(ref m_MessageHandlers, 2 * m_MessageHandlers.Length);
Array.Resize(ref m_ReverseTypeMap, 2 * m_ReverseTypeMap.Length);
}
m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler; m_MessageHandlers[m_HighMessageType] = messageWithHandler.Handler;
m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType; m_ReverseTypeMap[m_HighMessageType] = messageWithHandler.MessageType;
m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++; m_MessageTypes[messageWithHandler.MessageType] = m_HighMessageType++;
@@ -226,6 +270,70 @@ namespace Unity.Netcode
return true; return true;
} }
// Moves the handler for the type having hash `targetHash` to the `desiredOrder` position, in the handler list
// This allows the server to tell the client which id it is using for which message and make sure the right
// message is used when deserializing.
internal void ReorderMessage(int desiredOrder, uint targetHash)
{
if (desiredOrder < 0)
{
throw new ArgumentException("ReorderMessage desiredOrder must be positive");
}
if (desiredOrder < m_ReverseTypeMap.Length &&
XXHash.Hash32(m_ReverseTypeMap[desiredOrder].FullName) == targetHash)
{
// matching positions and hashes. All good.
return;
}
Debug.Log($"Unexpected hash for {desiredOrder}");
// Since the message at `desiredOrder` is not the expected one,
// insert an empty placeholder and move the messages down
var typesAsList = new List<Type>(m_ReverseTypeMap);
typesAsList.Insert(desiredOrder, null);
var handlersAsList = new List<MessageHandler>(m_MessageHandlers);
handlersAsList.Insert(desiredOrder, null);
// we added a dummy message, bump the end up
m_HighMessageType++;
// Here, we rely on the server telling us about all messages, in order.
// So, we know the handlers before desiredOrder are correct.
// We start at desiredOrder to not shift them when we insert.
int position = desiredOrder;
bool found = false;
while (position < typesAsList.Count)
{
if (typesAsList[position] != null &&
XXHash.Hash32(typesAsList[position].FullName) == targetHash)
{
found = true;
break;
}
position++;
}
if (found)
{
// Copy the handler and type to the right index
typesAsList[desiredOrder] = typesAsList[position];
handlersAsList[desiredOrder] = handlersAsList[position];
typesAsList.RemoveAt(position);
handlersAsList.RemoveAt(position);
// we removed a copy after moving a message, reduce the high message index
m_HighMessageType--;
}
m_ReverseTypeMap = typesAsList.ToArray();
m_MessageHandlers = handlersAsList.ToArray();
}
public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize) public void HandleMessage(in MessageHeader header, FastBufferReader reader, ulong senderId, float timestamp, int serializedHeaderSize)
{ {
if (header.MessageType >= m_HighMessageType) if (header.MessageType >= m_HighMessageType)
@@ -258,6 +366,16 @@ namespace Unity.Netcode
var handler = m_MessageHandlers[header.MessageType]; var handler = m_MessageHandlers[header.MessageType];
using (reader) using (reader)
{
// This will also log an exception is if the server knows about a message type the client doesn't know
// about. In this case the handler will be null. It is still an issue the user must deal with: If the
// two connecting builds know about different messages, the server should not send a message to a client
// that doesn't know about it
if (handler == null)
{
Debug.LogException(new HandlerNotRegisteredException(header.MessageType.ToString()));
}
else
{ {
// No user-land message handler exceptions should escape the receive loop. // No user-land message handler exceptions should escape the receive loop.
// If an exception is throw, the message is ignored. // If an exception is throw, the message is ignored.
@@ -273,6 +391,7 @@ namespace Unity.Netcode
Debug.LogException(e); Debug.LogException(e);
} }
} }
}
for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx)
{ {
m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>()); m_Hooks[hookIdx].OnAfterReceiveMessage(senderId, type, reader.Length + FastBufferWriter.GetWriteSize<MessageHeader>());

View File

@@ -63,6 +63,11 @@ namespace Unity.Netcode
return base.IsDirty() || m_DirtyEvents.Length > 0; return base.IsDirty() || m_DirtyEvents.Length > 0;
} }
internal void MarkNetworkObjectDirty()
{
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
/// <inheritdoc /> /// <inheritdoc />
public override void WriteDelta(FastBufferWriter writer) public override void WriteDelta(FastBufferWriter writer)
{ {
@@ -121,6 +126,13 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public override void WriteField(FastBufferWriter writer) public override void WriteField(FastBufferWriter writer)
{
// The listAtLastReset mechanism was put in place to deal with duplicate adds
// upon initial spawn. However, it causes issues with in-scene placed objects
// due to difference in spawn order. In order to address this, we pick the right
// list based on the type of object.
bool isSceneObject = m_NetworkBehaviour.NetworkObject.IsSceneObject != false;
if (isSceneObject)
{ {
writer.WriteValueSafe((ushort)m_ListAtLastReset.Length); writer.WriteValueSafe((ushort)m_ListAtLastReset.Length);
for (int i = 0; i < m_ListAtLastReset.Length; i++) for (int i = 0; i < m_ListAtLastReset.Length; i++)
@@ -128,6 +140,15 @@ namespace Unity.Netcode
NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i)); NetworkVariableSerialization<T>.Write(writer, ref m_ListAtLastReset.ElementAt(i));
} }
} }
else
{
writer.WriteValueSafe((ushort)m_List.Length);
for (int i = 0; i < m_List.Length; i++)
{
NetworkVariableSerialization<T>.Write(writer, ref m_List.ElementAt(i));
}
}
}
/// <inheritdoc /> /// <inheritdoc />
public override void ReadField(FastBufferReader reader) public override void ReadField(FastBufferReader reader)
@@ -173,6 +194,7 @@ namespace Unity.Netcode
Index = m_List.Length - 1, Index = m_List.Length - 1,
Value = m_List[m_List.Length - 1] Value = m_List[m_List.Length - 1]
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -180,8 +202,16 @@ namespace Unity.Netcode
{ {
reader.ReadValueSafe(out int index); reader.ReadValueSafe(out int index);
NetworkVariableSerialization<T>.Read(reader, out T value); NetworkVariableSerialization<T>.Read(reader, out T value);
if (index < m_List.Length)
{
m_List.InsertRangeWithBeginEnd(index, index + 1); m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = value; m_List[index] = value;
}
else
{
m_List.Add(value);
}
if (OnListChanged != null) if (OnListChanged != null)
{ {
@@ -201,6 +231,7 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = m_List[index] Value = m_List[index]
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -233,6 +264,7 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = value Value = value
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -260,6 +292,7 @@ namespace Unity.Netcode
Index = index, Index = index,
Value = value Value = value
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -295,6 +328,7 @@ namespace Unity.Netcode
Value = value, Value = value,
PreviousValue = previousValue PreviousValue = previousValue
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -317,6 +351,7 @@ namespace Unity.Netcode
{ {
Type = eventType Type = eventType
}); });
MarkNetworkObjectDirty();
} }
} }
break; break;
@@ -402,9 +437,16 @@ namespace Unity.Netcode
/// <inheritdoc /> /// <inheritdoc />
public void Insert(int index, T item) public void Insert(int index, T item)
{
if (index < m_List.Length)
{ {
m_List.InsertRangeWithBeginEnd(index, index + 1); m_List.InsertRangeWithBeginEnd(index, index + 1);
m_List[index] = item; m_List[index] = item;
}
else
{
m_List.Add(item);
}
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
{ {
@@ -436,13 +478,15 @@ namespace Unity.Netcode
get => m_List[index]; get => m_List[index];
set set
{ {
var previousValue = m_List[index];
m_List[index] = value; m_List[index] = value;
var listEvent = new NetworkListEvent<T>() var listEvent = new NetworkListEvent<T>()
{ {
Type = NetworkListEvent<T>.EventType.Value, Type = NetworkListEvent<T>.EventType.Value,
Index = index, Index = index,
Value = value Value = value,
PreviousValue = previousValue
}; };
HandleAddListEvent(listEvent); HandleAddListEvent(listEvent);
@@ -452,6 +496,7 @@ namespace Unity.Netcode
private void HandleAddListEvent(NetworkListEvent<T> listEvent) private void HandleAddListEvent(NetworkListEvent<T> listEvent)
{ {
m_DirtyEvents.Add(listEvent); m_DirtyEvents.Add(listEvent);
MarkNetworkObjectDirty();
OnListChanged?.Invoke(listEvent); OnListChanged?.Invoke(listEvent);
} }

View File

@@ -87,7 +87,7 @@ namespace Unity.Netcode
/// <param name="value">the new value of type `T` to be set/></param> /// <param name="value">the new value of type `T` to be set/></param>
private protected void Set(T value) private protected void Set(T value)
{ {
m_IsDirty = true; SetDirty(true);
T previousValue = m_InternalValue; T previousValue = m_InternalValue;
m_InternalValue = value; m_InternalValue = value;
OnValueChanged?.Invoke(previousValue, m_InternalValue); OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -119,7 +119,7 @@ namespace Unity.Netcode
if (keepDirtyDelta) if (keepDirtyDelta)
{ {
m_IsDirty = true; SetDirty(true);
} }
OnValueChanged?.Invoke(previousValue, m_InternalValue); OnValueChanged?.Invoke(previousValue, m_InternalValue);

View File

@@ -54,7 +54,7 @@ namespace Unity.Netcode
/// The <see cref="m_IsDirty"/> property is used to determine if the /// The <see cref="m_IsDirty"/> property is used to determine if the
/// value of the `NetworkVariable` has changed. /// value of the `NetworkVariable` has changed.
/// </summary> /// </summary>
private protected bool m_IsDirty; private bool m_IsDirty;
/// <summary> /// <summary>
/// Gets or sets the name of the network variable's instance /// Gets or sets the name of the network variable's instance
@@ -79,6 +79,10 @@ namespace Unity.Netcode
public virtual void SetDirty(bool isDirty) public virtual void SetDirty(bool isDirty)
{ {
m_IsDirty = isDirty; m_IsDirty = isDirty;
if (m_IsDirty && m_NetworkBehaviour != null)
{
m_NetworkBehaviour.NetworkManager.MarkNetworkObjectDirty(m_NetworkBehaviour.NetworkObject);
}
} }
/// <summary> /// <summary>
@@ -111,7 +115,7 @@ namespace Unity.Netcode
case NetworkVariableReadPermission.Everyone: case NetworkVariableReadPermission.Everyone:
return true; return true;
case NetworkVariableReadPermission.Owner: case NetworkVariableReadPermission.Owner:
return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId; return clientId == m_NetworkBehaviour.NetworkObject.OwnerClientId || NetworkManager.ServerClientId == clientId;
} }
} }

View File

@@ -21,6 +21,8 @@ namespace Unity.Netcode
private const int k_BitsPerByte = 8; private const int k_BitsPerByte = 8;
private int BytePosition => m_BitPosition >> 3;
/// <summary> /// <summary>
/// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary. /// Whether or not the current BitPosition is evenly divisible by 8. I.e. whether or not the BitPosition is at a byte boundary.
/// </summary> /// </summary>
@@ -98,11 +100,6 @@ namespace Unity.Netcode
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!"); throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read more than 64 bits from a 64-bit value!");
} }
if (bitCount < 0)
{
throw new ArgumentOutOfRangeException(nameof(bitCount), "Cannot read fewer than 0 bits!");
}
int checkPos = (int)(m_BitPosition + bitCount); int checkPos = (int)(m_BitPosition + bitCount);
if (checkPos > m_AllowedBitwiseReadMark) if (checkPos > m_AllowedBitwiseReadMark)
{ {
@@ -165,7 +162,7 @@ namespace Unity.Netcode
#endif #endif
int offset = m_BitPosition & 7; int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3; int pos = BytePosition;
bit = (m_BufferPointer[pos] & (1 << offset)) != 0; bit = (m_BufferPointer[pos] & (1 << offset)) != 0;
++m_BitPosition; ++m_BitPosition;
} }
@@ -175,7 +172,7 @@ namespace Unity.Netcode
{ {
var val = new T(); var val = new T();
byte* ptr = ((byte*)&val) + offsetBytes; byte* ptr = ((byte*)&val) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position; byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead); UnsafeUtility.MemCpy(ptr, bufferPointer, bytesToRead);
m_BitPosition += bytesToRead * k_BitsPerByte; m_BitPosition += bytesToRead * k_BitsPerByte;

View File

@@ -29,6 +29,8 @@ namespace Unity.Netcode
get => (m_BitPosition & 7) == 0; get => (m_BitPosition & 7) == 0;
} }
private int BytePosition => m_BitPosition >> 3;
internal unsafe BitWriter(FastBufferWriter writer) internal unsafe BitWriter(FastBufferWriter writer)
{ {
m_Writer = writer; m_Writer = writer;
@@ -181,7 +183,7 @@ namespace Unity.Netcode
#endif #endif
int offset = m_BitPosition & 7; int offset = m_BitPosition & 7;
int pos = m_BitPosition >> 3; int pos = BytePosition;
++m_BitPosition; ++m_BitPosition;
m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset))); m_BufferPointer[pos] = (byte)(bit ? (m_BufferPointer[pos] & ~(1 << offset)) | (1 << offset) : (m_BufferPointer[pos] & ~(1 << offset)));
} }
@@ -190,7 +192,7 @@ namespace Unity.Netcode
private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged private unsafe void WritePartialValue<T>(T value, int bytesToWrite, int offsetBytes = 0) where T : unmanaged
{ {
byte* ptr = ((byte*)&value) + offsetBytes; byte* ptr = ((byte*)&value) + offsetBytes;
byte* bufferPointer = m_BufferPointer + m_Position; byte* bufferPointer = m_BufferPointer + BytePosition;
UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite); UnsafeUtility.MemCpy(bufferPointer, ptr, bytesToWrite);
m_BitPosition += bytesToWrite * k_BitsPerByte; m_BitPosition += bytesToWrite * k_BitsPerByte;

View File

@@ -576,7 +576,7 @@ namespace Unity.Netcode
var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId); var size = NetworkManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, clientId);
NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size); NetworkManager.NetworkMetrics.TrackObjectSpawnSent(clientId, networkObject, size);
networkObject.MarkVariablesDirty(); networkObject.MarkVariablesDirty(true);
} }
internal ulong? GetSpawnParentId(NetworkObject networkObject) internal ulong? GetSpawnParentId(NetworkObject networkObject)

View File

@@ -158,11 +158,11 @@ namespace Unity.Netcode.Transports.UTP
set => m_MaxPacketQueueSize = value; set => m_MaxPacketQueueSize = value;
} }
[Tooltip("The maximum size of a payload that can be handled by the transport.")] [Tooltip("The maximum size of an unreliable payload that can be handled by the transport.")]
[SerializeField] [SerializeField]
private int m_MaxPayloadSize = InitialMaxPayloadSize; private int m_MaxPayloadSize = InitialMaxPayloadSize;
/// <summary>The maximum size of a payload that can be handled by the transport.</summary> /// <summary>The maximum size of an unreliable payload that can be handled by the transport.</summary>
public int MaxPayloadSize public int MaxPayloadSize
{ {
get => m_MaxPayloadSize; get => m_MaxPayloadSize;
@@ -1148,14 +1148,14 @@ namespace Unity.Netcode.Transports.UTP
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param> /// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery) public override void Send(ulong clientId, ArraySegment<byte> payload, NetworkDelivery networkDelivery)
{ {
if (payload.Count > m_MaxPayloadSize) var pipeline = SelectSendPipeline(networkDelivery);
if (pipeline != m_ReliableSequencedPipeline && payload.Count > m_MaxPayloadSize)
{ {
Debug.LogError($"Payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize})."); Debug.LogError($"Unreliable payload of size {payload.Count} larger than configured 'Max Payload Size' ({m_MaxPayloadSize}).");
return; return;
} }
var pipeline = SelectSendPipeline(networkDelivery);
var sendTarget = new SendTarget(clientId, pipeline); var sendTarget = new SendTarget(clientId, pipeline);
if (!m_SendQueue.TryGetValue(sendTarget, out var queue)) if (!m_SendQueue.TryGetValue(sendTarget, out var queue))
{ {
@@ -1285,10 +1285,10 @@ namespace Unity.Netcode.Transports.UTP
SendBatchedMessages(kvp.Key, kvp.Value); SendBatchedMessages(kvp.Key, kvp.Value);
} }
// The above flush only puts the message in UTP internal buffers, need the flush send // The above flush only puts the message in UTP internal buffers, need an update to
// job to execute to actually get things out on the wire. This will also ensure any // actually get the messages on the wire. (Normally a flush send would be sufficient,
// disconnect messages are sent out. // but there might be disconnect messages and those require an update call.)
m_Driver.ScheduleFlushSend(default).Complete(); m_Driver.ScheduleUpdate().Complete();
DisposeInternals(); DisposeInternals();
@@ -1325,10 +1325,8 @@ namespace Unity.Netcode.Transports.UTP
#if MULTIPLAYER_TOOLS_1_0_0_PRE_7 #if MULTIPLAYER_TOOLS_1_0_0_PRE_7
NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage()); NetworkPipelineStageCollection.RegisterPipelineStage(new NetworkMetricsPipelineStage());
#endif #endif
var maxFrameTimeMS = 0;
#if UNITY_EDITOR || DEVELOPMENT_BUILD #if UNITY_EDITOR || DEVELOPMENT_BUILD
maxFrameTimeMS = 100;
ConfigureSimulator(); ConfigureSimulator();
#endif #endif
@@ -1336,8 +1334,7 @@ namespace Unity.Netcode.Transports.UTP
maxConnectAttempts: transport.m_MaxConnectAttempts, maxConnectAttempts: transport.m_MaxConnectAttempts,
connectTimeoutMS: transport.m_ConnectTimeoutMS, connectTimeoutMS: transport.m_ConnectTimeoutMS,
disconnectTimeoutMS: transport.m_DisconnectTimeoutMS, disconnectTimeoutMS: transport.m_DisconnectTimeoutMS,
heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS, heartbeatTimeoutMS: transport.m_HeartbeatTimeoutMS);
maxFrameTimeMS: maxFrameTimeMS);
driver = NetworkDriver.Create(m_NetworkSettings); driver = NetworkDriver.Create(m_NetworkSettings);

View File

@@ -869,12 +869,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Constructor that allows you To break tests up as a host /// Constructor that allows you To break tests up as a host
/// and a server. /// and a server.
/// Example: Decorate your child derived class with TestFixture /// Example: Decorate your child derived class with TestFixture
/// and then create a constructor at the child level /// and then create a constructor at the child level.
/// Don't forget to set your constructor public, else Unity will
/// give you a hard to decipher error
/// [TestFixture(HostOrServer.Host)] /// [TestFixture(HostOrServer.Host)]
/// [TestFixture(HostOrServer.Server)] /// [TestFixture(HostOrServer.Server)]
/// public class MyChildClass : NetcodeIntegrationTest /// public class MyChildClass : NetcodeIntegrationTest
/// { /// {
/// MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { } /// public MyChildClass(HostOrServer hostOrServer) : base(hostOrServer) { }
/// } /// }
/// </summary> /// </summary>
/// <param name="hostOrServer"></param> /// <param name="hostOrServer"></param>

View File

@@ -11,6 +11,9 @@
"optionalUnityReferences": [ "optionalUnityReferences": [
"TestAssemblies" "TestAssemblies"
], ],
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [ "versionDefines": [
{ {
"name": "com.unity.multiplayer.tools", "name": "com.unity.multiplayer.tools",

View File

@@ -4,7 +4,6 @@ using NUnit.Framework;
using UnityEditor; using UnityEditor;
using UnityEditor.Build.Reporting; using UnityEditor.Build.Reporting;
using UnityEngine; using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.EditorTests namespace Unity.Netcode.EditorTests
{ {
@@ -21,6 +20,8 @@ namespace Unity.Netcode.EditorTests
var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget); var buildTargetGroup = BuildPipeline.GetBuildTargetGroup(buildTarget);
var buildTargetSupported = BuildPipeline.IsBuildTargetSupported(buildTargetGroup, buildTarget); var buildTargetSupported = BuildPipeline.IsBuildTargetSupported(buildTargetGroup, buildTarget);
if (buildTargetSupported)
{
var buildReport = BuildPipeline.BuildPlayer( var buildReport = BuildPipeline.BuildPlayer(
new[] { Path.Combine(packagePath, DefaultBuildScenePath) }, new[] { Path.Combine(packagePath, DefaultBuildScenePath) },
Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)), Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", nameof(BuildTests)),
@@ -28,13 +29,11 @@ namespace Unity.Netcode.EditorTests
BuildOptions.None BuildOptions.None
); );
if (buildTargetSupported)
{
Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result); Assert.AreEqual(BuildResult.Succeeded, buildReport.summary.result);
} }
else else
{ {
LogAssert.Expect(LogType.Error, "Error building player because build target was unsupported"); Debug.Log($"Skipped building player due to Unsupported Build Target");
} }
} }
} }

View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
@@ -179,5 +180,122 @@ namespace Unity.Netcode.EditorTests
Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]); Assert.AreEqual(handlerFour, systemThree.MessageHandlers[systemThree.GetMessageType(typeof(TestMessageFour))]);
} }
} }
internal class AAAEarlyLexicographicNetworkMessage : INetworkMessage
{
public void Serialize(FastBufferWriter writer)
{
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context)
{
return true;
}
public void Handle(ref NetworkContext context)
{
}
}
#pragma warning disable IDE1006
internal class zzzLateLexicographicNetworkMessage : AAAEarlyLexicographicNetworkMessage
{
}
#pragma warning restore IDE1006
internal class OrderingMessageProvider : IMessageProvider
{
public List<MessagingSystem.MessageWithHandler> GetMessages()
{
var listMessages = new List<MessagingSystem.MessageWithHandler>();
var messageWithHandler = new MessagingSystem.MessageWithHandler();
messageWithHandler.MessageType = typeof(zzzLateLexicographicNetworkMessage);
listMessages.Add(messageWithHandler);
messageWithHandler.MessageType = typeof(ConnectionRequestMessage);
listMessages.Add(messageWithHandler);
messageWithHandler.MessageType = typeof(ConnectionApprovedMessage);
listMessages.Add(messageWithHandler);
messageWithHandler.MessageType = typeof(OrderingMessage);
listMessages.Add(messageWithHandler);
messageWithHandler.MessageType = typeof(AAAEarlyLexicographicNetworkMessage);
listMessages.Add(messageWithHandler);
return listMessages;
}
}
[Test]
public void MessagesGetPrioritizedCorrectly()
{
var sender = new NopMessageSender();
var provider = new OrderingMessageProvider();
var messagingSystem = new MessagingSystem(sender, null, provider);
// the 3 priority messages should appear first, in lexicographic order
Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage));
Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage));
Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage));
// the other should follow after
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(AAAEarlyLexicographicNetworkMessage));
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage));
// there should not be any extras
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
// reorder the zzz one to position 3
messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
// the 3 priority messages should still appear first, in lexicographic order
Assert.AreEqual(messagingSystem.MessageTypes[0], typeof(ConnectionApprovedMessage));
Assert.AreEqual(messagingSystem.MessageTypes[1], typeof(ConnectionRequestMessage));
Assert.AreEqual(messagingSystem.MessageTypes[2], typeof(OrderingMessage));
// the other should follow after, but reordered
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage));
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage));
// there should still not be any extras
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
// verify we get an exception when asking for an invalid position
try
{
messagingSystem.ReorderMessage(-1, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
Assert.Fail();
}
catch (ArgumentException)
{
}
// reorder the zzz one to position 3, again, to check nothing bad happens
messagingSystem.ReorderMessage(3, XXHash.Hash32(typeof(zzzLateLexicographicNetworkMessage).FullName));
// the two non-priority should not have moved
Assert.AreEqual(messagingSystem.MessageTypes[3], typeof(zzzLateLexicographicNetworkMessage));
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(AAAEarlyLexicographicNetworkMessage));
// there should still not be any extras
Assert.AreEqual(messagingSystem.MessageHandlerCount, 5);
// 4242 is a random hash that should not match anything
messagingSystem.ReorderMessage(3, 4242);
// that should result in an extra entry
Assert.AreEqual(messagingSystem.MessageHandlerCount, 6);
// with a null handler
Assert.AreEqual(messagingSystem.MessageHandlers[3], null);
// and it should have bumped the previous messages down
Assert.AreEqual(messagingSystem.MessageTypes[4], typeof(zzzLateLexicographicNetworkMessage));
Assert.AreEqual(messagingSystem.MessageTypes[5], typeof(AAAEarlyLexicographicNetworkMessage));
}
} }
} }

View File

@@ -1,8 +1,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text.RegularExpressions;
using NUnit.Framework; using NUnit.Framework;
using Unity.Collections; using Unity.Collections;
using Unity.Collections.LowLevel.Unsafe; using Unity.Collections.LowLevel.Unsafe;
using UnityEngine;
using UnityEngine.TestTools;
using Random = System.Random;
namespace Unity.Netcode.EditorTests namespace Unity.Netcode.EditorTests
{ {
@@ -40,11 +44,24 @@ namespace Unity.Netcode.EditorTests
} }
} }
private class TestMessageProvider : IMessageProvider private class TestMessageProvider : IMessageProvider, IDisposable
{ {
// Keep track of what we sent
private List<List<MessagingSystem.MessageWithHandler>> m_CachedMessages = new List<List<MessagingSystem.MessageWithHandler>>();
public void Dispose()
{
foreach (var cachedItem in m_CachedMessages)
{
// Clear out any references to MessagingSystem.MessageWithHandlers
cachedItem.Clear();
}
m_CachedMessages.Clear();
}
public List<MessagingSystem.MessageWithHandler> GetMessages() public List<MessagingSystem.MessageWithHandler> GetMessages()
{ {
return new List<MessagingSystem.MessageWithHandler> var messageList = new List<MessagingSystem.MessageWithHandler>
{ {
new MessagingSystem.MessageWithHandler new MessagingSystem.MessageWithHandler
{ {
@@ -52,9 +69,13 @@ namespace Unity.Netcode.EditorTests
Handler = MessagingSystem.ReceiveMessage<TestMessage> Handler = MessagingSystem.ReceiveMessage<TestMessage>
} }
}; };
// Track messages sent
m_CachedMessages.Add(messageList);
return messageList;
} }
} }
private TestMessageProvider m_TestMessageProvider;
private TestMessageSender m_MessageSender; private TestMessageSender m_MessageSender;
private MessagingSystem m_MessagingSystem; private MessagingSystem m_MessagingSystem;
private ulong[] m_Clients = { 0 }; private ulong[] m_Clients = { 0 };
@@ -63,15 +84,16 @@ namespace Unity.Netcode.EditorTests
public void SetUp() public void SetUp()
{ {
TestMessage.Serialized = false; TestMessage.Serialized = false;
m_MessageSender = new TestMessageSender(); m_MessageSender = new TestMessageSender();
m_MessagingSystem = new MessagingSystem(m_MessageSender, this, new TestMessageProvider()); m_TestMessageProvider = new TestMessageProvider();
m_MessagingSystem = new MessagingSystem(m_MessageSender, this, m_TestMessageProvider);
m_MessagingSystem.ClientConnected(0); m_MessagingSystem.ClientConnected(0);
} }
[TearDown] [TearDown]
public void TearDown() public void TearDown()
{ {
m_TestMessageProvider.Dispose();
m_MessagingSystem.Dispose(); m_MessagingSystem.Dispose();
} }
@@ -224,5 +246,56 @@ namespace Unity.Netcode.EditorTests
Assert.AreEqual(message2, receivedMessage2); Assert.AreEqual(message2, receivedMessage2);
} }
} }
private class TestNoHandlerMessageProvider : IMessageProvider
{
public List<MessagingSystem.MessageWithHandler> GetMessages()
{
return new List<MessagingSystem.MessageWithHandler>
{
new MessagingSystem.MessageWithHandler
{
MessageType = typeof(TestMessage),
Handler = null
}
};
}
}
[Test]
public void WhenReceivingAMessageWithoutAHandler_ExceptionIsLogged()
{
// If a MessagingSystem already exists then dispose of it before creating a new MessagingSystem (otherwise memory leak)
if (m_MessagingSystem != null)
{
m_MessagingSystem.Dispose();
m_MessagingSystem = null;
}
// Since m_MessagingSystem is disposed during teardown we don't need to worry about that here.
m_MessagingSystem = new MessagingSystem(new NopMessageSender(), this, new TestNoHandlerMessageProvider());
m_MessagingSystem.ClientConnected(0);
var messageHeader = new MessageHeader
{
MessageSize = (ushort)UnsafeUtility.SizeOf<TestMessage>(),
MessageType = m_MessagingSystem.GetMessageType(typeof(TestMessage)),
};
var message = GetMessage();
var writer = new FastBufferWriter(1300, Allocator.Temp);
using (writer)
{
writer.TryBeginWrite(FastBufferWriter.GetWriteSize(message));
writer.WriteValue(message);
var reader = new FastBufferReader(writer, Allocator.Temp);
using (reader)
{
m_MessagingSystem.HandleMessage(messageHeader, reader, 0, 0, 0);
LogAssert.Expect(LogType.Exception, new Regex(".*HandlerNotRegisteredException.*"));
}
}
}
} }
} }

View File

@@ -1,41 +1,55 @@
using NUnit.Framework; using NUnit.Framework;
using UnityEngine;
namespace Unity.Netcode.EditorTests.NetworkVar namespace Unity.Netcode.EditorTests.NetworkVar
{ {
public class NetworkVarTests public class NetworkVarTests
{ {
public class NetworkVarComponent : NetworkBehaviour
{
public NetworkVariable<int> NetworkVariable = new NetworkVariable<int>();
}
[Test] [Test]
public void TestAssignmentUnchanged() public void TestAssignmentUnchanged()
{ {
var intVar = new NetworkVariable<int>(); var gameObjectMan = new GameObject();
var networkManager = gameObjectMan.AddComponent<NetworkManager>();
intVar.Value = 314159265; networkManager.BehaviourUpdater = new NetworkBehaviourUpdater();
var gameObject = new GameObject();
intVar.OnValueChanged += (value, newValue) => var networkObject = gameObject.AddComponent<NetworkObject>();
networkObject.NetworkManagerOwner = networkManager;
var networkVarComponent = gameObject.AddComponent<NetworkVarComponent>();
networkVarComponent.NetworkVariable.Initialize(networkVarComponent);
networkVarComponent.NetworkVariable.Value = 314159265;
networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) =>
{ {
Assert.Fail("OnValueChanged was invoked when setting the same value"); Assert.Fail("OnValueChanged was invoked when setting the same value");
}; };
networkVarComponent.NetworkVariable.Value = 314159265;
intVar.Value = 314159265; Object.DestroyImmediate(gameObject);
Object.DestroyImmediate(gameObjectMan);
} }
[Test] [Test]
public void TestAssignmentChanged() public void TestAssignmentChanged()
{ {
var intVar = new NetworkVariable<int>(); var gameObjectMan = new GameObject();
var networkManager = gameObjectMan.AddComponent<NetworkManager>();
intVar.Value = 314159265; networkManager.BehaviourUpdater = new NetworkBehaviourUpdater();
var gameObject = new GameObject();
var networkObject = gameObject.AddComponent<NetworkObject>();
var networkVarComponent = gameObject.AddComponent<NetworkVarComponent>();
networkObject.NetworkManagerOwner = networkManager;
networkVarComponent.NetworkVariable.Initialize(networkVarComponent);
networkVarComponent.NetworkVariable.Value = 314159265;
var changed = false; var changed = false;
networkVarComponent.NetworkVariable.OnValueChanged += (value, newValue) =>
intVar.OnValueChanged += (value, newValue) =>
{ {
changed = true; changed = true;
}; };
networkVarComponent.NetworkVariable.Value = 314159266;
intVar.Value = 314159266;
Assert.True(changed); Assert.True(changed);
Object.DestroyImmediate(gameObject);
Object.DestroyImmediate(gameObjectMan);
} }
} }
} }

View File

@@ -0,0 +1,95 @@
using NUnit.Framework;
using Unity.Collections;
namespace Unity.Netcode.EditorTests
{
public class UserBitReaderAndBitWriterTests_NCCBUG175
{
[Test]
public void WhenBitwiseWritingMoreThan8Bits_ValuesAreCorrect()
{
using var writer = new FastBufferWriter(1024, Allocator.Temp);
ulong inVal = 123456789;
for (int i = 0; i < 100; ++i)
{
writer.WriteValueSafe(i);
}
using (var bitWriter = writer.EnterBitwiseContext())
{
for (int i = 0; i < 16; ++i)
{
Assert.IsTrue((bitWriter.TryBeginWriteBits(32)));
bitWriter.WriteBits(inVal, 31);
bitWriter.WriteBit(true);
}
}
using var reader = new FastBufferReader(writer, Allocator.Temp);
for (int i = 0; i < 100; ++i)
{
reader.ReadValueSafe(out int outVal);
Assert.AreEqual(i, outVal);
}
using var bitReader = reader.EnterBitwiseContext();
for (int i = 0; i < 16; ++i)
{
Assert.IsTrue(bitReader.TryBeginReadBits(32));
bitReader.ReadBits(out ulong outVal, 31);
bitReader.ReadBit(out bool bit);
Assert.AreEqual(inVal, outVal);
Assert.AreEqual(true, bit);
}
}
[Test]
public void WhenBitwiseReadingMoreThan8Bits_ValuesAreCorrect()
{
using var writer = new FastBufferWriter(1024, Allocator.Temp);
ulong inVal = 123456789;
for (int i = 0; i < 100; ++i)
{
writer.WriteValueSafe(i);
}
uint combined = (uint)inVal | (1u << 31);
writer.WriteValueSafe(combined);
writer.WriteValueSafe(combined);
writer.WriteValueSafe(combined);
using var reader = new FastBufferReader(writer, Allocator.Temp);
for (int i = 0; i < 100; ++i)
{
reader.ReadValueSafe(out int outVal);
Assert.AreEqual(i, outVal);
}
using (var bitReader = reader.EnterBitwiseContext())
{
Assert.IsTrue(bitReader.TryBeginReadBits(32));
bitReader.ReadBits(out ulong outVal, 31);
bitReader.ReadBit(out bool bit);
Assert.AreEqual(inVal, outVal);
Assert.AreEqual(true, bit);
Assert.IsTrue(bitReader.TryBeginReadBits(32));
bitReader.ReadBits(out outVal, 31);
bitReader.ReadBit(out bit);
Assert.AreEqual(inVal, outVal);
Assert.AreEqual(true, bit);
Assert.IsTrue(bitReader.TryBeginReadBits(32));
bitReader.ReadBits(out outVal, 31);
bitReader.ReadBit(out bit);
Assert.AreEqual(inVal, outVal);
Assert.AreEqual(true, bit);
}
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: adfa622d42824b70a39a30b6aa22c9c5
timeCreated: 1660758428

View File

@@ -15,6 +15,9 @@
"optionalUnityReferences": [ "optionalUnityReferences": [
"TestAssemblies" "TestAssemblies"
], ],
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"includePlatforms": [ "includePlatforms": [
"Editor" "Editor"
], ],

View File

@@ -53,7 +53,6 @@ namespace Unity.Netcode.RuntimeTests
public bool EnableTesting; public bool EnableTesting;
private bool m_Initialized;
private bool m_FinishedTests; private bool m_FinishedTests;
private bool m_ChangesAppliedToNetworkVariables; private bool m_ChangesAppliedToNetworkVariables;
@@ -148,6 +147,11 @@ namespace Unity.Netcode.RuntimeTests
return m_FinishedTests; return m_FinishedTests;
} }
public void Awake()
{
InitializeTest();
}
// Update is called once per frame // Update is called once per frame
private void Update() private void Update()
{ {
@@ -163,13 +167,6 @@ namespace Unity.Netcode.RuntimeTests
else else
{ {
if (NetworkManager != null && NetworkManager.IsListening) if (NetworkManager != null && NetworkManager.IsListening)
{
if (!m_Initialized)
{
InitializeTest();
m_Initialized = true;
}
else
{ {
//Now change all of the values to make sure we are at least testing the local callback //Now change all of the values to make sure we are at least testing the local callback
m_NetworkVariableBool.Value = false; m_NetworkVariableBool.Value = false;
@@ -200,4 +197,3 @@ namespace Unity.Netcode.RuntimeTests
} }
} }
} }
}

View File

@@ -813,6 +813,7 @@ namespace Unity.Netcode.RuntimeTests
} }
[UnityTest] [UnityTest]
[Ignore("This test is unstable (MTT-4146)")]
public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout) public IEnumerator WhenAMessageIsDeferredForMoreThanTheConfiguredTime_ItIsRemoved([Values(1, 2, 3)] int timeout)
{ {
RegisterClientPrefabs(); RegisterClientPrefabs();

View File

@@ -0,0 +1,82 @@
using System.Collections;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{
public class NetworkListChangedTestComponent : NetworkBehaviour
{
}
public class ListChangedObject : NetworkBehaviour
{
public int ExpectedPreviousValue = 0;
public int ExpectedValue = 0;
public bool AddDone = false;
public NetworkList<int> MyNetworkList = new NetworkList<int>();
public override void OnNetworkSpawn()
{
MyNetworkList.OnListChanged += Changed;
base.OnNetworkSpawn();
}
public void Changed(NetworkListEvent<int> listEvent)
{
if (listEvent.Type == NetworkListEvent<int>.EventType.Value)
{
if (listEvent.PreviousValue != ExpectedPreviousValue)
{
Debug.Log($"Expected previous value mismatch {listEvent.PreviousValue} versus {ExpectedPreviousValue}");
Debug.Assert(listEvent.PreviousValue == ExpectedPreviousValue);
}
if (listEvent.Value != ExpectedValue)
{
Debug.Log($"Expected value mismatch {listEvent.Value} versus {ExpectedValue}");
Debug.Assert(listEvent.Value == ExpectedValue);
}
AddDone = true;
}
}
}
public class NetworkListChangedTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
private ulong m_ClientId0;
private GameObject m_PrefabToSpawn;
private NetworkObject m_NetSpawnedObject1;
protected override void OnServerAndClientsCreated()
{
m_PrefabToSpawn = CreateNetworkObjectPrefab("ListChangedObject");
m_PrefabToSpawn.AddComponent<ListChangedObject>();
}
[UnityTest]
public IEnumerator NetworkListChangedTest()
{
m_ClientId0 = m_ClientNetworkManagers[0].LocalClientId;
// create 3 objects
var spawnedObject1 = SpawnObject(m_PrefabToSpawn, m_ServerNetworkManager);
m_NetSpawnedObject1 = spawnedObject1.GetComponent<NetworkObject>();
m_NetSpawnedObject1.GetComponent<ListChangedObject>().MyNetworkList.Add(42);
m_NetSpawnedObject1.GetComponent<ListChangedObject>().ExpectedPreviousValue = 42;
m_NetSpawnedObject1.GetComponent<ListChangedObject>().ExpectedValue = 44;
m_NetSpawnedObject1.GetComponent<ListChangedObject>().MyNetworkList[0] = 44;
Debug.Assert(m_NetSpawnedObject1.GetComponent<ListChangedObject>().AddDone);
return null;
}
}
}

View File

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

View File

@@ -25,10 +25,17 @@ namespace Unity.Netcode.RuntimeTests.Metrics
var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound(); var metricValues = waitForMetricValues.AssertMetricValuesHaveBeenFound();
var networkVariableDeltaSent = metricValues.First(); bool found = false;
Assert.AreEqual(nameof(NetworkVariableComponent.MyNetworkVariable), networkVariableDeltaSent.Name); foreach (var networkVariableDeltaSent in metricValues)
Assert.AreEqual(Server.LocalClientId, networkVariableDeltaSent.Connection.Id); {
Assert.AreNotEqual(0, networkVariableDeltaSent.BytesCount); if (nameof(NetworkVariableComponent.MyNetworkVariable) == networkVariableDeltaSent.Name &&
Client.LocalClientId == networkVariableDeltaSent.Connection.Id &&
0 != networkVariableDeltaSent.BytesCount)
{
found = true;
}
}
Assert.IsTrue(found);
} }
[UnityTest] [UnityTest]

View File

@@ -1,239 +0,0 @@
#if COM_UNITY_MODULES_ANIMATION
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using Unity.Netcode.Components;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.Server)]
public class NetworkAnimatorTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
private GameObject m_PlayerOnServer;
private GameObject m_PlayerOnClient;
private Animator m_PlayerOnServerAnimator;
private Animator m_PlayerOnClientAnimator;
public NetworkAnimatorTests(HostOrServer hostOrServer) : base(hostOrServer) { }
protected override void OnCreatePlayerPrefab()
{
// ideally, we would build up the AnimatorController entirely in code and not need an asset,
// but after some attempts this doesn't seem readily doable. Instead, we load a controller
var controller = Resources.Load("TestAnimatorController") as RuntimeAnimatorController;
var animator = m_PlayerPrefab.AddComponent<Animator>();
animator.runtimeAnimatorController = controller;
var networkAnimator = m_PlayerPrefab.AddComponent<NetworkAnimator>();
networkAnimator.Animator = animator;
}
protected override IEnumerator OnServerAndClientsConnected()
{
m_PlayerOnServer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject;
m_PlayerOnServerAnimator = m_PlayerOnServerAnimator = m_PlayerOnServer.GetComponent<Animator>();
m_PlayerOnClient = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].gameObject;
m_PlayerOnClientAnimator = m_PlayerOnClient.GetComponent<Animator>();
return base.OnServerAndClientsConnected();
}
// helper function to scan an animator and verify a given clip is present
private bool HasClip(Animator animator, string clipName)
{
var clips = new List<AnimatorClipInfo>();
animator.GetCurrentAnimatorClipInfo(0, clips);
foreach (var clip in clips)
{
if (clip.clip.name == clipName)
{
return true;
}
}
return false;
}
[UnityTest]
public IEnumerator AnimationTriggerReset([Values(true, false)] bool asHash)
{
// We have "UnboundTrigger" purposely not bound to any animations so we can test resetting.
// If we used a trigger that was bound to a transition, then the trigger would reset as soon as the
// transition happens. This way it will stay stuck on
string triggerString = "UnboundTrigger";
int triggerHash = Animator.StringToHash(triggerString);
// Verify trigger is off
Assert.True(m_PlayerOnServerAnimator.GetBool(triggerString) == false);
Assert.True(m_PlayerOnClientAnimator.GetBool(triggerString) == false);
// trigger.
if (asHash)
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().SetTrigger(triggerHash);
}
else
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().SetTrigger(triggerString);
}
// verify trigger is set for client and server
yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) : m_PlayerOnServerAnimator.GetBool(triggerString));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server trigger set check");
yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) : m_PlayerOnClientAnimator.GetBool(triggerString));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client trigger set check");
// reset the trigger
if (asHash)
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().ResetTrigger(triggerHash);
}
else
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().ResetTrigger(triggerString);
}
// verify trigger is reset for client and server
yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnServerAnimator.GetBool(triggerHash) == false : m_PlayerOnServerAnimator.GetBool(triggerString) == false);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on server reset check");
yield return WaitForConditionOrTimeOut(() => asHash ? m_PlayerOnClientAnimator.GetBool(triggerHash) == false : m_PlayerOnClientAnimator.GetBool(triggerString) == false);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out on client reset check");
}
[UnityTest]
public IEnumerator AnimationStateSyncTest()
{
// check that we have started in the default state
Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState"));
Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState"));
// cause a change to the AlphaState state by setting AlphaParameter, which is
// the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset)
m_PlayerOnServerAnimator.SetBool("AlphaParameter", true);
// ...and now we should be in the AlphaState having triggered the AlphaParameter
yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
// ...and now the client should also have sync'd and arrived at the correct state
yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("AlphaState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server");
}
[UnityTest]
public IEnumerator AnimationLayerStateSyncTest()
{
int layer = 1;
// check that we have started in the default state
Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2"));
Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("DefaultStateLayer2"));
// cause a change to the AlphaState state by setting AlphaParameter, which is
// the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset)
m_PlayerOnServerAnimator.SetBool("Layer2AlphaParameter", true);
// ...and now we should be in the AlphaState having triggered the AlphaParameter
yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
// ...and now the client should also have sync'd and arrived at the correct state
yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(layer).IsName("Layer2AlphaState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server");
}
[UnityTest]
public IEnumerator AnimationLayerWeightTest()
{
int layer = 1;
float targetWeight = 0.333f;
// check that we have started in the default state
Assert.True(Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), 1f));
Assert.True(Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), 1f));
m_PlayerOnServerAnimator.SetLayerWeight(layer, targetWeight);
// ...and now we should be in the AlphaState having triggered the AlphaParameter
yield return WaitForConditionOrTimeOut(() =>
Mathf.Approximately(m_PlayerOnServerAnimator.GetLayerWeight(layer), targetWeight)
);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
// ...and now the client should also have sync'd and arrived at the correct state
yield return WaitForConditionOrTimeOut(() =>
Mathf.Approximately(m_PlayerOnClientAnimator.GetLayerWeight(layer), targetWeight)
);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state");
}
[UnityTest]
public IEnumerator AnimationStateSyncTriggerTest([Values(true, false)] bool asHash)
{
string triggerString = "TestTrigger";
int triggerHash = Animator.StringToHash(triggerString);
// check that we have started in the default state
Assert.True(m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState"));
Assert.True(m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("DefaultState"));
// cause a change to the AlphaState state by setting TestTrigger
// note, we have a special test for triggers because activating triggers via the
// NetworkAnimator is special; for other parameters you set them on the Animator and NetworkAnimator
// listens. But because triggers are super short and transitory, we require users to call
// NetworkAnimator.SetTrigger so we don't miss it
if (asHash)
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().SetTrigger(triggerHash);
}
else
{
m_PlayerOnServer.GetComponent<NetworkAnimator>().SetTrigger(triggerString);
}
// ...and now we should be in the AlphaState having triggered the AlphaParameter
yield return WaitForConditionOrTimeOut(() => m_PlayerOnServerAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its animation state via trigger");
// ...and now the client should also have sync'd and arrived at the correct state
yield return WaitForConditionOrTimeOut(() => m_PlayerOnClientAnimator.GetCurrentAnimatorStateInfo(0).IsName("TriggeredState"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to sync its animation state from the server via trigger");
}
[UnityTest]
public IEnumerator AnimationStateSyncTestWithOverride()
{
// set up the animation override controller
var overrideController = Resources.Load("TestAnimatorOverrideController") as AnimatorOverrideController;
m_PlayerOnServer.GetComponent<Animator>().runtimeAnimatorController = overrideController;
m_PlayerOnClient.GetComponent<Animator>().runtimeAnimatorController = overrideController;
// in our default state, we should see the OverrideDefaultAnimation clip
Assert.True(HasClip(m_PlayerOnServerAnimator, "OverrideDefaultAnimation"));
Assert.True(HasClip(m_PlayerOnClientAnimator, "OverrideDefaultAnimation"));
// cause a change to the AlphaState state by setting AlphaParameter, which is
// the variable bound to the transition from default to AlphaState (see the TestAnimatorController asset)
m_PlayerOnServerAnimator.SetBool("AlphaParameter", true);
// ...and now we should be in the AlphaState having set the AlphaParameter
yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Server failed to reach its overriden animation state");
// ...and now the client should also have sync'd and arrived at the correct state
yield return WaitForConditionOrTimeOut(() => HasClip(m_PlayerOnServerAnimator, "OverrideAlphaAnimation"));
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Client failed to reach its overriden animation state");
}
}
}
#endif // COM_UNITY_MODULES_ANIMATION

View File

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

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: AlphaAnimation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: db8faf64ca46248abb6624513ac1fb1b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: DefaultAnimation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 1f6191147839943ab93e2171cc15c5e9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Layer2Animation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: d31c84f6372c54d7eb8decb27010d005
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: OverrideAlphaAnimation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: 05a2afc2ff8884d32afc64ed6765880a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,53 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!74 &7400000
AnimationClip:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: OverrideDefaultAnimation
serializedVersion: 6
m_Legacy: 0
m_Compressed: 0
m_UseHighQualityCurve: 1
m_RotationCurves: []
m_CompressedRotationCurves: []
m_EulerCurves: []
m_PositionCurves: []
m_ScaleCurves: []
m_FloatCurves: []
m_PPtrCurves: []
m_SampleRate: 60
m_WrapMode: 0
m_Bounds:
m_Center: {x: 0, y: 0, z: 0}
m_Extent: {x: 0, y: 0, z: 0}
m_ClipBindingConstant:
genericBindings: []
pptrCurveMapping: []
m_AnimationClipSettings:
serializedVersion: 2
m_AdditiveReferencePoseClip: {fileID: 0}
m_AdditiveReferencePoseTime: 0
m_StartTime: 0
m_StopTime: 1
m_OrientationOffsetY: 0
m_Level: 0
m_CycleOffset: 0
m_HasAdditiveReferencePose: 0
m_LoopTime: 0
m_LoopBlend: 0
m_LoopBlendOrientation: 0
m_LoopBlendPositionY: 0
m_LoopBlendPositionXZ: 0
m_KeepOriginalOrientation: 0
m_KeepOriginalPositionY: 1
m_KeepOriginalPositionXZ: 0
m_HeightFromFeet: 0
m_Mirror: 0
m_EditorCurves: []
m_EulerEditorCurves: []
m_HasGenericRootTransform: 0
m_HasMotionFloatCurves: 0
m_Events: []

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: cf503a5569d0b4df4910a26d09ce4530
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 7400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,449 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!1102 &-8144973961595650150
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: New State
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 0}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &-7257898091357968356
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: New State
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 0}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1101 &-7235917949335567458
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 2
m_ConditionEvent: Layer2AlphaParameter
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 6016706997111698284}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 2
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1101 &-6097014330458455406
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 2
m_ConditionEvent: AlphaParameter
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: -1198466922477486815}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1107 &-1914299053840757887
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Base Layer
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: -1198466922477486815}
m_Position: {x: 70, y: 290, z: 0}
- serializedVersion: 1
m_State: {fileID: 320527679719022362}
m_Position: {x: 110, y: 490, z: 0}
- serializedVersion: 1
m_State: {fileID: 3942933370568001311}
m_Position: {x: 380, y: 280, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 30, y: 180, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: -1198466922477486815}
--- !u!1102 &-1198466922477486815
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: DefaultState
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: 232953446134799302}
- {fileID: 8340347106517238820}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!91 &9100000
AnimatorController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: TestAnimatorController
serializedVersion: 5
m_AnimatorParameters:
- m_Name: AlphaParameter
m_Type: 4
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
- m_Name: TestTrigger
m_Type: 9
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
- m_Name: UnboundTrigger
m_Type: 9
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
- m_Name: Layer2AlphaParameter
m_Type: 4
m_DefaultFloat: 0
m_DefaultInt: 0
m_DefaultBool: 0
m_Controller: {fileID: 9100000}
m_AnimatorLayers:
- serializedVersion: 5
m_Name: Base Layer
m_StateMachine: {fileID: -1914299053840757887}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 0
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
- serializedVersion: 5
m_Name: Layer2
m_StateMachine: {fileID: 1433017894673297828}
m_Mask: {fileID: 0}
m_Motions: []
m_Behaviours: []
m_BlendingMode: 0
m_SyncedLayerIndex: -1
m_DefaultWeight: 1
m_IKPass: 0
m_SyncedLayerAffectsTiming: 0
m_Controller: {fileID: 9100000}
--- !u!1101 &232953446134799302
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 1
m_ConditionEvent: AlphaParameter
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 320527679719022362}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1102 &320527679719022362
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: AlphaState
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1102 &927597079590233140
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Layer2AlphaState
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: -7235917949335567458}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: d31c84f6372c54d7eb8decb27010d005, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1107 &1433017894673297828
AnimatorStateMachine:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: Layer2
m_ChildStates:
- serializedVersion: 1
m_State: {fileID: 6016706997111698284}
m_Position: {x: 160, y: 250, z: 0}
- serializedVersion: 1
m_State: {fileID: 927597079590233140}
m_Position: {x: 270, y: 370, z: 0}
m_ChildStateMachines: []
m_AnyStateTransitions: []
m_EntryTransitions: []
m_StateMachineTransitions: {}
m_StateMachineBehaviours: []
m_AnyStatePosition: {x: 50, y: 20, z: 0}
m_EntryPosition: {x: 50, y: 120, z: 0}
m_ExitPosition: {x: 800, y: 120, z: 0}
m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
m_DefaultState: {fileID: 6016706997111698284}
--- !u!1102 &3942933370568001311
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: TriggeredState
m_Speed: 1
m_CycleOffset: 0
m_Transitions: []
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1101 &5326371122012901575
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 2
m_ConditionEvent: AlphaParameter
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: -1198466922477486815}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1102 &6016706997111698284
AnimatorState:
serializedVersion: 6
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: DefaultStateLayer2
m_Speed: 1
m_CycleOffset: 0
m_Transitions:
- {fileID: 6324505406226331058}
m_StateMachineBehaviours: []
m_Position: {x: 50, y: 50, z: 0}
m_IKOnFeet: 0
m_WriteDefaultValues: 1
m_Mirror: 0
m_SpeedParameterActive: 0
m_MirrorParameterActive: 0
m_CycleOffsetParameterActive: 0
m_TimeParameterActive: 0
m_Motion: {fileID: 0}
m_Tag:
m_SpeedParameter:
m_MirrorParameter:
m_CycleOffsetParameter:
m_TimeParameter:
--- !u!1101 &6324505406226331058
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 1
m_ConditionEvent: Layer2AlphaParameter
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 927597079590233140}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 2
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1
--- !u!1101 &8340347106517238820
AnimatorStateTransition:
m_ObjectHideFlags: 1
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name:
m_Conditions:
- m_ConditionMode: 1
m_ConditionEvent: TestTrigger
m_EventTreshold: 0
m_DstStateMachine: {fileID: 0}
m_DstState: {fileID: 3942933370568001311}
m_Solo: 0
m_Mute: 0
m_IsExit: 0
serializedVersion: 3
m_TransitionDuration: 0.25
m_TransitionOffset: 0
m_ExitTime: 0.75
m_HasExitTime: 1
m_HasFixedDuration: 1
m_InterruptionSource: 0
m_OrderedInterruption: 1
m_CanTransitionToSelf: 1

View File

@@ -1,8 +0,0 @@
fileFormatVersion: 2
guid: a0b8ebecb362240989d16159bdfa067c
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 9100000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,15 +0,0 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!221 &22100000
AnimatorOverrideController:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_Name: TestAnimatorOverrideController
m_Controller: {fileID: 9100000, guid: a0b8ebecb362240989d16159bdfa067c, type: 2}
m_Clips:
- m_OriginalClip: {fileID: 7400000, guid: 1f6191147839943ab93e2171cc15c5e9, type: 2}
m_OverrideClip: {fileID: 7400000, guid: cf503a5569d0b4df4910a26d09ce4530, type: 2}
- m_OriginalClip: {fileID: 7400000, guid: db8faf64ca46248abb6624513ac1fb1b, type: 2}
m_OverrideClip: {fileID: 7400000, guid: 05a2afc2ff8884d32afc64ed6765880a, type: 2}

View File

@@ -2,6 +2,7 @@ using System.Collections;
using UnityEngine; using UnityEngine;
using UnityEngine.TestTools; using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime;
using Unity.Netcode.Components;
namespace Unity.Netcode.RuntimeTests namespace Unity.Netcode.RuntimeTests
{ {
@@ -21,6 +22,53 @@ namespace Unity.Netcode.RuntimeTests
public class SimpleNetworkBehaviour : NetworkBehaviour public class SimpleNetworkBehaviour : NetworkBehaviour
{ {
public bool OnNetworkDespawnCalled;
public override void OnNetworkDespawn()
{
OnNetworkDespawnCalled = true;
base.OnNetworkDespawn();
}
}
protected override IEnumerator OnSetup()
{
m_AllowServerToStart = false;
return base.OnSetup();
}
/// <summary>
/// This validates the fix for when a child GameObject with a NetworkBehaviour
/// is deleted while the parent GameObject with a NetworkObject is spawned and
/// is not deleted until a later time would cause an exception due to the
/// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours
/// list.
/// </summary>
[UnityTest]
public IEnumerator ValidatedDisableddNetworkBehaviourWarning()
{
m_AllowServerToStart = true;
yield return s_DefaultWaitForTick;
// Now just start the Host
yield return StartServerAndClients();
var parentObject = new GameObject();
var childObject = new GameObject();
childObject.name = "ChildObject";
childObject.transform.parent = parentObject.transform;
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
var childBehaviour = childObject.AddComponent<NetworkTransform>();
// Set the child object to be inactive in the hierarchy
childObject.SetActive(false);
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during ownership assignment!");
LogAssert.Expect(LogType.Warning, $"{childObject.name} is disabled! Netcode for GameObjects does not support spawning disabled NetworkBehaviours! The {childBehaviour.GetType().Name} component was skipped during spawn!");
parentNetworkObject.Spawn();
yield return s_DefaultWaitForTick;
} }
/// <summary> /// <summary>
@@ -42,6 +90,9 @@ namespace Unity.Netcode.RuntimeTests
// set the log level to developer // set the log level to developer
m_ServerNetworkManager.LogLevel = LogLevel.Developer; m_ServerNetworkManager.LogLevel = LogLevel.Developer;
// The only valid condition for this would be if the NetworkBehaviour is spawned.
simpleNetworkBehaviour.IsSpawned = true;
// Verify the warning gets logged under normal conditions // Verify the warning gets logged under normal conditions
var isNull = simpleNetworkBehaviour.NetworkObject == null; var isNull = simpleNetworkBehaviour.NetworkObject == null;
LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?"); LogAssert.Expect(LogType.Warning, $"[Netcode] Could not get {nameof(NetworkObject)} for the {nameof(NetworkBehaviour)}. Are you missing a {nameof(NetworkObject)} component?");
@@ -57,5 +108,44 @@ namespace Unity.Netcode.RuntimeTests
networkObjectToTest.Despawn(); networkObjectToTest.Despawn();
Object.Destroy(networkObjectToTest); Object.Destroy(networkObjectToTest);
} }
/// <summary>
/// This validates the fix for when a child GameObject with a NetworkBehaviour
/// is deleted while the parent GameObject with a NetworkObject is spawned and
/// is not deleted until a later time would cause an exception due to the
/// NetworkBehaviour not being removed from the NetworkObject.ChildNetworkBehaviours
/// list.
/// </summary>
[UnityTest]
public IEnumerator ValidateDeleteChildNetworkBehaviour()
{
m_AllowServerToStart = true;
yield return s_DefaultWaitForTick;
// Now just start the Host
yield return StartServerAndClients();
var parentObject = new GameObject();
var childObject = new GameObject();
childObject.transform.parent = parentObject.transform;
var parentNetworkObject = parentObject.AddComponent<NetworkObject>();
childObject.AddComponent<SimpleNetworkBehaviour>();
parentNetworkObject.Spawn();
yield return s_DefaultWaitForTick;
// Destroy the child object with child NetworkBehaviour
Object.Destroy(childObject);
yield return s_DefaultWaitForTick;
// Assure no log messages are logged when they should not be logged
LogAssert.NoUnexpectedReceived();
// Destroy the parent object which should not cause any exceptions
// (validating the fix)
Object.Destroy(parentObject);
}
} }
} }

View File

@@ -50,7 +50,7 @@ namespace Unity.Netcode.RuntimeTests
public NetworkVariable<int> MyNetworkVariable; public NetworkVariable<int> MyNetworkVariable;
private void Start() private void Awake()
{ {
MyNetworkVariable = new NetworkVariable<int>(); MyNetworkVariable = new NetworkVariable<int>();
MyNetworkVariable.OnValueChanged += Changed; MyNetworkVariable.OnValueChanged += Changed;

View File

@@ -1,11 +1,6 @@
using System;
using System.Collections; using System.Collections;
#if NGO_TRANSFORM_DEBUG
using System.Text.RegularExpressions;
#endif
using Unity.Netcode.Components; using Unity.Netcode.Components;
using NUnit.Framework; using NUnit.Framework;
// using Unity.Netcode.Samples;
using UnityEngine; using UnityEngine;
using UnityEngine.TestTools; using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime; using Unity.Netcode.TestHelpers.Runtime;
@@ -14,8 +9,15 @@ namespace Unity.Netcode.RuntimeTests
{ {
public class NetworkTransformTestComponent : NetworkTransform public class NetworkTransformTestComponent : NetworkTransform
{ {
public bool ServerAuthority;
public bool ReadyToReceivePositionUpdate = false; public bool ReadyToReceivePositionUpdate = false;
protected override bool OnIsServerAuthoritative()
{
return ServerAuthority;
}
public override void OnNetworkSpawn() public override void OnNetworkSpawn()
{ {
base.OnNetworkSpawn(); base.OnNetworkSpawn();
@@ -23,243 +25,531 @@ namespace Unity.Netcode.RuntimeTests
ReadyToReceivePositionUpdate = true; ReadyToReceivePositionUpdate = true;
} }
public void CommitToTransform()
{
TryCommitTransformToServer(transform, NetworkManager.LocalTime.Time);
}
public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState() public (bool isDirty, bool isPositionDirty, bool isRotationDirty, bool isScaleDirty) ApplyState()
{ {
return ApplyLocalNetworkState(transform); var transformState = ApplyLocalNetworkState(transform);
return (transformState.IsDirty, transformState.HasPositionChange, transformState.HasRotAngleChange, transformState.HasScaleChange);
} }
} }
// [TestFixture(true, true)] [TestFixture(HostOrServer.Host, Authority.Server)]
[TestFixture(true, false)] [TestFixture(HostOrServer.Host, Authority.Owner)]
// [TestFixture(false, true)] [TestFixture(HostOrServer.Server, Authority.Server)]
[TestFixture(false, false)] [TestFixture(HostOrServer.Server, Authority.Owner)]
public class NetworkTransformTests : NetcodeIntegrationTest public class NetworkTransformTests : NetcodeIntegrationTest
{ {
private NetworkObject m_ClientSideClientPlayer; private NetworkObject m_AuthoritativePlayer;
private NetworkObject m_ServerSideClientPlayer; private NetworkObject m_NonAuthoritativePlayer;
private readonly bool m_TestWithClientNetworkTransform; private NetworkTransformTestComponent m_AuthoritativeTransform;
private NetworkTransformTestComponent m_NonAuthoritativeTransform;
private NetworkTransformTestComponent m_OwnerTransform;
public NetworkTransformTests(bool testWithHost, bool testWithClientNetworkTransform) private readonly Authority m_Authority;
public enum Authority
{ {
m_UseHost = testWithHost; // from test fixture Server,
m_TestWithClientNetworkTransform = testWithClientNetworkTransform; Owner
}
public enum Interpolation
{
DisableInterpolate,
EnableInterpolate
}
/// <summary>
/// Constructor
/// </summary>
/// <param name="testWithHost">Value is set by TestFixture</param>
/// <param name="testWithClientNetworkTransform">Value is set by TestFixture</param>
public NetworkTransformTests(HostOrServer testWithHost, Authority authority)
{
m_UseHost = testWithHost == HostOrServer.Host ? true : false;
m_Authority = authority;
} }
protected override int NumberOfClients => 1; protected override int NumberOfClients => 1;
protected override void OnCreatePlayerPrefab() protected override void OnCreatePlayerPrefab()
{ {
if (m_TestWithClientNetworkTransform) var networkTransformTestComponent = m_PlayerPrefab.AddComponent<NetworkTransformTestComponent>();
{ networkTransformTestComponent.ServerAuthority = m_Authority == Authority.Server;
// m_PlayerPrefab.AddComponent<ClientNetworkTransform>();
}
else
{
var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransformTestComponent>();
networkTransform.Interpolate = false;
}
} }
protected override void OnServerAndClientsCreated() protected override void OnServerAndClientsCreated()
{ {
#if NGO_TRANSFORM_DEBUG if (m_EnableVerboseDebug)
// Log assert for writing without authority is a developer log... {
// TODO: This is why monolithic test base classes and test helpers are an anti-pattern - this is part of an individual test case setup but is separated from the code verifying it!
m_ServerNetworkManager.LogLevel = LogLevel.Developer; m_ServerNetworkManager.LogLevel = LogLevel.Developer;
m_ClientNetworkManagers[0].LogLevel = LogLevel.Developer; foreach (var clientNetworkManager in m_ClientNetworkManagers)
#endif {
clientNetworkManager.LogLevel = LogLevel.Developer;
}
}
} }
protected override IEnumerator OnServerAndClientsConnected() protected override IEnumerator OnServerAndClientsConnected()
{ {
// Get the client player representation on both the server and the client side // Get the client player representation on both the server and the client side
m_ServerSideClientPlayer = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; var serverSideClientPlayer = m_ServerNetworkManager.ConnectedClients[m_ClientNetworkManagers[0].LocalClientId].PlayerObject;
m_ClientSideClientPlayer = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId]; var clientSideClientPlayer = m_ClientNetworkManagers[0].LocalClient.PlayerObject;
m_AuthoritativePlayer = m_Authority == Authority.Server ? serverSideClientPlayer : clientSideClientPlayer;
m_NonAuthoritativePlayer = m_Authority == Authority.Server ? clientSideClientPlayer : serverSideClientPlayer;
// Get the NetworkTransformTestComponent to make sure the client side is ready before starting test // Get the NetworkTransformTestComponent to make sure the client side is ready before starting test
var otherSideNetworkTransformComponent = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>(); m_AuthoritativeTransform = m_AuthoritativePlayer.GetComponent<NetworkTransformTestComponent>();
m_NonAuthoritativeTransform = m_NonAuthoritativePlayer.GetComponent<NetworkTransformTestComponent>();
m_OwnerTransform = m_AuthoritativeTransform.IsOwner ? m_AuthoritativeTransform : m_NonAuthoritativeTransform;
// Wait for the client-side to notify it is finished initializing and spawning. // Wait for the client-side to notify it is finished initializing and spawning.
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransformComponent.ReadyToReceivePositionUpdate == true); yield return WaitForConditionOrTimeOut(() => m_NonAuthoritativeTransform.ReadyToReceivePositionUpdate == true);
AssertOnTimeout("Timed out waiting for client-side to notify it is ready!");
Assert.True(m_AuthoritativeTransform.CanCommitToTransform);
Assert.False(m_NonAuthoritativeTransform.CanCommitToTransform);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side to notify it is ready!");
yield return base.OnServerAndClientsConnected(); yield return base.OnServerAndClientsConnected();
} }
// TODO: rewrite after perms & authority changes public enum TransformSpace
{
World,
Local
}
public enum OverrideState
{
Update,
CommitToTransform
}
/// <summary>
/// Tests changing all axial values one at a time.
/// These tests are performed:
/// - While in local space and world space
/// - While interpolation is enabled and disabled
/// - Using the TryCommitTransformToServer "override" that can be used
/// from a child derived or external class.
/// </summary>
[UnityTest] [UnityTest]
public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] bool testLocalTransform) public IEnumerator TestAuthoritativeTransformChangeOneAtATime([Values] TransformSpace testLocalTransform, [Values] Interpolation interpolation, [Values] OverrideState overideState)
{ {
// Get the client player's NetworkTransform for both instances var overrideUpdate = overideState == OverrideState.CommitToTransform;
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>(); m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>(); m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
Assert.That(!otherSideNetworkTransform.CanCommitToTransform); m_AuthoritativeTransform.InLocalSpace = testLocalTransform == TransformSpace.Local;
Assert.That(authoritativeNetworkTransform.CanCommitToTransform);
if (authoritativeNetworkTransform.CanCommitToTransform)
{
authoritativeNetworkTransform.InLocalSpace = testLocalTransform;
}
if (otherSideNetworkTransform.CanCommitToTransform)
{
otherSideNetworkTransform.InLocalSpace = testLocalTransform;
}
float approximation = 0.05f;
// test position // test position
var authPlayerTransform = authoritativeNetworkTransform.transform; var authPlayerTransform = overrideUpdate ? m_OwnerTransform.transform : m_AuthoritativeTransform.transform;
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "server side pos should be zero at first"); // sanity check Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check
authPlayerTransform.position = new Vector3(10, 20, 30); authPlayerTransform.position = new Vector3(10, 20, 30);
if (overrideUpdate)
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.position.x > approximation); {
m_OwnerTransform.CommitToTransform();
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"timeout while waiting for position change! Otherside value {otherSideNetworkTransform.transform.position.x} vs. Approximation {approximation}");
Assert.True(new Vector3(10, 20, 30) == otherSideNetworkTransform.transform.position, $"wrong position on ghost, {otherSideNetworkTransform.transform.position}"); // Vector3 already does float approximation with ==
// test rotation
authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter
Assert.AreEqual(Quaternion.identity, otherSideNetworkTransform.transform.rotation, "wrong initial value for rotation"); // sanity check
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.rotation.eulerAngles.x > approximation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for rotation change");
// approximation needed here since eulerAngles isn't super precise.
Assert.LessOrEqual(Math.Abs(45 - otherSideNetworkTransform.transform.rotation.eulerAngles.x), approximation, $"wrong rotation on ghost on x, got {otherSideNetworkTransform.transform.rotation.eulerAngles.x}");
Assert.LessOrEqual(Math.Abs(40 - otherSideNetworkTransform.transform.rotation.eulerAngles.y), approximation, $"wrong rotation on ghost on y, got {otherSideNetworkTransform.transform.rotation.eulerAngles.y}");
Assert.LessOrEqual(Math.Abs(35 - otherSideNetworkTransform.transform.rotation.eulerAngles.z), approximation, $"wrong rotation on ghost on z, got {otherSideNetworkTransform.transform.rotation.eulerAngles.z}");
// test scale
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.x, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.y, "wrong initial value for scale"); // sanity check
UnityEngine.Assertions.Assert.AreApproximatelyEqual(1f, otherSideNetworkTransform.transform.lossyScale.z, "wrong initial value for scale"); // sanity check
authPlayerTransform.localScale = new Vector3(2, 3, 4);
yield return WaitForConditionOrTimeOut(() => otherSideNetworkTransform.transform.lossyScale.x > 1f + approximation);
Assert.False(s_GlobalTimeoutHelper.TimedOut, "timeout while waiting for scale change");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(2f, otherSideNetworkTransform.transform.lossyScale.x, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(3f, otherSideNetworkTransform.transform.lossyScale.y, "wrong scale on ghost");
UnityEngine.Assertions.Assert.AreApproximatelyEqual(4f, otherSideNetworkTransform.transform.lossyScale.z, "wrong scale on ghost");
// todo reparent and test
// todo test all public API
} }
[UnityTest] yield return WaitForConditionOrTimeOut(PositionsMatch);
public IEnumerator TestCantChangeTransformFromOtherSideAuthority([Values] bool testClientAuthority) AssertOnTimeout($"Timed out waiting for positions to match");
// test rotation
Assert.AreEqual(Quaternion.identity, m_NonAuthoritativeTransform.transform.rotation, "wrong initial value for rotation"); // sanity check
authPlayerTransform.rotation = Quaternion.Euler(45, 40, 35); // using euler angles instead of quaternions directly to really see issues users might encounter
if (overrideUpdate)
{ {
// Get the client player's NetworkTransform for both instances m_OwnerTransform.CommitToTransform();
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransform>(); }
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransform>();
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "other side pos should be zero at first"); // sanity check yield return WaitForConditionOrTimeOut(RotationsMatch);
AssertOnTimeout($"Timed out waiting for rotations to match");
otherSideNetworkTransform.transform.position = new Vector3(4, 5, 6); authPlayerTransform.localScale = new Vector3(2, 3, 4);
if (overrideUpdate)
{
m_OwnerTransform.CommitToTransform();
}
yield return WaitForConditionOrTimeOut(ScaleValuesMatch);
AssertOnTimeout($"Timed out waiting for scale values to match");
}
/// <summary>
/// Test to verify nonAuthority cannot change the transform directly
/// </summary>
[UnityTest]
public IEnumerator VerifyNonAuthorityCantChangeTransform([Values] Interpolation interpolation)
{
m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "other side pos should be zero at first"); // sanity check
m_NonAuthoritativeTransform.transform.position = new Vector3(4, 5, 6);
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
Assert.AreEqual(Vector3.zero, otherSideNetworkTransform.transform.position, "got authority error, but other side still moved!"); Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "[Position] NonAuthority was able to change the position!");
#if NGO_TRANSFORM_DEBUG
// We are no longer emitting this warning, and we are banishing tests that rely on console output, so
// needs re-implementation
// TODO: This should be a separate test - verify 1 behavior per test
LogAssert.Expect(LogType.Warning, new Regex(".*without authority detected.*"));
#endif
}
var nonAuthorityRotation = m_NonAuthoritativeTransform.transform.rotation;
var originalNonAuthorityEulerRotation = nonAuthorityRotation.eulerAngles;
var nonAuthorityEulerRotation = originalNonAuthorityEulerRotation;
// Verify rotation is not marked dirty when rotated by half of the threshold
nonAuthorityEulerRotation.y += 20.0f;
nonAuthorityRotation.eulerAngles = nonAuthorityEulerRotation;
m_NonAuthoritativeTransform.transform.rotation = nonAuthorityRotation;
yield return s_DefaultWaitForTick;
var nonAuthorityCurrentEuler = m_NonAuthoritativeTransform.transform.rotation.eulerAngles;
Assert.True(originalNonAuthorityEulerRotation.Equals(nonAuthorityCurrentEuler), "[Rotation] NonAuthority was able to change the rotation!");
var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale;
m_NonAuthoritativeTransform.transform.localScale = nonAuthorityScale * 100;
yield return s_DefaultWaitForTick;
Assert.True(nonAuthorityScale.Equals(m_NonAuthoritativeTransform.transform.localScale), "[Scale] NonAuthority was able to change the scale!");
}
/// <summary> /// <summary>
/// Validates that rotation checks don't produce false positive /// Validates that rotation checks don't produce false positive
/// results when rolling over between 0 and 360 degrees /// results when rolling over between 0 and 360 degrees
/// </summary> /// </summary>
[UnityTest] [UnityTest]
public IEnumerator TestRotationThresholdDeltaCheck() public IEnumerator TestRotationThresholdDeltaCheck([Values] Interpolation interpolation)
{ {
// Get the client player's NetworkTransform for both instances m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
var authoritativeNetworkTransform = m_ServerSideClientPlayer.GetComponent<NetworkTransformTestComponent>(); m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
var otherSideNetworkTransform = m_ClientSideClientPlayer.GetComponent<NetworkTransformTestComponent>();
otherSideNetworkTransform.RotAngleThreshold = authoritativeNetworkTransform.RotAngleThreshold = 5.0f;
var halfThreshold = authoritativeNetworkTransform.RotAngleThreshold * 0.5001f; m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 5.0f;
var serverRotation = authoritativeNetworkTransform.transform.rotation;
var serverEulerRotation = serverRotation.eulerAngles; var halfThreshold = m_AuthoritativeTransform.RotAngleThreshold * 0.5001f;
var authorityRotation = m_AuthoritativeTransform.transform.rotation;
var authorityEulerRotation = authorityRotation.eulerAngles;
// Verify rotation is not marked dirty when rotated by half of the threshold // Verify rotation is not marked dirty when rotated by half of the threshold
serverEulerRotation.y += halfThreshold; authorityEulerRotation.y += halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
var results = authoritativeNetworkTransform.ApplyState(); var results = m_AuthoritativeTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!"); Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by {halfThreshold} degrees!");
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
// Verify rotation is marked dirty when rotated by another half threshold value // Verify rotation is marked dirty when rotated by another half threshold value
serverEulerRotation.y += halfThreshold; authorityEulerRotation.y += halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
results = authoritativeNetworkTransform.ApplyState(); results = m_AuthoritativeTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {authoritativeNetworkTransform.RotAngleThreshold} degrees!"); Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by the threshold value: {m_AuthoritativeTransform.RotAngleThreshold} degrees!");
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
//Reset rotation back to zero on all axis //Reset rotation back to zero on all axis
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero; authorityRotation.eulerAngles = authorityEulerRotation = Vector3.zero;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
// Rotate by 360 minus halfThreshold (which is really just negative halfThreshold) and verify rotation is not marked dirty // Rotate by 360 minus halfThreshold (which is really just negative halfThreshold) and verify rotation is not marked dirty
serverEulerRotation.y = 360 - halfThreshold; authorityEulerRotation.y = 360 - halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
results = authoritativeNetworkTransform.ApplyState(); results = m_AuthoritativeTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " + Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by " +
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); $"{Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!");
serverEulerRotation.y -= halfThreshold; authorityEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
results = authoritativeNetworkTransform.ApplyState(); results = m_AuthoritativeTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!");
//Reset rotation back to zero on all axis //Reset rotation back to zero on all axis
serverRotation.eulerAngles = serverEulerRotation = Vector3.zero; authorityRotation.eulerAngles = authorityEulerRotation = Vector3.zero;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
serverEulerRotation.y -= halfThreshold; authorityEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
results = authoritativeNetworkTransform.ApplyState(); results = m_AuthoritativeTransform.ApplyState();
Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {authoritativeNetworkTransform.RotAngleThreshold} degrees and only adjusted by " + Assert.IsFalse(results.isRotationDirty, $"Rotation is dirty when rotation threshold is {m_AuthoritativeTransform.RotAngleThreshold} degrees and only adjusted by " +
$"{Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); $"{Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!");
serverEulerRotation.y -= halfThreshold; authorityEulerRotation.y -= halfThreshold;
serverRotation.eulerAngles = serverEulerRotation; authorityRotation.eulerAngles = authorityEulerRotation;
authoritativeNetworkTransform.transform.rotation = serverRotation; m_AuthoritativeTransform.transform.rotation = authorityRotation;
results = authoritativeNetworkTransform.ApplyState(); results = m_AuthoritativeTransform.ApplyState();
Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, serverEulerRotation.y)} degrees!"); Assert.IsTrue(results.isRotationDirty, $"Rotation was not dirty when rotated by {Mathf.DeltaAngle(0, authorityEulerRotation.y)} degrees!");
}
private bool ValidateBitSetValues(NetworkTransform.NetworkTransformState serverState, NetworkTransform.NetworkTransformState clientState)
{
if (serverState.HasPositionX == clientState.HasPositionX && serverState.HasPositionY == clientState.HasPositionY && serverState.HasPositionZ == clientState.HasPositionZ &&
serverState.HasRotAngleX == clientState.HasRotAngleX && serverState.HasRotAngleY == clientState.HasRotAngleY && serverState.HasRotAngleZ == clientState.HasRotAngleZ &&
serverState.HasScaleX == clientState.HasScaleX && serverState.HasScaleY == clientState.HasScaleY && serverState.HasScaleZ == clientState.HasScaleZ)
{
return true;
}
return false;
}
/// <summary>
/// Test to make sure that the bitset value is updated properly
/// </summary>
[UnityTest]
public IEnumerator TestBitsetValue([Values] Interpolation interpolation)
{
m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f;
yield return s_DefaultWaitForTick;
m_AuthoritativeTransform.transform.rotation = Quaternion.Euler(1, 2, 3);
var serverLastSentState = m_AuthoritativeTransform.GetLastSentState();
var clientReplicatedState = m_NonAuthoritativeTransform.ReplicatedNetworkState.Value;
yield return WaitForConditionOrTimeOut(() => ValidateBitSetValues(serverLastSentState, clientReplicatedState));
AssertOnTimeout($"Timed out waiting for Authoritative Bitset state to equal NonAuthoritative replicated Bitset state!");
yield return WaitForConditionOrTimeOut(RotationsMatch);
AssertOnTimeout($"[Timed-Out] Authoritative rotation {m_AuthoritativeTransform.transform.rotation.eulerAngles} != Non-Authoritative rotation {m_NonAuthoritativeTransform.transform.rotation.eulerAngles}");
}
private float m_DetectedPotentialInterpolatedTeleport;
/// <summary>
/// The tests teleporting with and without interpolation
/// </summary>
[UnityTest]
public IEnumerator TeleportTest([Values] Interpolation interpolation)
{
m_AuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
m_NonAuthoritativeTransform.Interpolate = interpolation == Interpolation.EnableInterpolate;
var authTransform = m_AuthoritativeTransform.transform;
var nonAuthPosition = m_NonAuthoritativeTransform.transform.position;
var currentTick = m_AuthoritativeTransform.NetworkManager.ServerTime.Tick;
m_DetectedPotentialInterpolatedTeleport = 0.0f;
var teleportDestination = new Vector3(100.00f, 100.00f, 100.00f);
var targetDistance = Mathf.Abs(Vector3.Distance(nonAuthPosition, teleportDestination));
m_AuthoritativeTransform.Teleport(new Vector3(100.00f, 100.00f, 100.00f), authTransform.rotation, authTransform.localScale);
yield return WaitForConditionOrTimeOut(() => TeleportPositionMatches(nonAuthPosition));
AssertOnTimeout($"[Timed-Out][Teleport] Timed out waiting for NonAuthoritative position to !");
Assert.IsTrue(m_DetectedPotentialInterpolatedTeleport == 0.0f, $"Detected possible interpolation on non-authority side! NonAuthority distance: {m_DetectedPotentialInterpolatedTeleport} | Target distance: {targetDistance}");
}
/// <summary>
/// This test validates the <see cref="NetworkTransform.SetState(Vector3?, Quaternion?, Vector3?, bool)"/> method
/// usage for the non-authoritative side. It will either be the owner or the server making/requesting state changes.
/// This validates that:
/// - The owner authoritative mode can still be controlled by the server (i.e. owner authoritative with server authority override capabilities)
/// - The server authoritative mode can still be directed by the client owner.
/// </summary>
/// <remarks>
/// This also tests that the original server authoritative model with client-owner driven NetworkTransforms is preserved.
/// </remarks>
[UnityTest]
public IEnumerator NonAuthorityOwnerSettingStateTest([Values] Interpolation interpolation)
{
var interpolate = interpolation == Interpolation.EnableInterpolate;
m_AuthoritativeTransform.Interpolate = interpolate;
m_NonAuthoritativeTransform.Interpolate = interpolate;
m_NonAuthoritativeTransform.RotAngleThreshold = m_AuthoritativeTransform.RotAngleThreshold = 0.1f;
// Test one parameter at a time first
var newPosition = new Vector3(125f, 35f, 65f);
var newRotation = Quaternion.Euler(1, 2, 3);
var newScale = new Vector3(2.0f, 2.0f, 2.0f);
m_NonAuthoritativeTransform.SetState(newPosition, null, null, interpolate);
yield return WaitForConditionOrTimeOut(() => PositionsMatchesValue(newPosition));
AssertOnTimeout($"Timed out waiting for non-authoritative position state request to be applied!");
Assert.True(Aproximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!");
Assert.True(Aproximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!");
m_NonAuthoritativeTransform.SetState(null, newRotation, null, interpolate);
yield return WaitForConditionOrTimeOut(() => RotationMatchesValue(newRotation.eulerAngles));
AssertOnTimeout($"Timed out waiting for non-authoritative rotation state request to be applied!");
Assert.True(Aproximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!");
Assert.True(Aproximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!");
m_NonAuthoritativeTransform.SetState(null, null, newScale, interpolate);
yield return WaitForConditionOrTimeOut(() => ScaleMatchesValue(newScale));
AssertOnTimeout($"Timed out waiting for non-authoritative scale state request to be applied!");
Assert.True(Aproximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!");
Assert.True(Aproximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!");
// Test all parameters at once
newPosition = new Vector3(55f, 95f, -25f);
newRotation = Quaternion.Euler(20, 5, 322);
newScale = new Vector3(0.5f, 0.5f, 0.5f);
m_NonAuthoritativeTransform.SetState(newPosition, newRotation, newScale, interpolate);
yield return WaitForConditionOrTimeOut(() => PositionRotationScaleMatches(newPosition, newRotation.eulerAngles, newScale));
AssertOnTimeout($"Timed out waiting for non-authoritative position, rotation, and scale state request to be applied!");
Assert.True(Aproximately(newPosition, m_AuthoritativeTransform.transform.position), "Authoritative position does not match!");
Assert.True(Aproximately(newPosition, m_NonAuthoritativeTransform.transform.position), "Non-Authoritative position does not match!");
Assert.True(Aproximately(newRotation.eulerAngles, m_AuthoritativeTransform.transform.rotation.eulerAngles), "Authoritative rotation does not match!");
Assert.True(Aproximately(newRotation.eulerAngles, m_NonAuthoritativeTransform.transform.rotation.eulerAngles), "Non-Authoritative rotation does not match!");
Assert.True(Aproximately(newScale, m_AuthoritativeTransform.transform.localScale), "Authoritative scale does not match!");
Assert.True(Aproximately(newScale, m_NonAuthoritativeTransform.transform.localScale), "Non-Authoritative scale does not match!");
}
private bool Aproximately(float x, float y)
{
return Mathf.Abs(x - y) <= k_AproximateDeltaVariance;
}
private bool Aproximately(Vector3 a, Vector3 b)
{
return Mathf.Abs(a.x - b.x) <= k_AproximateDeltaVariance &&
Mathf.Abs(a.y - b.y) <= k_AproximateDeltaVariance &&
Mathf.Abs(a.z - b.z) <= k_AproximateDeltaVariance;
}
private const float k_AproximateDeltaVariance = 0.01f;
private bool PositionsMatchesValue(Vector3 positionToMatch)
{
var authorityPosition = m_AuthoritativeTransform.transform.position;
var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position;
var auhtorityIsEqual = Aproximately(authorityPosition, positionToMatch);
var nonauthorityIsEqual = Aproximately(nonAuthorityPosition, positionToMatch);
if (!auhtorityIsEqual)
{
VerboseDebug($"Authority position {authorityPosition} != position to match: {positionToMatch}!");
}
if (!nonauthorityIsEqual)
{
VerboseDebug($"NonAuthority position {nonAuthorityPosition} != position to match: {positionToMatch}!");
}
return auhtorityIsEqual && nonauthorityIsEqual;
}
private bool RotationMatchesValue(Vector3 rotationEulerToMatch)
{
var authorityRotationEuler = m_AuthoritativeTransform.transform.rotation.eulerAngles;
var nonAuthorityRotationEuler = m_NonAuthoritativeTransform.transform.rotation.eulerAngles;
var auhtorityIsEqual = Aproximately(authorityRotationEuler, rotationEulerToMatch);
var nonauthorityIsEqual = Aproximately(nonAuthorityRotationEuler, rotationEulerToMatch);
if (!auhtorityIsEqual)
{
VerboseDebug($"Authority rotation {authorityRotationEuler} != rotation to match: {rotationEulerToMatch}!");
}
if (!nonauthorityIsEqual)
{
VerboseDebug($"NonAuthority position {nonAuthorityRotationEuler} != rotation to match: {rotationEulerToMatch}!");
}
return auhtorityIsEqual && nonauthorityIsEqual;
}
private bool ScaleMatchesValue(Vector3 scaleToMatch)
{
var authorityScale = m_AuthoritativeTransform.transform.localScale;
var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale;
var auhtorityIsEqual = Aproximately(authorityScale, scaleToMatch);
var nonauthorityIsEqual = Aproximately(nonAuthorityScale, scaleToMatch);
if (!auhtorityIsEqual)
{
VerboseDebug($"Authority scale {authorityScale} != scale to match: {scaleToMatch}!");
}
if (!nonauthorityIsEqual)
{
VerboseDebug($"NonAuthority scale {nonAuthorityScale} != scale to match: {scaleToMatch}!");
}
return auhtorityIsEqual && nonauthorityIsEqual;
}
private bool TeleportPositionMatches(Vector3 nonAuthorityOriginalPosition)
{
var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position;
var authorityPosition = m_AuthoritativeTransform.transform.position;
var targetDistance = Mathf.Abs(Vector3.Distance(nonAuthorityOriginalPosition, authorityPosition));
var nonAuthorityCurrentDistance = Mathf.Abs(Vector3.Distance(nonAuthorityPosition, nonAuthorityOriginalPosition));
if (!Aproximately(targetDistance, nonAuthorityCurrentDistance))
{
if (nonAuthorityCurrentDistance >= 0.15f * targetDistance && nonAuthorityCurrentDistance <= 0.75f * targetDistance)
{
m_DetectedPotentialInterpolatedTeleport = nonAuthorityCurrentDistance;
}
return false;
}
var xIsEqual = Aproximately(authorityPosition.x, nonAuthorityPosition.x);
var yIsEqual = Aproximately(authorityPosition.y, nonAuthorityPosition.y);
var zIsEqual = Aproximately(authorityPosition.z, nonAuthorityPosition.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Authority position {authorityPosition} != NonAuthority position {nonAuthorityPosition}");
}
return xIsEqual && yIsEqual && zIsEqual; ;
}
private bool PositionRotationScaleMatches(Vector3 position, Vector3 eulerRotation, Vector3 scale)
{
return PositionsMatchesValue(position) && RotationMatchesValue(eulerRotation) && ScaleMatchesValue(scale);
}
private bool RotationsMatch()
{
var authorityEulerRotation = m_AuthoritativeTransform.transform.rotation.eulerAngles;
var nonAuthorityEulerRotation = m_NonAuthoritativeTransform.transform.rotation.eulerAngles;
var xIsEqual = Aproximately(authorityEulerRotation.x, nonAuthorityEulerRotation.x);
var yIsEqual = Aproximately(authorityEulerRotation.y, nonAuthorityEulerRotation.y);
var zIsEqual = Aproximately(authorityEulerRotation.z, nonAuthorityEulerRotation.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Authority rotation {authorityEulerRotation} != NonAuthority rotation {nonAuthorityEulerRotation}");
}
return xIsEqual && yIsEqual && zIsEqual;
}
private bool PositionsMatch()
{
var authorityPosition = m_AuthoritativeTransform.transform.position;
var nonAuthorityPosition = m_NonAuthoritativeTransform.transform.position;
var xIsEqual = Aproximately(authorityPosition.x, nonAuthorityPosition.x);
var yIsEqual = Aproximately(authorityPosition.y, nonAuthorityPosition.y);
var zIsEqual = Aproximately(authorityPosition.z, nonAuthorityPosition.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Authority position {authorityPosition} != NonAuthority position {nonAuthorityPosition}");
}
return xIsEqual && yIsEqual && zIsEqual;
}
private bool ScaleValuesMatch()
{
var authorityScale = m_AuthoritativeTransform.transform.localScale;
var nonAuthorityScale = m_NonAuthoritativeTransform.transform.localScale;
var xIsEqual = Aproximately(authorityScale.x, nonAuthorityScale.x);
var yIsEqual = Aproximately(authorityScale.y, nonAuthorityScale.y);
var zIsEqual = Aproximately(authorityScale.z, nonAuthorityScale.z);
if (!xIsEqual || !yIsEqual || !zIsEqual)
{
VerboseDebug($"Authority scale {authorityScale} != NonAuthority scale {nonAuthorityScale}");
}
return xIsEqual && yIsEqual && zIsEqual;
} }
/*
* ownership change
* test teleport with interpolation
* test teleport without interpolation
* test dynamic spawning
*/
protected override IEnumerator OnTearDown() protected override IEnumerator OnTearDown()
{ {
UnityEngine.Object.DestroyImmediate(m_PlayerPrefab); m_EnableVerboseDebug = false;
Object.DestroyImmediate(m_PlayerPrefab);
yield return base.OnTearDown(); yield return base.OnTearDown();
} }
} }

View File

@@ -15,17 +15,6 @@ namespace Unity.Netcode.RuntimeTests
public bool FieldWritten; public bool FieldWritten;
public bool DeltaRead; public bool DeltaRead;
public bool FieldRead; public bool FieldRead;
public bool Dirty = false;
public override void ResetDirty()
{
Dirty = false;
}
public override bool IsDirty()
{
return Dirty;
}
public override void WriteDelta(FastBufferWriter writer) public override void WriteDelta(FastBufferWriter writer)
{ {
@@ -138,12 +127,12 @@ namespace Unity.Netcode.RuntimeTests
Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!"); Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client side DummyNetBehaviour to register it was spawned!");
// Check that FieldWritten is written when dirty // Check that FieldWritten is written when dirty
serverComponent.NetVar.Dirty = true; serverComponent.NetVar.SetDirty(true);
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
Assert.True(serverComponent.NetVar.FieldWritten); Assert.True(serverComponent.NetVar.FieldWritten);
// Check that DeltaWritten is written when dirty // Check that DeltaWritten is written when dirty
serverComponent.NetVar.Dirty = true; serverComponent.NetVar.SetDirty(true);
yield return s_DefaultWaitForTick; yield return s_DefaultWaitForTick;
Assert.True(serverComponent.NetVar.DeltaWritten); Assert.True(serverComponent.NetVar.DeltaWritten);

View File

@@ -14,6 +14,7 @@ namespace Unity.Netcode.RuntimeTests
{ {
public NetworkVariable<Vector3> OwnerWritable_Position = new NetworkVariable<Vector3>(Vector3.one, NetworkVariableBase.DefaultReadPerm, NetworkVariableWritePermission.Owner); 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> 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);
} }
[TestFixtureSource(nameof(TestDataSource))] [TestFixtureSource(nameof(TestDataSource))]
@@ -104,6 +105,42 @@ namespace Unity.Netcode.RuntimeTests
return true; return true;
} }
private bool CheckOwnerReadWriteAreEqualOnOwnerAndServer()
{
var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId];
var testCompServer = testObjServer.GetComponent<NetVarPermTestComp>();
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId];
var testCompClient = testObjClient.GetComponent<NetVarPermTestComp>();
if (testObjServer.OwnerClientId == testObjClient.OwnerClientId &&
testCompServer.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value &&
testCompServer.OwnerReadWrite_Position.ReadPerm == testCompClient.ServerWritable_Position.ReadPerm &&
testCompServer.OwnerReadWrite_Position.WritePerm == testCompClient.ServerWritable_Position.WritePerm)
{
return true;
}
}
return false;
}
private bool CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(NetVarPermTestComp ownerReadWriteObject)
{
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
var testObjClient = clientNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId];
var testCompClient = testObjClient.GetComponent<NetVarPermTestComp>();
if (testObjClient.OwnerClientId != ownerReadWriteObject.OwnerClientId ||
ownerReadWriteObject.OwnerReadWrite_Position.Value == testCompClient.ServerWritable_Position.Value ||
ownerReadWriteObject.OwnerReadWrite_Position.ReadPerm != testCompClient.ServerWritable_Position.ReadPerm ||
ownerReadWriteObject.OwnerReadWrite_Position.WritePerm != testCompClient.ServerWritable_Position.WritePerm)
{
return false;
}
}
return true;
}
[UnityTest] [UnityTest]
public IEnumerator ServerChangesOwnerWritableNetVar() public IEnumerator ServerChangesOwnerWritableNetVar()
{ {
@@ -164,6 +201,44 @@ namespace Unity.Netcode.RuntimeTests
yield return WaitForOwnerWritableAreEqualOnAll(); yield return WaitForOwnerWritableAreEqualOnAll();
} }
/// <summary>
/// This tests the scenario where a client owner has both read and write
/// permissions set. The server should be the only instance that can read
/// the NetworkVariable. ServerCannotChangeOwnerWritableNetVar performs
/// the same check to make sure the server cannot write to a client owner
/// NetworkVariable with owner write permissions.
/// </summary>
[UnityTest]
public IEnumerator ClientOwnerWithReadWriteChangesNetVar()
{
yield return WaitForOwnerWritableAreEqualOnAll();
var testObjServer = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_TestObjId];
int clientManagerIndex = m_ClientNetworkManagers.Length - 1;
var newOwnerClientId = m_ClientNetworkManagers[clientManagerIndex].LocalClientId;
testObjServer.ChangeOwnership(newOwnerClientId);
yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 2);
yield return WaitForOwnerWritableAreEqualOnAll();
var testObjClient = m_ClientNetworkManagers[clientManagerIndex].SpawnManager.SpawnedObjects[m_TestObjId];
var testCompClient = testObjClient.GetComponent<NetVarPermTestComp>();
var oldValue = testCompClient.OwnerReadWrite_Position.Value;
var newValue = oldValue + new Vector3(Random.Range(0, 100.0f), Random.Range(0, 100.0f), Random.Range(0, 100.0f));
testCompClient.OwnerWritable_Position.Value = newValue;
yield return WaitForPositionsAreEqual(testCompClient.OwnerWritable_Position, newValue);
// Verify the client owner and server match
yield return CheckOwnerReadWriteAreEqualOnOwnerAndServer();
// Verify the non-owner clients do not have the same Value but do have the same permissions
yield return CheckOwnerReadWriteAreNotEqualOnNonOwnerClients(testCompClient);
}
[UnityTest] [UnityTest]
public IEnumerator ClientCannotChangeServerWritableNetVar() public IEnumerator ClientCannotChangeServerWritableNetVar()
{ {

View File

@@ -0,0 +1,103 @@
using System.Collections;
using System.Collections.Generic;
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using Unity.Netcode.TestHelpers.Runtime;
namespace Unity.Netcode.RuntimeTests
{
// This is a bit of a quirky test.
// Addresses MTT-4386 #2109
// Where the NetworkVariable updates would be repeated on some clients.
// The twist comes fom the updates needing to happens very specifically for the issue to repro in tests
public class OwnerModifiedObject : NetworkBehaviour, INetworkUpdateSystem
{
public NetworkList<int> MyNetworkList;
static internal int Updates = 0;
private void Awake()
{
MyNetworkList = new NetworkList<int>(new List<int>(), NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
MyNetworkList.OnListChanged += Changed;
}
public void Changed(NetworkListEvent<int> listEvent)
{
var expected = 0;
var listString = "";
foreach (var i in MyNetworkList)
{
Assert.AreEqual(i, expected);
expected++;
listString += i.ToString();
}
Debug.Log($"[{NetworkManager.LocalClientId}] Value changed to {listString}");
Updates++;
}
public bool AddValues;
public NetworkUpdateStage NetworkUpdateStageToCheck;
private int m_ValueToUpdate;
public void NetworkUpdate(NetworkUpdateStage updateStage)
{
if (updateStage == NetworkUpdateStageToCheck)
{
if (AddValues)
{
MyNetworkList.Add(m_ValueToUpdate++);
AddValues = false;
}
}
}
public override void OnDestroy()
{
NetworkUpdateLoop.UnregisterAllNetworkUpdates(this);
base.OnDestroy();
}
public void InitializeLastCient()
{
NetworkUpdateLoop.RegisterAllNetworkUpdates(this);
}
}
public class OwnerModifiedTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
protected override void OnCreatePlayerPrefab()
{
m_PlayerPrefab.AddComponent<OwnerModifiedObject>();
}
[UnityTest]
public IEnumerator OwnerModifiedTest()
{
// We use this to assure we are the "last client" connected.
yield return CreateAndStartNewClient();
var ownerModLastClient = m_ClientNetworkManagers[2].LocalClient.PlayerObject.GetComponent<OwnerModifiedObject>();
ownerModLastClient.InitializeLastCient();
// Run through all update loops setting the value once every 5 frames
foreach (var updateLoopType in System.Enum.GetValues(typeof(NetworkUpdateStage)))
{
ownerModLastClient.NetworkUpdateStageToCheck = (NetworkUpdateStage)updateLoopType;
Debug.Log($"Testing Update Stage: {ownerModLastClient.NetworkUpdateStageToCheck}");
ownerModLastClient.AddValues = true;
yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5);
}
yield return NetcodeIntegrationTestHelpers.WaitForTicks(m_ServerNetworkManager, 5);
// We'll have at least one update per stage per client, if all goes well.
Assert.True(OwnerModifiedObject.Updates > 20);
}
}
}

View File

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

View File

@@ -10,21 +10,19 @@ namespace Unity.Netcode.RuntimeTests
{ {
public class TransformInterpolationObject : NetworkBehaviour public class TransformInterpolationObject : NetworkBehaviour
{ {
// Set the minimum threshold which we will use as our margin of error
public const float MinThreshold = 0.001f;
public bool CheckPosition; public bool CheckPosition;
public bool IsMoving; public bool IsMoving;
public bool IsFixed; public bool IsFixed;
private void Update() private void Update()
{ {
// Since the local position is transformed from local to global and vice-versa on the server and client
// it may accumulate some error. We allow an error of 0.01 over the range of 1000 used in this test.
// This requires precision to 5 digits, so it doesn't weaken the test, while preventing spurious failures
const float maxRoundingError = 0.01f;
// Check the position of the nested object on the client // Check the position of the nested object on the client
if (CheckPosition) if (CheckPosition)
{ {
if (transform.position.y < -maxRoundingError || transform.position.y > 100.0f + maxRoundingError) if (transform.position.y < -MinThreshold || transform.position.y > 100.0f + MinThreshold)
{ {
Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0"); Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0");
} }
@@ -65,7 +63,8 @@ namespace Unity.Netcode.RuntimeTests
protected override void OnServerAndClientsCreated() protected override void OnServerAndClientsCreated()
{ {
m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject"); m_PrefabToSpawn = CreateNetworkObjectPrefab("InterpTestObject");
m_PrefabToSpawn.AddComponent<NetworkTransform>(); var networkTransform = m_PrefabToSpawn.AddComponent<NetworkTransform>();
networkTransform.PositionThreshold = TransformInterpolationObject.MinThreshold;
m_PrefabToSpawn.AddComponent<TransformInterpolationObject>(); m_PrefabToSpawn.AddComponent<TransformInterpolationObject>();
} }
@@ -85,8 +84,6 @@ namespace Unity.Netcode.RuntimeTests
m_SpawnedObjectOnClient = s_GlobalNetworkObjects[clientId][m_SpawnedAsNetworkObject.NetworkObjectId]; m_SpawnedObjectOnClient = s_GlobalNetworkObjects[clientId][m_SpawnedAsNetworkObject.NetworkObjectId];
// make sure the objects are set with the right network manager // make sure the objects are set with the right network manager
m_SpawnedObjectOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0]; m_SpawnedObjectOnClient.NetworkManagerOwner = m_ClientNetworkManagers[0];
} }
[UnityTest] [UnityTest]

View File

@@ -457,5 +457,26 @@ namespace Unity.Netcode.RuntimeTests
yield return null; yield return null;
} }
[UnityTest]
public IEnumerator ReliablePayloadsCanBeLargerThanMaximum()
{
InitializeTransport(out m_Server, out m_ServerEvents);
InitializeTransport(out m_Client1, out m_Client1Events);
m_Server.StartServer();
m_Client1.StartClient();
yield return WaitForNetworkEvent(NetworkEvent.Connect, m_Client1Events);
var payloadSize = UnityTransport.InitialMaxPayloadSize + 1;
var data = new ArraySegment<byte>(new byte[payloadSize]);
m_Server.Send(m_Client1.ServerClientId, data, NetworkDelivery.Reliable);
yield return WaitForNetworkEvent(NetworkEvent.Data, m_Client1Events);
yield return null;
}
} }
} }

View File

@@ -16,6 +16,9 @@
"optionalUnityReferences": [ "optionalUnityReferences": [
"TestAssemblies" "TestAssemblies"
], ],
"defineConstraints": [
"UNITY_INCLUDE_TESTS"
],
"versionDefines": [ "versionDefines": [
{ {
"name": "com.unity.multiplayer.tools", "name": "com.unity.multiplayer.tools",

View File

@@ -2,22 +2,19 @@
"name": "com.unity.netcode.gameobjects", "name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects", "displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
"version": "1.0.0", "version": "1.0.1",
"unity": "2020.3", "unity": "2020.3",
"dependencies": { "dependencies": {
"com.unity.nuget.mono-cecil": "1.10.1", "com.unity.nuget.mono-cecil": "1.10.1",
"com.unity.transport": "1.1.0" "com.unity.transport": "1.2.0"
},
"_upm": {
"changelog": "### Changed\n\n- Changed version to 1.0.0. (#2046)"
}, },
"upmCi": { "upmCi": {
"footprint": "382d762a40cdcb42ebaf495e373effb00362baf1" "footprint": "8824c99a21c438135052b8a8d42b6a8cb865bea3"
}, },
"repository": { "repository": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git", "type": "git",
"revision": "fddb7cd920e1db9e49d44846d7121e38f59bd137" "revision": "ce1ab3ca9495caf3f906d8ca5459677614214837"
}, },
"samples": [ "samples": [
{ {