com.unity.netcode.gameobjects@2.1.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). ## [2.1.1] - 2024-10-18 ### Added - Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097) - Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094) - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094) - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094) - Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088) - Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088) ### Fixed - Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103) - Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099) - Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096) - Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092) - Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081) - Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081) - Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081) - Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081) - Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078) - Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077) - Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075) ### Changed - Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097) - Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081)
This commit is contained in:
30
CHANGELOG.md
30
CHANGELOG.md
@@ -6,6 +6,36 @@ 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).
|
||||||
|
|
||||||
|
## [2.1.1] - 2024-10-18
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097)
|
||||||
|
- Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094)
|
||||||
|
- `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094)
|
||||||
|
- `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094)
|
||||||
|
- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088)
|
||||||
|
- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088)
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103)
|
||||||
|
- Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099)
|
||||||
|
- Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096)
|
||||||
|
- Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092)
|
||||||
|
- Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081)
|
||||||
|
- Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081)
|
||||||
|
- Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081)
|
||||||
|
- Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081)
|
||||||
|
- Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078)
|
||||||
|
- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077)
|
||||||
|
- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075)
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097)
|
||||||
|
- Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081)
|
||||||
|
|
||||||
## [2.0.0] - 2024-09-12
|
## [2.0.0] - 2024-09-12
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ namespace Unity.Netcode.Editor.Configuration
|
|||||||
|
|
||||||
// Process the imported and deleted assets
|
// Process the imported and deleted assets
|
||||||
var markDirty = ProcessImportedAssets(importedAssets);
|
var markDirty = ProcessImportedAssets(importedAssets);
|
||||||
markDirty &= ProcessDeletedAssets(deletedAssets);
|
markDirty |= ProcessDeletedAssets(deletedAssets);
|
||||||
|
|
||||||
if (markDirty)
|
if (markDirty)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ namespace Unity.Netcode.Editor
|
|||||||
private SerializedProperty m_NetworkTransportProperty;
|
private SerializedProperty m_NetworkTransportProperty;
|
||||||
private SerializedProperty m_TickRateProperty;
|
private SerializedProperty m_TickRateProperty;
|
||||||
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
||||||
|
private SerializedProperty m_AutoSpawnPlayerPrefabClientSide;
|
||||||
private SerializedProperty m_NetworkTopologyProperty;
|
private SerializedProperty m_NetworkTopologyProperty;
|
||||||
#endif
|
#endif
|
||||||
private SerializedProperty m_ClientConnectionBufferTimeoutProperty;
|
private SerializedProperty m_ClientConnectionBufferTimeoutProperty;
|
||||||
@@ -104,6 +105,11 @@ namespace Unity.Netcode.Editor
|
|||||||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
||||||
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
||||||
m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology");
|
m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology");
|
||||||
|
// Only display the auto spawn property when the distributed authority network topology is selected
|
||||||
|
if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority)
|
||||||
|
{
|
||||||
|
m_AutoSpawnPlayerPrefabClientSide = m_NetworkConfigProperty.FindPropertyRelative("AutoSpawnPlayerPrefabClientSide");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||||
@@ -120,8 +126,6 @@ namespace Unity.Netcode.Editor
|
|||||||
#if MULTIPLAYER_TOOLS
|
#if MULTIPLAYER_TOOLS
|
||||||
m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics");
|
m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
|
||||||
m_PrefabsList = m_NetworkConfigProperty
|
m_PrefabsList = m_NetworkConfigProperty
|
||||||
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
|
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
|
||||||
@@ -144,6 +148,11 @@ namespace Unity.Netcode.Editor
|
|||||||
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
|
||||||
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
||||||
m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology");
|
m_NetworkTopologyProperty = m_NetworkConfigProperty.FindPropertyRelative("NetworkTopology");
|
||||||
|
// Only display the auto spawn property when the distributed authority network topology is selected
|
||||||
|
if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority)
|
||||||
|
{
|
||||||
|
m_AutoSpawnPlayerPrefabClientSide = m_NetworkConfigProperty.FindPropertyRelative("AutoSpawnPlayerPrefabClientSide");
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
|
||||||
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
|
||||||
@@ -173,10 +182,11 @@ namespace Unity.Netcode.Editor
|
|||||||
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
|
||||||
{
|
{
|
||||||
serializedObject.Update();
|
serializedObject.Update();
|
||||||
|
|
||||||
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
|
||||||
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
EditorGUILayout.PropertyField(m_LogLevelProperty);
|
||||||
|
|
||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
|
|
||||||
EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel);
|
||||||
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
||||||
EditorGUILayout.PropertyField(m_NetworkTopologyProperty);
|
EditorGUILayout.PropertyField(m_NetworkTopologyProperty);
|
||||||
@@ -222,8 +232,17 @@ namespace Unity.Netcode.Editor
|
|||||||
EditorGUILayout.Space();
|
EditorGUILayout.Space();
|
||||||
EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel);
|
EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel);
|
||||||
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
EditorGUILayout.PropertyField(m_ForceSamePrefabsProperty);
|
||||||
|
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
|
||||||
|
// Only display the auto spawn property when the distributed authority network topology is selected
|
||||||
|
if (m_NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.DistributedAuthority)
|
||||||
|
{
|
||||||
|
EditorGUILayout.PropertyField(m_AutoSpawnPlayerPrefabClientSide, new GUIContent("Auto Spawn Player Prefab"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab"));
|
EditorGUILayout.PropertyField(m_PlayerPrefabProperty, new GUIContent("Default Player Prefab"));
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
|
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
|
||||||
{
|
{
|
||||||
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
|
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
|
||||||
|
|||||||
@@ -1463,8 +1463,6 @@ namespace Unity.Netcode.Components
|
|||||||
// For test logging purposes
|
// For test logging purposes
|
||||||
internal NetworkTransformState SynchronizeState;
|
internal NetworkTransformState SynchronizeState;
|
||||||
|
|
||||||
// DANGO-TODO: We will want to remove this when we migrate NetworkTransforms to a dedicated internal message
|
|
||||||
private const ushort k_NetworkTransformStateMagic = 0xf48d;
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ONSYNCHRONIZE
|
#region ONSYNCHRONIZE
|
||||||
@@ -1489,19 +1487,10 @@ namespace Unity.Netcode.Components
|
|||||||
HalfVectorRotation = new HalfVector4(),
|
HalfVectorRotation = new HalfVector4(),
|
||||||
HalfVectorScale = new HalfVector3(),
|
HalfVectorScale = new HalfVector3(),
|
||||||
NetworkDeltaPosition = new NetworkDeltaPosition(),
|
NetworkDeltaPosition = new NetworkDeltaPosition(),
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (serializer.IsWriter)
|
if (serializer.IsWriter)
|
||||||
{
|
{
|
||||||
// DANGO-TODO: This magic value is sent to the server in order to identify the network transform.
|
|
||||||
// The server discards it before forwarding synchronization data to other clients.
|
|
||||||
if (NetworkManager.DistributedAuthorityMode && NetworkManager.CMBServiceConnection)
|
|
||||||
{
|
|
||||||
var writer = serializer.GetFastBufferWriter();
|
|
||||||
writer.WriteValueSafe(k_NetworkTransformStateMagic);
|
|
||||||
}
|
|
||||||
|
|
||||||
SynchronizeState.IsTeleportingNextFrame = true;
|
SynchronizeState.IsTeleportingNextFrame = true;
|
||||||
var transformToCommit = transform;
|
var transformToCommit = transform;
|
||||||
// If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for
|
// If we are using Half Float Precision, then we want to only synchronize the authority's m_HalfPositionState.FullPosition in order for
|
||||||
@@ -3060,12 +3049,44 @@ namespace Unity.Netcode.Components
|
|||||||
base.InternalOnNetworkSessionSynchronized();
|
base.InternalOnNetworkSessionSynchronized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyPlayerTransformState()
|
||||||
|
{
|
||||||
|
SynchronizeState.InLocalSpace = InLocalSpace;
|
||||||
|
SynchronizeState.UseInterpolation = Interpolate;
|
||||||
|
SynchronizeState.QuaternionSync = UseQuaternionSynchronization;
|
||||||
|
SynchronizeState.UseHalfFloatPrecision = UseHalfFloatPrecision;
|
||||||
|
SynchronizeState.QuaternionCompression = UseQuaternionCompression;
|
||||||
|
SynchronizeState.UsePositionSlerp = SlerpPosition;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// For dynamically spawned NetworkObjects, when the non-authority instance's client is already connected and
|
/// For dynamically spawned NetworkObjects, when the non-authority instance's client is already connected and
|
||||||
/// the SynchronizeState is still pending synchronization then we want to finalize the synchornization at this time.
|
/// the SynchronizeState is still pending synchronization then we want to finalize the synchornization at this time.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected internal override void InternalOnNetworkPostSpawn()
|
protected internal override void InternalOnNetworkPostSpawn()
|
||||||
{
|
{
|
||||||
|
// This is a special case for client-server where a server is spawning an owner authoritative NetworkObject but has yet to serialize anything.
|
||||||
|
// When the server detects that:
|
||||||
|
// - We are not in a distributed authority session (DAHost check).
|
||||||
|
// - This is the first/root NetworkTransform.
|
||||||
|
// - We are in owner authoritative mode.
|
||||||
|
// - The NetworkObject is not owned by the server.
|
||||||
|
// - The SynchronizeState.IsSynchronizing is set to false.
|
||||||
|
// Then we want to:
|
||||||
|
// - Force the "IsSynchronizing" flag so the NetworkTransform has its state updated properly and runs through the initialization again.
|
||||||
|
// - Make sure the SynchronizingState is updated to the instantiated prefab's default flags/settings.
|
||||||
|
if (NetworkManager.IsServer && !NetworkManager.DistributedAuthorityMode && m_IsFirstNetworkTransform && !OnIsServerAuthoritative() && !IsOwner && !SynchronizeState.IsSynchronizing)
|
||||||
|
{
|
||||||
|
// Assure the first/root NetworkTransform has the synchronizing flag set so the server runs through the final post initialization steps
|
||||||
|
SynchronizeState.IsSynchronizing = true;
|
||||||
|
// Assure the SynchronizeState matches the initial prefab's values for each associated NetworkTransfrom (this includes root + all children)
|
||||||
|
foreach (var child in NetworkObject.NetworkTransforms)
|
||||||
|
{
|
||||||
|
child.ApplyPlayerTransformState();
|
||||||
|
}
|
||||||
|
// Now fall through to the final synchronization portion of the spawning for NetworkTransform
|
||||||
|
}
|
||||||
|
|
||||||
if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing)
|
if (!CanCommitToTransform && NetworkManager.IsConnectedClient && SynchronizeState.IsSynchronizing)
|
||||||
{
|
{
|
||||||
NonAuthorityFinalizeSynchronization();
|
NonAuthorityFinalizeSynchronization();
|
||||||
|
|||||||
@@ -6,13 +6,70 @@ using UnityEngine;
|
|||||||
|
|
||||||
namespace Unity.Netcode.Components
|
namespace Unity.Netcode.Components
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Information a <see cref="Rigidbody"/> returns to <see cref="RigidbodyContactEventManager"/> via <see cref="IContactEventHandlerWithInfo.GetContactEventHandlerInfo"/> <br />
|
||||||
|
/// if the <see cref="Rigidbody"/> registers itself with <see cref="IContactEventHandlerWithInfo"/> as opposed to <see cref="IContactEventHandler"/>.
|
||||||
|
/// </summary>
|
||||||
|
public struct ContactEventHandlerInfo
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will include non-Rigidbody based contact events.<br />
|
||||||
|
/// When the <see cref="RigidbodyContactEventManager"/> invokes the <see cref="IContactEventHandler.ContactEvent"/> it will return null in place <br />
|
||||||
|
/// of the collidingBody parameter if the contact event occurred with a collider that is not registered with the <see cref="RigidbodyContactEventManager"/>.
|
||||||
|
/// </summary>
|
||||||
|
public bool ProvideNonRigidBodyContactEvents;
|
||||||
|
/// <summary>
|
||||||
|
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br /></br>
|
||||||
|
/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a <see cref="NetworkObject"/> is owned by the local client <br />
|
||||||
|
/// will assure <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> is only invoked on the authoritative side.
|
||||||
|
/// </summary>
|
||||||
|
public bool HasContactEventPriority;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default implementation required to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Recommended to implement this method on a <see cref="NetworkBehaviour"/> component
|
||||||
|
/// </remarks>
|
||||||
public interface IContactEventHandler
|
public interface IContactEventHandler
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Should return a <see cref="Rigidbody"/>.
|
||||||
|
/// </summary>
|
||||||
Rigidbody GetRigidbody();
|
Rigidbody GetRigidbody();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked by the <see cref="RigidbodyContactEventManager"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="eventId">A unique contact event identifier.</param>
|
||||||
|
/// <param name="averagedCollisionNormal">The average normal of the collision between two colliders.</param>
|
||||||
|
/// <param name="collidingBody">If not null, this will be a registered <see cref="Rigidbody"/> that was part of the collision contact event.</param>
|
||||||
|
/// <param name="contactPoint">The world space location of the contact event.</param>
|
||||||
|
/// <param name="hasCollisionStay">Will be set if this is a collision stay contact event (i.e. it is not the first contact event and continually has contact)</param>
|
||||||
|
/// <param name="averagedCollisionStayNormal">The average normal of the collision stay contact over time.</param>
|
||||||
void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default);
|
void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This is an extended version of <see cref="IContactEventHandler"/> and can be used to register a <see cref="Rigidbody"/> with a <see cref="RigidbodyContactEventManager"/> instance. <br />
|
||||||
|
/// This provides additional <see cref="ContactEventHandlerInfo"/> information to the <see cref="RigidbodyContactEventManager"/> for each set of contact events it is processing.
|
||||||
|
/// </summary>
|
||||||
|
public interface IContactEventHandlerWithInfo : IContactEventHandler
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked by <see cref="RigidbodyContactEventManager"/> for each set of contact events it is processing (prior to processing).
|
||||||
|
/// </summary>
|
||||||
|
/// <returns><see cref="ContactEventHandlerInfo"/></returns>
|
||||||
|
ContactEventHandlerInfo GetContactEventHandlerInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Add this component to an in-scene placed GameObject to provide faster collision event processing between <see cref="Rigidbody"/> instances and optionally static colliders.
|
||||||
|
/// <see cref="IContactEventHandler"/> <br />
|
||||||
|
/// <see cref="IContactEventHandlerWithInfo"/> <br />
|
||||||
|
/// <see cref="ContactEventHandlerInfo"/> <br />
|
||||||
|
/// </summary>
|
||||||
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
|
[AddComponentMenu("Netcode/Rigidbody Contact Event Manager")]
|
||||||
public class RigidbodyContactEventManager : MonoBehaviour
|
public class RigidbodyContactEventManager : MonoBehaviour
|
||||||
{
|
{
|
||||||
@@ -34,6 +91,7 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
private readonly Dictionary<int, Rigidbody> m_RigidbodyMapping = new Dictionary<int, Rigidbody>();
|
private readonly Dictionary<int, Rigidbody> m_RigidbodyMapping = new Dictionary<int, Rigidbody>();
|
||||||
private readonly Dictionary<int, IContactEventHandler> m_HandlerMapping = new Dictionary<int, IContactEventHandler>();
|
private readonly Dictionary<int, IContactEventHandler> m_HandlerMapping = new Dictionary<int, IContactEventHandler>();
|
||||||
|
private readonly Dictionary<int, ContactEventHandlerInfo> m_HandlerInfo = new Dictionary<int, ContactEventHandlerInfo>();
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
{
|
{
|
||||||
@@ -49,6 +107,15 @@ namespace Unity.Netcode.Components
|
|||||||
Instance = this;
|
Instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Any <see cref="IContactEventHandler"/> implementation can register a <see cref="Rigidbody"/> to be handled by this <see cref="RigidbodyContactEventManager"/> instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// You should enable <see cref="Collider.providesContacts"/> for each <see cref="Collider"/> associated with the <see cref="Rigidbody"/> being registered.<br/>
|
||||||
|
/// You can enable this during run time or within the editor's inspector view.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="contactEventHandler"><see cref="IContactEventHandler"/> or <see cref="IContactEventHandlerWithInfo"/></param>
|
||||||
|
/// <param name="register">true to register and false to remove from being registered</param>
|
||||||
public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true)
|
public void RegisterHandler(IContactEventHandler contactEventHandler, bool register = true)
|
||||||
{
|
{
|
||||||
var rigidbody = contactEventHandler.GetRigidbody();
|
var rigidbody = contactEventHandler.GetRigidbody();
|
||||||
@@ -64,6 +131,22 @@ namespace Unity.Netcode.Components
|
|||||||
{
|
{
|
||||||
m_HandlerMapping.Add(instanceId, contactEventHandler);
|
m_HandlerMapping.Add(instanceId, contactEventHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_HandlerInfo.ContainsKey(instanceId))
|
||||||
|
{
|
||||||
|
var handlerInfo = new ContactEventHandlerInfo()
|
||||||
|
{
|
||||||
|
HasContactEventPriority = true,
|
||||||
|
ProvideNonRigidBodyContactEvents = false,
|
||||||
|
};
|
||||||
|
var handlerWithInfo = contactEventHandler as IContactEventHandlerWithInfo;
|
||||||
|
|
||||||
|
if (handlerWithInfo != null)
|
||||||
|
{
|
||||||
|
handlerInfo = handlerWithInfo.GetContactEventHandlerInfo();
|
||||||
|
}
|
||||||
|
m_HandlerInfo.Add(instanceId, handlerInfo);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -88,25 +171,98 @@ namespace Unity.Netcode.Components
|
|||||||
|
|
||||||
private void ProcessCollisions()
|
private void ProcessCollisions()
|
||||||
{
|
{
|
||||||
|
foreach (var contactEventHandler in m_HandlerMapping)
|
||||||
|
{
|
||||||
|
var handlerWithInfo = contactEventHandler.Value as IContactEventHandlerWithInfo;
|
||||||
|
|
||||||
|
if (handlerWithInfo != null)
|
||||||
|
{
|
||||||
|
m_HandlerInfo[contactEventHandler.Key] = handlerWithInfo.GetContactEventHandlerInfo();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var info = m_HandlerInfo[contactEventHandler.Key];
|
||||||
|
info.HasContactEventPriority = !m_RigidbodyMapping[contactEventHandler.Key].isKinematic;
|
||||||
|
m_HandlerInfo[contactEventHandler.Key] = info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactEventHandlerInfo contactEventHandlerInfo0;
|
||||||
|
ContactEventHandlerInfo contactEventHandlerInfo1;
|
||||||
|
|
||||||
// Process all collisions
|
// Process all collisions
|
||||||
for (int i = 0; i < m_Count; i++)
|
for (int i = 0; i < m_Count; i++)
|
||||||
{
|
{
|
||||||
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
|
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
|
||||||
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
|
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
|
||||||
var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID);
|
var contactHandler0 = (IContactEventHandler)null;
|
||||||
var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID);
|
var contactHandler1 = (IContactEventHandler)null;
|
||||||
// Only notify registered rigid bodies.
|
var preferredContactHandler = (IContactEventHandler)null;
|
||||||
if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID))
|
var preferredContactHandlerNonRigidbody = false;
|
||||||
|
var preferredRigidbody = (Rigidbody)null;
|
||||||
|
var otherContactHandler = (IContactEventHandler)null;
|
||||||
|
var otherRigidbody = (Rigidbody)null;
|
||||||
|
|
||||||
|
var otherContactHandlerNonRigidbody = false;
|
||||||
|
|
||||||
|
if (m_RigidbodyMapping.ContainsKey(thisInstanceID))
|
||||||
|
{
|
||||||
|
contactHandler0 = m_HandlerMapping[thisInstanceID];
|
||||||
|
contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID];
|
||||||
|
if (contactEventHandlerInfo0.HasContactEventPriority)
|
||||||
|
{
|
||||||
|
preferredContactHandler = contactHandler0;
|
||||||
|
preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
|
||||||
|
preferredRigidbody = m_RigidbodyMapping[thisInstanceID];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
otherContactHandler = contactHandler0;
|
||||||
|
otherContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
|
||||||
|
otherRigidbody = m_RigidbodyMapping[thisInstanceID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_RigidbodyMapping.ContainsKey(otherInstanceID))
|
||||||
|
{
|
||||||
|
contactHandler1 = m_HandlerMapping[otherInstanceID];
|
||||||
|
contactEventHandlerInfo1 = m_HandlerInfo[otherInstanceID];
|
||||||
|
if (contactEventHandlerInfo1.HasContactEventPriority && preferredContactHandler == null)
|
||||||
|
{
|
||||||
|
preferredContactHandler = contactHandler1;
|
||||||
|
preferredContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
|
||||||
|
preferredRigidbody = m_RigidbodyMapping[otherInstanceID];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
otherContactHandler = contactHandler1;
|
||||||
|
otherContactHandlerNonRigidbody = contactEventHandlerInfo1.ProvideNonRigidBodyContactEvents;
|
||||||
|
otherRigidbody = m_RigidbodyMapping[otherInstanceID];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredContactHandler == null && otherContactHandler != null)
|
||||||
|
{
|
||||||
|
preferredContactHandler = otherContactHandler;
|
||||||
|
preferredContactHandlerNonRigidbody = otherContactHandlerNonRigidbody;
|
||||||
|
preferredRigidbody = otherRigidbody;
|
||||||
|
otherContactHandler = null;
|
||||||
|
otherContactHandlerNonRigidbody = false;
|
||||||
|
otherRigidbody = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredContactHandler == null || (preferredContactHandler != null && otherContactHandler == null && !preferredContactHandlerNonRigidbody))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_ResultsArray[i].HasCollisionStay)
|
if (m_ResultsArray[i].HasCollisionStay)
|
||||||
{
|
{
|
||||||
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
|
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint);
|
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,8 +105,12 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
peerClientIds[idx] = peerId;
|
// This assures if the server has not timed out prior to the client synchronizing that it doesn't exceed the allocated peer count.
|
||||||
++idx;
|
if (peerClientIds.Length > idx)
|
||||||
|
{
|
||||||
|
peerClientIds[idx] = peerId;
|
||||||
|
++idx;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -496,24 +500,32 @@ namespace Unity.Netcode
|
|||||||
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
|
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
|
||||||
MessageManager.ProcessIncomingMessageQueue();
|
MessageManager.ProcessIncomingMessageQueue();
|
||||||
|
|
||||||
InvokeOnClientDisconnectCallback(clientId);
|
|
||||||
|
|
||||||
if (LocalClient.IsHost)
|
|
||||||
{
|
|
||||||
InvokeOnPeerDisconnectedCallback(clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (LocalClient.IsServer)
|
if (LocalClient.IsServer)
|
||||||
{
|
{
|
||||||
|
// We need to process the disconnection before notifying
|
||||||
OnClientDisconnectFromServer(clientId);
|
OnClientDisconnectFromServer(clientId);
|
||||||
|
|
||||||
|
// Now notify the client has disconnected
|
||||||
|
InvokeOnClientDisconnectCallback(clientId);
|
||||||
|
|
||||||
|
if (LocalClient.IsHost)
|
||||||
|
{
|
||||||
|
InvokeOnPeerDisconnectedCallback(clientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else // As long as we are not in the middle of a shutdown
|
else
|
||||||
if (!NetworkManager.ShutdownInProgress)
|
|
||||||
{
|
{
|
||||||
// We must pass true here and not process any sends messages as we are no longer connected.
|
// Notify local client of disconnection
|
||||||
// Otherwise, attempting to process messages here can cause an exception within UnityTransport
|
InvokeOnClientDisconnectCallback(clientId);
|
||||||
// as the client ID is no longer valid.
|
|
||||||
NetworkManager.Shutdown(true);
|
// As long as we are not in the middle of a shutdown
|
||||||
|
if (!NetworkManager.ShutdownInProgress)
|
||||||
|
{
|
||||||
|
// We must pass true here and not process any sends messages as we are no longer connected.
|
||||||
|
// Otherwise, attempting to process messages here can cause an exception within UnityTransport
|
||||||
|
// as the client ID is no longer valid.
|
||||||
|
NetworkManager.Shutdown(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
s_TransportDisconnect.End();
|
s_TransportDisconnect.End();
|
||||||
@@ -552,9 +564,6 @@ namespace Unity.Netcode
|
|||||||
var message = new ConnectionRequestMessage
|
var message = new ConnectionRequestMessage
|
||||||
{
|
{
|
||||||
CMBServiceConnection = NetworkManager.CMBServiceConnection,
|
CMBServiceConnection = NetworkManager.CMBServiceConnection,
|
||||||
TickRate = NetworkManager.NetworkConfig.TickRate,
|
|
||||||
EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement,
|
|
||||||
|
|
||||||
// Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value
|
// Since only a remote client will send a connection request, we should always force the rebuilding of the NetworkConfig hash value
|
||||||
ConfigHash = NetworkManager.NetworkConfig.GetConfig(false),
|
ConfigHash = NetworkManager.NetworkConfig.GetConfig(false),
|
||||||
ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval,
|
ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval,
|
||||||
@@ -562,6 +571,12 @@ namespace Unity.Netcode
|
|||||||
MessageVersions = new NativeArray<MessageVersionData>(MessageManager.MessageHandlers.Length, Allocator.Temp)
|
MessageVersions = new NativeArray<MessageVersionData>(MessageManager.MessageHandlers.Length, Allocator.Temp)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (NetworkManager.CMBServiceConnection)
|
||||||
|
{
|
||||||
|
message.ClientConfig.TickRate = NetworkManager.NetworkConfig.TickRate;
|
||||||
|
message.ClientConfig.EnableSceneManagement = NetworkManager.NetworkConfig.EnableSceneManagement;
|
||||||
|
}
|
||||||
|
|
||||||
for (int index = 0; index < MessageManager.MessageHandlers.Length; index++)
|
for (int index = 0; index < MessageManager.MessageHandlers.Length; index++)
|
||||||
{
|
{
|
||||||
if (MessageManager.MessageTypes[index] != null)
|
if (MessageManager.MessageTypes[index] != null)
|
||||||
@@ -739,8 +754,8 @@ namespace Unity.Netcode
|
|||||||
// Server-side spawning (only if there is a prefab hash or player prefab provided)
|
// Server-side spawning (only if there is a prefab hash or player prefab provided)
|
||||||
if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
|
if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && (response.PlayerPrefabHash.HasValue || NetworkManager.NetworkConfig.PlayerPrefab != null))
|
||||||
{
|
{
|
||||||
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault())
|
var playerObject = response.PlayerPrefabHash.HasValue ? NetworkManager.SpawnManager.GetNetworkObjectToSpawn(response.PlayerPrefabHash.Value, ownerClientId, response.Position ?? null, response.Rotation ?? null)
|
||||||
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position.GetValueOrDefault(), response.Rotation.GetValueOrDefault());
|
: NetworkManager.SpawnManager.GetNetworkObjectToSpawn(NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash, ownerClientId, response.Position ?? null, response.Rotation ?? null);
|
||||||
|
|
||||||
// Spawn the player NetworkObject locally
|
// Spawn the player NetworkObject locally
|
||||||
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
|
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
|
||||||
@@ -884,7 +899,7 @@ namespace Unity.Netcode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Client-Side Spawning in distributed authority mode uses this to spawn the player.
|
/// Client-Side Spawning in distributed authority mode uses this to spawn the player.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Quaternion rotation = default)
|
internal void CreateAndSpawnPlayer(ulong ownerId)
|
||||||
{
|
{
|
||||||
if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide)
|
if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide)
|
||||||
{
|
{
|
||||||
@@ -892,7 +907,7 @@ namespace Unity.Netcode
|
|||||||
if (playerPrefab != null)
|
if (playerPrefab != null)
|
||||||
{
|
{
|
||||||
var globalObjectIdHash = playerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
var globalObjectIdHash = playerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash;
|
||||||
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, position, rotation);
|
var networkObject = NetworkManager.SpawnManager.GetNetworkObjectToSpawn(globalObjectIdHash, ownerId, playerPrefab.transform.position, playerPrefab.transform.rotation);
|
||||||
networkObject.IsSceneObject = false;
|
networkObject.IsSceneObject = false;
|
||||||
networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);
|
networkObject.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -826,6 +826,13 @@ namespace Unity.Netcode
|
|||||||
internal void InternalOnGainedOwnership()
|
internal void InternalOnGainedOwnership()
|
||||||
{
|
{
|
||||||
UpdateNetworkProperties();
|
UpdateNetworkProperties();
|
||||||
|
// New owners need to assure any NetworkVariables they have write permissions
|
||||||
|
// to are updated so the previous and original values are aligned with the
|
||||||
|
// current value (primarily for collections).
|
||||||
|
if (OwnerClientId == NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
UpdateNetworkVariableOnOwnershipChanged();
|
||||||
|
}
|
||||||
OnGainedOwnership();
|
OnGainedOwnership();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1016,9 +1023,14 @@ namespace Unity.Netcode
|
|||||||
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
internal readonly List<int> NetworkVariableIndexesToReset = new List<int>();
|
||||||
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
internal readonly HashSet<int> NetworkVariableIndexesToResetSet = new HashSet<int>();
|
||||||
|
|
||||||
internal void NetworkVariableUpdate(ulong targetClientId)
|
/// <summary>
|
||||||
|
/// Determines if a NetworkVariable should have any changes to state sent out
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="targetClientId">target to send the updates to</param>
|
||||||
|
/// <param name="forceSend">specific to change in ownership</param>
|
||||||
|
internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false)
|
||||||
{
|
{
|
||||||
if (!CouldHaveDirtyNetworkVariables())
|
if (!forceSend && !CouldHaveDirtyNetworkVariables())
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1069,7 +1081,11 @@ namespace Unity.Netcode
|
|||||||
NetworkBehaviourIndex = behaviourIndex,
|
NetworkBehaviourIndex = behaviourIndex,
|
||||||
NetworkBehaviour = this,
|
NetworkBehaviour = this,
|
||||||
TargetClientId = targetClientId,
|
TargetClientId = targetClientId,
|
||||||
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j]
|
DeliveryMappedNetworkVariableIndex = m_DeliveryMappedNetworkVariableIndices[j],
|
||||||
|
// By sending the network delivery we can forward messages immediately as opposed to processing them
|
||||||
|
// at the end. While this will send updates to clients that cannot read, the handler will ignore anything
|
||||||
|
// sent to a client that does not have read permissions.
|
||||||
|
NetworkDelivery = m_DeliveryTypesForNetworkVariableGroups[j]
|
||||||
};
|
};
|
||||||
// TODO: Serialization is where the IsDirty flag gets changed.
|
// TODO: Serialization is where the IsDirty flag gets changed.
|
||||||
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
|
||||||
@@ -1114,6 +1130,26 @@ namespace Unity.Netcode
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Invoked on a new client to assure the previous and original values
|
||||||
|
/// are synchronized with the current known value.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Primarily for collections to assure the previous value(s) is/are the
|
||||||
|
/// same as the current value(s) in order to not re-send already known entries.
|
||||||
|
/// </remarks>
|
||||||
|
internal void UpdateNetworkVariableOnOwnershipChanged()
|
||||||
|
{
|
||||||
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||||
|
{
|
||||||
|
// Only invoke OnInitialize on NetworkVariables the owner can write to
|
||||||
|
if (NetworkVariableFields[j].CanClientWrite(OwnerClientId))
|
||||||
|
{
|
||||||
|
NetworkVariableFields[j].OnInitialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal void MarkVariablesDirty(bool dirty)
|
internal void MarkVariablesDirty(bool dirty)
|
||||||
{
|
{
|
||||||
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||||
@@ -1122,6 +1158,17 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void MarkOwnerReadVariablesDirty()
|
||||||
|
{
|
||||||
|
for (int j = 0; j < NetworkVariableFields.Count; j++)
|
||||||
|
{
|
||||||
|
if (NetworkVariableFields[j].ReadPerm == NetworkVariableReadPermission.Owner)
|
||||||
|
{
|
||||||
|
NetworkVariableFields[j].SetDirty(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
/// Synchronizes by setting only the NetworkVariable field values that the client has permission to read.
|
||||||
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
/// Note: This is only invoked when first synchronizing a NetworkBehaviour (i.e. late join or spawned NetworkObject)
|
||||||
@@ -1172,17 +1219,24 @@ namespace Unity.Netcode
|
|||||||
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
|
// The way we do packing, any value > 63 in a ushort will use the full 2 bytes to represent.
|
||||||
writer.WriteValueSafe((ushort)0);
|
writer.WriteValueSafe((ushort)0);
|
||||||
var startPos = writer.Position;
|
var startPos = writer.Position;
|
||||||
NetworkVariableFields[j].WriteField(writer);
|
// Write the NetworkVariable field value
|
||||||
|
// WriteFieldSynchronization will write the current value only if there are no pending changes.
|
||||||
|
// Otherwise, it will write the previous value if there are pending changes since the pending
|
||||||
|
// changes will be sent shortly after the client's synchronization.
|
||||||
|
NetworkVariableFields[j].WriteFieldSynchronization(writer);
|
||||||
var size = writer.Position - startPos;
|
var size = writer.Position - startPos;
|
||||||
writer.Seek(writePos);
|
writer.Seek(writePos);
|
||||||
// Write the NetworkVariable value
|
// Write the NetworkVariable field value size
|
||||||
writer.WriteValueSafe((ushort)size);
|
writer.WriteValueSafe((ushort)size);
|
||||||
writer.Seek(startPos + size);
|
writer.Seek(startPos + size);
|
||||||
}
|
}
|
||||||
else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
|
else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
|
||||||
{
|
{
|
||||||
// Write the NetworkVariable value
|
// Write the NetworkVariable field value
|
||||||
NetworkVariableFields[j].WriteField(writer);
|
// WriteFieldSynchronization will write the current value only if there are no pending changes.
|
||||||
|
// Otherwise, it will write the previous value if there are pending changes since the pending
|
||||||
|
// changes will be sent shortly after the client's synchronization.
|
||||||
|
NetworkVariableFields[j].WriteFieldSynchronization(writer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (ensureLengthSafety)
|
else if (ensureLengthSafety)
|
||||||
|
|||||||
@@ -19,10 +19,15 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal void AddForUpdate(NetworkObject networkObject)
|
internal void AddForUpdate(NetworkObject networkObject)
|
||||||
{
|
{
|
||||||
|
// Since this is a HashSet, we don't need to worry about duplicate entries
|
||||||
m_PendingDirtyNetworkObjects.Add(networkObject);
|
m_PendingDirtyNetworkObjects.Add(networkObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void NetworkBehaviourUpdate()
|
/// <summary>
|
||||||
|
/// Sends NetworkVariable deltas
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="forceSend">internal only, when changing ownership we want to send this before the change in ownership message</param>
|
||||||
|
internal void NetworkBehaviourUpdate(bool forceSend = false)
|
||||||
{
|
{
|
||||||
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
#if DEVELOPMENT_BUILD || UNITY_EDITOR
|
||||||
m_NetworkBehaviourUpdate.Begin();
|
m_NetworkBehaviourUpdate.Begin();
|
||||||
@@ -53,7 +58,7 @@ namespace Unity.Netcode
|
|||||||
// Sync just the variables for just the objects this client sees
|
// Sync just the variables for just the objects this client sees
|
||||||
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
|
for (int k = 0; k < dirtyObj.ChildNetworkBehaviours.Count; k++)
|
||||||
{
|
{
|
||||||
dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId);
|
dirtyObj.ChildNetworkBehaviours[k].NetworkVariableUpdate(client.ClientId, forceSend);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -72,7 +77,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
for (int k = 0; k < sobj.ChildNetworkBehaviours.Count; k++)
|
||||||
{
|
{
|
||||||
sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId);
|
sobj.ChildNetworkBehaviours[k].NetworkVariableUpdate(NetworkManager.ServerClientId, forceSend);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,19 +90,24 @@ namespace Unity.Netcode
|
|||||||
var behaviour = dirtyObj.ChildNetworkBehaviours[k];
|
var behaviour = dirtyObj.ChildNetworkBehaviours[k];
|
||||||
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
for (int i = 0; i < behaviour.NetworkVariableFields.Count; i++)
|
||||||
{
|
{
|
||||||
|
// Set to true for NetworkVariable to ignore duplication of the
|
||||||
|
// "internal original value" for collections support.
|
||||||
|
behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = true;
|
||||||
if (behaviour.NetworkVariableFields[i].IsDirty() &&
|
if (behaviour.NetworkVariableFields[i].IsDirty() &&
|
||||||
!behaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
!behaviour.NetworkVariableIndexesToResetSet.Contains(i))
|
||||||
{
|
{
|
||||||
behaviour.NetworkVariableIndexesToResetSet.Add(i);
|
behaviour.NetworkVariableIndexesToResetSet.Add(i);
|
||||||
behaviour.NetworkVariableIndexesToReset.Add(i);
|
behaviour.NetworkVariableIndexesToReset.Add(i);
|
||||||
}
|
}
|
||||||
|
// Reset back to false when done
|
||||||
|
behaviour.NetworkVariableFields[i].NetworkUpdaterCheck = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Now, reset all the no-longer-dirty variables
|
// Now, reset all the no-longer-dirty variables
|
||||||
foreach (var dirtyobj in m_DirtyNetworkObjects)
|
foreach (var dirtyobj in m_DirtyNetworkObjects)
|
||||||
{
|
{
|
||||||
dirtyobj.PostNetworkVariableWrite();
|
dirtyobj.PostNetworkVariableWrite(forceSend);
|
||||||
// Once done processing, we set the previous owner id to the current owner id
|
// Once done processing, we set the previous owner id to the current owner id
|
||||||
dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId;
|
dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
using UnityEditor;
|
using UnityEditor;
|
||||||
|
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
|
||||||
#endif
|
#endif
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
using Debug = UnityEngine.Debug;
|
using Debug = UnityEngine.Debug;
|
||||||
@@ -17,6 +18,17 @@ namespace Unity.Netcode
|
|||||||
[AddComponentMenu("Netcode/Network Manager", -100)]
|
[AddComponentMenu("Netcode/Network Manager", -100)]
|
||||||
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
|
public class NetworkManager : MonoBehaviour, INetworkUpdateSystem
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance has been instantiated.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<NetworkManager> OnInstantiated;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscribe to this static event to get notifications when a <see cref="NetworkManager"/> instance is being destroyed.
|
||||||
|
/// </summary>
|
||||||
|
public static event Action<NetworkManager> OnDestroying;
|
||||||
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
// Inspector view expand/collapse settings for this derived child class
|
// Inspector view expand/collapse settings for this derived child class
|
||||||
[HideInInspector]
|
[HideInInspector]
|
||||||
@@ -874,6 +886,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal Override<ushort> PortOverride;
|
internal Override<ushort> PortOverride;
|
||||||
|
|
||||||
|
|
||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
internal static INetworkManagerHelper NetworkManagerHelper;
|
internal static INetworkManagerHelper NetworkManagerHelper;
|
||||||
|
|
||||||
@@ -900,6 +913,11 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private PackageInfo GetPackageInfo(string packageName)
|
||||||
|
{
|
||||||
|
return AssetDatabase.FindAssets("package").Select(AssetDatabase.GUIDToAssetPath).Where(x => AssetDatabase.LoadAssetAtPath<TextAsset>(x) != null).Select(PackageInfo.FindForAssetPath).Where(x => x != null).First(x => x.name == packageName);
|
||||||
|
}
|
||||||
|
|
||||||
internal void OnValidate()
|
internal void OnValidate()
|
||||||
{
|
{
|
||||||
if (NetworkConfig == null)
|
if (NetworkConfig == null)
|
||||||
@@ -1030,6 +1048,8 @@ namespace Unity.Netcode
|
|||||||
#if UNITY_EDITOR
|
#if UNITY_EDITOR
|
||||||
EditorApplication.playModeStateChanged += ModeChanged;
|
EditorApplication.playModeStateChanged += ModeChanged;
|
||||||
#endif
|
#endif
|
||||||
|
// Notify we have instantiated a new instance of NetworkManager.
|
||||||
|
OnInstantiated?.Invoke(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnEnable()
|
private void OnEnable()
|
||||||
@@ -1141,9 +1161,6 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
UpdateTopology();
|
UpdateTopology();
|
||||||
|
|
||||||
//DANGOEXP TODO: Remove this before finalizing the experimental release
|
|
||||||
NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode;
|
|
||||||
|
|
||||||
// Make sure the ServerShutdownState is reset when initializing
|
// Make sure the ServerShutdownState is reset when initializing
|
||||||
if (server)
|
if (server)
|
||||||
{
|
{
|
||||||
@@ -1632,6 +1649,9 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
|
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
|
||||||
|
|
||||||
|
// Notify we are destroying NetworkManager
|
||||||
|
OnDestroying?.Invoke(this);
|
||||||
|
|
||||||
if (Singleton == this)
|
if (Singleton == this)
|
||||||
{
|
{
|
||||||
Singleton = null;
|
Singleton = null;
|
||||||
|
|||||||
@@ -113,11 +113,6 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle updating the currently active scene
|
// Handle updating the currently active scene
|
||||||
var networkObjects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
|
|
||||||
foreach (var networkObject in networkObjects)
|
|
||||||
{
|
|
||||||
networkObject.OnValidate();
|
|
||||||
}
|
|
||||||
NetworkObjectRefreshTool.ProcessActiveScene();
|
NetworkObjectRefreshTool.ProcessActiveScene();
|
||||||
|
|
||||||
// Refresh all build settings scenes
|
// Refresh all build settings scenes
|
||||||
@@ -130,14 +125,14 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
// Add the scene to be processed
|
// Add the scene to be processed
|
||||||
NetworkObjectRefreshTool.ProcessScene(editorScene.path, false);
|
NetworkObjectRefreshTool.ProcessScene(editorScene.path, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process all added scenes
|
// Process all added scenes
|
||||||
NetworkObjectRefreshTool.ProcessScenes();
|
NetworkObjectRefreshTool.ProcessScenes();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnValidate()
|
internal void OnValidate()
|
||||||
{
|
{
|
||||||
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
|
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
|
||||||
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
|
||||||
@@ -229,6 +224,7 @@ namespace Unity.Netcode
|
|||||||
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
|
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
|
||||||
{
|
{
|
||||||
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
|
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
|
||||||
|
EditorUtility.SetDirty(this);
|
||||||
}
|
}
|
||||||
IsSceneObject = true;
|
IsSceneObject = true;
|
||||||
}
|
}
|
||||||
@@ -340,7 +336,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (!HasAuthority)
|
if (!HasAuthority)
|
||||||
{
|
{
|
||||||
NetworkLog.LogError($"Only the authoirty can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!");
|
NetworkLog.LogError($"Only the authority can invoke {nameof(DeferDespawn)} and local Client-{NetworkManager.LocalClientId} is not the authority of {name}!");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1613,7 +1609,12 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
|
else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
|
||||||
{
|
{
|
||||||
NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this);
|
// If spawning with observers or if not spawning with observers but the observer count is greater than 1 (i.e. owner/authority creating),
|
||||||
|
// then we want to send a spawn notification.
|
||||||
|
if (SpawnWithObservers || !SpawnWithObservers && Observers.Count > 1)
|
||||||
|
{
|
||||||
|
NetworkManager.SpawnManager.SendSpawnCallForObject(NetworkManager.ServerClientId, this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2444,6 +2445,14 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal void MarkOwnerReadVariablesDirty()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
|
||||||
|
{
|
||||||
|
ChildNetworkBehaviours[i].MarkOwnerReadVariablesDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
|
// NGO currently guarantees that the client will receive spawn data for all objects in one network tick.
|
||||||
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
|
// Children may arrive before their parents; when they do they are stored in OrphanedChildren and then
|
||||||
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
|
// resolved when their parents arrived. Because we don't send a partial list of spawns (yet), something
|
||||||
@@ -2770,11 +2779,11 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void PostNetworkVariableWrite()
|
internal void PostNetworkVariableWrite(bool forced = false)
|
||||||
{
|
{
|
||||||
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
|
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
|
||||||
{
|
{
|
||||||
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
|
ChildNetworkBehaviours[k].PostNetworkVariableWrite(forced);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3053,10 +3062,15 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all known players to the observers list if they don't already exist
|
// Only add all other players as observers if we are spawning with observers,
|
||||||
foreach (var player in networkManager.SpawnManager.PlayerObjects)
|
// otherwise user controls via NetworkShow.
|
||||||
|
if (networkObject.SpawnWithObservers)
|
||||||
{
|
{
|
||||||
networkObject.Observers.Add(player.OwnerClientId);
|
// Add all known players to the observers list if they don't already exist
|
||||||
|
foreach (var player in networkManager.SpawnManager.PlayerObjects)
|
||||||
|
{
|
||||||
|
networkObject.Observers.Add(player.OwnerClientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using UnityEditor;
|
||||||
using UnityEditor.SceneManagement;
|
using UnityEditor.SceneManagement;
|
||||||
using UnityEngine;
|
using UnityEngine;
|
||||||
using UnityEngine.SceneManagement;
|
using UnityEngine.SceneManagement;
|
||||||
@@ -21,6 +23,28 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal static Action AllScenesProcessed;
|
internal static Action AllScenesProcessed;
|
||||||
|
|
||||||
|
internal static NetworkObject PrefabNetworkObject;
|
||||||
|
|
||||||
|
internal static void LogInfo(string msg, bool append = false)
|
||||||
|
{
|
||||||
|
if (!append)
|
||||||
|
{
|
||||||
|
s_Log.AppendLine(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_Log.Append(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void FlushLog()
|
||||||
|
{
|
||||||
|
Debug.Log(s_Log.ToString());
|
||||||
|
s_Log.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StringBuilder s_Log = new StringBuilder();
|
||||||
|
|
||||||
internal static void ProcessScene(string scenePath, bool processScenes = true)
|
internal static void ProcessScene(string scenePath, bool processScenes = true)
|
||||||
{
|
{
|
||||||
if (!s_ScenesToUpdate.Contains(scenePath))
|
if (!s_ScenesToUpdate.Contains(scenePath))
|
||||||
@@ -29,7 +53,10 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
|
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
|
||||||
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
|
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
|
||||||
|
s_Log.Clear();
|
||||||
|
LogInfo("NetworkObject Refresh Scenes to Process:");
|
||||||
}
|
}
|
||||||
|
LogInfo($"[{scenePath}]", true);
|
||||||
s_ScenesToUpdate.Add(scenePath);
|
s_ScenesToUpdate.Add(scenePath);
|
||||||
}
|
}
|
||||||
s_ProcessScenes = processScenes;
|
s_ProcessScenes = processScenes;
|
||||||
@@ -37,6 +64,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
internal static void ProcessActiveScene()
|
internal static void ProcessActiveScene()
|
||||||
{
|
{
|
||||||
|
FlushLog();
|
||||||
var activeScene = SceneManager.GetActiveScene();
|
var activeScene = SceneManager.GetActiveScene();
|
||||||
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
|
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
|
||||||
{
|
{
|
||||||
@@ -54,10 +82,12 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
s_ProcessScenes = false;
|
||||||
s_CloseScenes = false;
|
s_CloseScenes = false;
|
||||||
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
|
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
|
||||||
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
|
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
|
||||||
AllScenesProcessed?.Invoke();
|
AllScenesProcessed?.Invoke();
|
||||||
|
FlushLog();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,9 +98,8 @@ namespace Unity.Netcode
|
|||||||
// Provide a log of all scenes that were modified to the user
|
// Provide a log of all scenes that were modified to the user
|
||||||
if (refreshed)
|
if (refreshed)
|
||||||
{
|
{
|
||||||
Debug.Log($"Refreshed and saved updates to scene: {scene.name}");
|
LogInfo($"Refreshed and saved updates to scene: {scene.name}");
|
||||||
}
|
}
|
||||||
s_ProcessScenes = false;
|
|
||||||
s_ScenesToUpdate.Remove(scene.path);
|
s_ScenesToUpdate.Remove(scene.path);
|
||||||
|
|
||||||
if (scene != SceneManager.GetActiveScene())
|
if (scene != SceneManager.GetActiveScene())
|
||||||
@@ -88,24 +117,41 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
private static void SceneOpened(Scene scene)
|
private static void SceneOpened(Scene scene)
|
||||||
{
|
{
|
||||||
|
LogInfo($"Processing scene {scene.name}:");
|
||||||
if (s_ScenesToUpdate.Contains(scene.path))
|
if (s_ScenesToUpdate.Contains(scene.path))
|
||||||
{
|
{
|
||||||
if (s_ProcessScenes)
|
if (s_ProcessScenes)
|
||||||
{
|
{
|
||||||
if (!EditorSceneManager.MarkSceneDirty(scene))
|
var prefabInstances = PrefabUtility.FindAllInstancesOfPrefab(PrefabNetworkObject.gameObject);
|
||||||
|
|
||||||
|
if (prefabInstances.Length > 0)
|
||||||
{
|
{
|
||||||
Debug.Log($"Scene {scene.name} did not get marked as dirty!");
|
var instancesSceneLoadedSpecific = prefabInstances.Where((c) => c.scene == scene).ToList();
|
||||||
FinishedProcessingScene(scene);
|
|
||||||
}
|
if (instancesSceneLoadedSpecific.Count > 0)
|
||||||
else
|
{
|
||||||
{
|
foreach (var prefabInstance in instancesSceneLoadedSpecific)
|
||||||
EditorSceneManager.SaveScene(scene);
|
{
|
||||||
|
prefabInstance.GetComponent<NetworkObject>().OnValidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!EditorSceneManager.MarkSceneDirty(scene))
|
||||||
|
{
|
||||||
|
LogInfo($"Scene {scene.name} did not get marked as dirty!");
|
||||||
|
FinishedProcessingScene(scene);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LogInfo($"Changes detected and applied!");
|
||||||
|
EditorSceneManager.SaveScene(scene);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
LogInfo($"No changes required.");
|
||||||
FinishedProcessingScene(scene);
|
FinishedProcessingScene(scene);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
public ulong OwnerClientId;
|
public ulong OwnerClientId;
|
||||||
// DANGOEXP TODO: Remove these notes or change their format
|
|
||||||
// SERVICE NOTES:
|
// SERVICE NOTES:
|
||||||
// When forwarding the message to clients on the CMB Service side,
|
// When forwarding the message to clients on the CMB Service side,
|
||||||
// you can set the ClientIdCount to 0 and skip writing the ClientIds.
|
// you can set the ClientIdCount to 0 and skip writing the ClientIds.
|
||||||
@@ -258,15 +257,18 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If ownership is changing and this is not an ownership request approval then ignore the OnwerClientId
|
// If ownership is changing and this is not an ownership request approval then ignore the SenderId
|
||||||
// If it is just updating flags then ignore sending to the owner
|
if (OwnershipIsChanging && !RequestApproved && context.SenderId == clientId)
|
||||||
// If it is a request or approving request, then ignore the RequestClientId
|
|
||||||
if ((OwnershipIsChanging && !RequestApproved && OwnerClientId == clientId) || (OwnershipFlagsUpdate && clientId == OwnerClientId)
|
|
||||||
|| ((RequestOwnership || RequestApproved) && clientId == RequestClientId))
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If it is just updating flags then ignore sending to the owner
|
||||||
|
// If it is a request or approving request, then ignore the RequestClientId
|
||||||
|
if ((OwnershipFlagsUpdate && clientId == OwnerClientId) || ((RequestOwnership || RequestApproved) && clientId == RequestClientId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId);
|
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery.Reliable, clientId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -327,10 +329,12 @@ namespace Unity.Netcode
|
|||||||
var networkManager = (NetworkManager)context.SystemOwner;
|
var networkManager = (NetworkManager)context.SystemOwner;
|
||||||
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
var networkObject = networkManager.SpawnManager.SpawnedObjects[NetworkObjectId];
|
||||||
|
|
||||||
// DANGO-TODO: This probably shouldn't be allowed to happen.
|
// Sanity check that we are not sending duplicated change ownership messages
|
||||||
if (networkObject.OwnerClientId == OwnerClientId)
|
if (networkObject.OwnerClientId == OwnerClientId)
|
||||||
{
|
{
|
||||||
UnityEngine.Debug.LogWarning($"Unnecessary ownership changed message for {NetworkObjectId}");
|
UnityEngine.Debug.LogError($"Unnecessary ownership changed message for {NetworkObjectId}.");
|
||||||
|
// Ignore the message
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var originalOwner = networkObject.OwnerClientId;
|
var originalOwner = networkObject.OwnerClientId;
|
||||||
@@ -347,12 +351,6 @@ namespace Unity.Netcode
|
|||||||
networkObject.InvokeBehaviourOnLostOwnership();
|
networkObject.InvokeBehaviourOnLostOwnership();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We are new owner or (client-server) or running in distributed authority mode
|
|
||||||
if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode)
|
|
||||||
{
|
|
||||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
|
||||||
}
|
|
||||||
|
|
||||||
// If in distributed authority mode
|
// If in distributed authority mode
|
||||||
if (networkManager.DistributedAuthorityMode)
|
if (networkManager.DistributedAuthorityMode)
|
||||||
{
|
{
|
||||||
@@ -374,6 +372,22 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We are new owner or (client-server) or running in distributed authority mode
|
||||||
|
if (OwnerClientId == networkManager.LocalClientId || networkManager.DistributedAuthorityMode)
|
||||||
|
{
|
||||||
|
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (originalOwner == networkManager.LocalClientId && !networkManager.DistributedAuthorityMode)
|
||||||
|
{
|
||||||
|
// Mark any owner read variables as dirty
|
||||||
|
networkObject.MarkOwnerReadVariablesDirty();
|
||||||
|
// Immediately queue any pending deltas and order the message before the
|
||||||
|
// change in ownership message.
|
||||||
|
networkManager.BehaviourUpdater.NetworkBehaviourUpdate(true);
|
||||||
|
}
|
||||||
|
|
||||||
// Always invoke ownership change notifications
|
// Always invoke ownership change notifications
|
||||||
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
|
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);
|
||||||
|
|
||||||
|
|||||||
@@ -3,14 +3,39 @@ using Unity.Collections;
|
|||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
|
internal struct ServiceConfig : INetworkSerializable
|
||||||
|
{
|
||||||
|
public uint Version;
|
||||||
|
public bool IsRestoredSession;
|
||||||
|
public ulong CurrentSessionOwner;
|
||||||
|
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
|
{
|
||||||
|
if (serializer.IsWriter)
|
||||||
|
{
|
||||||
|
BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), Version);
|
||||||
|
serializer.SerializeValue(ref IsRestoredSession);
|
||||||
|
BytePacker.WriteValueBitPacked(serializer.GetFastBufferWriter(), CurrentSessionOwner);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out Version);
|
||||||
|
serializer.SerializeValue(ref IsRestoredSession);
|
||||||
|
ByteUnpacker.ReadValueBitPacked(serializer.GetFastBufferReader(), out CurrentSessionOwner);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal struct ConnectionApprovedMessage : INetworkMessage
|
internal struct ConnectionApprovedMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
|
private const int k_AddCMBServiceConfig = 2;
|
||||||
private const int k_VersionAddClientIds = 1;
|
private const int k_VersionAddClientIds = 1;
|
||||||
public int Version => k_VersionAddClientIds;
|
public int Version => k_AddCMBServiceConfig;
|
||||||
|
|
||||||
public ulong OwnerClientId;
|
public ulong OwnerClientId;
|
||||||
public int NetworkTick;
|
public int NetworkTick;
|
||||||
// The cloud state service should set this if we are restoring a session
|
// The cloud state service should set this if we are restoring a session
|
||||||
|
public ServiceConfig ServiceConfig;
|
||||||
public bool IsRestoredSession;
|
public bool IsRestoredSession;
|
||||||
public ulong CurrentSessionOwner;
|
public ulong CurrentSessionOwner;
|
||||||
// Not serialized
|
// Not serialized
|
||||||
@@ -25,6 +50,32 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
public NativeArray<ulong> ConnectedClientIds;
|
public NativeArray<ulong> ConnectedClientIds;
|
||||||
|
|
||||||
|
private int m_ReceiveMessageVersion;
|
||||||
|
|
||||||
|
private ulong GetSessionOwner()
|
||||||
|
{
|
||||||
|
if (m_ReceiveMessageVersion >= k_AddCMBServiceConfig)
|
||||||
|
{
|
||||||
|
return ServiceConfig.CurrentSessionOwner;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return CurrentSessionOwner;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool GetIsSessionRestor()
|
||||||
|
{
|
||||||
|
if (m_ReceiveMessageVersion >= k_AddCMBServiceConfig)
|
||||||
|
{
|
||||||
|
return ServiceConfig.IsRestoredSession;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return IsRestoredSession;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
{
|
{
|
||||||
// ============================================================
|
// ============================================================
|
||||||
@@ -45,8 +96,17 @@ namespace Unity.Netcode
|
|||||||
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
BytePacker.WriteValueBitPacked(writer, NetworkTick);
|
||||||
if (IsDistributedAuthority)
|
if (IsDistributedAuthority)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(IsRestoredSession);
|
if (targetVersion >= k_AddCMBServiceConfig)
|
||||||
BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner);
|
{
|
||||||
|
ServiceConfig.IsRestoredSession = false;
|
||||||
|
ServiceConfig.CurrentSessionOwner = CurrentSessionOwner;
|
||||||
|
writer.WriteNetworkSerializable(ServiceConfig);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(IsRestoredSession);
|
||||||
|
BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (targetVersion >= k_VersionAddClientIds)
|
if (targetVersion >= k_VersionAddClientIds)
|
||||||
@@ -122,13 +182,20 @@ namespace Unity.Netcode
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// END FORBIDDEN SEGMENT
|
// END FORBIDDEN SEGMENT
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
m_ReceiveMessageVersion = receivedMessageVersion;
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
|
||||||
if (networkManager.DistributedAuthorityMode)
|
if (networkManager.DistributedAuthorityMode)
|
||||||
{
|
{
|
||||||
reader.ReadValueSafe(out IsRestoredSession);
|
if (receivedMessageVersion >= k_AddCMBServiceConfig)
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner);
|
{
|
||||||
|
reader.ReadNetworkSerializable(out ServiceConfig);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out IsRestoredSession);
|
||||||
|
ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (receivedMessageVersion >= k_VersionAddClientIds)
|
if (receivedMessageVersion >= k_VersionAddClientIds)
|
||||||
@@ -157,7 +224,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (networkManager.DistributedAuthorityMode)
|
if (networkManager.DistributedAuthorityMode)
|
||||||
{
|
{
|
||||||
networkManager.SetSessionOwner(CurrentSessionOwner);
|
networkManager.SetSessionOwner(GetSessionOwner());
|
||||||
if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
|
||||||
{
|
{
|
||||||
networkManager.SceneManager.InitializeScenesLoaded();
|
networkManager.SceneManager.InitializeScenesLoaded();
|
||||||
@@ -233,9 +300,9 @@ namespace Unity.Netcode
|
|||||||
// Mark the client being connected
|
// Mark the client being connected
|
||||||
networkManager.IsConnectedClient = true;
|
networkManager.IsConnectedClient = true;
|
||||||
|
|
||||||
networkManager.SceneManager.IsRestoringSession = IsRestoredSession;
|
networkManager.SceneManager.IsRestoringSession = GetIsSessionRestor();
|
||||||
|
|
||||||
if (!IsRestoredSession)
|
if (!networkManager.SceneManager.IsRestoringSession)
|
||||||
{
|
{
|
||||||
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
// Synchronize the service with the initial session owner's loaded scenes and spawned objects
|
||||||
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);
|
||||||
|
|||||||
@@ -2,16 +2,54 @@ using Unity.Collections;
|
|||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
{
|
{
|
||||||
internal struct ConnectionRequestMessage : INetworkMessage
|
/// <summary>
|
||||||
|
/// Only used when connecting to the distributed authority service
|
||||||
|
/// </summary>
|
||||||
|
internal struct ClientConfig : INetworkSerializable
|
||||||
{
|
{
|
||||||
public int Version => 0;
|
/// <summary>
|
||||||
|
/// We start at version 1, where anything less than version 1 on the service side
|
||||||
public ulong ConfigHash;
|
/// is not bypass feature compatible.
|
||||||
|
/// </summary>
|
||||||
public bool CMBServiceConnection;
|
private const int k_BypassFeatureCompatible = 1;
|
||||||
|
public int Version => k_BypassFeatureCompatible;
|
||||||
public uint TickRate;
|
public uint TickRate;
|
||||||
public bool EnableSceneManagement;
|
public bool EnableSceneManagement;
|
||||||
|
|
||||||
|
// Only gets deserialized but should never be used unless testing
|
||||||
|
public int RemoteClientVersion;
|
||||||
|
|
||||||
|
public void NetworkSerialize<T>(BufferSerializer<T> serializer) where T : IReaderWriter
|
||||||
|
{
|
||||||
|
if (serializer.IsWriter)
|
||||||
|
{
|
||||||
|
var writer = serializer.GetFastBufferWriter();
|
||||||
|
BytePacker.WriteValueBitPacked(writer, Version);
|
||||||
|
BytePacker.WriteValueBitPacked(writer, TickRate);
|
||||||
|
writer.WriteValueSafe(EnableSceneManagement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var reader = serializer.GetFastBufferReader();
|
||||||
|
ByteUnpacker.ReadValueBitPacked(reader, out RemoteClientVersion);
|
||||||
|
ByteUnpacker.ReadValueBitPacked(reader, out TickRate);
|
||||||
|
reader.ReadValueSafe(out EnableSceneManagement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ConnectionRequestMessage : INetworkMessage
|
||||||
|
{
|
||||||
|
// This version update is unidirectional (client to service) and version
|
||||||
|
// handling occurs on the service side. This serialized data is never sent
|
||||||
|
// to a host or server.
|
||||||
|
private const int k_SendClientConfigToService = 1;
|
||||||
|
public int Version => k_SendClientConfigToService;
|
||||||
|
|
||||||
|
public ulong ConfigHash;
|
||||||
|
public bool CMBServiceConnection;
|
||||||
|
public ClientConfig ClientConfig;
|
||||||
|
|
||||||
public byte[] ConnectionData;
|
public byte[] ConnectionData;
|
||||||
|
|
||||||
public bool ShouldSendConnectionData;
|
public bool ShouldSendConnectionData;
|
||||||
@@ -36,8 +74,7 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (CMBServiceConnection)
|
if (CMBServiceConnection)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe(TickRate);
|
writer.WriteNetworkSerializable(ClientConfig);
|
||||||
writer.WriteValueSafe(EnableSceneManagement);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ShouldSendConnectionData)
|
if (ShouldSendConnectionData)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
using Unity.Collections;
|
using Unity.Collections;
|
||||||
|
|
||||||
namespace Unity.Netcode
|
namespace Unity.Netcode
|
||||||
@@ -10,9 +11,22 @@ namespace Unity.Netcode
|
|||||||
/// serialization. This is due to the generally amorphous nature of network variable
|
/// serialization. This is due to the generally amorphous nature of network variable
|
||||||
/// deltas, since they're all driven by custom virtual method overloads.
|
/// deltas, since they're all driven by custom virtual method overloads.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Version 1:
|
||||||
|
/// This version -does not- use the "KeepDirty" approach. Instead, the server will forward any state updates
|
||||||
|
/// to the connected clients that are not the sender or the server itself. Each NetworkVariable state update
|
||||||
|
/// included, on a per client basis, is first validated that the client can read the NetworkVariable before
|
||||||
|
/// being added to the m_ForwardUpdates table.
|
||||||
|
/// Version 0:
|
||||||
|
/// The original version uses the "KeepDirty" approach in a client-server network topology where the server
|
||||||
|
/// proxies state updates by "keeping the NetworkVariable(s) dirty" so it will send state updates
|
||||||
|
/// at the end of the frame (but could delay until the next tick).
|
||||||
|
/// </remarks>
|
||||||
internal struct NetworkVariableDeltaMessage : INetworkMessage
|
internal struct NetworkVariableDeltaMessage : INetworkMessage
|
||||||
{
|
{
|
||||||
public int Version => 0;
|
private const int k_ServerDeltaForwardingAndNetworkDelivery = 1;
|
||||||
|
public int Version => k_ServerDeltaForwardingAndNetworkDelivery;
|
||||||
|
|
||||||
|
|
||||||
public ulong NetworkObjectId;
|
public ulong NetworkObjectId;
|
||||||
public ushort NetworkBehaviourIndex;
|
public ushort NetworkBehaviourIndex;
|
||||||
@@ -21,10 +35,62 @@ namespace Unity.Netcode
|
|||||||
public ulong TargetClientId;
|
public ulong TargetClientId;
|
||||||
public NetworkBehaviour NetworkBehaviour;
|
public NetworkBehaviour NetworkBehaviour;
|
||||||
|
|
||||||
|
public NetworkDelivery NetworkDelivery;
|
||||||
|
|
||||||
private FastBufferReader m_ReceivedNetworkVariableData;
|
private FastBufferReader m_ReceivedNetworkVariableData;
|
||||||
|
|
||||||
|
private bool m_ForwardingMessage;
|
||||||
|
|
||||||
|
private int m_ReceivedMessageVersion;
|
||||||
|
|
||||||
private const string k_Name = "NetworkVariableDeltaMessage";
|
private const string k_Name = "NetworkVariableDeltaMessage";
|
||||||
|
|
||||||
|
private Dictionary<ulong, List<int>> m_ForwardUpdates;
|
||||||
|
|
||||||
|
private List<int> m_UpdatedNetworkVariables;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void WriteNetworkVariable(ref FastBufferWriter writer, ref NetworkVariableBase networkVariable, bool distributedAuthorityMode, bool ensureNetworkVariableLengthSafety, int nonfragmentedSize, int fragmentedSize)
|
||||||
|
{
|
||||||
|
if (ensureNetworkVariableLengthSafety)
|
||||||
|
{
|
||||||
|
var tempWriter = new FastBufferWriter(nonfragmentedSize, Allocator.Temp, fragmentedSize);
|
||||||
|
networkVariable.WriteDelta(tempWriter);
|
||||||
|
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
||||||
|
|
||||||
|
if (!writer.TryBeginWrite(tempWriter.Length))
|
||||||
|
{
|
||||||
|
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
||||||
|
}
|
||||||
|
|
||||||
|
tempWriter.CopyTo(writer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// TODO: Determine if we need to remove this with the 6.1 service updates
|
||||||
|
if (distributedAuthorityMode)
|
||||||
|
{
|
||||||
|
var size_marker = writer.Position;
|
||||||
|
writer.WriteValueSafe<ushort>(0);
|
||||||
|
var start_marker = writer.Position;
|
||||||
|
networkVariable.WriteDelta(writer);
|
||||||
|
var end_marker = writer.Position;
|
||||||
|
writer.Seek(size_marker);
|
||||||
|
var size = end_marker - start_marker;
|
||||||
|
if (size == 0)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"Invalid write size of zero!");
|
||||||
|
}
|
||||||
|
writer.WriteValueSafe((ushort)size);
|
||||||
|
writer.Seek(end_marker);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
networkVariable.WriteDelta(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Serialize(FastBufferWriter writer, int targetVersion)
|
public void Serialize(FastBufferWriter writer, int targetVersion)
|
||||||
{
|
{
|
||||||
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
|
||||||
@@ -34,10 +100,67 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
var obj = NetworkBehaviour.NetworkObject;
|
var obj = NetworkBehaviour.NetworkObject;
|
||||||
var networkManager = obj.NetworkManagerOwner;
|
var networkManager = obj.NetworkManagerOwner;
|
||||||
|
var typeName = NetworkBehaviour.__getTypeName();
|
||||||
|
var nonFragmentedMessageMaxSize = networkManager.MessageManager.NonFragmentedMessageMaxSize;
|
||||||
|
var fragmentedMessageMaxSize = networkManager.MessageManager.FragmentedMessageMaxSize;
|
||||||
|
var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
|
||||||
|
var distributedAuthorityMode = networkManager.DistributedAuthorityMode;
|
||||||
|
|
||||||
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
BytePacker.WriteValueBitPacked(writer, NetworkObjectId);
|
||||||
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
|
BytePacker.WriteValueBitPacked(writer, NetworkBehaviourIndex);
|
||||||
if (networkManager.DistributedAuthorityMode)
|
|
||||||
|
// If using k_IncludeNetworkDelivery version, then we want to write the network delivery used and if we
|
||||||
|
// are forwarding state updates then serialize any NetworkVariable states specific to this client.
|
||||||
|
if (targetVersion >= k_ServerDeltaForwardingAndNetworkDelivery)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(NetworkDelivery);
|
||||||
|
// If we are forwarding the message, then proceed to forward state updates specific to the targeted client
|
||||||
|
if (m_ForwardingMessage)
|
||||||
|
{
|
||||||
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NetworkBehaviour.NetworkVariableFields.Count; i++)
|
||||||
|
{
|
||||||
|
var startingSize = writer.Length;
|
||||||
|
var networkVariable = NetworkBehaviour.NetworkVariableFields[i];
|
||||||
|
var shouldWrite = m_ForwardUpdates[TargetClientId].Contains(i);
|
||||||
|
|
||||||
|
// This var does not belong to the currently iterating delivery group.
|
||||||
|
if (distributedAuthorityMode)
|
||||||
|
{
|
||||||
|
if (!shouldWrite)
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe<ushort>(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ensureNetworkVariableLengthSafety)
|
||||||
|
{
|
||||||
|
if (!shouldWrite)
|
||||||
|
{
|
||||||
|
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
writer.WriteValueSafe(shouldWrite);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldWrite)
|
||||||
|
{
|
||||||
|
WriteNetworkVariable(ref writer, ref networkVariable, distributedAuthorityMode, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize);
|
||||||
|
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DANGO TODO: Remove this when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count);
|
writer.WriteValueSafe((ushort)NetworkBehaviour.NetworkVariableFields.Count);
|
||||||
}
|
}
|
||||||
@@ -46,12 +169,12 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
|
||||||
{
|
{
|
||||||
// This var does not belong to the currently iterating delivery group.
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
if (networkManager.DistributedAuthorityMode)
|
if (distributedAuthorityMode)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe<ushort>(0);
|
writer.WriteValueSafe<ushort>(0);
|
||||||
}
|
}
|
||||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
else if (ensureNetworkVariableLengthSafety)
|
||||||
{
|
{
|
||||||
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
BytePacker.WriteValueBitPacked(writer, (ushort)0);
|
||||||
}
|
}
|
||||||
@@ -88,14 +211,15 @@ namespace Unity.Netcode
|
|||||||
shouldWrite = false;
|
shouldWrite = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (networkManager.DistributedAuthorityMode)
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode)
|
||||||
{
|
{
|
||||||
if (!shouldWrite)
|
if (!shouldWrite)
|
||||||
{
|
{
|
||||||
writer.WriteValueSafe<ushort>(0);
|
writer.WriteValueSafe<ushort>(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
else if (ensureNetworkVariableLengthSafety)
|
||||||
{
|
{
|
||||||
if (!shouldWrite)
|
if (!shouldWrite)
|
||||||
{
|
{
|
||||||
@@ -109,53 +233,22 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (shouldWrite)
|
if (shouldWrite)
|
||||||
{
|
{
|
||||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
WriteNetworkVariable(ref writer, ref networkVariable, distributedAuthorityMode, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize);
|
||||||
{
|
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize);
|
||||||
var tempWriter = new FastBufferWriter(networkManager.MessageManager.NonFragmentedMessageMaxSize, Allocator.Temp, networkManager.MessageManager.FragmentedMessageMaxSize);
|
|
||||||
NetworkBehaviour.NetworkVariableFields[i].WriteDelta(tempWriter);
|
|
||||||
BytePacker.WriteValueBitPacked(writer, tempWriter.Length);
|
|
||||||
|
|
||||||
if (!writer.TryBeginWrite(tempWriter.Length))
|
|
||||||
{
|
|
||||||
throw new OverflowException($"Not enough space in the buffer to write {nameof(NetworkVariableDeltaMessage)}");
|
|
||||||
}
|
|
||||||
|
|
||||||
tempWriter.CopyTo(writer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (networkManager.DistributedAuthorityMode)
|
|
||||||
{
|
|
||||||
var size_marker = writer.Position;
|
|
||||||
writer.WriteValueSafe<ushort>(0);
|
|
||||||
var start_marker = writer.Position;
|
|
||||||
networkVariable.WriteDelta(writer);
|
|
||||||
var end_marker = writer.Position;
|
|
||||||
writer.Seek(size_marker);
|
|
||||||
var size = end_marker - start_marker;
|
|
||||||
writer.WriteValueSafe((ushort)size);
|
|
||||||
writer.Seek(end_marker);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
networkVariable.WriteDelta(writer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(
|
|
||||||
TargetClientId,
|
|
||||||
obj,
|
|
||||||
networkVariable.Name,
|
|
||||||
NetworkBehaviour.__getTypeName(),
|
|
||||||
writer.Length - startingSize);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
|
||||||
{
|
{
|
||||||
|
m_ReceivedMessageVersion = receivedMessageVersion;
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
|
||||||
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
|
ByteUnpacker.ReadValueBitPacked(reader, out NetworkBehaviourIndex);
|
||||||
|
// If we are using the k_IncludeNetworkDelivery message version, then read the NetworkDelivery used
|
||||||
|
if (receivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery)
|
||||||
|
{
|
||||||
|
reader.ReadValueSafe(out NetworkDelivery);
|
||||||
|
}
|
||||||
m_ReceivedNetworkVariableData = reader;
|
m_ReceivedNetworkVariableData = reader;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -167,7 +260,12 @@ namespace Unity.Netcode
|
|||||||
|
|
||||||
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
|
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
|
||||||
{
|
{
|
||||||
|
var distributedAuthorityMode = networkManager.DistributedAuthorityMode;
|
||||||
|
var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
|
||||||
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(NetworkBehaviourIndex);
|
||||||
|
var isServerAndDeltaForwarding = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery && networkManager.IsServer;
|
||||||
|
var markNetworkVariableDirty = m_ReceivedMessageVersion >= k_ServerDeltaForwardingAndNetworkDelivery ? false : networkManager.IsServer;
|
||||||
|
m_UpdatedNetworkVariables = new List<int>();
|
||||||
|
|
||||||
if (networkBehaviour == null)
|
if (networkBehaviour == null)
|
||||||
{
|
{
|
||||||
@@ -178,7 +276,8 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (networkManager.DistributedAuthorityMode)
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode)
|
||||||
{
|
{
|
||||||
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount);
|
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableCount);
|
||||||
if (variableCount != networkBehaviour.NetworkVariableFields.Count)
|
if (variableCount != networkBehaviour.NetworkVariableFields.Count)
|
||||||
@@ -187,10 +286,30 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable
|
||||||
|
// updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded
|
||||||
|
// to the client. This creates a list of all remaining connected clients that could have updates applied.
|
||||||
|
if (isServerAndDeltaForwarding)
|
||||||
|
{
|
||||||
|
m_ForwardUpdates = new Dictionary<ulong, List<int>>();
|
||||||
|
foreach (var clientId in networkManager.ConnectedClientsIds)
|
||||||
|
{
|
||||||
|
if (clientId == context.SenderId || clientId == networkManager.LocalClientId || !networkObject.Observers.Contains(clientId))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_ForwardUpdates.Add(clientId, new List<int>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update NetworkVariable Fields
|
||||||
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
for (int i = 0; i < networkBehaviour.NetworkVariableFields.Count; i++)
|
||||||
{
|
{
|
||||||
int varSize = 0;
|
int varSize = 0;
|
||||||
if (networkManager.DistributedAuthorityMode)
|
var networkVariable = networkBehaviour.NetworkVariableFields[i];
|
||||||
|
|
||||||
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode)
|
||||||
{
|
{
|
||||||
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize);
|
m_ReceivedNetworkVariableData.ReadValueSafe(out ushort variableSize);
|
||||||
varSize = variableSize;
|
varSize = variableSize;
|
||||||
@@ -200,10 +319,9 @@ namespace Unity.Netcode
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
|
else if (ensureNetworkVariableLengthSafety)
|
||||||
{
|
{
|
||||||
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
|
||||||
|
|
||||||
if (varSize == 0)
|
if (varSize == 0)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -218,8 +336,6 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var networkVariable = networkBehaviour.NetworkVariableFields[i];
|
|
||||||
|
|
||||||
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
|
if (networkManager.IsServer && !networkVariable.CanClientWrite(context.SenderId))
|
||||||
{
|
{
|
||||||
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
|
// we are choosing not to fire an exception here, because otherwise a malicious client could use this to crash the server
|
||||||
@@ -247,13 +363,58 @@ namespace Unity.Netcode
|
|||||||
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
NetworkLog.LogError($"Client wrote to {typeof(NetworkVariable<>).Name} without permission. No more variables can be read. This is critical. => {nameof(NetworkObjectId)}: {NetworkObjectId} - {nameof(NetworkObject.GetNetworkBehaviourOrderIndex)}(): {networkObject.GetNetworkBehaviourOrderIndex(networkBehaviour)} - VariableIndex: {i}");
|
||||||
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
NetworkLog.LogError($"[{networkVariable.GetType().Name}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
int readStartPos = m_ReceivedNetworkVariableData.Position;
|
||||||
|
|
||||||
// Read Delta so we also notify any subscribers to a change in the NetworkVariable
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
|
if (distributedAuthorityMode || ensureNetworkVariableLengthSafety)
|
||||||
|
{
|
||||||
|
var remainingBufferSize = m_ReceivedNetworkVariableData.Length - m_ReceivedNetworkVariableData.Position;
|
||||||
|
if (varSize > (remainingBufferSize))
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogError($"[{networkBehaviour.name}][Delta State Read Error] Expecting to read {varSize} but only {remainingBufferSize} remains!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Added a try catch here to assure any failure will only fail on this one message and not disrupt the stack
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// Read the delta
|
||||||
|
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, markNetworkVariableDirty);
|
||||||
|
|
||||||
|
// Add the NetworkVariable field index so we can invoke the PostDeltaRead
|
||||||
|
m_UpdatedNetworkVariables.Add(i);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
UnityEngine.Debug.LogException(ex);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// (For client-server) As opposed to worrying about adding additional processing on the server to send NetworkVariable
|
||||||
|
// updates at the end of the frame, we now track all NetworkVariable state updates, per client, that need to be forwarded
|
||||||
|
// to the client. This happens once the server is finished processing all state updates for this message.
|
||||||
|
if (isServerAndDeltaForwarding)
|
||||||
|
{
|
||||||
|
foreach (var forwardEntry in m_ForwardUpdates)
|
||||||
|
{
|
||||||
|
// Only track things that the client can read
|
||||||
|
if (networkVariable.CanClientRead(forwardEntry.Key))
|
||||||
|
{
|
||||||
|
// If the object is about to be shown to the client then don't send an update as it will
|
||||||
|
// send a full update when shown.
|
||||||
|
if (networkManager.SpawnManager.ObjectsToShowToClient.ContainsKey(forwardEntry.Key) &&
|
||||||
|
networkManager.SpawnManager.ObjectsToShowToClient[forwardEntry.Key]
|
||||||
|
.Contains(networkObject))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
forwardEntry.Value.Add(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
networkManager.NetworkMetrics.TrackNetworkVariableDeltaReceived(
|
||||||
context.SenderId,
|
context.SenderId,
|
||||||
@@ -262,7 +423,8 @@ namespace Unity.Netcode
|
|||||||
networkBehaviour.__getTypeName(),
|
networkBehaviour.__getTypeName(),
|
||||||
context.MessageSize);
|
context.MessageSize);
|
||||||
|
|
||||||
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety || networkManager.DistributedAuthorityMode)
|
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
|
||||||
|
if (distributedAuthorityMode || ensureNetworkVariableLengthSafety)
|
||||||
{
|
{
|
||||||
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))
|
if (m_ReceivedNetworkVariableData.Position > (readStartPos + varSize))
|
||||||
{
|
{
|
||||||
@@ -284,6 +446,40 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If we are using the version of this message that includes network delivery, then
|
||||||
|
// forward this update to all connected clients (other than the sender and the server).
|
||||||
|
if (isServerAndDeltaForwarding)
|
||||||
|
{
|
||||||
|
var message = new NetworkVariableDeltaMessage()
|
||||||
|
{
|
||||||
|
NetworkBehaviour = networkBehaviour,
|
||||||
|
NetworkBehaviourIndex = NetworkBehaviourIndex,
|
||||||
|
NetworkObjectId = NetworkObjectId,
|
||||||
|
m_ForwardingMessage = true,
|
||||||
|
m_ForwardUpdates = m_ForwardUpdates,
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var forwardEntry in m_ForwardUpdates)
|
||||||
|
{
|
||||||
|
// Only forward updates to any client that has visibility to the state updates included in this message
|
||||||
|
if (forwardEntry.Value.Count > 0)
|
||||||
|
{
|
||||||
|
message.TargetClientId = forwardEntry.Key;
|
||||||
|
networkManager.ConnectionManager.SendMessage(ref message, NetworkDelivery, forwardEntry.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should be always invoked (client & server) to assure the previous values are set
|
||||||
|
// !! IMPORTANT ORDER OF OPERATIONS !! (Has to happen after forwarding deltas)
|
||||||
|
// When a server forwards delta updates to connected clients, it needs to preserve the previous value
|
||||||
|
// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This
|
||||||
|
// is invoked after it is done forwarding the deltas.
|
||||||
|
foreach (var fieldIndex in m_UpdatedNetworkVariables)
|
||||||
|
{
|
||||||
|
networkBehaviour.NetworkVariableFields[fieldIndex].PostDeltaRead();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -177,6 +177,13 @@ namespace Unity.Netcode
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||||
{
|
{
|
||||||
|
/// This is only invoked by <see cref="NetworkVariableDeltaMessage"/> and the only time
|
||||||
|
/// keepDirtyDelta is set is when it is the server processing. To be able to handle previous
|
||||||
|
/// versions, we use IsServer to keep the dirty states received and the keepDirtyDelta to
|
||||||
|
/// actually mark this as dirty and add it to the list of <see cref="NetworkObject"/>s to
|
||||||
|
/// be updated. With the forwarding of deltas being handled by <see cref="NetworkVariableDeltaMessage"/>,
|
||||||
|
/// once all clients have been forwarded the dirty events, we clear them by invoking <see cref="PostDeltaRead"/>.
|
||||||
|
var isServer = m_NetworkManager.IsServer;
|
||||||
reader.ReadValueSafe(out ushort deltaCount);
|
reader.ReadValueSafe(out ushort deltaCount);
|
||||||
for (int i = 0; i < deltaCount; i++)
|
for (int i = 0; i < deltaCount; i++)
|
||||||
{
|
{
|
||||||
@@ -199,7 +206,7 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
@@ -207,7 +214,11 @@ 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();
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -237,7 +248,7 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
@@ -245,7 +256,11 @@ namespace Unity.Netcode
|
|||||||
Index = index,
|
Index = index,
|
||||||
Value = m_List[index]
|
Value = m_List[index]
|
||||||
});
|
});
|
||||||
MarkNetworkObjectDirty();
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -271,7 +286,7 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
@@ -279,7 +294,11 @@ namespace Unity.Netcode
|
|||||||
Index = index,
|
Index = index,
|
||||||
Value = value
|
Value = value
|
||||||
});
|
});
|
||||||
MarkNetworkObjectDirty();
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -299,7 +318,7 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
@@ -307,7 +326,11 @@ namespace Unity.Netcode
|
|||||||
Index = index,
|
Index = index,
|
||||||
Value = value
|
Value = value
|
||||||
});
|
});
|
||||||
MarkNetworkObjectDirty();
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -335,7 +358,7 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
@@ -344,7 +367,11 @@ namespace Unity.Netcode
|
|||||||
Value = value,
|
Value = value,
|
||||||
PreviousValue = previousValue
|
PreviousValue = previousValue
|
||||||
});
|
});
|
||||||
MarkNetworkObjectDirty();
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -361,13 +388,18 @@ namespace Unity.Netcode
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepDirtyDelta)
|
if (isServer)
|
||||||
{
|
{
|
||||||
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
m_DirtyEvents.Add(new NetworkListEvent<T>()
|
||||||
{
|
{
|
||||||
Type = eventType
|
Type = eventType
|
||||||
});
|
});
|
||||||
MarkNetworkObjectDirty();
|
|
||||||
|
// Preserve the legacy way of handling this
|
||||||
|
if (keepDirtyDelta)
|
||||||
|
{
|
||||||
|
MarkNetworkObjectDirty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -381,6 +413,18 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
/// <remarks>
|
||||||
|
/// For NetworkList, we just need to reset dirty if a server has read deltas
|
||||||
|
/// </remarks>
|
||||||
|
internal override void PostDeltaRead()
|
||||||
|
{
|
||||||
|
if (m_NetworkManager.IsServer)
|
||||||
|
{
|
||||||
|
ResetDirty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerator<T> GetEnumerator()
|
public IEnumerator<T> GetEnumerator()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ namespace Unity.Netcode
|
|||||||
base.OnInitialize();
|
base.OnInitialize();
|
||||||
|
|
||||||
m_HasPreviousValue = true;
|
m_HasPreviousValue = true;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,6 +59,7 @@ namespace Unity.Netcode
|
|||||||
: base(readPerm, writePerm)
|
: base(readPerm, writePerm)
|
||||||
{
|
{
|
||||||
m_InternalValue = value;
|
m_InternalValue = value;
|
||||||
|
m_InternalOriginalValue = default;
|
||||||
// Since we start with IsDirty = true, this doesn't need to be duplicated
|
// Since we start with IsDirty = true, this doesn't need to be duplicated
|
||||||
// right away. It won't get read until after ResetDirty() is called, and
|
// right away. It won't get read until after ResetDirty() is called, and
|
||||||
// the duplicate will be made there. Avoiding calling
|
// the duplicate will be made there. Avoiding calling
|
||||||
@@ -76,6 +78,7 @@ namespace Unity.Netcode
|
|||||||
if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned)
|
if (m_NetworkBehaviour == null || m_NetworkBehaviour != null && !m_NetworkBehaviour.NetworkObject.IsSpawned)
|
||||||
{
|
{
|
||||||
m_InternalValue = value;
|
m_InternalValue = value;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
m_PreviousValue = default;
|
m_PreviousValue = default;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,6 +89,12 @@ namespace Unity.Netcode
|
|||||||
[SerializeField]
|
[SerializeField]
|
||||||
private protected T m_InternalValue;
|
private protected T m_InternalValue;
|
||||||
|
|
||||||
|
// The introduction of standard .NET collections caused an issue with permissions since there is no way to detect changes in the
|
||||||
|
// collection without doing a full comparison. While this approach does consume more memory per collection instance, it is the
|
||||||
|
// lowest risk approach to resolving the issue where a client with no write permissions could make changes to a collection locally
|
||||||
|
// which can cause a myriad of issues.
|
||||||
|
private protected T m_InternalOriginalValue;
|
||||||
|
|
||||||
private protected T m_PreviousValue;
|
private protected T m_PreviousValue;
|
||||||
|
|
||||||
private bool m_HasPreviousValue;
|
private bool m_HasPreviousValue;
|
||||||
@@ -116,6 +125,7 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
T previousValue = m_InternalValue;
|
T previousValue = m_InternalValue;
|
||||||
m_InternalValue = value;
|
m_InternalValue = value;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
SetDirty(true);
|
SetDirty(true);
|
||||||
m_IsDisposed = false;
|
m_IsDisposed = false;
|
||||||
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
OnValueChanged?.Invoke(previousValue, m_InternalValue);
|
||||||
@@ -136,6 +146,17 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
var isDirty = base.IsDirty();
|
var isDirty = base.IsDirty();
|
||||||
|
|
||||||
|
// A client without permissions invoking this method should only check to assure the current value is equal to the last known current value
|
||||||
|
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId))
|
||||||
|
{
|
||||||
|
// If modifications are detected, then revert back to the last known current value
|
||||||
|
if (!NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue))
|
||||||
|
{
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Compare the previous with the current if not dirty or forcing a check.
|
// Compare the previous with the current if not dirty or forcing a check.
|
||||||
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
|
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
|
||||||
{
|
{
|
||||||
@@ -166,6 +187,7 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
|
|
||||||
m_InternalValue = default;
|
m_InternalValue = default;
|
||||||
|
m_InternalOriginalValue = default;
|
||||||
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
|
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
|
||||||
{
|
{
|
||||||
m_HasPreviousValue = false;
|
m_HasPreviousValue = false;
|
||||||
@@ -188,6 +210,13 @@ namespace Unity.Netcode
|
|||||||
/// <returns>Whether or not the container is dirty</returns>
|
/// <returns>Whether or not the container is dirty</returns>
|
||||||
public override bool IsDirty()
|
public override bool IsDirty()
|
||||||
{
|
{
|
||||||
|
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
|
||||||
|
// to the original collection value prior to applying updates (primarily for collections).
|
||||||
|
if (!NetworkUpdaterCheck && m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalValue, ref m_InternalOriginalValue))
|
||||||
|
{
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
// For most cases we can use the dirty flag.
|
// For most cases we can use the dirty flag.
|
||||||
// This doesn't work for cases where we're wrapping more complex types
|
// This doesn't work for cases where we're wrapping more complex types
|
||||||
// like INetworkSerializable, NativeList, NativeArray, etc.
|
// like INetworkSerializable, NativeList, NativeArray, etc.
|
||||||
@@ -199,11 +228,11 @@ namespace Unity.Netcode
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var dirty = !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue);
|
||||||
// Cache the dirty value so we don't perform this again if we already know we're dirty
|
// Cache the dirty value so we don't perform this again if we already know we're dirty
|
||||||
// Unfortunately we can't cache the NOT dirty state, because that might change
|
// Unfortunately we can't cache the NOT dirty state, because that might change
|
||||||
// in between to checks... but the DIRTY state won't change until ResetDirty()
|
// in between to checks... but the DIRTY state won't change until ResetDirty()
|
||||||
// is called.
|
// is called.
|
||||||
var dirty = !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue);
|
|
||||||
SetDirty(dirty);
|
SetDirty(dirty);
|
||||||
return dirty;
|
return dirty;
|
||||||
}
|
}
|
||||||
@@ -221,6 +250,8 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
m_HasPreviousValue = true;
|
m_HasPreviousValue = true;
|
||||||
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
||||||
|
// Once updated, assure the original current value is updated for future comparison purposes
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
}
|
}
|
||||||
base.ResetDirty();
|
base.ResetDirty();
|
||||||
}
|
}
|
||||||
@@ -241,16 +272,20 @@ namespace Unity.Netcode
|
|||||||
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
/// <param name="keepDirtyDelta">Whether or not the container should keep the dirty delta, or mark the delta as consumed</param>
|
||||||
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
|
||||||
{
|
{
|
||||||
// In order to get managed collections to properly have a previous and current value, we have to
|
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
|
||||||
// duplicate the collection at this point before making any modifications to the current.
|
// to the original collection value prior to applying updates (primarily for collections).
|
||||||
m_HasPreviousValue = true;
|
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue))
|
||||||
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
{
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
|
||||||
|
}
|
||||||
|
|
||||||
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
|
NetworkVariableSerialization<T>.ReadDelta(reader, ref m_InternalValue);
|
||||||
|
|
||||||
// todo:
|
|
||||||
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
|
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
|
||||||
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
|
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
|
||||||
// would be stored in different fields
|
// would be stored in different fields
|
||||||
|
// LEGACY NOTE: This is only to handle NetworkVariableDeltaMessage Version 0 connections. The updated
|
||||||
|
// NetworkVariableDeltaMessage no longer uses this approach.
|
||||||
if (keepDirtyDelta)
|
if (keepDirtyDelta)
|
||||||
{
|
{
|
||||||
SetDirty(true);
|
SetDirty(true);
|
||||||
@@ -259,10 +294,43 @@ namespace Unity.Netcode
|
|||||||
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
|
OnValueChanged?.Invoke(m_PreviousValue, m_InternalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This should be always invoked (client & server) to assure the previous values are set
|
||||||
|
/// !! IMPORTANT !!
|
||||||
|
/// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s)
|
||||||
|
/// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked
|
||||||
|
/// after it is done forwarding the deltas at the end of the <see cref="NetworkVariableDeltaMessage.Handle(ref NetworkContext)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
internal override void PostDeltaRead()
|
||||||
|
{
|
||||||
|
// In order to get managed collections to properly have a previous and current value, we have to
|
||||||
|
// duplicate the collection at this point before making any modifications to the current.
|
||||||
|
m_HasPreviousValue = true;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
||||||
|
// Once updated, assure the original current value is updated for future comparison purposes
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public override void ReadField(FastBufferReader reader)
|
public override void ReadField(FastBufferReader reader)
|
||||||
{
|
{
|
||||||
|
// If the client does not have write permissions but the internal value is determined to be locally modified and we are applying updates, then we should revert
|
||||||
|
// to the original collection value prior to applying updates (primarily for collections).
|
||||||
|
if (m_NetworkManager && !CanClientWrite(m_NetworkManager.LocalClientId) && !NetworkVariableSerialization<T>.AreEqual(ref m_InternalOriginalValue, ref m_InternalValue))
|
||||||
|
{
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalOriginalValue, ref m_InternalValue);
|
||||||
|
}
|
||||||
|
|
||||||
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
NetworkVariableSerialization<T>.Read(reader, ref m_InternalValue);
|
||||||
|
// In order to get managed collections to properly have a previous and current value, we have to
|
||||||
|
// duplicate the collection at this point before making any modifications to the current.
|
||||||
|
// We duplicate the final value after the read (for ReadField ONLY) so the previous value is at par
|
||||||
|
// with the current value (since this is only invoked when initially synchronizing).
|
||||||
|
m_HasPreviousValue = true;
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
|
||||||
|
|
||||||
|
// Once updated, assure the original current value is updated for future comparison purposes
|
||||||
|
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -270,5 +338,20 @@ namespace Unity.Netcode
|
|||||||
{
|
{
|
||||||
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
NetworkVariableSerialization<T>.Write(writer, ref m_InternalValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal override void WriteFieldSynchronization(FastBufferWriter writer)
|
||||||
|
{
|
||||||
|
// If we have a pending update, then synchronize the client with the previously known
|
||||||
|
// value since the updated version will be sent on the next tick or next time it is
|
||||||
|
// set to be updated
|
||||||
|
if (base.IsDirty() && m_HasPreviousValue)
|
||||||
|
{
|
||||||
|
NetworkVariableSerialization<T>.Write(writer, ref m_PreviousValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
base.WriteFieldSynchronization(writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -251,6 +251,12 @@ namespace Unity.Netcode
|
|||||||
m_IsDirty = false;
|
m_IsDirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Only used during the NetworkBehaviourUpdater pass and only used for NetworkVariable.
|
||||||
|
/// This is to bypass duplication of the "original internal value" for collections.
|
||||||
|
/// </summary>
|
||||||
|
internal bool NetworkUpdaterCheck;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets Whether or not the container is dirty
|
/// Gets Whether or not the container is dirty
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -341,6 +347,32 @@ namespace Unity.Netcode
|
|||||||
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
|
||||||
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
public abstract void ReadDelta(FastBufferReader reader, bool keepDirtyDelta);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This should be always invoked (client & server) to assure the previous values are set
|
||||||
|
/// !! IMPORTANT !!
|
||||||
|
/// When a server forwards delta updates to connected clients, it needs to preserve the previous dirty value(s)
|
||||||
|
/// until it is done serializing all valid NetworkVariable field deltas (relative to each client). This is invoked
|
||||||
|
/// after it is done forwarding the deltas at the end of the <see cref="NetworkVariableDeltaMessage.Handle(ref NetworkContext)"/> method.
|
||||||
|
/// </summary>
|
||||||
|
internal virtual void PostDeltaRead()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// There are scenarios, specifically with collections, where a client could be synchronizing and
|
||||||
|
/// some NetworkVariables have pending updates. To avoid duplicating entries, this is invoked only
|
||||||
|
/// when sending the full synchronization information.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Derrived classes should send the previous value for synchronization so when the updated value
|
||||||
|
/// is sent (after synchronizing the client) it will apply the updates.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="writer"></param>
|
||||||
|
internal virtual void WriteFieldSynchronization(FastBufferWriter writer)
|
||||||
|
{
|
||||||
|
WriteField(writer);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Virtual <see cref="IDisposable"/> implementation
|
/// Virtual <see cref="IDisposable"/> implementation
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -320,9 +320,11 @@ namespace Unity.Netcode
|
|||||||
internal void AddSpawnedNetworkObjects()
|
internal void AddSpawnedNetworkObjects()
|
||||||
{
|
{
|
||||||
m_NetworkObjectsSync.Clear();
|
m_NetworkObjectsSync.Clear();
|
||||||
|
// If distributed authority mode and sending to the service, then ignore observers
|
||||||
|
var distributedAuthoritySendingToService = m_NetworkManager.DistributedAuthorityMode && TargetClientId == NetworkManager.ServerClientId;
|
||||||
foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList)
|
foreach (var sobj in m_NetworkManager.SpawnManager.SpawnedObjectsList)
|
||||||
{
|
{
|
||||||
if (sobj.Observers.Contains(TargetClientId))
|
if (sobj.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService)
|
||||||
{
|
{
|
||||||
m_NetworkObjectsSync.Add(sobj);
|
m_NetworkObjectsSync.Add(sobj);
|
||||||
}
|
}
|
||||||
@@ -666,12 +668,14 @@ namespace Unity.Netcode
|
|||||||
// Write our count place holder (must not be packed!)
|
// Write our count place holder (must not be packed!)
|
||||||
writer.WriteValueSafe((ushort)0);
|
writer.WriteValueSafe((ushort)0);
|
||||||
var distributedAuthority = m_NetworkManager.DistributedAuthorityMode;
|
var distributedAuthority = m_NetworkManager.DistributedAuthorityMode;
|
||||||
|
// If distributed authority mode and sending to the service, then ignore observers
|
||||||
|
var distributedAuthoritySendingToService = distributedAuthority && TargetClientId == NetworkManager.ServerClientId;
|
||||||
|
|
||||||
foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects)
|
foreach (var keyValuePairByGlobalObjectIdHash in m_NetworkManager.SceneManager.ScenePlacedObjects)
|
||||||
{
|
{
|
||||||
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
|
foreach (var keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
|
||||||
{
|
{
|
||||||
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
|
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService)
|
||||||
{
|
{
|
||||||
// Serialize the NetworkObject
|
// Serialize the NetworkObject
|
||||||
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority);
|
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority);
|
||||||
|
|||||||
@@ -72,11 +72,22 @@ namespace Unity.Netcode
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var player in m_PlayerObjects)
|
foreach (var player in m_PlayerObjects)
|
||||||
{
|
{
|
||||||
player.Observers.Add(playerObject.OwnerClientId);
|
// If the player's SpawnWithObservers is not set then do not add the new player object's owner as an observer.
|
||||||
playerObject.Observers.Add(player.OwnerClientId);
|
if (player.SpawnWithObservers)
|
||||||
|
{
|
||||||
|
player.Observers.Add(playerObject.OwnerClientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the new player object's SpawnWithObservers is not set then do not add this player as an observer to the new player object.
|
||||||
|
if (playerObject.SpawnWithObservers)
|
||||||
|
{
|
||||||
|
playerObject.Observers.Add(player.OwnerClientId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_PlayerObjects.Add(playerObject);
|
m_PlayerObjects.Add(playerObject);
|
||||||
if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId))
|
if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId))
|
||||||
{
|
{
|
||||||
@@ -423,8 +434,31 @@ namespace Unity.Netcode
|
|||||||
ChangeOwnership(networkObject, NetworkManager.ServerClientId, true);
|
ChangeOwnership(networkObject, NetworkManager.ServerClientId, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Dictionary<ulong, float> m_LastChangeInOwnership = new Dictionary<ulong, float>();
|
||||||
|
private const int k_MaximumTickOwnershipChangeMultiplier = 6;
|
||||||
|
|
||||||
internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false)
|
internal void ChangeOwnership(NetworkObject networkObject, ulong clientId, bool isAuthorized, bool isRequestApproval = false)
|
||||||
{
|
{
|
||||||
|
// For client-server:
|
||||||
|
// If ownership changes faster than the latency between the client-server and there are NetworkVariables being updated during ownership changes,
|
||||||
|
// then notify the user they could potentially lose state updates if developer logging is enabled.
|
||||||
|
if (!NetworkManager.DistributedAuthorityMode && m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId) && m_LastChangeInOwnership[networkObject.NetworkObjectId] > Time.realtimeSinceStartup)
|
||||||
|
{
|
||||||
|
var hasNetworkVariables = false;
|
||||||
|
for (int i = 0; i < networkObject.ChildNetworkBehaviours.Count; i++)
|
||||||
|
{
|
||||||
|
hasNetworkVariables = networkObject.ChildNetworkBehaviours[i].NetworkVariableFields.Count > 0;
|
||||||
|
if (hasNetworkVariables)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hasNetworkVariables && NetworkManager.LogLevel == LogLevel.Developer)
|
||||||
|
{
|
||||||
|
NetworkLog.LogWarningServer($"[Rapid Ownership Change Detected][Potential Loss in State] Detected a rapid change in ownership that exceeds a frequency less than {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate! Provide at least {k_MaximumTickOwnershipChangeMultiplier}x the current network tick rate between ownership changes to avoid NetworkVariable state loss.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (NetworkManager.DistributedAuthorityMode)
|
if (NetworkManager.DistributedAuthorityMode)
|
||||||
{
|
{
|
||||||
// If are not authorized and this is not an approved ownership change, then check to see if we can change ownership
|
// If are not authorized and this is not an approved ownership change, then check to see if we can change ownership
|
||||||
@@ -497,15 +531,21 @@ namespace Unity.Netcode
|
|||||||
// Always notify locally on the server when ownership is lost
|
// Always notify locally on the server when ownership is lost
|
||||||
networkObject.InvokeBehaviourOnLostOwnership();
|
networkObject.InvokeBehaviourOnLostOwnership();
|
||||||
|
|
||||||
networkObject.MarkVariablesDirty(true);
|
|
||||||
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
|
|
||||||
|
|
||||||
// Authority adds entries for all client ownership
|
// Authority adds entries for all client ownership
|
||||||
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
|
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
|
||||||
|
|
||||||
// Always notify locally on the server when a new owner is assigned
|
// Always notify locally on the server when a new owner is assigned
|
||||||
networkObject.InvokeBehaviourOnGainedOwnership();
|
networkObject.InvokeBehaviourOnGainedOwnership();
|
||||||
|
|
||||||
|
if (networkObject.PreviousOwnerId == NetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
// Mark any owner read variables as dirty
|
||||||
|
networkObject.MarkOwnerReadVariablesDirty();
|
||||||
|
// Immediately queue any pending deltas and order the message before the
|
||||||
|
// change in ownership message.
|
||||||
|
NetworkManager.BehaviourUpdater.NetworkBehaviourUpdate(true);
|
||||||
|
}
|
||||||
|
|
||||||
var size = 0;
|
var size = 0;
|
||||||
if (NetworkManager.DistributedAuthorityMode)
|
if (NetworkManager.DistributedAuthorityMode)
|
||||||
{
|
{
|
||||||
@@ -569,6 +609,17 @@ namespace Unity.Netcode
|
|||||||
/// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership
|
/// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership
|
||||||
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
|
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
|
||||||
networkObject.InvokeOwnershipChanged(networkObject.PreviousOwnerId, clientId);
|
networkObject.InvokeOwnershipChanged(networkObject.PreviousOwnerId, clientId);
|
||||||
|
|
||||||
|
// Keep track of the ownership change frequency to assure a user is not exceeding changes faster than 2x the current Tick Rate.
|
||||||
|
if (!NetworkManager.DistributedAuthorityMode)
|
||||||
|
{
|
||||||
|
if (!m_LastChangeInOwnership.ContainsKey(networkObject.NetworkObjectId))
|
||||||
|
{
|
||||||
|
m_LastChangeInOwnership.Add(networkObject.NetworkObjectId, 0.0f);
|
||||||
|
}
|
||||||
|
var tickFrequency = 1.0f / NetworkManager.NetworkConfig.TickRate;
|
||||||
|
m_LastChangeInOwnership[networkObject.NetworkObjectId] = Time.realtimeSinceStartup + (tickFrequency * k_MaximumTickOwnershipChangeMultiplier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
internal bool HasPrefab(NetworkObject.SceneObject sceneObject)
|
||||||
@@ -695,14 +746,14 @@ namespace Unity.Netcode
|
|||||||
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
|
/// Gets the right NetworkObject prefab instance to spawn. If a handler is registered or there is an override assigned to the
|
||||||
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
|
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3 position = default, Quaternion rotation = default, bool isScenePlaced = false)
|
internal NetworkObject GetNetworkObjectToSpawn(uint globalObjectIdHash, ulong ownerId, Vector3? position, Quaternion? rotation, bool isScenePlaced = false)
|
||||||
{
|
{
|
||||||
NetworkObject networkObject = null;
|
NetworkObject networkObject = null;
|
||||||
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
|
||||||
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
|
||||||
{
|
{
|
||||||
// Let the handler spawn the NetworkObject
|
// Let the handler spawn the NetworkObject
|
||||||
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position, rotation);
|
networkObject = NetworkManager.PrefabHandler.HandleNetworkPrefabSpawn(globalObjectIdHash, ownerId, position ?? default, rotation ?? default);
|
||||||
networkObject.NetworkManagerOwner = NetworkManager;
|
networkObject.NetworkManagerOwner = NetworkManager;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -752,8 +803,10 @@ namespace Unity.Netcode
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Create prefab instance
|
// Create prefab instance while applying any pre-assigned position and rotation values
|
||||||
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
networkObject = UnityEngine.Object.Instantiate(networkPrefabReference).GetComponent<NetworkObject>();
|
||||||
|
networkObject.transform.position = position ?? networkObject.transform.position;
|
||||||
|
networkObject.transform.rotation = rotation ?? networkObject.transform.rotation;
|
||||||
networkObject.NetworkManagerOwner = NetworkManager;
|
networkObject.NetworkManagerOwner = NetworkManager;
|
||||||
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
|
networkObject.PrefabGlobalObjectIdHash = globalObjectIdHash;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,10 +24,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
/// Used to determine if a NetcodeIntegrationTest is currently running to
|
/// Used to determine if a NetcodeIntegrationTest is currently running to
|
||||||
/// determine how clients will load scenes
|
/// determine how clients will load scenes
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
protected const float k_DefaultTimeoutPeriod = 8.0f;
|
||||||
|
protected const float k_TickFrequency = 1.0f / k_DefaultTickRate;
|
||||||
internal static bool IsRunning { get; private set; }
|
internal static bool IsRunning { get; private set; }
|
||||||
|
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(k_DefaultTimeoutPeriod);
|
||||||
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
|
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(k_TickFrequency);
|
||||||
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
|
|
||||||
|
|
||||||
public NetcodeLogAssert NetcodeLogAssert;
|
public NetcodeLogAssert NetcodeLogAssert;
|
||||||
public enum SceneManagementState
|
public enum SceneManagementState
|
||||||
@@ -544,9 +545,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
|
|||||||
private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient)
|
private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient)
|
||||||
{
|
{
|
||||||
m_InternalErrorLog.Clear();
|
m_InternalErrorLog.Clear();
|
||||||
|
// If we are not checking for spawned players then exit early with a success
|
||||||
|
if (!ShouldCheckForSpawnedPlayers())
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Continue to populate the PlayerObjects list until all player object (local and clone) are found
|
// Continue to populate the PlayerObjects list until all player object (local and clone) are found
|
||||||
ClientNetworkManagerPostStart(joinedClient);
|
ClientNetworkManagerPostStart(joinedClient);
|
||||||
|
|
||||||
var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault();
|
var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault();
|
||||||
if (playerObjectRelative == null)
|
if (playerObjectRelative == null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System.Collections.Generic;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
|
using UnityEngine;
|
||||||
using UnityEngine.TestTools;
|
using UnityEngine.TestTools;
|
||||||
|
|
||||||
namespace Unity.Netcode.RuntimeTests
|
namespace Unity.Netcode.RuntimeTests
|
||||||
@@ -12,9 +13,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
[TestFixture(PlayerCreation.PrefabHash)]
|
[TestFixture(PlayerCreation.PrefabHash)]
|
||||||
[TestFixture(PlayerCreation.NoPlayer)]
|
[TestFixture(PlayerCreation.NoPlayer)]
|
||||||
[TestFixture(PlayerCreation.FailValidation)]
|
[TestFixture(PlayerCreation.FailValidation)]
|
||||||
internal class ConnectionApprovalTests : NetcodeIntegrationTest
|
internal class ConnectionApprovalTests : IntegrationTestWithApproximation
|
||||||
{
|
{
|
||||||
private const string k_InvalidToken = "Invalid validation token!";
|
private const string k_InvalidToken = "Invalid validation token!";
|
||||||
|
|
||||||
public enum PlayerCreation
|
public enum PlayerCreation
|
||||||
{
|
{
|
||||||
Prefab,
|
Prefab,
|
||||||
@@ -24,6 +26,8 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
private PlayerCreation m_PlayerCreation;
|
private PlayerCreation m_PlayerCreation;
|
||||||
private bool m_ClientDisconnectReasonValidated;
|
private bool m_ClientDisconnectReasonValidated;
|
||||||
|
private Vector3 m_ExpectedPosition;
|
||||||
|
private Quaternion m_ExpectedRotation;
|
||||||
|
|
||||||
private Dictionary<ulong, bool> m_Validated = new Dictionary<ulong, bool>();
|
private Dictionary<ulong, bool> m_Validated = new Dictionary<ulong, bool>();
|
||||||
|
|
||||||
@@ -43,6 +47,12 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
protected override void OnServerAndClientsCreated()
|
protected override void OnServerAndClientsCreated()
|
||||||
{
|
{
|
||||||
|
if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash)
|
||||||
|
{
|
||||||
|
m_ExpectedPosition = GetRandomVector3(-10.0f, 10.0f);
|
||||||
|
m_ExpectedRotation = Quaternion.Euler(GetRandomVector3(-359.98f, 359.98f));
|
||||||
|
}
|
||||||
|
|
||||||
m_ClientDisconnectReasonValidated = false;
|
m_ClientDisconnectReasonValidated = false;
|
||||||
m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation;
|
m_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation;
|
||||||
m_Validated.Clear();
|
m_Validated.Clear();
|
||||||
@@ -104,11 +114,36 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ValidatePlayersPositionRotation()
|
||||||
|
{
|
||||||
|
foreach (var playerEntries in m_PlayerNetworkObjects)
|
||||||
|
{
|
||||||
|
foreach (var player in playerEntries.Value)
|
||||||
|
{
|
||||||
|
if (!Approximately(player.Value.transform.position, m_ExpectedPosition))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Approximately(player.Value.transform.rotation, m_ExpectedRotation))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator ConnectionApproval()
|
public IEnumerator ConnectionApproval()
|
||||||
{
|
{
|
||||||
yield return WaitForConditionOrTimeOut(ClientAndHostValidated);
|
yield return WaitForConditionOrTimeOut(ClientAndHostValidated);
|
||||||
AssertOnTimeout("Timed out waiting for all clients to be approved!");
|
AssertOnTimeout("Timed out waiting for all clients to be approved!");
|
||||||
|
|
||||||
|
if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeOut(ValidatePlayersPositionRotation);
|
||||||
|
AssertOnTimeout("Not all player prefabs spawned in the correct position and/or rotation!");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
|
private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
|
||||||
@@ -127,8 +162,8 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
response.CreatePlayerObject = ShouldCheckForSpawnedPlayers();
|
response.CreatePlayerObject = ShouldCheckForSpawnedPlayers();
|
||||||
response.Position = null;
|
response.Position = m_ExpectedPosition;
|
||||||
response.Rotation = null;
|
response.Rotation = m_ExpectedRotation;
|
||||||
response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash : null;
|
response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
public IEnumerator ValidateApprovalTimeout()
|
public IEnumerator ValidateApprovalTimeout()
|
||||||
{
|
{
|
||||||
// Just delay for a second
|
// Just delay for a second
|
||||||
yield return new WaitForSeconds(1);
|
yield return new WaitForSeconds(k_TestTimeoutPeriod * 0.25f);
|
||||||
|
|
||||||
// Verify we haven't received the time out message yet
|
// Verify we haven't received the time out message yet
|
||||||
NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage);
|
NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage);
|
||||||
|
|||||||
@@ -670,7 +670,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
|
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
{
|
{
|
||||||
@@ -678,9 +678,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.IsTrue(manager.DeferMessageCalled);
|
Assert.IsTrue(manager.DeferMessageCalled);
|
||||||
Assert.IsFalse(manager.ProcessTriggersCalled);
|
Assert.IsFalse(manager.ProcessTriggersCalled);
|
||||||
|
|
||||||
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
|
||||||
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||||
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||||
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
||||||
AddPrefabsToClient(client);
|
AddPrefabsToClient(client);
|
||||||
}
|
}
|
||||||
@@ -812,7 +812,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
|
||||||
|
|
||||||
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
|
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
|
||||||
|
|
||||||
// Validate messages are deferred and pending
|
// Validate messages are deferred and pending
|
||||||
foreach (var client in m_ClientNetworkManagers)
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
@@ -821,10 +821,10 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.IsTrue(manager.DeferMessageCalled);
|
Assert.IsTrue(manager.DeferMessageCalled);
|
||||||
Assert.IsFalse(manager.ProcessTriggersCalled);
|
Assert.IsFalse(manager.ProcessTriggersCalled);
|
||||||
|
|
||||||
Assert.AreEqual(5, manager.DeferredMessageCountTotal());
|
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
|
||||||
|
|
||||||
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
|
||||||
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
|
||||||
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
|
||||||
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent<NetworkObject>().GlobalObjectIdHash));
|
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent<NetworkObject>().GlobalObjectIdHash));
|
||||||
AddPrefabsToClient(client);
|
AddPrefabsToClient(client);
|
||||||
|
|||||||
@@ -180,6 +180,9 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
|
Assert.IsTrue(m_DisconnectedEvent[m_ServerNetworkManager].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the server {nameof(NetworkManager)} disconnect event entry!");
|
||||||
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
|
Assert.IsTrue(m_DisconnectedEvent.ContainsKey(m_ClientNetworkManagers[0]), $"Could not find the client {nameof(NetworkManager)} disconnect event entry!");
|
||||||
Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
|
Assert.IsTrue(m_DisconnectedEvent[m_ClientNetworkManagers[0]].ClientId == m_ClientId, $"Expected ClientID {m_ClientId} but found ClientID {m_DisconnectedEvent[m_ServerNetworkManager].ClientId} for the client {nameof(NetworkManager)} disconnect event entry!");
|
||||||
|
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsIds.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsIds.Count}!");
|
||||||
|
Assert.IsTrue(m_ServerNetworkManager.ConnectedClients.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClients.Count}!");
|
||||||
|
Assert.IsTrue(m_ServerNetworkManager.ConnectedClientsList.Count == 1, $"Expected connected client identifiers count to be 1 but it was {m_ServerNetworkManager.ConnectedClientsList.Count}!");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner)
|
if (m_OwnerPersistence == OwnerPersistence.DestroyWithOwner)
|
||||||
|
|||||||
@@ -59,13 +59,13 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
|
|
||||||
public void Changed(int before, int after)
|
public void Changed(int before, int after)
|
||||||
{
|
{
|
||||||
VerboseDebug($"Value changed from {before} to {after} on {NetworkManager.LocalClientId}");
|
VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkVariable] Value changed from {before} to {after}");
|
||||||
ValueOnClient[NetworkManager.LocalClientId] = after;
|
ValueOnClient[NetworkManager.LocalClientId] = after;
|
||||||
}
|
}
|
||||||
public void ListChanged(NetworkListEvent<int> listEvent)
|
public void ListChanged(NetworkListEvent<int> listEvent)
|
||||||
{
|
{
|
||||||
Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
|
VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkList] ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
|
||||||
Debug.Assert(ExpectedSize == MyNetworkList.Count);
|
Debug.Assert(ExpectedSize == MyNetworkList.Count, $"[{name}] List change failure! Expected Count: {ExpectedSize} Actual Count:{MyNetworkList.Count}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,10 +185,13 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
var otherClient = m_ServerNetworkManager.ConnectedClientsList[2];
|
var otherClient = m_ServerNetworkManager.ConnectedClientsList[2];
|
||||||
m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent<NetworkObject>();
|
m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent<NetworkObject>();
|
||||||
|
|
||||||
yield return RefreshGameObects(4);
|
yield return RefreshGameObects(NumberOfClients);
|
||||||
|
|
||||||
// === Check spawn occurred
|
// === Check spawn occurred
|
||||||
yield return WaitForSpawnCount(NumberOfClients + 1);
|
yield return WaitForSpawnCount(NumberOfClients + 1);
|
||||||
|
|
||||||
|
AssertOnTimeout($"Timed out waiting for all clients to spawn {m_NetSpawnedObject.name}");
|
||||||
|
|
||||||
Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1);
|
Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1);
|
||||||
VerboseDebug("Objects spawned");
|
VerboseDebug("Objects spawned");
|
||||||
|
|
||||||
@@ -205,7 +208,6 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
// ==== Hide our object to a different client
|
// ==== Hide our object to a different client
|
||||||
HiddenVariableObject.ExpectedSize = 2;
|
HiddenVariableObject.ExpectedSize = 2;
|
||||||
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
|
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
|
||||||
|
|
||||||
currentValueSet = 3;
|
currentValueSet = 3;
|
||||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
||||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(currentValueSet);
|
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(currentValueSet);
|
||||||
@@ -222,7 +224,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
VerboseDebug("Object spawned");
|
VerboseDebug("Object spawned");
|
||||||
|
|
||||||
// ==== We need a refresh for the newly re-spawned object
|
// ==== We need a refresh for the newly re-spawned object
|
||||||
yield return RefreshGameObects(4);
|
yield return RefreshGameObects(NumberOfClients);
|
||||||
|
|
||||||
currentValueSet = 4;
|
currentValueSet = 4;
|
||||||
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
|
||||||
|
|||||||
@@ -13,6 +13,60 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
private NetworkManager m_ClientManager;
|
private NetworkManager m_ClientManager;
|
||||||
private NetworkManager m_ServerManager;
|
private NetworkManager m_ServerManager;
|
||||||
|
|
||||||
|
private NetworkManager m_NetworkManagerInstantiated;
|
||||||
|
private bool m_Instantiated;
|
||||||
|
private bool m_Destroyed;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the <see cref="NetworkManager.OnInstantiated"/> and <see cref="NetworkManager.OnDestroying"/> event notifications
|
||||||
|
/// </summary>
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator InstantiatedAndDestroyingNotifications()
|
||||||
|
{
|
||||||
|
NetworkManager.OnInstantiated += NetworkManager_OnInstantiated;
|
||||||
|
NetworkManager.OnDestroying += NetworkManager_OnDestroying;
|
||||||
|
var waitPeriod = new WaitForSeconds(0.01f);
|
||||||
|
var prefab = new GameObject("InstantiateDestroy");
|
||||||
|
var networkManagerPrefab = prefab.AddComponent<NetworkManager>();
|
||||||
|
|
||||||
|
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} prefab did not get instantiated event notification!");
|
||||||
|
Assert.IsTrue(m_NetworkManagerInstantiated == networkManagerPrefab, $"{nameof(NetworkManager)} prefab parameter did not match!");
|
||||||
|
|
||||||
|
m_Instantiated = false;
|
||||||
|
m_NetworkManagerInstantiated = null;
|
||||||
|
|
||||||
|
for (int i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
var instance = Object.Instantiate(prefab);
|
||||||
|
var networkManager = instance.GetComponent<NetworkManager>();
|
||||||
|
Assert.IsTrue(m_Instantiated, $"{nameof(NetworkManager)} instance-{i} did not get instantiated event notification!");
|
||||||
|
Assert.IsTrue(m_NetworkManagerInstantiated == networkManager, $"{nameof(NetworkManager)} instance-{i} parameter did not match!");
|
||||||
|
Object.DestroyImmediate(instance);
|
||||||
|
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} instance-{i} did not get destroying event notification!");
|
||||||
|
m_Instantiated = false;
|
||||||
|
m_NetworkManagerInstantiated = null;
|
||||||
|
m_Destroyed = false;
|
||||||
|
}
|
||||||
|
m_NetworkManagerInstantiated = networkManagerPrefab;
|
||||||
|
Object.Destroy(prefab);
|
||||||
|
yield return null;
|
||||||
|
Assert.IsTrue(m_Destroyed, $"{nameof(NetworkManager)} prefab did not get destroying event notification!");
|
||||||
|
NetworkManager.OnInstantiated -= NetworkManager_OnInstantiated;
|
||||||
|
NetworkManager.OnDestroying -= NetworkManager_OnDestroying;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NetworkManager_OnInstantiated(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
m_Instantiated = true;
|
||||||
|
m_NetworkManagerInstantiated = networkManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void NetworkManager_OnDestroying(NetworkManager networkManager)
|
||||||
|
{
|
||||||
|
m_Destroyed = true;
|
||||||
|
Assert.True(m_NetworkManagerInstantiated == networkManager, $"Destroying {nameof(NetworkManager)} and current instance is not a match for the one passed into the event!");
|
||||||
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
public IEnumerator OnServerStoppedCalledWhenServerStops()
|
public IEnumerator OnServerStoppedCalledWhenServerStops()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -265,6 +265,8 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
// After the 1st client has been given ownership to the object, this will be used to make sure each previous owner properly received the remove ownership message
|
// After the 1st client has been given ownership to the object, this will be used to make sure each previous owner properly received the remove ownership message
|
||||||
var previousClientComponent = (NetworkObjectOwnershipComponent)null;
|
var previousClientComponent = (NetworkObjectOwnershipComponent)null;
|
||||||
|
|
||||||
|
var networkManagersDAMode = new List<NetworkManager>();
|
||||||
|
|
||||||
for (int clientIndex = 0; clientIndex < NumberOfClients; clientIndex++)
|
for (int clientIndex = 0; clientIndex < NumberOfClients; clientIndex++)
|
||||||
{
|
{
|
||||||
clientObject = clientObjects[clientIndex];
|
clientObject = clientObjects[clientIndex];
|
||||||
@@ -322,6 +324,21 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
// In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service)
|
// In distributed authority mode, the current owner just rolls the ownership back over to the DAHost client (i.e. host mocking CMB Service)
|
||||||
if (m_DistributedAuthority)
|
if (m_DistributedAuthority)
|
||||||
{
|
{
|
||||||
|
// In distributed authority, we have to clear out the NetworkManager instances as this changes relative to authority.
|
||||||
|
networkManagersDAMode.Clear();
|
||||||
|
foreach (var clientNetworkManager in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
if (clientNetworkManager.LocalClientId == clientObject.OwnerClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
networkManagersDAMode.Add(clientNetworkManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UseCMBService() && clientObject.OwnerClientId != m_ServerNetworkManager.LocalClientId)
|
||||||
|
{
|
||||||
|
networkManagersDAMode.Add(m_ServerNetworkManager);
|
||||||
|
}
|
||||||
clientObject.ChangeOwnership(NetworkManager.ServerClientId);
|
clientObject.ChangeOwnership(NetworkManager.ServerClientId);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -330,7 +347,18 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
|
if (m_DistributedAuthority)
|
||||||
|
{
|
||||||
|
// We use an alternate method (other than message hooks) to verify each client received the ownership message since message hooks becomes problematic when you need
|
||||||
|
// to make dynamic changes to your targets.
|
||||||
|
yield return WaitForConditionOrTimeOut(() => OwnershipChangedOnAllTargetedClients(networkManagersDAMode, clientObject.NetworkObjectId, NetworkManager.ServerClientId));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeOut(ownershipMessageHooks);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server).");
|
Assert.False(s_GlobalTimeoutHelper.TimedOut, $"Timed out waiting for all clients to receive the {nameof(ChangeOwnershipMessage)} message (back to server).");
|
||||||
|
|
||||||
Assert.That(serverComponent.OnGainedOwnershipFired);
|
Assert.That(serverComponent.OnGainedOwnershipFired);
|
||||||
@@ -351,6 +379,22 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
serverComponent.ResetFlags();
|
serverComponent.ResetFlags();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool OwnershipChangedOnAllTargetedClients(List<NetworkManager> networkManagers, ulong networkObjectId, ulong expectedOwner)
|
||||||
|
{
|
||||||
|
foreach (var networkManager in networkManagers)
|
||||||
|
{
|
||||||
|
if (!networkManager.SpawnManager.SpawnedObjects.ContainsKey(networkObjectId))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (networkManager.SpawnManager.SpawnedObjects[networkObjectId].OwnerClientId != expectedOwner)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private const int k_NumberOfSpawnedObjects = 5;
|
private const int k_NumberOfSpawnedObjects = 5;
|
||||||
|
|
||||||
private bool AllClientsHaveCorrectObjectCount()
|
private bool AllClientsHaveCorrectObjectCount()
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest
|
internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest
|
||||||
{
|
{
|
||||||
protected override int NumberOfClients => 1;
|
protected override int NumberOfClients => 1;
|
||||||
// "many" in this case means enough to exceed a ushort_max message size written in the header
|
|
||||||
// 1500 is not a magic number except that it's big enough to trigger a failure
|
|
||||||
private const int k_SpawnedObjects = 1500;
|
private const int k_SpawnedObjects = 1500;
|
||||||
|
|
||||||
private NetworkPrefab m_PrefabToSpawn;
|
private NetworkPrefab m_PrefabToSpawn;
|
||||||
@@ -52,19 +50,23 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[UnityTest]
|
[UnityTest]
|
||||||
// When this test fails it does so without an exception and will wait the default ~6 minutes
|
|
||||||
[Timeout(10000)]
|
|
||||||
public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived()
|
public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived()
|
||||||
{
|
{
|
||||||
|
var timeStarted = Time.realtimeSinceStartup;
|
||||||
for (int x = 0; x < k_SpawnedObjects; x++)
|
for (int x = 0; x < k_SpawnedObjects; x++)
|
||||||
{
|
{
|
||||||
NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent<NetworkObject>();
|
NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent<NetworkObject>();
|
||||||
serverObject.NetworkManagerOwner = m_ServerNetworkManager;
|
serverObject.NetworkManagerOwner = m_ServerNetworkManager;
|
||||||
serverObject.Spawn();
|
serverObject.Spawn();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var timeSpawned = Time.realtimeSinceStartup - timeStarted;
|
||||||
|
// Provide plenty of time to spawn all 1500 objects in case the CI VM is running slow
|
||||||
|
var timeoutHelper = new TimeoutHelper(30);
|
||||||
// ensure all objects are replicated
|
// ensure all objects are replicated
|
||||||
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects);
|
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects, timeoutHelper);
|
||||||
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects!");
|
|
||||||
|
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects! Time to spawn: {timeSpawned} | Time to timeout: {timeStarted - Time.realtimeSinceStartup}", timeoutHelper);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.Components;
|
using Unity.Netcode.Components;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
@@ -539,5 +540,279 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestFixture(HostOrServer.DAHost, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using distributed authority
|
||||||
|
[TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Server)] // Validate we have not impacted NetworkTransform server authoritative mode
|
||||||
|
[TestFixture(HostOrServer.Host, NetworkTransform.AuthorityModes.Owner)] // Validate the NetworkTransform owner authoritative mode fix using client-server
|
||||||
|
internal class NestedNetworkTransformTests : IntegrationTestWithApproximation
|
||||||
|
{
|
||||||
|
private const int k_NestedChildren = 5;
|
||||||
|
protected override int NumberOfClients => 2;
|
||||||
|
|
||||||
|
private GameObject m_SpawnObject;
|
||||||
|
|
||||||
|
private NetworkTransform.AuthorityModes m_AuthorityMode;
|
||||||
|
|
||||||
|
private StringBuilder m_ErrorLog = new StringBuilder();
|
||||||
|
|
||||||
|
private List<NetworkManager> m_NetworkManagers = new List<NetworkManager>();
|
||||||
|
private List<GameObject> m_SpawnedObjects = new List<GameObject>();
|
||||||
|
|
||||||
|
public NestedNetworkTransformTests(HostOrServer hostOrServer, NetworkTransform.AuthorityModes authorityMode) : base(hostOrServer)
|
||||||
|
{
|
||||||
|
m_AuthorityMode = authorityMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a player prefab with several nested NetworkTransforms
|
||||||
|
/// </summary>
|
||||||
|
protected override void OnCreatePlayerPrefab()
|
||||||
|
{
|
||||||
|
var networkTransform = m_PlayerPrefab.AddComponent<NetworkTransform>();
|
||||||
|
networkTransform.AuthorityMode = m_AuthorityMode;
|
||||||
|
var parent = m_PlayerPrefab;
|
||||||
|
// Add several nested NetworkTransforms
|
||||||
|
for (int i = 0; i < k_NestedChildren; i++)
|
||||||
|
{
|
||||||
|
var nestedChild = new GameObject();
|
||||||
|
nestedChild.transform.parent = parent.transform;
|
||||||
|
var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>();
|
||||||
|
nestedNetworkTransform.AuthorityMode = m_AuthorityMode;
|
||||||
|
nestedNetworkTransform.InLocalSpace = true;
|
||||||
|
parent = nestedChild;
|
||||||
|
}
|
||||||
|
base.OnCreatePlayerPrefab();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RandomizeObjectTransformPositions(GameObject gameObject)
|
||||||
|
{
|
||||||
|
var networkObject = gameObject.GetComponent<NetworkObject>();
|
||||||
|
Assert.True(networkObject.ChildNetworkBehaviours.Count > 0);
|
||||||
|
|
||||||
|
foreach (var networkTransform in networkObject.NetworkTransforms)
|
||||||
|
{
|
||||||
|
networkTransform.gameObject.transform.position = GetRandomVector3(-15.0f, 15.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Randomizes each player's position when validating distributed authority
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private GameObject FetchLocalPlayerPrefabToSpawn()
|
||||||
|
{
|
||||||
|
RandomizeObjectTransformPositions(m_PlayerPrefab);
|
||||||
|
return m_PlayerPrefab;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Randomizes the player position when validating client-server
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="connectionApprovalRequest"></param>
|
||||||
|
/// <param name="connectionApprovalResponse"></param>
|
||||||
|
private void ConnectionApprovalHandler(NetworkManager.ConnectionApprovalRequest connectionApprovalRequest, NetworkManager.ConnectionApprovalResponse connectionApprovalResponse)
|
||||||
|
{
|
||||||
|
connectionApprovalResponse.Approved = true;
|
||||||
|
connectionApprovalResponse.CreatePlayerObject = true;
|
||||||
|
RandomizeObjectTransformPositions(m_PlayerPrefab);
|
||||||
|
connectionApprovalResponse.Position = GetRandomVector3(-15.0f, 15.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnServerAndClientsCreated()
|
||||||
|
{
|
||||||
|
// Create a prefab to spawn with each NetworkManager as the owner
|
||||||
|
m_SpawnObject = CreateNetworkObjectPrefab("SpawnObj");
|
||||||
|
var networkTransform = m_SpawnObject.AddComponent<NetworkTransform>();
|
||||||
|
networkTransform.AuthorityMode = m_AuthorityMode;
|
||||||
|
var parent = m_SpawnObject;
|
||||||
|
// Add several nested NetworkTransforms
|
||||||
|
for (int i = 0; i < k_NestedChildren; i++)
|
||||||
|
{
|
||||||
|
var nestedChild = new GameObject();
|
||||||
|
nestedChild.transform.parent = parent.transform;
|
||||||
|
var nestedNetworkTransform = nestedChild.AddComponent<NetworkTransform>();
|
||||||
|
nestedNetworkTransform.AuthorityMode = m_AuthorityMode;
|
||||||
|
nestedNetworkTransform.InLocalSpace = true;
|
||||||
|
parent = nestedChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_DistributedAuthority)
|
||||||
|
{
|
||||||
|
if (!UseCMBService())
|
||||||
|
{
|
||||||
|
m_ServerNetworkManager.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
client.OnFetchLocalPlayerPrefabToSpawn = FetchLocalPlayerPrefabToSpawn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true;
|
||||||
|
m_ServerNetworkManager.ConnectionApprovalCallback += ConnectionApprovalHandler;
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
client.NetworkConfig.ConnectionApproval = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
base.OnServerAndClientsCreated();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates the transform positions of two NetworkObject instances
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="current">the local instance (source of truth)</param>
|
||||||
|
/// <param name="testing">the remote instance</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private bool ValidateTransforms(NetworkObject current, NetworkObject testing)
|
||||||
|
{
|
||||||
|
if (current.ChildNetworkBehaviours.Count == 0 || testing.ChildNetworkBehaviours.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < current.NetworkTransforms.Count - 1; i++)
|
||||||
|
{
|
||||||
|
var transformA = current.NetworkTransforms[i].transform;
|
||||||
|
var transformB = testing.NetworkTransforms[i].transform;
|
||||||
|
if (!Approximately(transformA.position, transformB.position))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"TransformA Position {transformA.position} != TransformB Position {transformB.position}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!Approximately(transformA.localPosition, transformB.localPosition))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"TransformA Local Position {transformA.position} != TransformB Local Position {transformB.position}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (transformA.parent != null)
|
||||||
|
{
|
||||||
|
if (current.NetworkTransforms[i].InLocalSpace != testing.NetworkTransforms[i].InLocalSpace)
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"NetworkTransform-{current.OwnerClientId}-{current.NetworkTransforms[i].NetworkBehaviourId} InLocalSpace ({current.NetworkTransforms[i].InLocalSpace}) is different from the remote instance version on Client-{testing.NetworkManager.LocalClientId}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates all player instances spawned with the correct positions including all nested NetworkTransforms
|
||||||
|
/// When running in server authority mode we are validating this fix did not impact that.
|
||||||
|
/// </summary>
|
||||||
|
private bool AllClientInstancesSynchronized()
|
||||||
|
{
|
||||||
|
m_ErrorLog.Clear();
|
||||||
|
|
||||||
|
foreach (var current in m_NetworkManagers)
|
||||||
|
{
|
||||||
|
var currentPlayer = current.LocalClient.PlayerObject;
|
||||||
|
var currentNetworkObjectId = currentPlayer.NetworkObjectId;
|
||||||
|
foreach (var testing in m_NetworkManagers)
|
||||||
|
{
|
||||||
|
if (currentPlayer == testing.LocalClient.PlayerObject)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"Failed to find Client-{currentPlayer.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId];
|
||||||
|
if (!ValidateTransforms(currentPlayer, remoteInstance))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"Failed to validate Client-{currentPlayer.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that dynamically spawning works the same.
|
||||||
|
/// When running in server authority mode we are validating this fix did not impact that.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private bool AllSpawnedObjectsSynchronized()
|
||||||
|
{
|
||||||
|
m_ErrorLog.Clear();
|
||||||
|
|
||||||
|
foreach (var current in m_SpawnedObjects)
|
||||||
|
{
|
||||||
|
var currentNetworkObject = current.GetComponent<NetworkObject>();
|
||||||
|
var currentNetworkObjectId = currentNetworkObject.NetworkObjectId;
|
||||||
|
foreach (var testing in m_NetworkManagers)
|
||||||
|
{
|
||||||
|
if (currentNetworkObject.OwnerClientId == testing.LocalClientId)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!testing.SpawnManager.SpawnedObjects.ContainsKey(currentNetworkObjectId))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"Failed to find Client-{currentNetworkObject.OwnerClientId}'s player instance on Client-{testing.LocalClientId}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var remoteInstance = testing.SpawnManager.SpawnedObjects[currentNetworkObjectId];
|
||||||
|
if (!ValidateTransforms(currentNetworkObject, remoteInstance))
|
||||||
|
{
|
||||||
|
m_ErrorLog.AppendLine($"Failed to validate Client-{currentNetworkObject.OwnerClientId} against its remote instance on Client-{testing.LocalClientId}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validates that spawning player and dynamically spawned prefab instances with nested NetworkTransforms
|
||||||
|
/// synchronizes properly in both client-server and distributed authority when using owner authoritative mode.
|
||||||
|
/// </summary>
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator NestedNetworkTransformSpawnPositionTest()
|
||||||
|
{
|
||||||
|
if (!m_DistributedAuthority || (m_DistributedAuthority && !UseCMBService()))
|
||||||
|
{
|
||||||
|
m_NetworkManagers.Add(m_ServerNetworkManager);
|
||||||
|
}
|
||||||
|
m_NetworkManagers.AddRange(m_ClientNetworkManagers);
|
||||||
|
|
||||||
|
yield return WaitForConditionOrTimeOut(AllClientInstancesSynchronized);
|
||||||
|
AssertOnTimeout($"Failed to synchronize all client instances!\n{m_ErrorLog}");
|
||||||
|
|
||||||
|
foreach (var networkManager in m_NetworkManagers)
|
||||||
|
{
|
||||||
|
// Randomize the position
|
||||||
|
RandomizeObjectTransformPositions(m_SpawnObject);
|
||||||
|
|
||||||
|
// Create an instance owned by the specified networkmanager
|
||||||
|
m_SpawnedObjects.Add(SpawnObject(m_SpawnObject, networkManager));
|
||||||
|
}
|
||||||
|
// Randomize the position once more just to assure we are instantiating remote instances
|
||||||
|
// with a completely different position
|
||||||
|
RandomizeObjectTransformPositions(m_SpawnObject);
|
||||||
|
yield return WaitForConditionOrTimeOut(AllSpawnedObjectsSynchronized);
|
||||||
|
AssertOnTimeout($"Failed to synchronize all spawned NetworkObject instances!\n{m_ErrorLog}");
|
||||||
|
m_SpawnedObjects.Clear();
|
||||||
|
m_NetworkManagers.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerator OnTearDown()
|
||||||
|
{
|
||||||
|
// In case there was a failure, go ahead and clear these lists out for any pending TextFixture passes
|
||||||
|
m_SpawnedObjects.Clear();
|
||||||
|
m_NetworkManagers.Clear();
|
||||||
|
return base.OnTearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,7 @@
|
|||||||
#if COM_UNITY_MODULES_PHYSICS
|
#if COM_UNITY_MODULES_PHYSICS
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using Unity.Netcode.Components;
|
using Unity.Netcode.Components;
|
||||||
using Unity.Netcode.TestHelpers.Runtime;
|
using Unity.Netcode.TestHelpers.Runtime;
|
||||||
@@ -108,5 +110,386 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!");
|
Assert.IsTrue(clientPlayerInstance == null, $"[Client-Side] Player {nameof(NetworkObject)} is not null!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ContactEventTransformHelperWithInfo : ContactEventTransformHelper, IContactEventHandlerWithInfo
|
||||||
|
{
|
||||||
|
public ContactEventHandlerInfo GetContactEventHandlerInfo()
|
||||||
|
{
|
||||||
|
var contactEventHandlerInfo = new ContactEventHandlerInfo()
|
||||||
|
{
|
||||||
|
HasContactEventPriority = IsOwner,
|
||||||
|
ProvideNonRigidBodyContactEvents = m_EnableNonRigidbodyContacts.Value,
|
||||||
|
};
|
||||||
|
return contactEventHandlerInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnRegisterForContactEvents(bool isRegistering)
|
||||||
|
{
|
||||||
|
RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class ContactEventTransformHelper : NetworkTransform, IContactEventHandler
|
||||||
|
{
|
||||||
|
public static Vector3 SessionOwnerSpawnPoint;
|
||||||
|
public static Vector3 ClientSpawnPoint;
|
||||||
|
public static bool VerboseDebug;
|
||||||
|
public enum HelperStates
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
MoveForward,
|
||||||
|
}
|
||||||
|
|
||||||
|
private HelperStates m_HelperState;
|
||||||
|
|
||||||
|
public void SetHelperState(HelperStates state)
|
||||||
|
{
|
||||||
|
m_HelperState = state;
|
||||||
|
if (!m_NetworkRigidbody.IsKinematic())
|
||||||
|
{
|
||||||
|
m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero;
|
||||||
|
m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero;
|
||||||
|
}
|
||||||
|
m_NetworkRigidbody.Rigidbody.isKinematic = m_HelperState == HelperStates.None;
|
||||||
|
if (!m_NetworkRigidbody.IsKinematic())
|
||||||
|
{
|
||||||
|
m_NetworkRigidbody.Rigidbody.angularVelocity = Vector3.zero;
|
||||||
|
m_NetworkRigidbody.Rigidbody.linearVelocity = Vector3.zero;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected struct ContactEventInfo
|
||||||
|
{
|
||||||
|
public ulong EventId;
|
||||||
|
public Vector3 AveragedCollisionNormal;
|
||||||
|
public Rigidbody CollidingBody;
|
||||||
|
public Vector3 ContactPoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<ContactEventInfo> m_ContactEvents = new List<ContactEventInfo>();
|
||||||
|
|
||||||
|
protected NetworkVariable<bool> m_EnableNonRigidbodyContacts = new NetworkVariable<bool>(false, NetworkVariableReadPermission.Everyone, NetworkVariableWritePermission.Owner);
|
||||||
|
|
||||||
|
protected NetworkRigidbody m_NetworkRigidbody;
|
||||||
|
public ContactEventTransformHelper Target;
|
||||||
|
|
||||||
|
public bool HasContactEvents()
|
||||||
|
{
|
||||||
|
return m_ContactEvents.Count > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rigidbody GetRigidbody()
|
||||||
|
{
|
||||||
|
return m_NetworkRigidbody.Rigidbody;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HadContactWith(ContactEventTransformHelper otherObject)
|
||||||
|
{
|
||||||
|
if (otherObject == null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
foreach (var contactEvent in m_ContactEvents)
|
||||||
|
{
|
||||||
|
if (contactEvent.CollidingBody == otherObject.m_NetworkRigidbody.Rigidbody)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void CheckToStopMoving()
|
||||||
|
{
|
||||||
|
SetHelperState(HadContactWith(Target) ? HelperStates.None : HelperStates.MoveForward);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ContactEvent(ulong eventId, Vector3 averagedCollisionNormal, Rigidbody collidingBody, Vector3 contactPoint, bool hasCollisionStay = false, Vector3 averagedCollisionStayNormal = default)
|
||||||
|
{
|
||||||
|
if (Target == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (collidingBody != null)
|
||||||
|
{
|
||||||
|
Log($">>>>>>> contact event with {collidingBody.name}!");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Log($">>>>>>> contact event with non-rigidbody!");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ContactEvents.Add(new ContactEventInfo()
|
||||||
|
{
|
||||||
|
EventId = eventId,
|
||||||
|
AveragedCollisionNormal = averagedCollisionNormal,
|
||||||
|
CollidingBody = collidingBody,
|
||||||
|
ContactPoint = contactPoint,
|
||||||
|
});
|
||||||
|
CheckToStopMoving();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetInitialPositionClientServer()
|
||||||
|
{
|
||||||
|
if (IsServer)
|
||||||
|
{
|
||||||
|
if (!NetworkManager.DistributedAuthorityMode && !IsLocalPlayer)
|
||||||
|
{
|
||||||
|
transform.position = ClientSpawnPoint;
|
||||||
|
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transform.position = SessionOwnerSpawnPoint;
|
||||||
|
m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transform.position = ClientSpawnPoint;
|
||||||
|
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetInitialPositionDistributedAuthority()
|
||||||
|
{
|
||||||
|
if (HasAuthority)
|
||||||
|
{
|
||||||
|
if (IsSessionOwner)
|
||||||
|
{
|
||||||
|
transform.position = SessionOwnerSpawnPoint;
|
||||||
|
m_NetworkRigidbody.Rigidbody.position = SessionOwnerSpawnPoint;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
transform.position = ClientSpawnPoint;
|
||||||
|
m_NetworkRigidbody.Rigidbody.position = ClientSpawnPoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void OnNetworkSpawn()
|
||||||
|
{
|
||||||
|
m_NetworkRigidbody = GetComponent<NetworkRigidbody>();
|
||||||
|
|
||||||
|
m_NetworkRigidbody.Rigidbody.maxLinearVelocity = 15;
|
||||||
|
m_NetworkRigidbody.Rigidbody.maxAngularVelocity = 10;
|
||||||
|
|
||||||
|
if (NetworkManager.DistributedAuthorityMode)
|
||||||
|
{
|
||||||
|
SetInitialPositionDistributedAuthority();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetInitialPositionClientServer();
|
||||||
|
}
|
||||||
|
if (IsLocalPlayer)
|
||||||
|
{
|
||||||
|
RegisterForContactEvents(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_NetworkRigidbody.Rigidbody.detectCollisions = false;
|
||||||
|
}
|
||||||
|
base.OnNetworkSpawn();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void OnRegisterForContactEvents(bool isRegistering)
|
||||||
|
{
|
||||||
|
RigidbodyContactEventManager.Instance.RegisterHandler(this, isRegistering);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterForContactEvents(bool isRegistering)
|
||||||
|
{
|
||||||
|
OnRegisterForContactEvents(isRegistering);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void FixedUpdate()
|
||||||
|
{
|
||||||
|
if (!IsSpawned || !IsOwner || m_HelperState != HelperStates.MoveForward)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var distance = Vector3.Distance(Target.transform.position, transform.position);
|
||||||
|
var moveAmount = Mathf.Max(1.2f, distance);
|
||||||
|
// Head towards our target
|
||||||
|
var dir = (Target.transform.position - transform.position).normalized;
|
||||||
|
var deltaMove = dir * moveAmount * Time.fixedDeltaTime;
|
||||||
|
m_NetworkRigidbody.Rigidbody.MovePosition(m_NetworkRigidbody.Rigidbody.position + deltaMove);
|
||||||
|
|
||||||
|
|
||||||
|
Log($" Loc: {transform.position} | Dest: {Target.transform.position} | Dist: {distance} | MoveDelta: {deltaMove}");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void Log(string msg)
|
||||||
|
{
|
||||||
|
if (VerboseDebug)
|
||||||
|
{
|
||||||
|
Debug.Log($"Client-{OwnerClientId} {msg}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[TestFixture(HostOrServer.Host, ContactEventTypes.Default)]
|
||||||
|
[TestFixture(HostOrServer.DAHost, ContactEventTypes.Default)]
|
||||||
|
[TestFixture(HostOrServer.Host, ContactEventTypes.WithInfo)]
|
||||||
|
[TestFixture(HostOrServer.DAHost, ContactEventTypes.WithInfo)]
|
||||||
|
internal class RigidbodyContactEventManagerTests : IntegrationTestWithApproximation
|
||||||
|
{
|
||||||
|
protected override int NumberOfClients => 1;
|
||||||
|
|
||||||
|
|
||||||
|
private GameObject m_RigidbodyContactEventManager;
|
||||||
|
|
||||||
|
public enum ContactEventTypes
|
||||||
|
{
|
||||||
|
Default,
|
||||||
|
WithInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContactEventTypes m_ContactEventType;
|
||||||
|
private StringBuilder m_ErrorLogger = new StringBuilder();
|
||||||
|
|
||||||
|
public RigidbodyContactEventManagerTests(HostOrServer hostOrServer, ContactEventTypes contactEventType) : base(hostOrServer)
|
||||||
|
{
|
||||||
|
m_ContactEventType = contactEventType;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCreatePlayerPrefab()
|
||||||
|
{
|
||||||
|
ContactEventTransformHelper.SessionOwnerSpawnPoint = GetRandomVector3(-4, -3);
|
||||||
|
ContactEventTransformHelper.ClientSpawnPoint = GetRandomVector3(3, 4);
|
||||||
|
if (m_ContactEventType == ContactEventTypes.Default)
|
||||||
|
{
|
||||||
|
var helper = m_PlayerPrefab.AddComponent<ContactEventTransformHelper>();
|
||||||
|
helper.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var helperWithInfo = m_PlayerPrefab.AddComponent<ContactEventTransformHelperWithInfo>();
|
||||||
|
helperWithInfo.AuthorityMode = NetworkTransform.AuthorityModes.Owner;
|
||||||
|
}
|
||||||
|
|
||||||
|
var rigidbody = m_PlayerPrefab.AddComponent<Rigidbody>();
|
||||||
|
rigidbody.useGravity = false;
|
||||||
|
rigidbody.isKinematic = true;
|
||||||
|
rigidbody.mass = 5.0f;
|
||||||
|
rigidbody.collisionDetectionMode = CollisionDetectionMode.Continuous;
|
||||||
|
var sphereCollider = m_PlayerPrefab.AddComponent<SphereCollider>();
|
||||||
|
sphereCollider.radius = 0.5f;
|
||||||
|
sphereCollider.providesContacts = true;
|
||||||
|
|
||||||
|
var networkRigidbody = m_PlayerPrefab.AddComponent<NetworkRigidbody>();
|
||||||
|
networkRigidbody.UseRigidBodyForMotion = true;
|
||||||
|
networkRigidbody.AutoUpdateKinematicState = false;
|
||||||
|
|
||||||
|
m_RigidbodyContactEventManager = new GameObject();
|
||||||
|
m_RigidbodyContactEventManager.AddComponent<RigidbodyContactEventManager>();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private bool PlayersSpawnedInRightLocation()
|
||||||
|
{
|
||||||
|
var position = m_ServerNetworkManager.LocalClient.PlayerObject.transform.position;
|
||||||
|
if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, position))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
position = m_ClientNetworkManagers[0].LocalClient.PlayerObject.transform.position;
|
||||||
|
if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
var playerObject = (NetworkObject)null;
|
||||||
|
if (!m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} cannot find a local spawned instance of Client-{m_ClientNetworkManagers[0].LocalClientId}'s player object!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playerObject = m_ServerNetworkManager.SpawnManager.SpawnedObjects[m_ClientNetworkManagers[0].LocalClient.PlayerObject.NetworkObjectId];
|
||||||
|
position = playerObject.transform.position;
|
||||||
|
|
||||||
|
if (!Approximately(ContactEventTransformHelper.ClientSpawnPoint, position))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ServerNetworkManager.LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.ClientSpawnPoint}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} cannot find a local spawned instance of Client-{m_ServerNetworkManager.LocalClientId}'s player object!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
playerObject = m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId];
|
||||||
|
position = playerObject.transform.position;
|
||||||
|
if (!Approximately(ContactEventTransformHelper.SessionOwnerSpawnPoint, playerObject.transform.position))
|
||||||
|
{
|
||||||
|
m_ErrorLogger.AppendLine($"Client-{m_ClientNetworkManagers[0].LocalClientId} player position {position} for Client-{playerObject.OwnerClientId} does not match the assigned player position {ContactEventTransformHelper.SessionOwnerSpawnPoint}!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator TestContactEvents()
|
||||||
|
{
|
||||||
|
ContactEventTransformHelper.VerboseDebug = m_EnableVerboseDebug;
|
||||||
|
|
||||||
|
m_PlayerPrefab.SetActive(false);
|
||||||
|
m_ErrorLogger.Clear();
|
||||||
|
// Validate all instances are spawned in the right location
|
||||||
|
yield return WaitForConditionOrTimeOut(PlayersSpawnedInRightLocation);
|
||||||
|
AssertOnTimeout($"Timed out waiting for all player instances to spawn in the corect location:\n {m_ErrorLogger}");
|
||||||
|
m_ErrorLogger.Clear();
|
||||||
|
|
||||||
|
var sessionOwnerPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent<ContactEventTransformHelper>() :
|
||||||
|
m_ServerNetworkManager.LocalClient.PlayerObject.GetComponent<ContactEventTransformHelperWithInfo>();
|
||||||
|
var clientPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<ContactEventTransformHelper>() :
|
||||||
|
m_ClientNetworkManagers[0].LocalClient.PlayerObject.GetComponent<ContactEventTransformHelperWithInfo>();
|
||||||
|
|
||||||
|
// Get both players to point towards each other
|
||||||
|
sessionOwnerPlayer.Target = clientPlayer;
|
||||||
|
clientPlayer.Target = sessionOwnerPlayer;
|
||||||
|
|
||||||
|
sessionOwnerPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward);
|
||||||
|
clientPlayer.SetHelperState(ContactEventTransformHelper.HelperStates.MoveForward);
|
||||||
|
|
||||||
|
|
||||||
|
yield return WaitForConditionOrTimeOut(() => sessionOwnerPlayer.HadContactWith(clientPlayer) || clientPlayer.HadContactWith(sessionOwnerPlayer));
|
||||||
|
AssertOnTimeout("Timed out waiting for a player to collide with another player!");
|
||||||
|
|
||||||
|
clientPlayer.RegisterForContactEvents(false);
|
||||||
|
sessionOwnerPlayer.RegisterForContactEvents(false);
|
||||||
|
var otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelper>() :
|
||||||
|
m_ServerNetworkManager.SpawnManager.SpawnedObjects[clientPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelperWithInfo>();
|
||||||
|
otherPlayer.RegisterForContactEvents(false);
|
||||||
|
otherPlayer = m_ContactEventType == ContactEventTypes.Default ? m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelper>() :
|
||||||
|
m_ClientNetworkManagers[0].SpawnManager.SpawnedObjects[sessionOwnerPlayer.NetworkObjectId].GetComponent<ContactEventTransformHelperWithInfo>();
|
||||||
|
otherPlayer.RegisterForContactEvents(false);
|
||||||
|
|
||||||
|
Object.Destroy(m_RigidbodyContactEventManager);
|
||||||
|
m_RigidbodyContactEventManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override IEnumerator OnTearDown()
|
||||||
|
{
|
||||||
|
// In case of a test failure
|
||||||
|
if (m_RigidbodyContactEventManager)
|
||||||
|
{
|
||||||
|
Object.Destroy(m_RigidbodyContactEventManager);
|
||||||
|
m_RigidbodyContactEventManager = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnTearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif // COM_UNITY_MODULES_PHYSICS
|
#endif // COM_UNITY_MODULES_PHYSICS
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
[TestFixture(HostOrServer.Server)]
|
[TestFixture(HostOrServer.Server)]
|
||||||
internal class PlayerObjectTests : NetcodeIntegrationTest
|
internal class PlayerObjectTests : NetcodeIntegrationTest
|
||||||
{
|
{
|
||||||
protected override int NumberOfClients => 1;
|
protected override int NumberOfClients => 2;
|
||||||
|
|
||||||
protected GameObject m_NewPlayerToSpawn;
|
protected GameObject m_NewPlayerToSpawn;
|
||||||
|
|
||||||
@@ -52,4 +52,136 @@ namespace Unity.Netcode.RuntimeTests
|
|||||||
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!");
|
Assert.False(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for client-side player object to change!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Validate that when auto-player spawning but SpawnWithObservers is disabled,
|
||||||
|
/// the player instantiated is only spawned on the authority side.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture(HostOrServer.DAHost)]
|
||||||
|
[TestFixture(HostOrServer.Host)]
|
||||||
|
[TestFixture(HostOrServer.Server)]
|
||||||
|
internal class PlayerSpawnNoObserversTest : NetcodeIntegrationTest
|
||||||
|
{
|
||||||
|
protected override int NumberOfClients => 2;
|
||||||
|
|
||||||
|
public PlayerSpawnNoObserversTest(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||||
|
|
||||||
|
protected override bool ShouldCheckForSpawnedPlayers()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnCreatePlayerPrefab()
|
||||||
|
{
|
||||||
|
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
|
||||||
|
playerNetworkObject.SpawnWithObservers = false;
|
||||||
|
base.OnCreatePlayerPrefab();
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator SpawnWithNoObservers()
|
||||||
|
{
|
||||||
|
yield return s_DefaultWaitForTick;
|
||||||
|
|
||||||
|
if (!m_DistributedAuthority)
|
||||||
|
{
|
||||||
|
// Make sure clients did not spawn their player object on any of the clients including the owner.
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
foreach (var playerObject in m_ServerNetworkManager.SpawnManager.PlayerObjects)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObject.NetworkObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{playerObject.NetworkObjectId}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// For distributed authority, we want to make sure the player object is only spawned on the authority side and all non-authority instances did not spawn it.
|
||||||
|
var playerObjectId = m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId;
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{m_ServerNetworkManager.LocalClientId}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var clientPlayer in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
playerObjectId = clientPlayer.LocalClient.PlayerObject.NetworkObjectId;
|
||||||
|
Assert.IsFalse(m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{m_ServerNetworkManager.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
if (clientPlayer == client)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Assert.IsFalse(client.SpawnManager.SpawnedObjects.ContainsKey(playerObjectId), $"Client-{client.LocalClientId} spawned player object for Client-{clientPlayer.LocalClientId}!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This test validates the player position and rotation is correct
|
||||||
|
/// relative to the prefab's initial settings if no changes are applied.
|
||||||
|
/// </summary>
|
||||||
|
[TestFixture(HostOrServer.DAHost)]
|
||||||
|
[TestFixture(HostOrServer.Host)]
|
||||||
|
[TestFixture(HostOrServer.Server)]
|
||||||
|
internal class PlayerSpawnPositionTests : IntegrationTestWithApproximation
|
||||||
|
{
|
||||||
|
protected override int NumberOfClients => 2;
|
||||||
|
|
||||||
|
public PlayerSpawnPositionTests(HostOrServer hostOrServer) : base(hostOrServer) { }
|
||||||
|
|
||||||
|
private Vector3 m_PlayerPosition;
|
||||||
|
private Quaternion m_PlayerRotation;
|
||||||
|
|
||||||
|
protected override void OnCreatePlayerPrefab()
|
||||||
|
{
|
||||||
|
var playerNetworkObject = m_PlayerPrefab.GetComponent<NetworkObject>();
|
||||||
|
m_PlayerPosition = GetRandomVector3(-10.0f, 10.0f);
|
||||||
|
m_PlayerRotation = Quaternion.Euler(GetRandomVector3(-180.0f, 180.0f));
|
||||||
|
playerNetworkObject.transform.position = m_PlayerPosition;
|
||||||
|
playerNetworkObject.transform.rotation = m_PlayerRotation;
|
||||||
|
base.OnCreatePlayerPrefab();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlayerTransformMatches(NetworkObject player)
|
||||||
|
{
|
||||||
|
var position = player.transform.position;
|
||||||
|
var rotation = player.transform.rotation;
|
||||||
|
Assert.True(Approximately(m_PlayerPosition, position), $"Client-{player.OwnerClientId} position {position} does not match the prefab position {m_PlayerPosition}!");
|
||||||
|
Assert.True(Approximately(m_PlayerRotation, rotation), $"Client-{player.OwnerClientId} rotation {rotation.eulerAngles} does not match the prefab rotation {m_PlayerRotation.eulerAngles}!");
|
||||||
|
}
|
||||||
|
|
||||||
|
[UnityTest]
|
||||||
|
public IEnumerator PlayerSpawnPosition()
|
||||||
|
{
|
||||||
|
if (m_ServerNetworkManager.IsHost)
|
||||||
|
{
|
||||||
|
PlayerTransformMatches(m_ServerNetworkManager.LocalClient.PlayerObject);
|
||||||
|
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeOut(() => client.SpawnManager.SpawnedObjects.ContainsKey(m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId));
|
||||||
|
AssertOnTimeout($"Client-{client.LocalClientId} does not contain a player prefab instance for client-{m_ServerNetworkManager.LocalClientId}!");
|
||||||
|
PlayerTransformMatches(client.SpawnManager.SpawnedObjects[m_ServerNetworkManager.LocalClient.PlayerObject.NetworkObjectId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var client in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeOut(() => m_ServerNetworkManager.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
|
||||||
|
AssertOnTimeout($"Client-{m_ServerNetworkManager.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
|
||||||
|
PlayerTransformMatches(m_ServerNetworkManager.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
|
||||||
|
foreach (var subClient in m_ClientNetworkManagers)
|
||||||
|
{
|
||||||
|
yield return WaitForConditionOrTimeOut(() => subClient.SpawnManager.SpawnedObjects.ContainsKey(client.LocalClient.PlayerObject.NetworkObjectId));
|
||||||
|
AssertOnTimeout($"Client-{subClient.LocalClientId} does not contain a player prefab instance for client-{client.LocalClientId}!");
|
||||||
|
PlayerTransformMatches(subClient.SpawnManager.SpawnedObjects[client.LocalClient.PlayerObject.NetworkObjectId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
package.json
10
package.json
@@ -2,23 +2,23 @@
|
|||||||
"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": "2.0.0",
|
"version": "2.1.1",
|
||||||
"unity": "6000.0",
|
"unity": "6000.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"com.unity.nuget.mono-cecil": "1.11.4",
|
"com.unity.nuget.mono-cecil": "1.11.4",
|
||||||
"com.unity.transport": "2.3.0"
|
"com.unity.transport": "2.3.0"
|
||||||
},
|
},
|
||||||
"_upm": {
|
"_upm": {
|
||||||
"changelog": "### Added\n\n- Added tooltips for all of the `NetworkObject` component's properties. (#3052)\n- Added message size validation to named and unnamed message sending functions for better error messages. (#3049)\n- Added \"Check for NetworkObject Component\" property to the Multiplayer->Netcode for GameObjects project settings. When disabled, this will bypass the in-editor `NetworkObject` check on `NetworkBehaviour` components. (#3031)\n- Added `NetworkTransform.SwitchTransformSpaceWhenParented` property that, when enabled, will handle the world to local, local to world, and local to local transform space transitions when interpolation is enabled. (#3013)\n- Added `NetworkTransform.TickSyncChildren` that, when enabled, will tick synchronize nested and/or child `NetworkTransform` components to eliminate any potential visual jittering that could occur if the `NetworkTransform` instances get into a state where their state updates are landing on different network ticks. (#3013)\n- Added `NetworkObject.AllowOwnerToParent` property to provide the ability to allow clients to parent owned objects when running in a client-server network topology. (#3013)\n- Added `NetworkObject.SyncOwnerTransformWhenParented` property to provide a way to disable applying the server's transform information in the parenting message on the client owner instance which can be useful for owner authoritative motion models. (#3013)\n- Added `NetcodeEditorBase` editor helper class to provide easier modification and extension of the SDK's components. (#3013)\n\n### Fixed\n\n- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057)\n- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052)\n- Fixed issue where a NetworkObject hidden from a client that is then promoted to be session owner was not being synchronized with newly joining clients.(#3051)\n- Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045)\n- Fixed issue where setting a prefab hash value during connection approval but not having a player prefab assigned could cause an exception when spawning a player. (#3042)\n- Fixed issue where the `NetworkSpawnManager.HandleNetworkObjectShow` could throw an exception if one of the `NetworkObject` components to show was destroyed during the same frame. (#3030)\n- Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026)\n- Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013)\n- Fixed issue with smooth transitions between transform spaces when interpolation is enabled (requires `NetworkTransform.SwitchTransformSpaceWhenParented` to be enabled). (#3013)\n\n### Changed\n\n- Changed `NetworkTransformEditor` now uses `NetworkTransform` as the base type class to assure it doesn't display a foldout group when using the base `NetworkTransform` component class. (#3052)\n- Changed `NetworkAnimator.Awake` is now a protected virtual method. (#3052)\n- Changed when invoking `NetworkManager.ConnectionManager.DisconnectClient` during a distributed authority session a more appropriate message is logged. (#3052)\n- Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013)\n- Changed `NetworkRigidbodyBaseEditor` so it now derives from `NetcodeEditorBase`. (#3013)\n- Changed `NetworkManagerEditor` so it now derives from `NetcodeEditorBase`. (#3013)"
|
"changelog": "### Added\n\n- Added ability to edit the `NetworkConfig.AutoSpawnPlayerPrefabClientSide` within the inspector view. (#3097)\n- Added `IContactEventHandlerWithInfo` that derives from `IContactEventHandler` that can be updated per frame to provide `ContactEventHandlerInfo` information to the `RigidbodyContactEventManager` when processing collisions. (#3094)\n - `ContactEventHandlerInfo.ProvideNonRigidBodyContactEvents`: When set to true, non-`Rigidbody` collisions with the registered `Rigidbody` will generate contact event notifications. (#3094)\n - `ContactEventHandlerInfo.HasContactEventPriority`: When set to true, the `Rigidbody` will be prioritized as the instance that generates the event if the `Rigidbody` colliding does not have priority. (#3094)\n- Added a static `NetworkManager.OnInstantiated` event notification to be able to track when a new `NetworkManager` instance has been instantiated. (#3088)\n- Added a static `NetworkManager.OnDestroying` event notification to be able to track when an existing `NetworkManager` instance is being destroyed. (#3088)\n\n### Fixed\n\n- Fixed issue where `NetworkPrefabProcessor` would not mark the prefab list as dirty and prevent saving the `DefaultNetworkPrefabs` asset when only imports or only deletes were detected.(#3103)\n- Fixed an issue where nested `NetworkTransform` components in owner authoritative mode cleared their initial settings on the server, causing improper synchronization. (#3099)\n- Fixed issue with service not getting synchronized with in-scene placed `NetworkObject` instances when a session owner starts a `SceneEventType.Load` event. (#3096)\n- Fixed issue with the in-scene network prefab instance update menu tool where it was not properly updating scenes when invoked on the root prefab instance. (#3092)\n- Fixed an issue where newly synchronizing clients would always receive current `NetworkVariable` values, potentially causing issues with collections if there were pending updates. Now, pending state updates serialize previous values to avoid duplicates on new clients. (#3081)\n- Fixed issue where changing ownership would mark every `NetworkVariable` dirty. Now, it will only mark any `NetworkVariable` with owner read permissions as dirty and will send/flush any pending updates to all clients prior to sending the change in ownership message. (#3081)\n- Fixed an issue where transferring ownership of `NetworkVariable` collections didn't update the new owner’s previous value, causing the last added value to be detected as a change during additions or removals. (#3081)\n- Fixed issue where a client (or server) with no write permissions for a `NetworkVariable` using a standard .NET collection type could still modify the collection which could cause various issues depending upon the modification and collection type. (#3081)\n- Fixed issue where applying the position and/or rotation to the `NetworkManager.ConnectionApprovalResponse` when connection approval and auto-spawn player prefab were enabled would not apply the position and/or rotation when the player prefab was instantiated. (#3078)\n- Fixed issue where `NetworkObject.SpawnWithObservers` was not being honored when spawning the player prefab. (#3077)\n- Fixed issue with the client count not being correct on the host or server side when a client disconnects itself from a session. (#3075)\n\n### Changed\n\n- Changed `NetworkConfig.AutoSpawnPlayerPrefabClientSide` is no longer automatically set when starting `NetworkManager`. (#3097)\n- Updated `NetworkVariableDeltaMessage` so the server now forwards delta state updates from clients immediately, instead of waiting until the end of the frame or the next network tick. (#3081)"
|
||||||
},
|
},
|
||||||
"upmCi": {
|
"upmCi": {
|
||||||
"footprint": "f1ef7566b7a89b1ee9c34cc13400735ae63964d4"
|
"footprint": "8331c76150e539e36659d8b7be3ba0fb6d21027a"
|
||||||
},
|
},
|
||||||
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.0/manual/index.html",
|
"documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@2.1/manual/index.html",
|
||||||
"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": "8a7ae9f91a53bdcabe5e7df783dd1884c07bcd6f"
|
"revision": "264b30d176dd71fcedd022a8d6f4d59a2e3922bc"
|
||||||
},
|
},
|
||||||
"samples": [
|
"samples": [
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user