2 Commits

Author SHA1 Message Date
Unity Technologies
016788c21e 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)
2024-10-18 00:00:00 +00:00
Unity Technologies
48c6a6121c com.unity.netcode.gameobjects@2.0.0
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

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

## [2.0.0] - 2024-09-12

### Added

- Added tooltips for all of the `NetworkObject` component's properties. (#3052)
- Added message size validation to named and unnamed message sending functions for better error messages. (#3049)
- 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)
- 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)
- 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)
- Added `NetworkObject.AllowOwnerToParent` property to provide the ability to allow clients to parent owned objects when running in a client-server network topology. (#3013)
- 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)
- Added `NetcodeEditorBase` editor helper class to provide easier modification and extension of the SDK's components. (#3013)

### Fixed

- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057)
- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052)
- 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)
- Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045)
- 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)
- 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)
- Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026)
- Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013)
- Fixed issue with smooth transitions between transform spaces when interpolation is enabled (requires `NetworkTransform.SwitchTransformSpaceWhenParented` to be enabled). (#3013)

### Changed

- 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)
- Changed `NetworkAnimator.Awake` is now a protected virtual method. (#3052)
- Changed  when invoking `NetworkManager.ConnectionManager.DisconnectClient` during a distributed authority session a more appropriate message is logged. (#3052)
- Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013)
- Changed `NetworkRigidbodyBaseEditor` so it now derives from `NetcodeEditorBase`. (#3013)
- Changed `NetworkManagerEditor` so it now derives from `NetcodeEditorBase`. (#3013)
2024-09-12 00:00:00 +00:00
68 changed files with 5023 additions and 787 deletions

View File

@@ -6,6 +6,71 @@ 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).
## [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 owners 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
### Added
- Added tooltips for all of the `NetworkObject` component's properties. (#3052)
- Added message size validation to named and unnamed message sending functions for better error messages. (#3049)
- 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)
- 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)
- 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)
- Added `NetworkObject.AllowOwnerToParent` property to provide the ability to allow clients to parent owned objects when running in a client-server network topology. (#3013)
- 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)
- Added `NetcodeEditorBase` editor helper class to provide easier modification and extension of the SDK's components. (#3013)
### Fixed
- Fixed issue where `NetworkAnimator` would send updates to non-observer clients. (#3057)
- Fixed issue where an exception could occur when receiving a universal RPC for a `NetworkObject` that has been despawned. (#3052)
- 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)
- Fixed issue where clients could have a wrong time delta on `NetworkVariableBase` which could prevent from sending delta state updates. (#3045)
- 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)
- 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)
- Fixed issue where the `NetworkManagerHelper` was continuing to check for hierarchy changes when in play mode. (#3026)
- Fixed issue with newly/late joined clients and `NetworkTransform` synchronization of parented `NetworkObject` instances. (#3013)
- Fixed issue with smooth transitions between transform spaces when interpolation is enabled (requires `NetworkTransform.SwitchTransformSpaceWhenParented` to be enabled). (#3013)
### Changed
- 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)
- Changed `NetworkAnimator.Awake` is now a protected virtual method. (#3052)
- Changed when invoking `NetworkManager.ConnectionManager.DisconnectClient` during a distributed authority session a more appropriate message is logged. (#3052)
- Changed `NetworkTransformEditor` so it now derives from `NetcodeEditorBase`. (#3013)
- Changed `NetworkRigidbodyBaseEditor` so it now derives from `NetcodeEditorBase`. (#3013)
- Changed `NetworkManagerEditor` so it now derives from `NetcodeEditorBase`. (#3013)
## [2.0.0-pre.4] - 2024-08-21
### Added

View File

@@ -5,6 +5,7 @@ namespace Unity.Netcode.Editor.Configuration
internal class NetcodeForGameObjectsEditorSettings
{
internal const string AutoAddNetworkObjectIfNoneExists = "AutoAdd-NetworkObject-When-None-Exist";
internal const string CheckForNetworkObject = "NetworkBehaviour-Check-For-NetworkObject";
internal const string InstallMultiplayerToolsTipDismissedPlayerPrefKey = "Netcode_Tip_InstallMPTools_Dismissed";
internal static int GetNetcodeInstallMultiplayerToolTips()
@@ -28,7 +29,7 @@ namespace Unity.Netcode.Editor.Configuration
{
return EditorPrefs.GetBool(AutoAddNetworkObjectIfNoneExists);
}
// Default for this is false
return false;
}
@@ -36,5 +37,20 @@ namespace Unity.Netcode.Editor.Configuration
{
EditorPrefs.SetBool(AutoAddNetworkObjectIfNoneExists, autoAddSetting);
}
internal static bool GetCheckForNetworkObjectSetting()
{
if (EditorPrefs.HasKey(CheckForNetworkObject))
{
return EditorPrefs.GetBool(CheckForNetworkObject);
}
// Default for this is true
return true;
}
internal static void SetCheckForNetworkObjectSetting(bool checkForNetworkObject)
{
EditorPrefs.SetBool(CheckForNetworkObject, checkForNetworkObject);
}
}
}

View File

@@ -81,6 +81,7 @@ namespace Unity.Netcode.Editor.Configuration
internal static NetcodeSettingsLabel NetworkObjectsSectionLabel;
internal static NetcodeSettingsToggle AutoAddNetworkObjectToggle;
internal static NetcodeSettingsToggle CheckForNetworkObjectToggle;
internal static NetcodeSettingsLabel MultiplayerToolsLabel;
internal static NetcodeSettingsToggle MultiplayerToolTipStatusToggle;
@@ -103,6 +104,11 @@ namespace Unity.Netcode.Editor.Configuration
AutoAddNetworkObjectToggle = new NetcodeSettingsToggle("Auto-Add NetworkObject Component", "When enabled, NetworkObject components are automatically added to GameObjects when NetworkBehaviour components are added first.", 20);
}
if (CheckForNetworkObjectToggle == null)
{
CheckForNetworkObjectToggle = new NetcodeSettingsToggle("Check for NetworkObject Component", "When disabled, the automatic check on NetworkBehaviours for an associated NetworkObject component will not be performed and Auto-Add NetworkObject Component will be disabled.", 20);
}
if (MultiplayerToolsLabel == null)
{
MultiplayerToolsLabel = new NetcodeSettingsLabel("Multiplayer Tools", 20);
@@ -120,7 +126,9 @@ namespace Unity.Netcode.Editor.Configuration
CheckForInitialize();
var autoAddNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetAutoAddNetworkObjectSetting();
var checkForNetworkObjectSetting = NetcodeForGameObjectsEditorSettings.GetCheckForNetworkObjectSetting();
var multiplayerToolsTipStatus = NetcodeForGameObjectsEditorSettings.GetNetcodeInstallMultiplayerToolTips() == 0;
var settings = NetcodeForGameObjectsProjectSettings.instance;
var generateDefaultPrefabs = settings.GenerateDefaultNetworkPrefabs;
var networkPrefabsPath = settings.TempNetworkPrefabsPath;
@@ -134,7 +142,13 @@ namespace Unity.Netcode.Editor.Configuration
{
GUILayout.BeginVertical("Box");
NetworkObjectsSectionLabel.DrawLabel();
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting);
autoAddNetworkObjectSetting = AutoAddNetworkObjectToggle.DrawToggle(autoAddNetworkObjectSetting, checkForNetworkObjectSetting);
checkForNetworkObjectSetting = CheckForNetworkObjectToggle.DrawToggle(checkForNetworkObjectSetting);
if (autoAddNetworkObjectSetting && !checkForNetworkObjectSetting)
{
autoAddNetworkObjectSetting = false;
}
GUILayout.EndVertical();
GUILayout.BeginVertical("Box");
@@ -184,6 +198,7 @@ namespace Unity.Netcode.Editor.Configuration
if (EditorGUI.EndChangeCheck())
{
NetcodeForGameObjectsEditorSettings.SetAutoAddNetworkObjectSetting(autoAddNetworkObjectSetting);
NetcodeForGameObjectsEditorSettings.SetCheckForNetworkObjectSetting(checkForNetworkObjectSetting);
NetcodeForGameObjectsEditorSettings.SetNetcodeInstallMultiplayerToolTips(multiplayerToolsTipStatus ? 0 : 1);
settings.GenerateDefaultNetworkPrefabs = generateDefaultPrefabs;
settings.TempNetworkPrefabsPath = networkPrefabsPath;
@@ -213,10 +228,13 @@ namespace Unity.Netcode.Editor.Configuration
{
private GUIContent m_ToggleContent;
public bool DrawToggle(bool currentSetting)
public bool DrawToggle(bool currentSetting, bool enabled = true)
{
EditorGUIUtility.labelWidth = m_LabelSize;
return EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth);
GUI.enabled = enabled;
var returnValue = EditorGUILayout.Toggle(m_ToggleContent, currentSetting, m_LayoutWidth);
GUI.enabled = true;
return returnValue;
}
public NetcodeSettingsToggle(string labelText, string toolTip, float layoutOffset)

View File

@@ -132,7 +132,7 @@ namespace Unity.Netcode.Editor.Configuration
// Process the imported and deleted assets
var markDirty = ProcessImportedAssets(importedAssets);
markDirty &= ProcessDeletedAssets(deletedAssets);
markDirty |= ProcessDeletedAssets(deletedAssets);
if (markDirty)
{

View File

@@ -0,0 +1,62 @@
using System;
using UnityEditor;
using UnityEngine;
namespace Unity.Netcode.Editor
{
/// <summary>
/// The base Netcode Editor helper class to display derived <see cref="MonoBehaviour"/> based components <br />
/// where each child generation's properties will be displayed within a FoldoutHeaderGroup.
/// </summary>
[CanEditMultipleObjects]
public partial class NetcodeEditorBase<TT> : UnityEditor.Editor where TT : MonoBehaviour
{
/// <inheritdoc/>
public virtual void OnEnable()
{
}
/// <summary>
/// Helper method to draw the properties of the specified child type <typeparamref name="T"/> component within a FoldoutHeaderGroup.
/// </summary>
/// <typeparam name="T">The specific child type that should have its properties drawn.</typeparam>
/// <param name="type">The component type of the <see cref="UnityEditor.Editor.target"/>.</param>
/// <param name="displayProperties">The <see cref="Action"/> to invoke that will draw the type <typeparamref name="T"/> properties.</param>
/// <param name="expanded">The <typeparamref name="T"/> current expanded property value</param>
/// <param name="setExpandedProperty">The <see cref="Action{bool}"/> invoked to apply the updated <paramref name="expanded"/> value.</param>
protected void DrawFoldOutGroup<T>(Type type, Action displayProperties, bool expanded, Action<bool> setExpandedProperty)
{
var baseClass = target as TT;
EditorGUI.BeginChangeCheck();
serializedObject.Update();
var currentClass = typeof(T);
if (type.IsSubclassOf(currentClass) || (!type.IsSubclassOf(currentClass) && currentClass.IsSubclassOf(typeof(TT))))
{
var expandedValue = EditorGUILayout.BeginFoldoutHeaderGroup(expanded, $"{currentClass.Name} Properties");
if (expandedValue)
{
EditorGUILayout.EndFoldoutHeaderGroup();
displayProperties.Invoke();
}
else
{
EditorGUILayout.EndFoldoutHeaderGroup();
}
EditorGUILayout.Space();
setExpandedProperty.Invoke(expandedValue);
}
else
{
displayProperties.Invoke();
}
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
serializedObject.ApplyModifiedProperties();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 4ce97256a2d80f94bb340e13c71a24b8

View File

@@ -301,9 +301,8 @@ namespace Unity.Netcode.Editor
expanded = false;
}
serializedObject.ApplyModifiedProperties();
EditorGUI.EndChangeCheck();
serializedObject.ApplyModifiedProperties();
}
/// <summary>
@@ -352,6 +351,12 @@ namespace Unity.Netcode.Editor
return;
}
// If this automatic check is disabled, then do not perform this check.
if (!NetcodeForGameObjectsEditorSettings.GetCheckForNetworkObjectSetting())
{
return;
}
// Now get the root parent transform to the current GameObject (or itself)
var rootTransform = GetRootParentTransform(gameObject.transform);
if (!rootTransform.TryGetComponent<NetworkManager>(out var networkManager))

View File

@@ -13,7 +13,7 @@ namespace Unity.Netcode.Editor
/// </summary>
[CustomEditor(typeof(NetworkManager), true)]
[CanEditMultipleObjects]
public class NetworkManagerEditor : UnityEditor.Editor
public class NetworkManagerEditor : NetcodeEditorBase<NetworkManager>
{
private static GUIStyle s_CenteredWordWrappedLabelStyle;
private static GUIStyle s_HelpBoxStyle;
@@ -31,6 +31,7 @@ namespace Unity.Netcode.Editor
private SerializedProperty m_NetworkTransportProperty;
private SerializedProperty m_TickRateProperty;
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
private SerializedProperty m_AutoSpawnPlayerPrefabClientSide;
private SerializedProperty m_NetworkTopologyProperty;
#endif
private SerializedProperty m_ClientConnectionBufferTimeoutProperty;
@@ -104,6 +105,11 @@ namespace Unity.Netcode.Editor
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
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
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
@@ -120,8 +126,6 @@ namespace Unity.Netcode.Editor
#if MULTIPLAYER_TOOLS
m_NetworkMessageMetrics = m_NetworkConfigProperty.FindPropertyRelative("NetworkMessageMetrics");
#endif
m_RpcHashSizeProperty = m_NetworkConfigProperty.FindPropertyRelative("RpcHashSize");
m_PrefabsList = m_NetworkConfigProperty
.FindPropertyRelative(nameof(NetworkConfig.Prefabs))
@@ -144,6 +148,11 @@ namespace Unity.Netcode.Editor
m_TickRateProperty = m_NetworkConfigProperty.FindPropertyRelative("TickRate");
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
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
m_ClientConnectionBufferTimeoutProperty = m_NetworkConfigProperty.FindPropertyRelative("ClientConnectionBufferTimeout");
m_ConnectionApprovalProperty = m_NetworkConfigProperty.FindPropertyRelative("ConnectionApproval");
@@ -168,23 +177,16 @@ namespace Unity.Netcode.Editor
.FindPropertyRelative(nameof(NetworkPrefabs.NetworkPrefabsLists));
}
/// <inheritdoc/>
public override void OnInspectorGUI()
private void DisplayNetworkManagerProperties()
{
Initialize();
CheckNullProperties();
#if !MULTIPLAYER_TOOLS
DrawInstallMultiplayerToolsTip();
#endif
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
{
serializedObject.Update();
EditorGUILayout.PropertyField(m_RunInBackgroundProperty);
EditorGUILayout.PropertyField(m_LogLevelProperty);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Network Settings", EditorStyles.boldLabel);
#if MULTIPLAYER_SERVICES_SDK_INSTALLED
EditorGUILayout.PropertyField(m_NetworkTopologyProperty);
@@ -230,8 +232,17 @@ namespace Unity.Netcode.Editor
EditorGUILayout.Space();
EditorGUILayout.LabelField("Prefab Settings", EditorStyles.boldLabel);
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"));
if (m_NetworkManager.NetworkConfig.HasOldPrefabList())
{
EditorGUILayout.HelpBox("Network Prefabs serialized in old format. Migrate to new format to edit the list.", MessageType.Info);
@@ -298,9 +309,12 @@ namespace Unity.Netcode.Editor
}
serializedObject.ApplyModifiedProperties();
}
}
// Start buttons below
private void DisplayCallToActionButtons()
{
if (!m_NetworkManager.IsServer && !m_NetworkManager.IsClient)
{
string buttonDisabledReasonSuffix = "";
@@ -341,7 +355,6 @@ namespace Unity.Netcode.Editor
GUI.enabled = true;
}
}
}
else
{
string instanceType = string.Empty;
@@ -368,6 +381,21 @@ namespace Unity.Netcode.Editor
}
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
var networkManager = target as NetworkManager;
Initialize();
CheckNullProperties();
#if !MULTIPLAYER_TOOLS
DrawInstallMultiplayerToolsTip();
#endif
void SetExpanded(bool expanded) { networkManager.NetworkManagerExpanded = expanded; };
DrawFoldOutGroup<NetworkManager>(networkManager.GetType(), DisplayNetworkManagerProperties, networkManager.NetworkManagerExpanded, SetExpanded);
DisplayCallToActionButtons();
base.OnInspectorGUI();
}
private static void DrawInstallMultiplayerToolsTip()
{
const string getToolsText = "Access additional tools for multiplayer development by installing the Multiplayer Tools package in the Package Manager.";

View File

@@ -61,6 +61,12 @@ namespace Unity.Netcode.Editor
{
s_LastKnownNetworkManagerParents.Clear();
ScenesInBuildActiveSceneCheck();
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
break;
}
case PlayModeStateChange.EnteredEditMode:
{
EditorApplication.hierarchyChanged += EditorApplication_hierarchyChanged;
break;
}
}
@@ -110,6 +116,12 @@ namespace Unity.Netcode.Editor
/// </summary>
private static void EditorApplication_hierarchyChanged()
{
if (Application.isPlaying)
{
EditorApplication.hierarchyChanged -= EditorApplication_hierarchyChanged;
return;
}
var allNetworkManagers = Resources.FindObjectsOfTypeAll<NetworkManager>();
foreach (var networkManager in allNetworkManagers)
{

View File

@@ -0,0 +1,42 @@
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
using Unity.Netcode.Components;
using UnityEditor;
namespace Unity.Netcode.Editor
{
[CustomEditor(typeof(NetworkRigidbodyBase), true)]
[CanEditMultipleObjects]
public class NetworkRigidbodyBaseEditor : NetcodeEditorBase<NetworkBehaviour>
{
private SerializedProperty m_UseRigidBodyForMotion;
private SerializedProperty m_AutoUpdateKinematicState;
private SerializedProperty m_AutoSetKinematicOnDespawn;
public override void OnEnable()
{
m_UseRigidBodyForMotion = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.UseRigidBodyForMotion));
m_AutoUpdateKinematicState = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.AutoUpdateKinematicState));
m_AutoSetKinematicOnDespawn = serializedObject.FindProperty(nameof(NetworkRigidbodyBase.AutoSetKinematicOnDespawn));
base.OnEnable();
}
private void DisplayNetworkRigidbodyProperties()
{
EditorGUILayout.PropertyField(m_UseRigidBodyForMotion);
EditorGUILayout.PropertyField(m_AutoUpdateKinematicState);
EditorGUILayout.PropertyField(m_AutoSetKinematicOnDespawn);
}
/// <inheritdoc/>
public override void OnInspectorGUI()
{
var networkRigidbodyBase = target as NetworkRigidbodyBase;
void SetExpanded(bool expanded) { networkRigidbodyBase.NetworkRigidbodyBaseExpanded = expanded; };
DrawFoldOutGroup<NetworkRigidbodyBase>(networkRigidbodyBase.GetType(), DisplayNetworkRigidbodyProperties, networkRigidbodyBase.NetworkRigidbodyBaseExpanded, SetExpanded);
base.OnInspectorGUI();
}
}
}
#endif

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 06561c57f81a6354f8bb16076f1de3a9

View File

@@ -8,8 +8,11 @@ namespace Unity.Netcode.Editor
/// The <see cref="CustomEditor"/> for <see cref="NetworkTransform"/>
/// </summary>
[CustomEditor(typeof(NetworkTransform), true)]
public class NetworkTransformEditor : UnityEditor.Editor
[CanEditMultipleObjects]
public class NetworkTransformEditor : NetcodeEditorBase<NetworkTransform>
{
private SerializedProperty m_SwitchTransformSpaceWhenParented;
private SerializedProperty m_TickSyncChildren;
private SerializedProperty m_UseUnreliableDeltas;
private SerializedProperty m_SyncPositionXProperty;
private SerializedProperty m_SyncPositionYProperty;
@@ -39,8 +42,10 @@ namespace Unity.Netcode.Editor
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");
/// <inheritdoc/>
public virtual void OnEnable()
public override void OnEnable()
{
m_SwitchTransformSpaceWhenParented = serializedObject.FindProperty(nameof(NetworkTransform.SwitchTransformSpaceWhenParented));
m_TickSyncChildren = serializedObject.FindProperty(nameof(NetworkTransform.TickSyncChildren));
m_UseUnreliableDeltas = serializedObject.FindProperty(nameof(NetworkTransform.UseUnreliableDeltas));
m_SyncPositionXProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionX));
m_SyncPositionYProperty = serializedObject.FindProperty(nameof(NetworkTransform.SyncPositionY));
@@ -61,10 +66,10 @@ namespace Unity.Netcode.Editor
m_UseHalfFloatPrecision = serializedObject.FindProperty(nameof(NetworkTransform.UseHalfFloatPrecision));
m_SlerpPosition = serializedObject.FindProperty(nameof(NetworkTransform.SlerpPosition));
m_AuthorityMode = serializedObject.FindProperty(nameof(NetworkTransform.AuthorityMode));
base.OnEnable();
}
/// <inheritdoc/>
public override void OnInspectorGUI()
private void DisplayNetworkTransformProperties()
{
var networkTransform = target as NetworkTransform;
EditorGUILayout.LabelField("Axis to Synchronize", EditorStyles.boldLabel);
@@ -141,9 +146,15 @@ namespace Unity.Netcode.Editor
EditorGUILayout.PropertyField(m_ScaleThresholdProperty);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Delivery", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_TickSyncChildren);
EditorGUILayout.PropertyField(m_UseUnreliableDeltas);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_SwitchTransformSpaceWhenParented);
if (m_SwitchTransformSpaceWhenParented.boolValue)
{
m_TickSyncChildren.boolValue = true;
}
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
if (!networkTransform.HideInterpolateValue)
{
@@ -163,8 +174,7 @@ namespace Unity.Netcode.Editor
#if COM_UNITY_MODULES_PHYSICS
// if rigidbody is present but network rigidbody is not present
var go = ((NetworkTransform)target).gameObject;
if (go.TryGetComponent<Rigidbody>(out _) && go.TryGetComponent<NetworkRigidbody>(out _) == false)
if (networkTransform.TryGetComponent<Rigidbody>(out _) && networkTransform.TryGetComponent<NetworkRigidbody>(out _) == false)
{
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody but no NetworkRigidbody.\n" +
"Add a NetworkRigidbody component to improve Rigidbody synchronization.", MessageType.Warning);
@@ -172,14 +182,23 @@ namespace Unity.Netcode.Editor
#endif // COM_UNITY_MODULES_PHYSICS
#if COM_UNITY_MODULES_PHYSICS2D
if (go.TryGetComponent<Rigidbody2D>(out _) && go.TryGetComponent<NetworkRigidbody2D>(out _) == false)
if (networkTransform.TryGetComponent<Rigidbody2D>(out _) && networkTransform.TryGetComponent<NetworkRigidbody2D>(out _) == false)
{
EditorGUILayout.HelpBox("This GameObject contains a Rigidbody2D but no NetworkRigidbody2D.\n" +
"Add a NetworkRigidbody2D component to improve Rigidbody2D synchronization.", MessageType.Warning);
}
#endif // COM_UNITY_MODULES_PHYSICS2D
}
serializedObject.ApplyModifiedProperties();
/// <inheritdoc/>
public override void OnInspectorGUI()
{
var networkTransform = target as NetworkTransform;
void SetExpanded(bool expanded) { networkTransform.NetworkTransformExpanded = expanded; };
DrawFoldOutGroup<NetworkTransform>(networkTransform.GetType(), DisplayNetworkTransformProperties, networkTransform.NetworkTransformExpanded, SetExpanded);
base.OnInspectorGUI();
}
}
}

View File

@@ -239,19 +239,13 @@ namespace Unity.Netcode.Components
m_CurrentSmoothTime = 0;
}
public override void OnUpdate()
private void ProcessSmoothing()
{
// If not spawned or this instance has authority, exit early
if (!IsSpawned)
{
return;
}
// Do not call the base class implementation...
// AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update
// This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating
// and we will want all reanticipation done before anything else wants to reference the transform in
// OnUpdate()
//base.Update();
if (m_CurrentSmoothTime < m_SmoothDuration)
{
@@ -262,7 +256,7 @@ namespace Unity.Netcode.Components
m_AnticipatedTransform = new TransformState
{
Position = Vector3.Lerp(m_SmoothFrom.Position, m_SmoothTo.Position, pct),
Rotation = Quaternion.Slerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct),
Rotation = Quaternion.Lerp(m_SmoothFrom.Rotation, m_SmoothTo.Rotation, pct),
Scale = Vector3.Lerp(m_SmoothFrom.Scale, m_SmoothTo.Scale, pct)
};
m_PreviousAnticipatedTransform = m_AnticipatedTransform;
@@ -275,6 +269,32 @@ namespace Unity.Netcode.Components
}
}
// TODO: This does not handle OnFixedUpdate
// This requires a complete overhaul in this class to switch between using
// NetworkRigidbody's position and rotation values.
public override void OnUpdate()
{
ProcessSmoothing();
// Do not call the base class implementation...
// AnticipatedNetworkTransform applies its authoritative state immediately rather than waiting for update
// This is because AnticipatedNetworkTransforms may need to reference each other in reanticipating
// and we will want all reanticipation done before anything else wants to reference the transform in
// OnUpdate()
//base.OnUpdate();
}
/// <summary>
/// Since authority does not subscribe to updates (OnUpdate or OnFixedUpdate),
/// we have to update every frame to assure authority processes soothing.
/// </summary>
private void Update()
{
if (CanCommitToTransform && IsSpawned)
{
ProcessSmoothing();
}
}
internal class AnticipatedObject : IAnticipationEventReceiver, IAnticipatedObject
{
public AnticipatedNetworkTransform Transform;
@@ -347,14 +367,44 @@ namespace Unity.Netcode.Components
m_CurrentSmoothTime = 0;
}
protected override void OnSynchronize<T>(ref BufferSerializer<T> serializer)
/// <summary>
/// (This replaces the first OnSynchronize for NetworkTransforms)
/// This is needed to initialize when fully synchronized since non-authority instances
/// don't apply the initial synchronization (new client synchronization) until after
/// everything has been spawned and synchronized.
/// </summary>
protected internal override void InternalOnNetworkSessionSynchronized()
{
base.OnSynchronize(ref serializer);
if (!CanCommitToTransform)
var wasSynchronizing = SynchronizeState.IsSynchronizing;
base.InternalOnNetworkSessionSynchronized();
if (!CanCommitToTransform && wasSynchronizing && !SynchronizeState.IsSynchronizing)
{
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
}
/// <summary>
/// (This replaces the any subsequent OnSynchronize for NetworkTransforms post client synchronization)
/// This occurs on already connected clients when dynamically spawning a NetworkObject for
/// non-authoritative instances.
/// </summary>
protected internal override void InternalOnNetworkPostSpawn()
{
base.InternalOnNetworkPostSpawn();
if (!CanCommitToTransform && NetworkManager.IsConnectedClient && !SynchronizeState.IsSynchronizing)
{
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();
m_AnticipatedObject = new AnticipatedObject { Transform = this };
NetworkManager.AnticipationSystem.RegisterForAnticipationEvents(m_AnticipatedObject);
NetworkManager.AnticipationSystem.AllAnticipatedObjects.Add(m_AnticipatedObject);
}
}
@@ -365,6 +415,13 @@ namespace Unity.Netcode.Components
Debug.LogWarning($"This component is not currently supported in distributed authority.");
}
base.OnNetworkSpawn();
// Non-authoritative instances exit early if the synchronization has yet to
// be applied at this point
if (SynchronizeState.IsSynchronizing && !CanCommitToTransform)
{
return;
}
m_OutstandingAuthorityChange = true;
ApplyAuthoritativeState();
ResetAnticipatedState();

View File

@@ -12,7 +12,7 @@ namespace Unity.Netcode
public abstract class BufferedLinearInterpolator<T> where T : struct
{
internal float MaxInterpolationBound = 3.0f;
private struct BufferedItem
protected internal struct BufferedItem
{
public T Item;
public double TimeSent;
@@ -31,14 +31,16 @@ namespace Unity.Netcode
private const double k_SmallValue = 9.999999439624929E-11; // copied from Vector3's equal operator
private T m_InterpStartValue;
private T m_CurrentInterpValue;
private T m_InterpEndValue;
protected internal T m_InterpStartValue;
protected internal T m_CurrentInterpValue;
protected internal T m_InterpEndValue;
private double m_EndTimeConsumed;
private double m_StartTimeConsumed;
private readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
protected internal readonly List<BufferedItem> m_Buffer = new List<BufferedItem>(k_BufferCountLimit);
// Buffer consumption scenarios
// Perfect case consumption
@@ -73,6 +75,21 @@ namespace Unity.Netcode
private bool InvalidState => m_Buffer.Count == 0 && m_LifetimeConsumedCount == 0;
internal bool EndOfBuffer => m_Buffer.Count == 0;
internal bool InLocalSpace;
protected internal virtual void OnConvertTransformSpace(Transform transform, bool inLocalSpace)
{
}
internal void ConvertTransformSpace(Transform transform, bool inLocalSpace)
{
OnConvertTransformSpace(transform, inLocalSpace);
InLocalSpace = inLocalSpace;
}
/// <summary>
/// Resets interpolator to initial state
/// </summary>
@@ -351,6 +368,35 @@ namespace Unity.Netcode
return Quaternion.Lerp(start, end, time);
}
}
private Quaternion ConvertToNewTransformSpace(Transform transform, Quaternion rotation, bool inLocalSpace)
{
if (inLocalSpace)
{
return Quaternion.Inverse(transform.rotation) * rotation;
}
else
{
return transform.rotation * rotation;
}
}
protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace)
{
for (int i = 0; i < m_Buffer.Count; i++)
{
var entry = m_Buffer[i];
entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace);
m_Buffer[i] = entry;
}
m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace);
m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace);
m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace);
base.OnConvertTransformSpace(transform, inLocalSpace);
}
}
/// <summary>
@@ -388,5 +434,34 @@ namespace Unity.Netcode
return Vector3.Lerp(start, end, time);
}
}
private Vector3 ConvertToNewTransformSpace(Transform transform, Vector3 position, bool inLocalSpace)
{
if (inLocalSpace)
{
return transform.InverseTransformPoint(position);
}
else
{
return transform.TransformPoint(position);
}
}
protected internal override void OnConvertTransformSpace(Transform transform, bool inLocalSpace)
{
for (int i = 0; i < m_Buffer.Count; i++)
{
var entry = m_Buffer[i];
entry.Item = ConvertToNewTransformSpace(transform, entry.Item, inLocalSpace);
m_Buffer[i] = entry;
}
m_InterpStartValue = ConvertToNewTransformSpace(transform, m_InterpStartValue, inLocalSpace);
m_CurrentInterpValue = ConvertToNewTransformSpace(transform, m_CurrentInterpValue, inLocalSpace);
m_InterpEndValue = ConvertToNewTransformSpace(transform, m_InterpEndValue, inLocalSpace);
base.OnConvertTransformSpace(transform, inLocalSpace);
}
}
}

View File

@@ -584,7 +584,7 @@ namespace Unity.Netcode.Components
base.OnDestroy();
}
private void Awake()
protected virtual void Awake()
{
int layers = m_Animator.layerCount;
// Initializing the below arrays for everyone handles an issue
@@ -952,8 +952,14 @@ namespace Unity.Netcode.Components
{
// Just notify all remote clients and not the local server
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.LocalClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == NetworkManager.LocalClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
SendAnimStateClientRpc(m_AnimationMessage, m_ClientRpcParams);
}
@@ -1264,9 +1270,15 @@ namespace Unity.Netcode.Components
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendParameterUpdate(parametersUpdate, m_ClientRpcParams);
}
@@ -1321,9 +1333,14 @@ namespace Unity.Netcode.Components
if (NetworkManager.ConnectedClientsIds.Count > (IsHost ? 2 : 1))
{
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(serverRpcParams.Receive.SenderClientId);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == serverRpcParams.Receive.SenderClientId || clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
m_ClientRpcParams.Send.TargetClientIds = m_ClientSendList;
m_NetworkAnimatorStateChangeHandler.SendAnimationUpdate(animationMessage, m_ClientRpcParams);
}
@@ -1390,9 +1407,14 @@ namespace Unity.Netcode.Components
InternalSetTrigger(animationTriggerMessage.Hash, animationTriggerMessage.IsTriggerSet);
m_ClientSendList.Clear();
m_ClientSendList.AddRange(NetworkManager.ConnectedClientsIds);
m_ClientSendList.Remove(NetworkManager.ServerClientId);
foreach (var clientId in NetworkManager.ConnectedClientsIds)
{
if (clientId == NetworkManager.ServerClientId || !NetworkObject.Observers.Contains(clientId))
{
continue;
}
m_ClientSendList.Add(clientId);
}
if (IsServerAuthoritative())
{
m_NetworkAnimatorStateChangeHandler.QueueTriggerUpdateToClient(animationTriggerMessage, m_ClientRpcParams);

View File

@@ -1,4 +1,4 @@
#if COM_UNITY_MODULES_PHYSICS
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
using System.Runtime.CompilerServices;
using UnityEngine;
@@ -14,6 +14,12 @@ namespace Unity.Netcode.Components
/// </remarks>
public abstract class NetworkRigidbodyBase : NetworkBehaviour
{
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
internal bool NetworkRigidbodyBaseExpanded;
#endif
/// <summary>
/// When enabled, the associated <see cref="NetworkTransform"/> will use the Rigidbody/Rigidbody2D to apply and synchronize changes in position, rotation, and
/// allows for the use of Rigidbody interpolation/extrapolation.
@@ -42,8 +48,10 @@ namespace Unity.Netcode.Components
private bool m_IsRigidbody2D => RigidbodyType == RigidbodyTypes.Rigidbody2D;
// Used to cache the authority state of this Rigidbody during the last frame
private bool m_IsAuthority;
private Rigidbody m_Rigidbody;
private Rigidbody2D m_Rigidbody2D;
protected internal Rigidbody m_InternalRigidbody { get; private set; }
protected internal Rigidbody2D m_InternalRigidbody2D { get; private set; }
internal NetworkTransform NetworkTransform;
private float m_TickFrequency;
private float m_TickRate;
@@ -87,18 +95,18 @@ namespace Unity.Netcode.Components
return;
}
RigidbodyType = rigidbodyType;
m_Rigidbody2D = rigidbody2D;
m_Rigidbody = rigidbody;
m_InternalRigidbody2D = rigidbody2D;
m_InternalRigidbody = rigidbody;
NetworkTransform = networkTransform;
if (m_IsRigidbody2D && m_Rigidbody2D == null)
if (m_IsRigidbody2D && m_InternalRigidbody2D == null)
{
m_Rigidbody2D = GetComponent<Rigidbody2D>();
m_InternalRigidbody2D = GetComponent<Rigidbody2D>();
}
else if (m_Rigidbody == null)
else if (m_InternalRigidbody == null)
{
m_Rigidbody = GetComponent<Rigidbody>();
m_InternalRigidbody = GetComponent<Rigidbody>();
}
SetOriginalInterpolation();
@@ -178,14 +186,14 @@ namespace Unity.Netcode.Components
if (m_IsRigidbody2D)
{
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
m_Rigidbody2D.linearVelocity = linearVelocity;
m_InternalRigidbody2D.linearVelocity = linearVelocity;
#else
m_Rigidbody2D.velocity = linearVelocity;
m_InternalRigidbody2D.velocity = linearVelocity;
#endif
}
else
{
m_Rigidbody.linearVelocity = linearVelocity;
m_InternalRigidbody.linearVelocity = linearVelocity;
}
}
@@ -202,14 +210,14 @@ namespace Unity.Netcode.Components
if (m_IsRigidbody2D)
{
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
return m_Rigidbody2D.linearVelocity;
return m_InternalRigidbody2D.linearVelocity;
#else
return m_Rigidbody2D.velocity;
return m_InternalRigidbody2D.velocity;
#endif
}
else
{
return m_Rigidbody.linearVelocity;
return m_InternalRigidbody.linearVelocity;
}
}
@@ -226,11 +234,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.angularVelocity = angularVelocity.z;
m_InternalRigidbody2D.angularVelocity = angularVelocity.z;
}
else
{
m_Rigidbody.angularVelocity = angularVelocity;
m_InternalRigidbody.angularVelocity = angularVelocity;
}
}
@@ -246,11 +254,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
return Vector3.forward * m_Rigidbody2D.angularVelocity;
return Vector3.forward * m_InternalRigidbody2D.angularVelocity;
}
else
{
return m_Rigidbody.angularVelocity;
return m_InternalRigidbody.angularVelocity;
}
}
@@ -263,11 +271,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
return m_Rigidbody2D.position;
return m_InternalRigidbody2D.position;
}
else
{
return m_Rigidbody.position;
return m_InternalRigidbody.position;
}
}
@@ -282,13 +290,13 @@ namespace Unity.Netcode.Components
{
var quaternion = Quaternion.identity;
var angles = quaternion.eulerAngles;
angles.z = m_Rigidbody2D.rotation;
angles.z = m_InternalRigidbody2D.rotation;
quaternion.eulerAngles = angles;
return quaternion;
}
else
{
return m_Rigidbody.rotation;
return m_InternalRigidbody.rotation;
}
}
@@ -301,11 +309,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.MovePosition(position);
m_InternalRigidbody2D.MovePosition(position);
}
else
{
m_Rigidbody.MovePosition(position);
m_InternalRigidbody.MovePosition(position);
}
}
@@ -318,11 +326,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.position = position;
m_InternalRigidbody2D.position = position;
}
else
{
m_Rigidbody.position = position;
m_InternalRigidbody.position = position;
}
}
@@ -334,13 +342,13 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.position = transform.position;
m_Rigidbody2D.rotation = transform.eulerAngles.z;
m_InternalRigidbody2D.position = transform.position;
m_InternalRigidbody2D.rotation = transform.eulerAngles.z;
}
else
{
m_Rigidbody.position = transform.position;
m_Rigidbody.rotation = transform.rotation;
m_InternalRigidbody.position = transform.position;
m_InternalRigidbody.rotation = transform.rotation;
}
}
@@ -358,9 +366,9 @@ namespace Unity.Netcode.Components
{
var quaternion = Quaternion.identity;
var angles = quaternion.eulerAngles;
angles.z = m_Rigidbody2D.rotation;
angles.z = m_InternalRigidbody2D.rotation;
quaternion.eulerAngles = angles;
m_Rigidbody2D.MoveRotation(quaternion);
m_InternalRigidbody2D.MoveRotation(quaternion);
}
else
{
@@ -375,7 +383,7 @@ namespace Unity.Netcode.Components
{
rotation.Normalize();
}
m_Rigidbody.MoveRotation(rotation);
m_InternalRigidbody.MoveRotation(rotation);
}
}
@@ -388,11 +396,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.rotation = rotation.eulerAngles.z;
m_InternalRigidbody2D.rotation = rotation.eulerAngles.z;
}
else
{
m_Rigidbody.rotation = rotation;
m_InternalRigidbody.rotation = rotation;
}
}
@@ -404,7 +412,7 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
switch (m_Rigidbody2D.interpolation)
switch (m_InternalRigidbody2D.interpolation)
{
case RigidbodyInterpolation2D.None:
{
@@ -425,7 +433,7 @@ namespace Unity.Netcode.Components
}
else
{
switch (m_Rigidbody.interpolation)
switch (m_InternalRigidbody.interpolation)
{
case RigidbodyInterpolation.None:
{
@@ -454,16 +462,16 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
if (m_Rigidbody2D.IsSleeping())
if (m_InternalRigidbody2D.IsSleeping())
{
m_Rigidbody2D.WakeUp();
m_InternalRigidbody2D.WakeUp();
}
}
else
{
if (m_Rigidbody.IsSleeping())
if (m_InternalRigidbody.IsSleeping())
{
m_Rigidbody.WakeUp();
m_InternalRigidbody.WakeUp();
}
}
}
@@ -476,11 +484,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.Sleep();
m_InternalRigidbody2D.Sleep();
}
else
{
m_Rigidbody.Sleep();
m_InternalRigidbody.Sleep();
}
}
@@ -489,11 +497,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
return m_Rigidbody2D.bodyType == RigidbodyType2D.Kinematic;
return m_InternalRigidbody2D.bodyType == RigidbodyType2D.Kinematic;
}
else
{
return m_Rigidbody.isKinematic;
return m_InternalRigidbody.isKinematic;
}
}
@@ -518,11 +526,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
m_InternalRigidbody2D.bodyType = isKinematic ? RigidbodyType2D.Kinematic : RigidbodyType2D.Dynamic;
}
else
{
m_Rigidbody.isKinematic = isKinematic;
m_InternalRigidbody.isKinematic = isKinematic;
}
// If we are not spawned, then exit early
@@ -539,7 +547,7 @@ namespace Unity.Netcode.Components
if (IsKinematic())
{
// If not already set to interpolate then set the Rigidbody to interpolate
if (m_Rigidbody.interpolation == RigidbodyInterpolation.Extrapolate)
if (m_InternalRigidbody.interpolation == RigidbodyInterpolation.Extrapolate)
{
// Sleep until the next fixed update when switching from extrapolation to interpolation
SleepRigidbody();
@@ -568,11 +576,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.None;
m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.None;
}
else
{
m_Rigidbody.interpolation = RigidbodyInterpolation.None;
m_InternalRigidbody.interpolation = RigidbodyInterpolation.None;
}
break;
}
@@ -580,11 +588,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;
m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.Interpolate;
}
else
{
m_Rigidbody.interpolation = RigidbodyInterpolation.Interpolate;
m_InternalRigidbody.interpolation = RigidbodyInterpolation.Interpolate;
}
break;
}
@@ -592,11 +600,11 @@ namespace Unity.Netcode.Components
{
if (m_IsRigidbody2D)
{
m_Rigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate;
m_InternalRigidbody2D.interpolation = RigidbodyInterpolation2D.Extrapolate;
}
else
{
m_Rigidbody.interpolation = RigidbodyInterpolation.Extrapolate;
m_InternalRigidbody.interpolation = RigidbodyInterpolation.Extrapolate;
}
break;
}
@@ -711,28 +719,28 @@ namespace Unity.Netcode.Components
private void ApplyFixedJoint2D(NetworkRigidbodyBase bodyToConnect, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true)
{
transform.position = position;
m_Rigidbody2D.position = position;
m_OriginalGravitySetting = bodyToConnect.m_Rigidbody.useGravity;
m_InternalRigidbody2D.position = position;
m_OriginalGravitySetting = bodyToConnect.m_InternalRigidbody.useGravity;
m_FixedJoint2DUsingGravity = useGravity;
if (!useGravity)
{
m_OriginalGravityScale = m_Rigidbody2D.gravityScale;
m_Rigidbody2D.gravityScale = 0.0f;
m_OriginalGravityScale = m_InternalRigidbody2D.gravityScale;
m_InternalRigidbody2D.gravityScale = 0.0f;
}
if (zeroVelocity)
{
#if COM_UNITY_MODULES_PHYSICS2D_LINEAR
m_Rigidbody2D.linearVelocity = Vector2.zero;
m_InternalRigidbody2D.linearVelocity = Vector2.zero;
#else
m_Rigidbody2D.velocity = Vector2.zero;
m_InternalRigidbody2D.velocity = Vector2.zero;
#endif
m_Rigidbody2D.angularVelocity = 0.0f;
m_InternalRigidbody2D.angularVelocity = 0.0f;
}
FixedJoint2D = gameObject.AddComponent<FixedJoint2D>();
FixedJoint2D.connectedBody = bodyToConnect.m_Rigidbody2D;
FixedJoint2D.connectedBody = bodyToConnect.m_InternalRigidbody2D;
OnFixedJoint2DCreated();
}
@@ -740,16 +748,16 @@ namespace Unity.Netcode.Components
private void ApplyFixedJoint(NetworkRigidbodyBase bodyToConnectTo, Vector3 position, float connectedMassScale = 0.0f, float massScale = 1.0f, bool useGravity = false, bool zeroVelocity = true)
{
transform.position = position;
m_Rigidbody.position = position;
m_InternalRigidbody.position = position;
if (zeroVelocity)
{
m_Rigidbody.linearVelocity = Vector3.zero;
m_Rigidbody.angularVelocity = Vector3.zero;
m_InternalRigidbody.linearVelocity = Vector3.zero;
m_InternalRigidbody.angularVelocity = Vector3.zero;
}
m_OriginalGravitySetting = m_Rigidbody.useGravity;
m_Rigidbody.useGravity = useGravity;
m_OriginalGravitySetting = m_InternalRigidbody.useGravity;
m_InternalRigidbody.useGravity = useGravity;
FixedJoint = gameObject.AddComponent<FixedJoint>();
FixedJoint.connectedBody = bodyToConnectTo.m_Rigidbody;
FixedJoint.connectedBody = bodyToConnectTo.m_InternalRigidbody;
FixedJoint.connectedMassScale = connectedMassScale;
FixedJoint.massScale = massScale;
OnFixedJointCreated();
@@ -861,7 +869,7 @@ namespace Unity.Netcode.Components
if (FixedJoint != null)
{
FixedJoint.connectedBody = null;
m_Rigidbody.useGravity = m_OriginalGravitySetting;
m_InternalRigidbody.useGravity = m_OriginalGravitySetting;
Destroy(FixedJoint);
FixedJoint = null;
ResetInterpolation();

View File

@@ -12,6 +12,9 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Rigidbody")]
public class NetworkRigidbody : NetworkRigidbodyBase
{
public Rigidbody Rigidbody => m_InternalRigidbody;
protected virtual void Awake()
{
Initialize(RigidbodyTypes.Rigidbody);

View File

@@ -12,6 +12,7 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Rigidbody 2D")]
public class NetworkRigidbody2D : NetworkRigidbodyBase
{
public Rigidbody2D Rigidbody2D => m_InternalRigidbody2D;
protected virtual void Awake()
{
Initialize(RigidbodyTypes.Rigidbody2D);

File diff suppressed because it is too large Load Diff

View File

@@ -6,13 +6,70 @@ using UnityEngine;
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
{
/// <summary>
/// Should return a <see cref="Rigidbody"/>.
/// </summary>
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);
}
/// <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")]
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, IContactEventHandler> m_HandlerMapping = new Dictionary<int, IContactEventHandler>();
private readonly Dictionary<int, ContactEventHandlerInfo> m_HandlerInfo = new Dictionary<int, ContactEventHandlerInfo>();
private void OnEnable()
{
@@ -49,6 +107,15 @@ namespace Unity.Netcode.Components
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)
{
var rigidbody = contactEventHandler.GetRigidbody();
@@ -64,6 +131,22 @@ namespace Unity.Netcode.Components
{
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
{
@@ -88,25 +171,98 @@ namespace Unity.Netcode.Components
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
for (int i = 0; i < m_Count; i++)
{
var thisInstanceID = m_ResultsArray[i].ThisInstanceID;
var otherInstanceID = m_ResultsArray[i].OtherInstanceID;
var rb0Valid = thisInstanceID != 0 && m_RigidbodyMapping.ContainsKey(thisInstanceID);
var rb1Valid = otherInstanceID != 0 && m_RigidbodyMapping.ContainsKey(otherInstanceID);
// Only notify registered rigid bodies.
if (!rb0Valid || !rb1Valid || !m_HandlerMapping.ContainsKey(thisInstanceID))
var contactHandler0 = (IContactEventHandler)null;
var contactHandler1 = (IContactEventHandler)null;
var preferredContactHandler = (IContactEventHandler)null;
var preferredContactHandlerNonRigidbody = false;
var preferredRigidbody = (Rigidbody)null;
var otherContactHandler = (IContactEventHandler)null;
var otherRigidbody = (Rigidbody)null;
var otherContactHandlerNonRigidbody = false;
if (m_RigidbodyMapping.ContainsKey(thisInstanceID))
{
continue;
}
if (m_ResultsArray[i].HasCollisionStay)
contactHandler0 = m_HandlerMapping[thisInstanceID];
contactEventHandlerInfo0 = m_HandlerInfo[thisInstanceID];
if (contactEventHandlerInfo0.HasContactEventPriority)
{
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 = contactHandler0;
preferredContactHandlerNonRigidbody = contactEventHandlerInfo0.ProvideNonRigidBodyContactEvents;
preferredRigidbody = m_RigidbodyMapping[thisInstanceID];
}
else
{
m_HandlerMapping[thisInstanceID].ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, m_RigidbodyMapping[otherInstanceID], m_ResultsArray[i].ContactPoint);
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;
}
if (m_ResultsArray[i].HasCollisionStay)
{
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint, m_ResultsArray[i].HasCollisionStay, m_ResultsArray[i].AverageCollisionStayNormal);
}
else
{
preferredContactHandler.ContactEvent(m_EventId, m_ResultsArray[i].AverageNormal, otherRigidbody, m_ResultsArray[i].ContactPoint);
}
}
}

View File

@@ -105,9 +105,13 @@ namespace Unity.Netcode
continue;
}
// This assures if the server has not timed out prior to the client synchronizing that it doesn't exceed the allocated peer count.
if (peerClientIds.Length > idx)
{
peerClientIds[idx] = peerId;
++idx;
}
}
try
{
@@ -496,18 +500,25 @@ namespace Unity.Netcode
// Process the incoming message queue so that we get everything from the server disconnecting us or, if we are the server, so we got everything from that client.
MessageManager.ProcessIncomingMessageQueue();
if (LocalClient.IsServer)
{
// We need to process the disconnection before notifying
OnClientDisconnectFromServer(clientId);
// Now notify the client has disconnected
InvokeOnClientDisconnectCallback(clientId);
if (LocalClient.IsHost)
{
InvokeOnPeerDisconnectedCallback(clientId);
}
if (LocalClient.IsServer)
{
OnClientDisconnectFromServer(clientId);
}
else // As long as we are not in the middle of a shutdown
else
{
// Notify local client of disconnection
InvokeOnClientDisconnectCallback(clientId);
// As long as we are not in the middle of a shutdown
if (!NetworkManager.ShutdownInProgress)
{
// We must pass true here and not process any sends messages as we are no longer connected.
@@ -515,6 +526,7 @@ namespace Unity.Netcode
// as the client ID is no longer valid.
NetworkManager.Shutdown(true);
}
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
@@ -552,9 +564,6 @@ namespace Unity.Netcode
var message = new ConnectionRequestMessage
{
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
ConfigHash = NetworkManager.NetworkConfig.GetConfig(false),
ShouldSendConnectionData = NetworkManager.NetworkConfig.ConnectionApproval,
@@ -562,6 +571,12 @@ namespace Unity.Netcode
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++)
{
if (MessageManager.MessageTypes[index] != null)
@@ -735,42 +750,23 @@ namespace Unity.Netcode
RemovePendingClient(ownerClientId);
var client = AddClient(ownerClientId);
if (!NetworkManager.DistributedAuthorityMode && response.CreatePlayerObject && NetworkManager.NetworkConfig.PlayerPrefab != null)
{
var prefabNetworkObject = NetworkManager.NetworkConfig.PlayerPrefab.GetComponent<NetworkObject>();
var playerPrefabHash = response.PlayerPrefabHash ?? prefabNetworkObject.GlobalObjectIdHash;
// Generate a SceneObject for the player object to spawn
// Note: This is only to create the local NetworkObject, many of the serialized properties of the player prefab will be set when instantiated.
var sceneObject = new NetworkObject.SceneObject
// 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))
{
OwnerClientId = ownerClientId,
IsPlayerObject = true,
IsSceneObject = false,
HasTransform = prefabNetworkObject.SynchronizeTransform,
Hash = playerPrefabHash,
TargetClientId = ownerClientId,
DontDestroyWithOwner = prefabNetworkObject.DontDestroyWithOwner,
Transform = new NetworkObject.SceneObject.TransformData
{
Position = response.Position.GetValueOrDefault(),
Rotation = response.Rotation.GetValueOrDefault()
}
};
// Create the player NetworkObject locally
var networkObject = NetworkManager.SpawnManager.CreateLocalNetworkObject(sceneObject);
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 ?? null, response.Rotation ?? null);
// Spawn the player NetworkObject locally
NetworkManager.SpawnManager.SpawnNetworkObjectLocally(
networkObject,
playerObject,
NetworkManager.SpawnManager.GetNetworkObjectId(),
sceneObject: false,
playerObject: true,
ownerClientId,
destroyWithScene: false);
client.AssignPlayerObject(ref networkObject);
client.AssignPlayerObject(ref playerObject);
}
// Server doesn't send itself the connection approved message
@@ -871,6 +867,7 @@ namespace Unity.Netcode
}
}
// Exit early if no player object was spawned
if (!response.CreatePlayerObject || (response.PlayerPrefabHash == null && NetworkManager.NetworkConfig.PlayerPrefab == null))
{
return;
@@ -902,7 +899,7 @@ namespace Unity.Netcode
/// <summary>
/// Client-Side Spawning in distributed authority mode uses this to spawn the player.
/// </summary>
internal void CreateAndSpawnPlayer(ulong ownerId, Vector3 position = default, Quaternion rotation = default)
internal void CreateAndSpawnPlayer(ulong ownerId)
{
if (NetworkManager.DistributedAuthorityMode && NetworkManager.AutoSpawnPlayerPrefabClientSide)
{
@@ -910,7 +907,7 @@ namespace Unity.Netcode
if (playerPrefab != null)
{
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.SpawnAsPlayerObject(ownerId, networkObject.DestroyWithScene);
}
@@ -1003,10 +1000,18 @@ namespace Unity.Netcode
ConnectedClientIds.Add(clientId);
}
var distributedAuthority = NetworkManager.DistributedAuthorityMode;
var sessionOwnerId = NetworkManager.CurrentSessionOwner;
var isSessionOwner = NetworkManager.LocalClient.IsSessionOwner;
foreach (var networkObject in NetworkManager.SpawnManager.SpawnedObjectsList)
{
if (networkObject.SpawnWithObservers)
{
// Don't add the client to the observers if hidden from the session owner
if (networkObject.IsOwner && distributedAuthority && !isSessionOwner && !networkObject.Observers.Contains(sessionOwnerId))
{
continue;
}
networkObject.Observers.Add(clientId);
}
}
@@ -1308,9 +1313,17 @@ namespace Unity.Netcode
internal void DisconnectClient(ulong clientId, string reason = null)
{
if (!LocalClient.IsServer)
{
if (NetworkManager.NetworkConfig.NetworkTopology == NetworkTopologyTypes.ClientServer)
{
throw new NotServerException($"Only server can disconnect remote clients. Please use `{nameof(Shutdown)}()` instead.");
}
else
{
Debug.LogWarning($"Currently, clients cannot disconnect other clients from a distributed authority session. Please use `{nameof(Shutdown)}()` instead.");
return;
}
}
if (clientId == NetworkManager.ServerClientId)
{

View File

@@ -19,6 +19,12 @@ namespace Unity.Netcode
/// </summary>
public abstract class NetworkBehaviour : MonoBehaviour
{
#if UNITY_EDITOR
[HideInInspector]
[SerializeField]
internal bool ShowTopMostFoldoutHeaderGroup = true;
#endif
#pragma warning disable IDE1006 // disable naming rule violation check
// RuntimeAccessModifiersILPP will make this `public`
@@ -688,6 +694,8 @@ namespace Unity.Netcode
/// </remarks>
protected virtual void OnNetworkPostSpawn() { }
protected internal virtual void InternalOnNetworkPostSpawn() { }
/// <summary>
/// This method is only available client-side.
/// When a new client joins it's synchronized with all spawned NetworkObjects and scenes loaded for the session joined. At the end of the synchronization process, when all
@@ -700,6 +708,8 @@ namespace Unity.Netcode
/// </remarks>
protected virtual void OnNetworkSessionSynchronized() { }
protected internal virtual void InternalOnNetworkSessionSynchronized() { }
/// <summary>
/// When a scene is loaded and in-scene placed NetworkObjects are finished spawning, this method is invoked on all of the newly spawned in-scene placed NetworkObjects.
/// This method runs both client and server side.
@@ -759,6 +769,7 @@ namespace Unity.Netcode
{
try
{
InternalOnNetworkPostSpawn();
OnNetworkPostSpawn();
}
catch (Exception e)
@@ -771,6 +782,7 @@ namespace Unity.Netcode
{
try
{
InternalOnNetworkSessionSynchronized();
OnNetworkSessionSynchronized();
}
catch (Exception e)
@@ -814,6 +826,13 @@ namespace Unity.Netcode
internal void InternalOnGainedOwnership()
{
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();
}
@@ -853,6 +872,8 @@ namespace Unity.Netcode
/// <param name="parentNetworkObject">the new <see cref="NetworkObject"/> parent</param>
public virtual void OnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
internal virtual void InternalOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { }
private bool m_VarInit = false;
private readonly List<HashSet<int>> m_DeliveryMappedNetworkVariableIndices = new List<HashSet<int>>();
@@ -1002,9 +1023,14 @@ namespace Unity.Netcode
internal readonly List<int> NetworkVariableIndexesToReset = new List<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;
}
@@ -1055,7 +1081,11 @@ namespace Unity.Netcode
NetworkBehaviourIndex = behaviourIndex,
NetworkBehaviour = this,
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.
// Messages don't get sent from the server to itself, so if we're host and sending to ourselves,
@@ -1100,6 +1130,26 @@ namespace Unity.Netcode
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)
{
for (int j = 0; j < NetworkVariableFields.Count; j++)
@@ -1108,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>
/// 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)
@@ -1158,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.
writer.WriteValueSafe((ushort)0);
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;
writer.Seek(writePos);
// Write the NetworkVariable value
// Write the NetworkVariable field value size
writer.WriteValueSafe((ushort)size);
writer.Seek(startPos + size);
}
else // Client-Server Only: Should only ever be invoked when using a client-server NetworkTopology
{
// Write the NetworkVariable value
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);
}
}
else if (ensureLengthSafety)

View File

@@ -19,10 +19,15 @@ namespace Unity.Netcode
internal void AddForUpdate(NetworkObject networkObject)
{
// Since this is a HashSet, we don't need to worry about duplicate entries
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
m_NetworkBehaviourUpdate.Begin();
@@ -53,7 +58,7 @@ namespace Unity.Netcode
// Sync just the variables for just the objects this client sees
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++)
{
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];
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() &&
!behaviour.NetworkVariableIndexesToResetSet.Contains(i))
{
behaviour.NetworkVariableIndexesToResetSet.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
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
dirtyobj.PreviousOwnerId = dirtyobj.OwnerClientId;
}

View File

@@ -5,6 +5,7 @@ using System.Linq;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using PackageInfo = UnityEditor.PackageManager.PackageInfo;
#endif
using UnityEngine.SceneManagement;
using Debug = UnityEngine.Debug;
@@ -17,6 +18,23 @@ namespace Unity.Netcode
[AddComponentMenu("Netcode/Network Manager", -100)]
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
// Inspector view expand/collapse settings for this derived child class
[HideInInspector]
public bool NetworkManagerExpanded;
#endif
// TODO: Deprecate...
// The following internal values are not used, but because ILPP makes them public in the assembly, they cannot
// be removed thanks to our semver validation.
@@ -189,7 +207,6 @@ namespace Unity.Netcode
OnSessionOwnerPromoted?.Invoke(sessionOwner);
}
// TODO: Make this internal after testing
internal void PromoteSessionOwner(ulong clientId)
{
if (!DistributedAuthorityMode)
@@ -869,6 +886,7 @@ namespace Unity.Netcode
internal Override<ushort> PortOverride;
#if UNITY_EDITOR
internal static INetworkManagerHelper NetworkManagerHelper;
@@ -890,6 +908,16 @@ namespace Unity.Netcode
OnNetworkManagerReset?.Invoke(this);
}
protected virtual void OnValidateComponent()
{
}
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()
{
if (NetworkConfig == null)
@@ -950,6 +978,15 @@ namespace Unity.Netcode
}
}
}
try
{
OnValidateComponent();
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
private void ModeChanged(PlayModeStateChange change)
@@ -1011,6 +1048,8 @@ namespace Unity.Netcode
#if UNITY_EDITOR
EditorApplication.playModeStateChanged += ModeChanged;
#endif
// Notify we have instantiated a new instance of NetworkManager.
OnInstantiated?.Invoke(this);
}
private void OnEnable()
@@ -1122,9 +1161,6 @@ namespace Unity.Netcode
UpdateTopology();
//DANGOEXP TODO: Remove this before finalizing the experimental release
NetworkConfig.AutoSpawnPlayerPrefabClientSide = DistributedAuthorityMode;
// Make sure the ServerShutdownState is reset when initializing
if (server)
{
@@ -1613,6 +1649,9 @@ namespace Unity.Netcode
UnityEngine.SceneManagement.SceneManager.sceneUnloaded -= OnSceneUnloaded;
// Notify we are destroying NetworkManager
OnDestroying?.Invoke(this);
if (Singleton == this)
{
Singleton = null;

View File

@@ -67,6 +67,7 @@ namespace Unity.Netcode
/// </remarks>
public List<NetworkTransform> NetworkTransforms { get; private set; }
#if COM_UNITY_MODULES_PHYSICS
/// <summary>
/// All <see cref="NetworkRigidbodyBase"></see> component instances associated with a <see cref="NetworkObject"/> component instance.
@@ -112,11 +113,6 @@ namespace Unity.Netcode
}
// Handle updating the currently active scene
var networkObjects = FindObjectsByType<NetworkObject>(FindObjectsInactive.Include, FindObjectsSortMode.None);
foreach (var networkObject in networkObjects)
{
networkObject.OnValidate();
}
NetworkObjectRefreshTool.ProcessActiveScene();
// Refresh all build settings scenes
@@ -129,14 +125,14 @@ namespace Unity.Netcode
continue;
}
// Add the scene to be processed
NetworkObjectRefreshTool.ProcessScene(editorScene.path, false);
NetworkObjectRefreshTool.ProcessScene(editorScene.path, true);
}
// Process all added scenes
NetworkObjectRefreshTool.ProcessScenes();
}
private void OnValidate()
internal void OnValidate()
{
// do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode
if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name))
@@ -228,6 +224,7 @@ namespace Unity.Netcode
if (sourceAsset != null && sourceAsset.GlobalObjectIdHash != 0 && InScenePlacedSourceGlobalObjectIdHash != sourceAsset.GlobalObjectIdHash)
{
InScenePlacedSourceGlobalObjectIdHash = sourceAsset.GlobalObjectIdHash;
EditorUtility.SetDirty(this);
}
IsSceneObject = true;
}
@@ -339,7 +336,7 @@ namespace Unity.Netcode
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;
}
@@ -937,6 +934,7 @@ namespace Unity.Netcode
/// <summary>
/// If true, the object will always be replicated as root on clients and the parent will be ignored.
/// </summary>
[Tooltip("If enabled (default disabled), instances of this NetworkObject will ignore any parent(s) it might have and replicate on clients as the root being its parent.")]
public bool AlwaysReplicateAsRoot;
/// <summary>
@@ -954,6 +952,8 @@ namespace Unity.Netcode
/// bandwidth cost. This can also be useful for UI elements that have
/// a predetermined fixed position.
/// </remarks>
[Tooltip("If enabled (default enabled), newly joining clients will be synchronized with the transform of the associated GameObject this component is attached to. Typical use case" +
" scenario would be for managment objects or in-scene placed objects that don't move and already have their transform settings applied within the scene information.")]
public bool SynchronizeTransform = true;
/// <summary>
@@ -1011,6 +1011,7 @@ namespace Unity.Netcode
/// To synchronize clients of a <see cref="NetworkObject"/>'s scene being changed via <see cref="SceneManager.MoveGameObjectToScene(GameObject, Scene)"/>,
/// make sure <see cref="SceneMigrationSynchronization"/> is enabled (it is by default).
/// </remarks>
[Tooltip("When enabled (default disabled), spawned instances of this NetworkObject will automatically migrate to any newly assigned active scene.")]
public bool ActiveSceneSynchronization;
/// <summary>
@@ -1029,6 +1030,7 @@ namespace Unity.Netcode
/// is <see cref="true"/> and <see cref="ActiveSceneSynchronization"/> is <see cref="false"/> and the scene is not the currently
/// active scene, then the <see cref="NetworkObject"/> will be destroyed.
/// </remarks>
[Tooltip("When enabled (default enabled), dynamically spawned instances of this NetworkObject's migration to a different scene will automatically be synchonize amongst clients.")]
public bool SceneMigrationSynchronization = true;
/// <summary>
@@ -1044,7 +1046,7 @@ namespace Unity.Netcode
/// <summary>
/// When set to false, the NetworkObject will be spawned with no observers initially (other than the server)
/// </summary>
[Tooltip("When false, the NetworkObject will spawn with no observers initially. (default is true)")]
[Tooltip("When disabled (default enabled), the NetworkObject will spawn with no observers. You control object visibility using NetworkShow. This applies to newly joining clients as well.")]
public bool SpawnWithObservers = true;
/// <summary>
@@ -1073,13 +1075,35 @@ namespace Unity.Netcode
/// Whether or not to destroy this object if it's owner is destroyed.
/// If true, the objects ownership will be given to the server.
/// </summary>
[Tooltip("When enabled (default disabled), instances of this NetworkObject will not be destroyed if the owning client disconnects.")]
public bool DontDestroyWithOwner;
/// <summary>
/// Whether or not to enable automatic NetworkObject parent synchronization.
/// </summary>
[Tooltip("When disabled (default enabled), NetworkObject parenting will not be automatically synchronized. This is typically used when you want to implement your own custom parenting solution.")]
public bool AutoObjectParentSync = true;
/// <summary>
/// Determines if the owner will apply transform values sent by the parenting message.
/// </summary>
/// <remarks>
/// When enabled, the resultant parenting transform changes sent by the authority will be applied on all instances. <br />
/// When disabled, the resultant parenting transform changes sent by the authority will not be applied on the owner's instance. <br />
/// When disabled, all non-owner instances will still be synchronized by the authority's transform values when parented.
/// When using a <see cref="NetworkTopologyTypes.ClientServer"/> network topology and an owner authoritative motion model, disabling this can help smooth parenting transitions.
/// When using a <see cref="NetworkTopologyTypes.DistributedAuthority"/> network topology this will have no impact on the owner's instance since only the authority/owner can parent.
/// </remarks>
[Tooltip("When disabled (default enabled), the owner will not apply a server or host's transform properties when parenting changes. Primarily useful for client-server network topology configurations.")]
public bool SyncOwnerTransformWhenParented = true;
/// <summary>
/// Client-Server specific, when enabled an owner of a NetworkObject can parent locally as opposed to requiring the owner to notify the server it would like to be parented.
/// This behavior is always true when using a distributed authority network topology and does not require it to be set.
/// </summary>
[Tooltip("When enabled (default disabled), owner's can parent a NetworkObject locally without having to send an RPC to the server or host. Only pertinent when using client-server network topology configurations.")]
public bool AllowOwnerToParent;
internal readonly HashSet<ulong> Observers = new HashSet<ulong>();
#if MULTIPLAYER_TOOLS
@@ -1584,9 +1608,14 @@ namespace Unity.Netcode
}
}
else if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
{
// 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
{
NetworkLog.LogWarningServer($"Ran into unknown conditional check during spawn when determining distributed authority mode or not");
@@ -1787,6 +1816,9 @@ namespace Unity.Netcode
{
for (int i = 0; i < ChildNetworkBehaviours.Count; i++)
{
// Invoke internal notification
ChildNetworkBehaviours[i].InternalOnNetworkObjectParentChanged(parentNetworkObject);
// Invoke public notification
ChildNetworkBehaviours[i].OnNetworkObjectParentChanged(parentNetworkObject);
}
}
@@ -1918,7 +1950,7 @@ namespace Unity.Netcode
// DANGO-TODO: Do we want to worry about ownership permissions here?
// It wouldn't make sense to not allow parenting, but keeping this note here as a reminder.
var isAuthority = HasAuthority;
var isAuthority = HasAuthority || (AllowOwnerToParent && IsOwner);
// If we don't have authority and we are not shutting down, then don't allow any parenting.
// If we are shutting down and don't have authority then allow it.
@@ -1984,7 +2016,7 @@ namespace Unity.Netcode
var isAuthority = false;
// With distributed authority, we need to track "valid authoritative" parenting changes.
// So, either the authority or AuthorityAppliedParenting is considered a "valid parenting change".
isAuthority = HasAuthority || AuthorityAppliedParenting;
isAuthority = HasAuthority || AuthorityAppliedParenting || (AllowOwnerToParent && IsOwner);
var distributedAuthority = NetworkManager.DistributedAuthorityMode;
// If we do not have authority and we are spawned
@@ -2076,7 +2108,7 @@ namespace Unity.Netcode
}
// If we are connected to a CMB service or we are running a mock CMB service then send to the "server" identifier
if (distributedAuthority)
if (distributedAuthority || (!distributedAuthority && AllowOwnerToParent && IsOwner && !NetworkManager.IsServer))
{
if (!NetworkManager.DAHost)
{
@@ -2365,7 +2397,9 @@ namespace Unity.Netcode
{
NetworkTransforms = new List<NetworkTransform>();
}
NetworkTransforms.Add(networkBehaviours[i] as NetworkTransform);
var networkTransform = networkBehaviours[i] as NetworkTransform;
networkTransform.IsNested = i != 0 && networkTransform.gameObject != gameObject;
NetworkTransforms.Add(networkTransform);
}
#if COM_UNITY_MODULES_PHYSICS
else if (type.IsSubclassOf(typeof(NetworkRigidbodyBase)))
@@ -2411,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.
// 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
@@ -2737,11 +2779,11 @@ namespace Unity.Netcode
}
}
internal void PostNetworkVariableWrite()
internal void PostNetworkVariableWrite(bool forced = false)
{
for (int k = 0; k < ChildNetworkBehaviours.Count; k++)
{
ChildNetworkBehaviours[k].PostNetworkVariableWrite();
ChildNetworkBehaviours[k].PostNetworkVariableWrite(forced);
}
}
@@ -3020,6 +3062,10 @@ namespace Unity.Netcode
}
}
// Only add all other players as observers if we are spawning with observers,
// otherwise user controls via NetworkShow.
if (networkObject.SpawnWithObservers)
{
// Add all known players to the observers list if they don't already exist
foreach (var player in networkManager.SpawnManager.PlayerObjects)
{
@@ -3027,6 +3073,7 @@ namespace Unity.Netcode
}
}
}
}
return networkObject;
}

View File

@@ -2,6 +2,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -21,6 +23,28 @@ namespace Unity.Netcode
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)
{
if (!s_ScenesToUpdate.Contains(scenePath))
@@ -29,7 +53,10 @@ namespace Unity.Netcode
{
EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened;
EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved;
s_Log.Clear();
LogInfo("NetworkObject Refresh Scenes to Process:");
}
LogInfo($"[{scenePath}]", true);
s_ScenesToUpdate.Add(scenePath);
}
s_ProcessScenes = processScenes;
@@ -37,6 +64,7 @@ namespace Unity.Netcode
internal static void ProcessActiveScene()
{
FlushLog();
var activeScene = SceneManager.GetActiveScene();
if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes)
{
@@ -54,10 +82,12 @@ namespace Unity.Netcode
}
else
{
s_ProcessScenes = false;
s_CloseScenes = false;
EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved;
EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened;
AllScenesProcessed?.Invoke();
FlushLog();
}
}
@@ -68,9 +98,8 @@ namespace Unity.Netcode
// Provide a log of all scenes that were modified to the user
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);
if (scene != SceneManager.GetActiveScene())
@@ -88,26 +117,43 @@ namespace Unity.Netcode
private static void SceneOpened(Scene scene)
{
LogInfo($"Processing scene {scene.name}:");
if (s_ScenesToUpdate.Contains(scene.path))
{
if (s_ProcessScenes)
{
var prefabInstances = PrefabUtility.FindAllInstancesOfPrefab(PrefabNetworkObject.gameObject);
if (prefabInstances.Length > 0)
{
var instancesSceneLoadedSpecific = prefabInstances.Where((c) => c.scene == scene).ToList();
if (instancesSceneLoadedSpecific.Count > 0)
{
foreach (var prefabInstance in instancesSceneLoadedSpecific)
{
prefabInstance.GetComponent<NetworkObject>().OnValidate();
}
if (!EditorSceneManager.MarkSceneDirty(scene))
{
Debug.Log($"Scene {scene.name} did not get marked as dirty!");
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);
}
}
}
private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode)
{

View File

@@ -95,6 +95,7 @@ namespace Unity.Netcode
return;
}
ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false);
if (m_NetworkManager.IsHost)
{
@@ -131,6 +132,8 @@ namespace Unity.Netcode
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendUnnamedMessage(ulong clientId, FastBufferWriter messageBuffer, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
ValidateMessageSize(messageBuffer, networkDelivery, isNamed: false);
if (m_NetworkManager.IsHost)
{
if (clientId == m_NetworkManager.LocalClientId)
@@ -286,6 +289,8 @@ namespace Unity.Netcode
/// <param name="networkDelivery">The delivery type (QoS) to send data with</param>
public void SendNamedMessage(string messageName, ulong clientId, FastBufferWriter messageStream, NetworkDelivery networkDelivery = NetworkDelivery.ReliableSequenced)
{
ValidateMessageSize(messageStream, networkDelivery, isNamed: true);
ulong hash = 0;
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
{
@@ -367,6 +372,8 @@ namespace Unity.Netcode
return;
}
ValidateMessageSize(messageStream, networkDelivery, isNamed: true);
ulong hash = 0;
switch (m_NetworkManager.NetworkConfig.RpcHashSize)
{
@@ -405,5 +412,32 @@ namespace Unity.Netcode
m_NetworkManager.NetworkMetrics.TrackNamedMessageSent(clientIds, messageName, size);
}
}
/// <summary>
/// Validate the size of the message. If it's a non-fragmented delivery type the message must fit within the
/// max allowed size with headers also subtracted. Named messages also include the hash
/// of the name string. Only validates in editor and development builds.
/// </summary>
/// <param name="messageStream">The named message payload</param>
/// <param name="networkDelivery">Delivery method</param>
/// <param name="isNamed">Is the message named (or unnamed)</param>
/// <exception cref="OverflowException">Exception thrown in case validation fails</exception>
private unsafe void ValidateMessageSize(FastBufferWriter messageStream, NetworkDelivery networkDelivery, bool isNamed)
{
#if DEVELOPMENT_BUILD || UNITY_EDITOR
var maxNonFragmentedSize = m_NetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(NetworkBatchHeader);
if (isNamed)
{
maxNonFragmentedSize -= sizeof(ulong); // MessageName hash
}
if (networkDelivery != NetworkDelivery.ReliableFragmentedSequenced
&& messageStream.Length > maxNonFragmentedSize)
{
throw new OverflowException($"Given message size ({messageStream.Length} bytes) is greater than " +
$"the maximum allowed for the selected delivery method ({maxNonFragmentedSize} bytes). Try using " +
$"ReliableFragmentedSequenced delivery method instead.");
}
#endif
}
}
}

View File

@@ -9,7 +9,6 @@ namespace Unity.Netcode
public ulong NetworkObjectId;
public ulong OwnerClientId;
// DANGOEXP TODO: Remove these notes or change their format
// SERVICE NOTES:
// When forwarding the message to clients on the CMB Service side,
// you can set the ClientIdCount to 0 and skip writing the ClientIds.
@@ -258,15 +257,18 @@ namespace Unity.Netcode
continue;
}
// If ownership is changing and this is not an ownership request approval then ignore the OnwerClientId
// 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 ((OwnershipIsChanging && !RequestApproved && OwnerClientId == clientId) || (OwnershipFlagsUpdate && clientId == OwnerClientId)
|| ((RequestOwnership || RequestApproved) && clientId == RequestClientId))
// If ownership is changing and this is not an ownership request approval then ignore the SenderId
if (OwnershipIsChanging && !RequestApproved && context.SenderId == clientId)
{
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);
}
}
@@ -327,10 +329,12 @@ namespace Unity.Netcode
var networkManager = (NetworkManager)context.SystemOwner;
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)
{
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;
@@ -347,12 +351,6 @@ namespace Unity.Netcode
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 (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
networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId);

View File

@@ -55,6 +55,15 @@ namespace Unity.Netcode
// Don't redistribute for the local instance
if (ClientId != networkManager.LocalClientId)
{
// Show any NetworkObjects that are:
// - Hidden from the session owner
// - Owned by this client
// - Has NetworkObject.SpawnWithObservers set to true (the default)
if (!networkManager.LocalClient.IsSessionOwner)
{
networkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(ClientId);
}
// We defer redistribution to the end of the NetworkUpdateStage.PostLateUpdate
networkManager.RedistributeToClient = true;
networkManager.ClientToRedistribute = ClientId;

View File

@@ -3,14 +3,39 @@ using Unity.Collections;
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
{
private const int k_AddCMBServiceConfig = 2;
private const int k_VersionAddClientIds = 1;
public int Version => k_VersionAddClientIds;
public int Version => k_AddCMBServiceConfig;
public ulong OwnerClientId;
public int NetworkTick;
// The cloud state service should set this if we are restoring a session
public ServiceConfig ServiceConfig;
public bool IsRestoredSession;
public ulong CurrentSessionOwner;
// Not serialized
@@ -25,6 +50,32 @@ namespace Unity.Netcode
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)
{
// ============================================================
@@ -44,10 +95,19 @@ namespace Unity.Netcode
BytePacker.WriteValueBitPacked(writer, OwnerClientId);
BytePacker.WriteValueBitPacked(writer, NetworkTick);
if (IsDistributedAuthority)
{
if (targetVersion >= k_AddCMBServiceConfig)
{
ServiceConfig.IsRestoredSession = false;
ServiceConfig.CurrentSessionOwner = CurrentSessionOwner;
writer.WriteNetworkSerializable(ServiceConfig);
}
else
{
writer.WriteValueSafe(IsRestoredSession);
BytePacker.WriteValueBitPacked(writer, CurrentSessionOwner);
}
}
if (targetVersion >= k_VersionAddClientIds)
{
@@ -122,14 +182,21 @@ namespace Unity.Netcode
// ============================================================
// END FORBIDDEN SEGMENT
// ============================================================
m_ReceiveMessageVersion = receivedMessageVersion;
ByteUnpacker.ReadValueBitPacked(reader, out OwnerClientId);
ByteUnpacker.ReadValueBitPacked(reader, out NetworkTick);
if (networkManager.DistributedAuthorityMode)
{
if (receivedMessageVersion >= k_AddCMBServiceConfig)
{
reader.ReadNetworkSerializable(out ServiceConfig);
}
else
{
reader.ReadValueSafe(out IsRestoredSession);
ByteUnpacker.ReadValueBitPacked(reader, out CurrentSessionOwner);
}
}
if (receivedMessageVersion >= k_VersionAddClientIds)
{
@@ -157,7 +224,7 @@ namespace Unity.Netcode
if (networkManager.DistributedAuthorityMode)
{
networkManager.SetSessionOwner(CurrentSessionOwner);
networkManager.SetSessionOwner(GetSessionOwner());
if (networkManager.LocalClient.IsSessionOwner && networkManager.NetworkConfig.EnableSceneManagement)
{
networkManager.SceneManager.InitializeScenesLoaded();
@@ -233,9 +300,9 @@ namespace Unity.Netcode
// Mark the client being connected
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
networkManager.SceneManager.SynchronizeNetworkObjects(NetworkManager.ServerClientId);

View File

@@ -2,16 +2,54 @@ using Unity.Collections;
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;
public ulong ConfigHash;
public bool CMBServiceConnection;
/// <summary>
/// We start at version 1, where anything less than version 1 on the service side
/// is not bypass feature compatible.
/// </summary>
private const int k_BypassFeatureCompatible = 1;
public int Version => k_BypassFeatureCompatible;
public uint TickRate;
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 bool ShouldSendConnectionData;
@@ -36,8 +74,7 @@ namespace Unity.Netcode
if (CMBServiceConnection)
{
writer.WriteValueSafe(TickRate);
writer.WriteValueSafe(EnableSceneManagement);
writer.WriteNetworkSerializable(ClientConfig);
}
if (ShouldSendConnectionData)

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
namespace Unity.Netcode
@@ -10,9 +11,22 @@ namespace Unity.Netcode
/// serialization. This is due to the generally amorphous nature of network variable
/// deltas, since they're all driven by custom virtual method overloads.
/// </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
{
public int Version => 0;
private const int k_ServerDeltaForwardingAndNetworkDelivery = 1;
public int Version => k_ServerDeltaForwardingAndNetworkDelivery;
public ulong NetworkObjectId;
public ushort NetworkBehaviourIndex;
@@ -21,10 +35,62 @@ namespace Unity.Netcode
public ulong TargetClientId;
public NetworkBehaviour NetworkBehaviour;
public NetworkDelivery NetworkDelivery;
private FastBufferReader m_ReceivedNetworkVariableData;
private bool m_ForwardingMessage;
private int m_ReceivedMessageVersion;
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)
{
if (!writer.TryBeginWrite(FastBufferWriter.GetWriteSize(NetworkObjectId) + FastBufferWriter.GetWriteSize(NetworkBehaviourIndex)))
@@ -34,10 +100,67 @@ namespace Unity.Netcode
var obj = NetworkBehaviour.NetworkObject;
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, 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);
}
@@ -46,12 +169,12 @@ namespace Unity.Netcode
{
if (!DeliveryMappedNetworkVariableIndex.Contains(i))
{
// This var does not belong to the currently iterating delivery group.
if (networkManager.DistributedAuthorityMode)
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
if (distributedAuthorityMode)
{
writer.WriteValueSafe<ushort>(0);
}
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
else if (ensureNetworkVariableLengthSafety)
{
BytePacker.WriteValueBitPacked(writer, (ushort)0);
}
@@ -88,14 +211,15 @@ namespace Unity.Netcode
shouldWrite = false;
}
if (networkManager.DistributedAuthorityMode)
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
if (distributedAuthorityMode)
{
if (!shouldWrite)
{
writer.WriteValueSafe<ushort>(0);
}
}
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
else if (ensureNetworkVariableLengthSafety)
{
if (!shouldWrite)
{
@@ -109,53 +233,22 @@ namespace Unity.Netcode
if (shouldWrite)
{
if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
{
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);
WriteNetworkVariable(ref writer, ref networkVariable, distributedAuthorityMode, ensureNetworkVariableLengthSafety, nonFragmentedMessageMaxSize, fragmentedMessageMaxSize);
networkManager.NetworkMetrics.TrackNetworkVariableDeltaSent(TargetClientId, obj, networkVariable.Name, typeName, writer.Length - startingSize);
}
}
}
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
m_ReceivedMessageVersion = receivedMessageVersion;
ByteUnpacker.ReadValueBitPacked(reader, out NetworkObjectId);
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;
return true;
@@ -167,7 +260,12 @@ namespace Unity.Netcode
if (networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out NetworkObject networkObject))
{
var distributedAuthorityMode = networkManager.DistributedAuthorityMode;
var ensureNetworkVariableLengthSafety = networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety;
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)
{
@@ -178,7 +276,8 @@ namespace Unity.Netcode
}
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);
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++)
{
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);
varSize = variableSize;
@@ -200,10 +319,9 @@ namespace Unity.Netcode
continue;
}
}
else if (networkManager.NetworkConfig.EnsureNetworkVariableLengthSafety)
else if (ensureNetworkVariableLengthSafety)
{
ByteUnpacker.ReadValueBitPacked(m_ReceivedNetworkVariableData, out varSize);
if (varSize == 0)
{
continue;
@@ -218,8 +336,6 @@ namespace Unity.Netcode
}
}
var networkVariable = networkBehaviour.NetworkVariableFields[i];
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
@@ -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($"[{networkVariable.GetType().Name}]");
}
return;
}
int readStartPos = m_ReceivedNetworkVariableData.Position;
// Read Delta so we also notify any subscribers to a change in the NetworkVariable
networkVariable.ReadDelta(m_ReceivedNetworkVariableData, networkManager.IsServer);
// DANGO TODO: Remove distributedAuthorityMode portion when we remove the service specific NetworkVariable stuff
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(
context.SenderId,
@@ -262,7 +423,8 @@ namespace Unity.Netcode
networkBehaviour.__getTypeName(),
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))
{
@@ -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

View File

@@ -117,6 +117,11 @@ namespace Unity.Netcode
networkObject.SetNetworkParenting(LatestParent, WorldPositionStays);
networkObject.ApplyNetworkParenting(RemoveParent);
// This check is primarily for client-server network topologies when the motion model is owner authoritative:
// When SyncOwnerTransformWhenParented is enabled, then always apply the transform values.
// When SyncOwnerTransformWhenParented is disabled, then only synchronize the transform on non-owner instances.
if (networkObject.SyncOwnerTransformWhenParented || (!networkObject.SyncOwnerTransformWhenParented && !networkObject.IsOwner))
{
// We set all of the transform values after parenting as they are
// the values of the server-side post-parenting transform values
if (!WorldPositionStays)
@@ -130,9 +135,10 @@ namespace Unity.Netcode
networkObject.transform.rotation = Rotation;
}
networkObject.transform.localScale = Scale;
}
// If in distributed authority mode and we are running a DAHost and this is the DAHost, then forward the parent changed message to any remaining clients
if (networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost)
if ((networkManager.DistributedAuthorityMode && !networkManager.CMBServiceConnection && networkManager.DAHost) || (networkObject.AllowOwnerToParent && context.SenderId == networkObject.OwnerClientId && networkManager.IsServer))
{
var size = 0;
var message = this;

View File

@@ -1,4 +1,3 @@
using System;
using Unity.Collections;
namespace Unity.Netcode
@@ -34,22 +33,14 @@ namespace Unity.Netcode
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(WrappedMessage.Metadata.NetworkObjectId, out var networkObject))
{
// With distributed authority mode, we can send Rpcs before we have been notified the NetworkObject is despawned.
// DANGO-TODO: Should the CMB Service cull out any Rpcs targeting recently despawned NetworkObjects?
// DANGO-TODO: This would require the service to keep track of despawned NetworkObjects since we re-use NetworkObject identifiers.
if (networkManager.DistributedAuthorityMode)
{
// If the NetworkObject no longer exists then just log a warning when developer mode logging is enabled and exit.
// This can happen if NetworkObject is despawned and a client sends an RPC before receiving the despawn message.
if (networkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
}
return;
}
else
{
throw new InvalidOperationException($"[{WrappedMessage.Metadata.NetworkObjectId}, {WrappedMessage.Metadata.NetworkBehaviourId}, {WrappedMessage.Metadata.NetworkRpcMethodId}]An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
}
}
var observers = networkObject.Observers;

View File

@@ -60,7 +60,13 @@ namespace Unity.Netcode
var networkManager = (NetworkManager)context.SystemOwner;
if (!networkManager.SpawnManager.SpawnedObjects.TryGetValue(metadata.NetworkObjectId, out var networkObject))
{
throw new InvalidOperationException($"An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
// If the NetworkObject no longer exists then just log a warning when developer mode logging is enabled and exit.
// This can happen if NetworkObject is despawned and a client sends an RPC before receiving the despawn message.
if (networkManager.LogLevel == LogLevel.Developer)
{
NetworkLog.LogWarning($"[{metadata.NetworkObjectId}, {metadata.NetworkBehaviourId}, {metadata.NetworkRpcMethodId}] An RPC called on a {nameof(NetworkObject)} that is not in the spawned objects list. Please make sure the {nameof(NetworkObject)} is spawned before calling RPCs.");
}
return;
}
var networkBehaviour = networkObject.GetNetworkBehaviourAtOrderIndex(metadata.NetworkBehaviourId);

View File

@@ -20,7 +20,7 @@ namespace Unity.Netcode
public void Handle(ref NetworkContext context)
{
((NetworkManager)context.SystemOwner).CustomMessagingManager.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize);
((NetworkManager)context.SystemOwner).CustomMessagingManager?.InvokeUnnamedMessage(context.SenderId, m_ReceivedData, context.SerializedHeaderSize);
}
}
}

View File

@@ -733,7 +733,11 @@ namespace Unity.Netcode
}
ref var writeQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1);
writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length);
if (!writeQueueItem.Writer.TryBeginWrite(tmpSerializer.Length + headerSerializer.Length))
{
Debug.LogError($"Not enough space to write message, size={tmpSerializer.Length + headerSerializer.Length} space used={writeQueueItem.Writer.Position} total size={writeQueueItem.Writer.Capacity}");
continue;
}
writeQueueItem.Writer.WriteBytes(headerSerializer.GetUnsafePtr(), headerSerializer.Length);
writeQueueItem.Writer.WriteBytes(tmpSerializer.GetUnsafePtr(), tmpSerializer.Length);

View File

@@ -177,6 +177,13 @@ namespace Unity.Netcode
/// <inheritdoc />
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);
for (int i = 0; i < deltaCount; i++)
{
@@ -199,7 +206,7 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
@@ -207,9 +214,13 @@ namespace Unity.Netcode
Index = m_List.Length - 1,
Value = m_List[m_List.Length - 1]
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.Insert:
{
@@ -237,7 +248,7 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
@@ -245,9 +256,13 @@ namespace Unity.Netcode
Index = index,
Value = m_List[index]
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.Remove:
{
@@ -271,7 +286,7 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
@@ -279,9 +294,13 @@ namespace Unity.Netcode
Index = index,
Value = value
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.RemoveAt:
{
@@ -299,7 +318,7 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
@@ -307,9 +326,13 @@ namespace Unity.Netcode
Index = index,
Value = value
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.Value:
{
@@ -335,7 +358,7 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
@@ -344,9 +367,13 @@ namespace Unity.Netcode
Value = value,
PreviousValue = previousValue
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.Clear:
{
@@ -361,15 +388,20 @@ namespace Unity.Netcode
});
}
if (keepDirtyDelta)
if (isServer)
{
m_DirtyEvents.Add(new NetworkListEvent<T>()
{
Type = eventType
});
// Preserve the legacy way of handling this
if (keepDirtyDelta)
{
MarkNetworkObjectDirty();
}
}
}
break;
case NetworkListEvent<T>.EventType.Full:
{
@@ -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 />
public IEnumerator<T> GetEnumerator()
{

View File

@@ -41,6 +41,7 @@ namespace Unity.Netcode
base.OnInitialize();
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
}
@@ -58,6 +59,7 @@ namespace Unity.Netcode
: base(readPerm, writePerm)
{
m_InternalValue = value;
m_InternalOriginalValue = default;
// 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
// 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)
{
m_InternalValue = value;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
m_PreviousValue = default;
}
}
@@ -86,6 +89,12 @@ namespace Unity.Netcode
[SerializeField]
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 bool m_HasPreviousValue;
@@ -116,6 +125,7 @@ namespace Unity.Netcode
{
T previousValue = m_InternalValue;
m_InternalValue = value;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_InternalOriginalValue);
SetDirty(true);
m_IsDisposed = false;
OnValueChanged?.Invoke(previousValue, m_InternalValue);
@@ -136,6 +146,17 @@ namespace Unity.Netcode
{
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.
if ((!isDirty || forceCheck) && !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue))
{
@@ -166,6 +187,7 @@ namespace Unity.Netcode
}
m_InternalValue = default;
m_InternalOriginalValue = default;
if (m_HasPreviousValue && m_PreviousValue is IDisposable previousValueDisposable)
{
m_HasPreviousValue = false;
@@ -188,6 +210,13 @@ namespace Unity.Netcode
/// <returns>Whether or not the container is dirty</returns>
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.
// This doesn't work for cases where we're wrapping more complex types
// like INetworkSerializable, NativeList, NativeArray, etc.
@@ -199,11 +228,11 @@ namespace Unity.Netcode
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
// 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()
// is called.
var dirty = !NetworkVariableSerialization<T>.AreEqual(ref m_PreviousValue, ref m_InternalValue);
SetDirty(dirty);
return dirty;
}
@@ -221,6 +250,8 @@ namespace Unity.Netcode
{
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);
}
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>
public override void ReadDelta(FastBufferReader reader, bool keepDirtyDelta)
{
// In order to get managed collections to properly have a previous and current value, we have to
// duplicate the collection at this point before making any modifications to the current.
m_HasPreviousValue = true;
NetworkVariableSerialization<T>.Duplicate(m_InternalValue, ref m_PreviousValue);
// 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>.ReadDelta(reader, ref m_InternalValue);
// todo:
// keepDirtyDelta marks a variable received as dirty and causes the server to send the value to clients
// In a prefect world, whether a variable was A) modified locally or B) received and needs retransmit
// would be stored in different fields
// LEGACY NOTE: This is only to handle NetworkVariableDeltaMessage Version 0 connections. The updated
// NetworkVariableDeltaMessage no longer uses this approach.
if (keepDirtyDelta)
{
SetDirty(true);
@@ -259,10 +294,43 @@ namespace Unity.Netcode
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 />
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);
// 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 />
@@ -270,5 +338,20 @@ namespace Unity.Netcode
{
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);
}
}
}
}

View File

@@ -187,7 +187,9 @@ namespace Unity.Netcode
internal bool CanSend()
{
var timeSinceLastUpdate = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime - LastUpdateSent;
// When connected to a service or not the server, always use the synchronized server time as opposed to the local time
var time = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime;
var timeSinceLastUpdate = time - LastUpdateSent;
return
(
UpdateTraits.MaxSecondsBetweenUpdates > 0 &&
@@ -201,7 +203,8 @@ namespace Unity.Netcode
internal void UpdateLastSentTime()
{
LastUpdateSent = m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime;
// When connected to a service or not the server, always use the synchronized server time as opposed to the local time
LastUpdateSent = m_InternalNetworkManager.CMBServiceConnection || !m_InternalNetworkManager.IsServer ? m_NetworkBehaviour.NetworkManager.ServerTime.Time : m_NetworkBehaviour.NetworkManager.NetworkTimeSystem.LocalTime;
}
internal static bool IgnoreInitializeWarning;
@@ -248,6 +251,12 @@ namespace Unity.Netcode
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>
/// Gets Whether or not the container is dirty
/// </summary>
@@ -338,6 +347,32 @@ namespace Unity.Netcode
/// <param name="keepDirtyDelta">Whether or not the delta should be kept as dirty or consumed</param>
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>
/// Virtual <see cref="IDisposable"/> implementation
/// </summary>

View File

@@ -2550,17 +2550,6 @@ namespace Unity.Netcode
// At this point the client is considered fully "connected"
if ((NetworkManager.DistributedAuthorityMode && NetworkManager.LocalClient.IsSessionOwner) || !NetworkManager.DistributedAuthorityMode)
{
if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
{
// DANGO-EXP TODO: Remove this once service is sending the synchronization message to all clients
if (NetworkManager.ConnectedClients.ContainsKey(clientId) && NetworkManager.ConnectionManager.ConnectedClientIds.Contains(clientId) && NetworkManager.ConnectedClientsList.Contains(NetworkManager.ConnectedClients[clientId]))
{
EndSceneEvent(sceneEventId);
return;
}
NetworkManager.ConnectionManager.AddClient(clientId);
}
// Notify the local server that a client has finished synchronizing
OnSceneEvent?.Invoke(new SceneEvent()
{
@@ -2575,6 +2564,20 @@ namespace Unity.Netcode
}
else
{
// Notify the local server that a client has finished synchronizing
OnSceneEvent?.Invoke(new SceneEvent()
{
SceneEventType = sceneEventData.SceneEventType,
SceneName = string.Empty,
ClientId = clientId
});
// Show any NetworkObjects that are:
// - Hidden from the session owner
// - Owned by this client
// - Has NetworkObject.SpawnWithObservers set to true (the default)
NetworkManager.SpawnManager.ShowHiddenObjectsToNewlyJoinedClient(clientId);
// DANGO-EXP TODO: Remove this once service distributes objects
// Non-session owners receive this notification from newly connected clients and upon receiving
// the event they will redistribute their NetworkObjects
@@ -2589,9 +2592,6 @@ namespace Unity.Netcode
// At this time the client is fully synchronized with all loaded scenes and
// NetworkObjects and should be considered "fully connected". Send the
// notification that the client is connected.
// TODO 2023: We should have a better name for this or have multiple states the
// client progresses through (the name and associated legacy behavior/expected state
// of the client was persisted since MLAPI)
NetworkManager.ConnectionManager.InvokeOnClientConnectedCallback(clientId);
if (NetworkManager.IsHost)
@@ -2664,9 +2664,14 @@ namespace Unity.Netcode
EventData = sceneEventData,
};
// Forward synchronization to client then exit early because DAHost is not the current session owner
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, NetworkManager.CurrentSessionOwner);
EndSceneEvent(sceneEventData.SceneEventId);
return;
foreach (var client in NetworkManager.ConnectedClientsIds)
{
if (client == NetworkManager.LocalClientId)
{
continue;
}
NetworkManager.MessageManager.SendMessage(ref message, NetworkDelivery.ReliableFragmentedSequenced, client);
}
}
}
else

View File

@@ -320,9 +320,11 @@ namespace Unity.Netcode
internal void AddSpawnedNetworkObjects()
{
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)
{
if (sobj.Observers.Contains(TargetClientId))
if (sobj.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService)
{
m_NetworkObjectsSync.Add(sobj);
}
@@ -666,12 +668,14 @@ namespace Unity.Netcode
// Write our count place holder (must not be packed!)
writer.WriteValueSafe((ushort)0);
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 keyValuePairBySceneHandle in keyValuePairByGlobalObjectIdHash.Value)
{
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId))
if (keyValuePairBySceneHandle.Value.Observers.Contains(TargetClientId) || distributedAuthoritySendingToService)
{
// Serialize the NetworkObject
var sceneObject = keyValuePairBySceneHandle.Value.GetMessageSceneObject(TargetClientId, distributedAuthority);

View File

@@ -700,7 +700,7 @@ namespace Unity.Netcode
}
if (Handle->Position + size > Handle->AllowedWriteMark)
{
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}()");
throw new OverflowException($"Attempted to write without first calling {nameof(TryBeginWrite)}(), Position+Size={Handle->Position + size} > AllowedWriteMark={Handle->AllowedWriteMark}");
}
#endif
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
@@ -729,7 +729,7 @@ namespace Unity.Netcode
if (!TryBeginWriteInternal(size))
{
throw new OverflowException("Writing past the end of the buffer");
throw new OverflowException($"Writing past the end of the buffer, size is {size} bytes but remaining capacity is {Handle->Capacity - Handle->Position} bytes");
}
UnsafeUtility.MemCpy((Handle->BufferPointer + Handle->Position), value + offset, size);
Handle->Position += size;

View File

@@ -72,11 +72,22 @@ namespace Unity.Netcode
return;
}
}
foreach (var player in m_PlayerObjects)
{
// If the player's SpawnWithObservers is not set then do not add the new player object's owner as an observer.
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);
if (!m_PlayerObjectsTable.ContainsKey(playerObject.OwnerClientId))
{
@@ -423,8 +434,31 @@ namespace Unity.Netcode
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)
{
// 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 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
networkObject.InvokeBehaviourOnLostOwnership();
networkObject.MarkVariablesDirty(true);
NetworkManager.BehaviourUpdater.AddForUpdate(networkObject);
// Authority adds entries for all client ownership
UpdateOwnershipTable(networkObject, networkObject.OwnerClientId);
// Always notify locally on the server when a new owner is assigned
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;
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
/// change can be sent from NetworkBehaviours that override the <see cref="NetworkBehaviour.OnOwnershipChanged"></see>
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)
@@ -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
/// passed in globalObjectIdHash value, then that is what will be instantiated, spawned, and returned.
/// </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;
// If the prefab hash has a registered INetworkPrefabInstanceHandler derived class
if (NetworkManager.PrefabHandler.ContainsHandler(globalObjectIdHash))
{
// 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;
}
else
@@ -752,8 +803,10 @@ namespace Unity.Netcode
}
else
{
// Create prefab instance
// Create prefab instance while applying any pre-assigned position and rotation values
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.PrefabGlobalObjectIdHash = globalObjectIdHash;
}
@@ -1580,22 +1633,48 @@ namespace Unity.Netcode
if (NetworkManager.DistributedAuthorityMode && !NetworkManager.DAHost)
{
foreach (var entry in ClientsToShowObject)
{
if (entry.Key != null && entry.Key.IsSpawned)
{
try
{
SendSpawnCallForObserverUpdate(entry.Value.ToArray(), entry.Key);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Developer)
{
Debug.LogException(ex);
}
}
}
}
ClientsToShowObject.Clear();
ObjectsToShowToClient.Clear();
return;
}
// Handle NetworkObjects to show
// Server or Host handling of NetworkObjects to show
foreach (var client in ObjectsToShowToClient)
{
ulong clientId = client.Key;
foreach (var networkObject in client.Value)
{
if (networkObject != null && networkObject.IsSpawned)
{
try
{
SendSpawnCallForObject(clientId, networkObject);
}
catch (Exception ex)
{
if (NetworkManager.LogLevel <= LogLevel.Developer)
{
Debug.LogException(ex);
}
}
}
}
}
ObjectsToShowToClient.Clear();
}
@@ -1883,5 +1962,55 @@ namespace Unity.Netcode
networkObject.InternalNetworkSessionSynchronized();
}
}
/// <summary>
/// Distributed Authority Only
/// Should be invoked on non-session owner clients when a newly joined client is finished
/// synchronizing in order to "show" (spawn) anything that might be currently hidden from
/// the session owner.
/// </summary>
internal void ShowHiddenObjectsToNewlyJoinedClient(ulong newClientId)
{
if (!NetworkManager.DistributedAuthorityMode)
{
if (NetworkManager == null || !NetworkManager.ShutdownInProgress && NetworkManager.LogLevel <= LogLevel.Developer)
{
Debug.LogWarning($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} invoked while !");
}
return;
}
if (!NetworkManager.DistributedAuthorityMode)
{
Debug.LogError($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} should only be invoked when using a distributed authority network topology!");
return;
}
if (NetworkManager.LocalClient.IsSessionOwner)
{
Debug.LogError($"[Internal Error] {nameof(ShowHiddenObjectsToNewlyJoinedClient)} should only be invoked on a non-session owner client!");
return;
}
var localClientId = NetworkManager.LocalClient.ClientId;
var sessionOwnerId = NetworkManager.CurrentSessionOwner;
foreach (var networkObject in SpawnedObjectsList)
{
if (networkObject.SpawnWithObservers && networkObject.OwnerClientId == localClientId && !networkObject.Observers.Contains(sessionOwnerId))
{
if (networkObject.Observers.Contains(newClientId))
{
if (NetworkManager.LogLevel <= LogLevel.Developer)
{
// Track if there is some other location where the client is being added to the observers list when the object is hidden from the session owner
Debug.LogWarning($"[{networkObject.name}] Has new client as an observer but it is hidden from the session owner!");
}
// For now, remove the client (impossible for the new client to have an instance since the session owner doesn't) to make sure newly added
// code to handle this edge case works.
networkObject.Observers.Remove(newClientId);
}
networkObject.NetworkShow(newClientId);
}
}
}
}
}

View File

@@ -24,10 +24,11 @@ namespace Unity.Netcode.TestHelpers.Runtime
/// Used to determine if a NetcodeIntegrationTest is currently running to
/// determine how clients will load scenes
/// </summary>
protected const float k_DefaultTimeoutPeriod = 8.0f;
protected const float k_TickFrequency = 1.0f / k_DefaultTickRate;
internal static bool IsRunning { get; private set; }
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(8.0f);
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(1.0f / k_DefaultTickRate);
protected static TimeoutHelper s_GlobalTimeoutHelper = new TimeoutHelper(k_DefaultTimeoutPeriod);
protected static WaitForSecondsRealtime s_DefaultWaitForTick = new WaitForSecondsRealtime(k_TickFrequency);
public NetcodeLogAssert NetcodeLogAssert;
public enum SceneManagementState
@@ -544,9 +545,14 @@ namespace Unity.Netcode.TestHelpers.Runtime
private bool AllPlayerObjectClonesSpawned(NetworkManager joinedClient)
{
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
ClientNetworkManagerPostStart(joinedClient);
var playerObjectRelative = m_ServerNetworkManager.SpawnManager.PlayerObjects.Where((c) => c.OwnerClientId == joinedClient.LocalClientId).FirstOrDefault();
if (playerObjectRelative == null)
{
@@ -850,6 +856,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
protected virtual bool LogAllMessages => false;
protected virtual bool ShouldCheckForSpawnedPlayers()
{
return true;
}
/// <summary>
/// This starts the server and clients as long as <see cref="CanStartServerAndClients"/>
/// returns true.
@@ -938,7 +950,12 @@ namespace Unity.Netcode.TestHelpers.Runtime
AssertOnTimeout($"{nameof(CreateAndStartNewClient)} timed out waiting for all sessions to spawn Client-{m_ServerNetworkManager.LocalClientId}'s player object!\n {m_InternalErrorLog}");
}
}
if (ShouldCheckForSpawnedPlayers())
{
ClientNetworkManagerPostStartInit();
}
// Notification that at this time the server and client(s) are instantiated,
// started, and connected on both sides.
yield return OnServerAndClientsConnected();
@@ -1030,7 +1047,10 @@ namespace Unity.Netcode.TestHelpers.Runtime
}
}
if (ShouldCheckForSpawnedPlayers())
{
ClientNetworkManagerPostStartInit();
}
// Notification that at this time the server and client(s) are instantiated,
// started, and connected on both sides.

View File

@@ -326,21 +326,31 @@ namespace Unity.Netcode.TestHelpers.Runtime
s_IsStarted = false;
try
{
// Shutdown the server which forces clients to disconnect
foreach (var networkManager in NetworkManagerInstances)
{
networkManager.Shutdown();
if (networkManager != null && networkManager.IsListening)
{
networkManager?.Shutdown();
s_Hooks.Remove(networkManager);
}
}
// Destroy the network manager instances
foreach (var networkManager in NetworkManagerInstances)
{
if (networkManager.gameObject != null)
if (networkManager != null && networkManager.gameObject)
{
Object.DestroyImmediate(networkManager.gameObject);
}
}
}
catch (Exception ex)
{
Debug.LogException(ex);
}
NetworkManagerInstances.Clear();

View File

@@ -1,5 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
@@ -8,58 +9,162 @@ using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
internal class ConnectionApprovalTests
[TestFixture(PlayerCreation.Prefab)]
[TestFixture(PlayerCreation.PrefabHash)]
[TestFixture(PlayerCreation.NoPlayer)]
[TestFixture(PlayerCreation.FailValidation)]
internal class ConnectionApprovalTests : IntegrationTestWithApproximation
{
private Guid m_ValidationToken;
private bool m_IsValidated;
private const string k_InvalidToken = "Invalid validation token!";
[SetUp]
public void Setup()
public enum PlayerCreation
{
// Create, instantiate, and host
Assert.IsTrue(NetworkManagerHelper.StartNetworkManager(out _, NetworkManagerHelper.NetworkManagerOperatingMode.None));
Prefab,
PrefabHash,
NoPlayer,
FailValidation
}
private PlayerCreation m_PlayerCreation;
private bool m_ClientDisconnectReasonValidated;
private Vector3 m_ExpectedPosition;
private Quaternion m_ExpectedRotation;
private Dictionary<ulong, bool> m_Validated = new Dictionary<ulong, bool>();
public ConnectionApprovalTests(PlayerCreation playerCreation)
{
m_PlayerCreation = playerCreation;
}
protected override int NumberOfClients => 1;
private Guid m_ValidationToken;
protected override bool ShouldCheckForSpawnedPlayers()
{
return m_PlayerCreation != PlayerCreation.NoPlayer;
}
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_BypassConnectionTimeout = m_PlayerCreation == PlayerCreation.FailValidation;
m_Validated.Clear();
m_ValidationToken = Guid.NewGuid();
var validationToken = Encoding.UTF8.GetBytes(m_ValidationToken.ToString());
m_ServerNetworkManager.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback;
m_ServerNetworkManager.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null;
if (m_PlayerCreation == PlayerCreation.PrefabHash)
{
m_ServerNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab });
}
m_ServerNetworkManager.NetworkConfig.ConnectionApproval = true;
m_ServerNetworkManager.NetworkConfig.ConnectionData = validationToken;
foreach (var client in m_ClientNetworkManagers)
{
client.NetworkConfig.PlayerPrefab = m_PlayerCreation == PlayerCreation.Prefab ? m_PlayerPrefab : null;
if (m_PlayerCreation == PlayerCreation.PrefabHash)
{
client.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = m_PlayerPrefab });
}
client.NetworkConfig.ConnectionApproval = true;
client.NetworkConfig.ConnectionData = m_PlayerCreation == PlayerCreation.FailValidation ? Encoding.UTF8.GetBytes(Guid.NewGuid().ToString()) : validationToken;
if (m_PlayerCreation == PlayerCreation.FailValidation)
{
client.OnClientDisconnectCallback += Client_OnClientDisconnectCallback;
}
}
base.OnServerAndClientsCreated();
}
private void Client_OnClientDisconnectCallback(ulong clientId)
{
m_ClientNetworkManagers[0].OnClientDisconnectCallback -= Client_OnClientDisconnectCallback;
m_ClientDisconnectReasonValidated = m_ClientNetworkManagers[0].LocalClientId == clientId && m_ClientNetworkManagers[0].DisconnectReason == k_InvalidToken;
}
private bool ClientAndHostValidated()
{
if (!m_Validated.ContainsKey(m_ServerNetworkManager.LocalClientId) || !m_Validated[m_ServerNetworkManager.LocalClientId])
{
return false;
}
if (m_PlayerCreation == PlayerCreation.FailValidation)
{
return m_ClientDisconnectReasonValidated;
}
else
{
foreach (var client in m_ClientNetworkManagers)
{
if (!m_Validated.ContainsKey(client.LocalClientId) || !m_Validated[client.LocalClientId])
{
return false;
}
}
}
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]
public IEnumerator ConnectionApproval()
{
NetworkManagerHelper.NetworkManagerObject.ConnectionApprovalCallback = NetworkManagerObject_ConnectionApprovalCallback;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionApproval = true;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.PlayerPrefab = null;
NetworkManagerHelper.NetworkManagerObject.NetworkConfig.ConnectionData = Encoding.UTF8.GetBytes(m_ValidationToken.ToString());
m_IsValidated = false;
NetworkManagerHelper.NetworkManagerObject.StartHost();
yield return WaitForConditionOrTimeOut(ClientAndHostValidated);
AssertOnTimeout("Timed out waiting for all clients to be approved!");
var timeOut = Time.realtimeSinceStartup + 3.0f;
var timedOut = false;
while (!m_IsValidated)
if (m_PlayerCreation == PlayerCreation.Prefab || m_PlayerCreation == PlayerCreation.PrefabHash)
{
yield return new WaitForSeconds(0.01f);
if (timeOut < Time.realtimeSinceStartup)
{
timedOut = true;
yield return WaitForConditionOrTimeOut(ValidatePlayersPositionRotation);
AssertOnTimeout("Not all player prefabs spawned in the correct position and/or rotation!");
}
}
//Make sure we didn't time out
Assert.False(timedOut);
Assert.True(m_IsValidated);
}
private void NetworkManagerObject_ConnectionApprovalCallback(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
{
var stringGuid = Encoding.UTF8.GetString(request.Payload);
if (m_ValidationToken.ToString() == stringGuid)
{
m_IsValidated = true;
m_Validated.Add(request.ClientNetworkId, true);
response.Approved = true;
}
else
{
response.Approved = false;
response.Reason = "Invalid validation token!";
}
response.Approved = m_IsValidated;
response.CreatePlayerObject = false;
response.Position = null;
response.Rotation = null;
response.PlayerPrefabHash = null;
response.CreatePlayerObject = ShouldCheckForSpawnedPlayers();
response.Position = m_ExpectedPosition;
response.Rotation = m_ExpectedRotation;
response.PlayerPrefabHash = m_PlayerCreation == PlayerCreation.PrefabHash ? m_PlayerPrefab.GetComponent<NetworkObject>().GlobalObjectIdHash : null;
}
@@ -78,13 +183,6 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(currentHash != newHash, $"Hashed {nameof(NetworkConfig)} values {currentHash} and {newHash} should not be the same!");
}
[TearDown]
public void TearDown()
{
// Stop, shutdown, and destroy
NetworkManagerHelper.ShutdownNetworkManager();
}
}
}
}

View File

@@ -82,7 +82,7 @@ namespace Unity.Netcode.RuntimeTests
public IEnumerator ValidateApprovalTimeout()
{
// 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
NetcodeLogAssert.LogWasNotReceived(LogType.Log, m_ExpectedLogMessage);

View File

@@ -670,7 +670,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
foreach (var client in m_ClientNetworkManagers)
{
@@ -678,9 +678,9 @@ namespace Unity.Netcode.RuntimeTests
Assert.IsTrue(manager.DeferMessageCalled);
Assert.IsFalse(manager.ProcessTriggersCalled);
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
Assert.AreEqual(3, manager.DeferredMessageCountTotal());
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
Assert.AreEqual(0, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
AddPrefabsToClient(client);
}
@@ -812,7 +812,7 @@ namespace Unity.Netcode.RuntimeTests
serverObject.GetComponent<NetworkObject>().ChangeOwnership(m_ClientNetworkManagers[0].LocalClientId);
WaitForAllClientsToReceive<ChangeOwnershipMessage, NetworkVariableDeltaMessage>();
WaitForAllClientsToReceive<ChangeOwnershipMessage>();
// Validate messages are deferred and pending
foreach (var client in m_ClientNetworkManagers)
@@ -821,10 +821,10 @@ namespace Unity.Netcode.RuntimeTests
Assert.IsTrue(manager.DeferMessageCalled);
Assert.IsFalse(manager.ProcessTriggersCalled);
Assert.AreEqual(5, manager.DeferredMessageCountTotal());
Assert.AreEqual(4, manager.DeferredMessageCountTotal());
Assert.AreEqual(4, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(4, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
Assert.AreEqual(3, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnSpawn));
Assert.AreEqual(3, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnSpawn, serverObject.GetComponent<NetworkObject>().NetworkObjectId));
Assert.AreEqual(1, manager.DeferredMessageCountForType(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab));
Assert.AreEqual(1, manager.DeferredMessageCountForKey(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, serverObject.GetComponent<NetworkObject>().GlobalObjectIdHash));
AddPrefabsToClient(client);

View File

@@ -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.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_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)

View File

@@ -0,0 +1,154 @@
using System.Collections;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
using UnityEngine.TestTools;
namespace Unity.Netcode.RuntimeTests
{
[TestFixture(HostOrServer.DAHost, true)]
[TestFixture(HostOrServer.DAHost, false)]
public class ExtendedNetworkShowAndHideTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 3;
private bool m_EnableSceneManagement;
private GameObject m_ObjectToSpawn;
private NetworkObject m_SpawnedObject;
private NetworkManager m_ClientToHideFrom;
private NetworkManager m_LateJoinClient;
private NetworkManager m_SpawnOwner;
public ExtendedNetworkShowAndHideTests(HostOrServer hostOrServer, bool enableSceneManagement) : base(hostOrServer)
{
m_EnableSceneManagement = enableSceneManagement;
}
protected override void OnServerAndClientsCreated()
{
if (!UseCMBService())
{
m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
}
foreach (var client in m_ClientNetworkManagers)
{
client.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
}
m_ObjectToSpawn = CreateNetworkObjectPrefab("TestObject");
m_ObjectToSpawn.SetActive(false);
base.OnServerAndClientsCreated();
}
private bool AllClientsSpawnedObject()
{
if (!UseCMBService())
{
if (!s_GlobalNetworkObjects.ContainsKey(m_ServerNetworkManager.LocalClientId))
{
return false;
}
if (!s_GlobalNetworkObjects[m_ServerNetworkManager.LocalClientId].ContainsKey(m_SpawnedObject.NetworkObjectId))
{
return false;
}
}
foreach (var client in m_ClientNetworkManagers)
{
if (!s_GlobalNetworkObjects.ContainsKey(client.LocalClientId))
{
return false;
}
if (!s_GlobalNetworkObjects[client.LocalClientId].ContainsKey(m_SpawnedObject.NetworkObjectId))
{
return false;
}
}
return true;
}
private bool IsClientPromotedToSessionOwner()
{
if (!UseCMBService())
{
if (m_ServerNetworkManager.CurrentSessionOwner != m_ClientToHideFrom.LocalClientId)
{
return false;
}
}
foreach (var client in m_ClientNetworkManagers)
{
if (!client.IsConnectedClient)
{
continue;
}
if (client.CurrentSessionOwner != m_ClientToHideFrom.LocalClientId)
{
return false;
}
}
return true;
}
protected override void OnNewClientCreated(NetworkManager networkManager)
{
m_LateJoinClient = networkManager;
networkManager.NetworkConfig.EnableSceneManagement = m_EnableSceneManagement;
networkManager.NetworkConfig.Prefabs = m_SpawnOwner.NetworkConfig.Prefabs;
base.OnNewClientCreated(networkManager);
}
/// <summary>
/// This test validates the following NetworkShow - NetworkHide issue:
/// - During a session, a spawned object is hidden from a client.
/// - The current session owner disconnects and the client the object is hidden from is prommoted to the session owner.
/// - A new client joins and the newly promoted session owner synchronizes the newly joined client with only objects visible to it.
/// - Any already connected non-session owner client should "NetworkShow" the object to the newly connected client
/// (but only if the hidden object has SpawnWithObservers enabled)
/// </summary>
[UnityTest]
public IEnumerator HiddenObjectPromotedSessionOwnerNewClientSynchronizes()
{
// Get the test relative session owner
var sessionOwner = UseCMBService() ? m_ClientNetworkManagers[0] : m_ServerNetworkManager;
m_SpawnOwner = UseCMBService() ? m_ClientNetworkManagers[1] : m_ClientNetworkManagers[0];
m_ClientToHideFrom = UseCMBService() ? m_ClientNetworkManagers[NumberOfClients - 1] : m_ClientNetworkManagers[1];
m_ObjectToSpawn.SetActive(true);
// Spawn the object with a non-session owner client
m_SpawnedObject = SpawnObject(m_ObjectToSpawn, m_SpawnOwner).GetComponent<NetworkObject>();
yield return WaitForConditionOrTimeOut(AllClientsSpawnedObject);
AssertOnTimeout($"Not all clients spawned and instance of {m_SpawnedObject.name}");
// Hide the spawned object from the to be promoted session owner
m_SpawnedObject.NetworkHide(m_ClientToHideFrom.LocalClientId);
yield return WaitForConditionOrTimeOut(() => !m_ClientToHideFrom.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId));
AssertOnTimeout($"{m_SpawnedObject.name} was not hidden from Client-{m_ClientToHideFrom.LocalClientId}!");
// Promoted a new session owner (DAHost promotes while CMB Session we disconnect the current session owner)
if (!UseCMBService())
{
m_ServerNetworkManager.PromoteSessionOwner(m_ClientToHideFrom.LocalClientId);
}
else
{
sessionOwner.Shutdown();
}
// Wait for the new session owner to be promoted and for all clients to acknowledge the promotion
yield return WaitForConditionOrTimeOut(IsClientPromotedToSessionOwner);
AssertOnTimeout($"Client-{m_ClientToHideFrom.LocalClientId} was not promoted as session owner on all client instances!");
// Connect a new client instance
yield return CreateAndStartNewClient();
// Assure the newly connected client is synchronized with the NetworkObject hidden from the newly promoted session owner
yield return WaitForConditionOrTimeOut(() => m_LateJoinClient.SpawnManager.SpawnedObjects.ContainsKey(m_SpawnedObject.NetworkObjectId));
AssertOnTimeout($"Client-{m_LateJoinClient.LocalClientId} never spawned {nameof(NetworkObject)} {m_SpawnedObject.name}!");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a6389d04d9080b24b99de7e6900a064c

View File

@@ -59,13 +59,13 @@ namespace Unity.Netcode.RuntimeTests
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;
}
public void ListChanged(NetworkListEvent<int> listEvent)
{
Debug.Log($"ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
Debug.Assert(ExpectedSize == MyNetworkList.Count);
VerboseDebug($"[Client-{NetworkManager.LocalClientId}][{name}][MyNetworkList] ListEvent received: type {listEvent.Type}, index {listEvent.Index}, value {listEvent.Value}");
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];
m_NetSpawnedObject = SpawnObject(m_TestNetworkPrefab, m_ClientNetworkManagers[1]).GetComponent<NetworkObject>();
yield return RefreshGameObects(4);
yield return RefreshGameObects(NumberOfClients);
// === Check spawn occurred
yield return WaitForSpawnCount(NumberOfClients + 1);
AssertOnTimeout($"Timed out waiting for all clients to spawn {m_NetSpawnedObject.name}");
Debug.Assert(HiddenVariableObject.SpawnCount == NumberOfClients + 1);
VerboseDebug("Objects spawned");
@@ -205,7 +208,6 @@ namespace Unity.Netcode.RuntimeTests
// ==== Hide our object to a different client
HiddenVariableObject.ExpectedSize = 2;
m_NetSpawnedObject.NetworkHide(otherClient.ClientId);
currentValueSet = 3;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkList.Add(currentValueSet);
@@ -222,7 +224,7 @@ namespace Unity.Netcode.RuntimeTests
VerboseDebug("Object spawned");
// ==== We need a refresh for the newly re-spawned object
yield return RefreshGameObects(4);
yield return RefreshGameObects(NumberOfClients);
currentValueSet = 4;
m_NetSpawnedObject.GetComponent<HiddenVariableObject>().MyNetworkVariable.Value = currentValueSet;

View File

@@ -239,5 +239,45 @@ namespace Unity.Netcode.RuntimeTests
});
}
}
[Test]
public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendNamedMessageWithTooBigBuffer()
{
// First try a valid send with the maximum allowed size (this is atm 1264)
var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(ulong)/*MessageName hash*/ - sizeof(NetworkBatchHeader);
var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize;
var messageName = Guid.NewGuid().ToString();
var messageContent = new byte[msgSize];
var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
using (writer)
{
writer.TryBeginWrite(msgSize);
writer.WriteBytes(messageContent, msgSize, 0);
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List<ulong> { FirstClient.LocalClientId }, writer);
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer);
}
msgSize++;
messageContent = new byte[msgSize];
writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
using (writer)
{
writer.TryBeginWrite(msgSize);
writer.WriteBytes(messageContent, msgSize, 0);
var message = Assert.Throws<OverflowException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, new List<ulong> { FirstClient.LocalClientId }, writer);
}).Message;
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
message = Assert.Throws<OverflowException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendNamedMessage(messageName, FirstClient.LocalClientId, writer);
}).Message;
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
}
}
}
}

View File

@@ -194,5 +194,44 @@ namespace Unity.Netcode.RuntimeTests
});
}
}
[Test]
public unsafe void ErrorMessageIsPrintedWhenAttemptingToSendUnnamedMessageWithTooBigBuffer()
{
// First try a valid send with the maximum allowed size (this is atm 1272)
var msgSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize - FastBufferWriter.GetWriteSize<NetworkMessageHeader>() - sizeof(NetworkBatchHeader);
var bufferSize = m_ServerNetworkManager.MessageManager.NonFragmentedMessageMaxSize;
var messageContent = new byte[msgSize];
var writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
using (writer)
{
writer.TryBeginWrite(msgSize);
writer.WriteBytes(messageContent, msgSize, 0);
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List<ulong> { FirstClient.LocalClientId }, writer);
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
}
msgSize++;
messageContent = new byte[msgSize];
writer = new FastBufferWriter(bufferSize, Allocator.Temp, bufferSize * 2);
using (writer)
{
writer.TryBeginWrite(msgSize);
writer.WriteBytes(messageContent, msgSize, 0);
var message = Assert.Throws<OverflowException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(new List<ulong> { FirstClient.LocalClientId }, writer);
}).Message;
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
message = Assert.Throws<OverflowException>(
() =>
{
m_ServerNetworkManager.CustomMessagingManager.SendUnnamedMessage(FirstClient.LocalClientId, writer);
}).Message;
Assert.IsTrue(message.Contains($"Given message size ({msgSize} bytes) is greater than the maximum"), $"Unexpected exception: {message}");
}
}
}
}

View File

@@ -13,6 +13,60 @@ namespace Unity.Netcode.RuntimeTests
private NetworkManager m_ClientManager;
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]
public IEnumerator OnServerStoppedCalledWhenServerStops()
{

View File

@@ -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
var previousClientComponent = (NetworkObjectOwnershipComponent)null;
var networkManagersDAMode = new List<NetworkManager>();
for (int clientIndex = 0; clientIndex < NumberOfClients; 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)
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);
}
else
@@ -330,7 +347,18 @@ namespace Unity.Netcode.RuntimeTests
}
}
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.That(serverComponent.OnGainedOwnershipFired);
@@ -351,6 +379,22 @@ namespace Unity.Netcode.RuntimeTests
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 bool AllClientsHaveCorrectObjectCount()

View File

@@ -12,8 +12,6 @@ namespace Unity.Netcode.RuntimeTests
internal class NetworkObjectSpawnManyObjectsTests : NetcodeIntegrationTest
{
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 NetworkPrefab m_PrefabToSpawn;
@@ -52,19 +50,23 @@ namespace Unity.Netcode.RuntimeTests
}
[UnityTest]
// When this test fails it does so without an exception and will wait the default ~6 minutes
[Timeout(10000)]
public IEnumerator WhenManyObjectsAreSpawnedAtOnce_AllAreReceived()
{
var timeStarted = Time.realtimeSinceStartup;
for (int x = 0; x < k_SpawnedObjects; x++)
{
NetworkObject serverObject = Object.Instantiate(m_PrefabToSpawn.Prefab).GetComponent<NetworkObject>();
serverObject.NetworkManagerOwner = m_ServerNetworkManager;
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
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects);
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects!");
yield return WaitForConditionOrTimeOut(() => SpawnObjecTrackingComponent.SpawnedObjects == k_SpawnedObjects, timeoutHelper);
AssertOnTimeout($"Timed out waiting for the client to spawn {k_SpawnedObjects} objects! Time to spawn: {timeSpawned} | Time to timeout: {timeStarted - Time.realtimeSinceStartup}", timeoutHelper);
}
}
}

View File

@@ -200,9 +200,8 @@ namespace Unity.Netcode.RuntimeTests
/// </summary>
/// <param name="testWithHost">Determines if we are running as a server or host</param>
/// <param name="authority">Determines if we are using server or owner authority</param>
public NetworkTransformBase(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision)
public NetworkTransformBase(HostOrServer testWithHost, Authority authority, RotationCompression rotationCompression, Rotation rotation, Precision precision) : base(testWithHost)
{
m_UseHost = testWithHost == HostOrServer.Host;
m_Authority = authority;
m_Precision = precision;
m_RotationCompression = rotationCompression;
@@ -376,6 +375,18 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
protected bool AllFirstLevelChildObjectInstancesHaveChild()
{
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
{
if (instance.transform.parent == null)
{
return false;
}
}
return true;
}
protected bool AllChildObjectInstancesHaveChild()
{
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
@@ -398,6 +409,33 @@ namespace Unity.Netcode.RuntimeTests
return true;
}
protected bool AllFirstLevelChildObjectInstancesHaveNoParent()
{
foreach (var instance in ChildObjectComponent.ClientInstances.Values)
{
if (instance.transform.parent != null)
{
return false;
}
}
return true;
}
protected bool AllSubChildObjectInstancesHaveNoParent()
{
if (ChildObjectComponent.HasSubChild)
{
foreach (var instance in ChildObjectComponent.ClientSubChildInstances.Values)
{
if (instance.transform.parent != null)
{
return false;
}
}
}
return true;
}
/// <summary>
/// A wait condition specific method that assures the local space coordinates
/// are not impacted by NetworkTransform when parented.

View File

@@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.Components;
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

View File

@@ -101,6 +101,225 @@ namespace Unity.Netcode.RuntimeTests
}
#if !MULTIPLAYER_TOOLS
private void UpdateTransformLocal(Components.NetworkTransform networkTransformTestComponent)
{
networkTransformTestComponent.transform.localPosition += GetRandomVector3(0.5f, 2.0f);
var rotation = networkTransformTestComponent.transform.localRotation;
var eulerRotation = rotation.eulerAngles;
eulerRotation += GetRandomVector3(0.5f, 5.0f);
rotation.eulerAngles = eulerRotation;
networkTransformTestComponent.transform.localRotation = rotation;
}
private void UpdateTransformWorld(Components.NetworkTransform networkTransformTestComponent)
{
networkTransformTestComponent.transform.position += GetRandomVector3(0.5f, 2.0f);
var rotation = networkTransformTestComponent.transform.rotation;
var eulerRotation = rotation.eulerAngles;
eulerRotation += GetRandomVector3(0.5f, 5.0f);
rotation.eulerAngles = eulerRotation;
networkTransformTestComponent.transform.rotation = rotation;
}
/// <summary>
/// This test validates the SwitchTransformSpaceWhenParented setting under all network topologies
/// </summary>
[Test]
public void SwitchTransformSpaceWhenParentedTest([Values(0.5f, 1.0f, 5.0f)] float scale)
{
m_UseParentingThreshold = true;
// Get the NetworkManager that will have authority in order to spawn with the correct authority
var isServerAuthority = m_Authority == Authority.ServerAuthority;
var authorityNetworkManager = m_ServerNetworkManager;
if (!isServerAuthority)
{
authorityNetworkManager = m_ClientNetworkManagers[0];
}
var childAuthorityNetworkManager = m_ClientNetworkManagers[0];
if (!isServerAuthority)
{
childAuthorityNetworkManager = m_ServerNetworkManager;
}
// Spawn a parent and children
ChildObjectComponent.HasSubChild = true;
// Modify our prefabs for this specific test
m_ParentObject.GetComponent<NetworkTransformTestComponent>().TickSyncChildren = true;
m_ChildObject.GetComponent<ChildObjectComponent>().SwitchTransformSpaceWhenParented = true;
m_ChildObject.GetComponent<ChildObjectComponent>().TickSyncChildren = true;
m_SubChildObject.GetComponent<ChildObjectComponent>().SwitchTransformSpaceWhenParented = true;
m_SubChildObject.GetComponent<ChildObjectComponent>().TickSyncChildren = true;
m_ChildObject.AllowOwnerToParent = true;
m_SubChildObject.AllowOwnerToParent = true;
var authoritySideParent = SpawnObject(m_ParentObject.gameObject, authorityNetworkManager).GetComponent<NetworkObject>();
var authoritySideChild = SpawnObject(m_ChildObject.gameObject, childAuthorityNetworkManager).GetComponent<NetworkObject>();
var authoritySideSubChild = SpawnObject(m_SubChildObject.gameObject, childAuthorityNetworkManager).GetComponent<NetworkObject>();
// Assure all of the child object instances are spawned before proceeding to parenting
var success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned);
Assert.True(success, "Timed out waiting for all child instances to be spawned!");
// Get the owner instance if in client-server mode with owner authority
if (m_Authority == Authority.OwnerAuthority && !m_DistributedAuthority)
{
authoritySideParent = s_GlobalNetworkObjects[authoritySideParent.OwnerClientId][authoritySideParent.NetworkObjectId];
authoritySideChild = s_GlobalNetworkObjects[authoritySideChild.OwnerClientId][authoritySideChild.NetworkObjectId];
authoritySideSubChild = s_GlobalNetworkObjects[authoritySideSubChild.OwnerClientId][authoritySideSubChild.NetworkObjectId];
}
// Get the authority parent and child instances
m_AuthorityParentObject = NetworkTransformTestComponent.AuthorityInstance.NetworkObject;
m_AuthorityChildObject = ChildObjectComponent.AuthorityInstance.NetworkObject;
m_AuthoritySubChildObject = ChildObjectComponent.AuthoritySubInstance.NetworkObject;
// The child NetworkTransform will use world space when world position stays and
// local space when world position does not stay when parenting.
ChildObjectComponent.AuthorityInstance.UseHalfFloatPrecision = m_Precision == Precision.Half;
ChildObjectComponent.AuthorityInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion;
ChildObjectComponent.AuthorityInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress;
ChildObjectComponent.AuthoritySubInstance.UseHalfFloatPrecision = m_Precision == Precision.Half;
ChildObjectComponent.AuthoritySubInstance.UseQuaternionSynchronization = m_Rotation == Rotation.Quaternion;
ChildObjectComponent.AuthoritySubInstance.UseQuaternionCompression = m_RotationCompression == RotationCompression.QuaternionCompress;
// Set whether we are interpolating or not
m_AuthorityParentNetworkTransform = m_AuthorityParentObject.GetComponent<NetworkTransformTestComponent>();
m_AuthorityParentNetworkTransform.Interpolate = true;
m_AuthorityChildNetworkTransform = m_AuthorityChildObject.GetComponent<ChildObjectComponent>();
m_AuthorityChildNetworkTransform.Interpolate = true;
m_AuthoritySubChildNetworkTransform = m_AuthoritySubChildObject.GetComponent<ChildObjectComponent>();
m_AuthoritySubChildNetworkTransform.Interpolate = true;
// Apply a scale to the parent object to make sure the scale on the child is properly updated on
// non-authority instances.
var halfScale = scale * 0.5f;
m_AuthorityParentObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale);
m_AuthorityChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale);
m_AuthoritySubChildObject.transform.localScale = GetRandomVector3(scale - halfScale, scale + halfScale);
// Allow one tick for authority to update these changes
TimeTravelAdvanceTick();
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
Assert.True(success, "All transform values did not match prior to parenting!");
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches);
Assert.True(success, "All transform values did not match prior to parenting!");
// Move things around while parenting and removing the parent
// Not the absolute "perfect" test, but it validates the clients all synchronize
// parenting and transform values.
for (int i = 0; i < 30; i++)
{
// Provide two network ticks for interpolation to finalize
TimeTravelAdvanceTick();
TimeTravelAdvanceTick();
// This validates each child instance has preserved their local space values
AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Connected_Clients);
// This validates each sub-child instance has preserved their local space values
AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Connected_Clients);
// Parent while in motion
if (i == 5)
{
// Parent the child under the parent with the current world position stays setting
Assert.True(authoritySideChild.TrySetParent(authoritySideParent.transform), $"[Child][Client-{authoritySideChild.NetworkManagerOwner.LocalClientId}] Failed to set child's parent!");
// This waits for all child instances to be parented
success = WaitForConditionOrTimeOutWithTimeTravel(AllFirstLevelChildObjectInstancesHaveChild, 300);
Assert.True(success, "Timed out waiting for all instances to have parented a child!");
}
if (i == 10)
{
// Parent the sub-child under the child with the current world position stays setting
Assert.True(authoritySideSubChild.TrySetParent(authoritySideChild.transform), $"[Sub-Child][Client-{authoritySideSubChild.NetworkManagerOwner.LocalClientId}] Failed to set sub-child's parent!");
// This waits for all child instances to be parented
success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild, 300);
Assert.True(success, "Timed out waiting for all instances to have parented a child!");
}
if (i == 15)
{
// Verify that a late joining client will synchronize to the parented NetworkObjects properly
CreateAndStartNewClientWithTimeTravel();
// Assure all of the child object instances are spawned (basically for the newly connected client)
success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesAreSpawned, 300);
Assert.True(success, "Timed out waiting for all child instances to be spawned!");
// This waits for all child instances to be parented
success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild, 300);
Assert.True(success, "Timed out waiting for all instances to have parented a child!");
// This validates each child instance has preserved their local space values
AllChildrenLocalTransformValuesMatch(false, ChildrenTransformCheckType.Late_Join_Client);
// This validates each sub-child instance has preserved their local space values
AllChildrenLocalTransformValuesMatch(true, ChildrenTransformCheckType.Late_Join_Client);
}
if (i == 20)
{
// Remove the parent
Assert.True(authoritySideSubChild.TryRemoveParent(), $"[Sub-Child][Client-{authoritySideSubChild.NetworkManagerOwner.LocalClientId}] Failed to set sub-child's parent!");
// This waits for all child instances to have the parent removed
success = WaitForConditionOrTimeOutWithTimeTravel(AllSubChildObjectInstancesHaveNoParent, 300);
Assert.True(success, "Timed out waiting for all instances remove the parent!");
}
if (i == 25)
{
// Parent the child under the parent with the current world position stays setting
Assert.True(authoritySideChild.TryRemoveParent(), $"[Child][Client-{authoritySideChild.NetworkManagerOwner.LocalClientId}] Failed to remove parent!");
// This waits for all child instances to be parented
success = WaitForConditionOrTimeOutWithTimeTravel(AllFirstLevelChildObjectInstancesHaveNoParent, 300);
Assert.True(success, "Timed out waiting for all instances remove the parent!");
}
UpdateTransformWorld(m_AuthorityParentNetworkTransform);
if (m_AuthorityChildNetworkTransform.InLocalSpace)
{
UpdateTransformLocal(m_AuthorityChildNetworkTransform);
}
else
{
UpdateTransformWorld(m_AuthorityChildNetworkTransform);
}
if (m_AuthoritySubChildNetworkTransform.InLocalSpace)
{
UpdateTransformLocal(m_AuthoritySubChildNetworkTransform);
}
else
{
UpdateTransformWorld(m_AuthoritySubChildNetworkTransform);
}
}
success = WaitForConditionOrTimeOutWithTimeTravel(PositionRotationScaleMatches, 300);
Assert.True(success, "All transform values did not match prior to parenting!");
// Revert the modifications made for this specific test
m_ParentObject.GetComponent<NetworkTransformTestComponent>().TickSyncChildren = false;
m_ChildObject.GetComponent<ChildObjectComponent>().SwitchTransformSpaceWhenParented = false;
m_ChildObject.GetComponent<ChildObjectComponent>().TickSyncChildren = false;
m_ChildObject.AllowOwnerToParent = false;
m_SubChildObject.AllowOwnerToParent = false;
m_SubChildObject.GetComponent<ChildObjectComponent>().SwitchTransformSpaceWhenParented = false;
m_SubChildObject.GetComponent<ChildObjectComponent>().TickSyncChildren = false;
}
/// <summary>
/// Validates that transform values remain the same when a NetworkTransform is
/// parented under another NetworkTransform under all of the possible axial conditions
@@ -410,6 +629,7 @@ namespace Unity.Netcode.RuntimeTests
Assert.AreEqual(Vector3.zero, m_NonAuthoritativeTransform.transform.position, "server side pos should be zero at first"); // sanity check
TimeTravelAdvanceTick();
TimeTravelToNextTick();
m_AuthoritativeTransform.StatePushed = false;
var nextPosition = GetRandomVector3(2f, 30f);

View File

@@ -299,16 +299,15 @@ namespace Unity.Netcode.RuntimeTests
}, new List<NetworkManager> { m_ServerNetworkManager });
WaitForMessageReceivedWithTimeTravel<NetworkTransformMessage>(m_ClientNetworkManagers.ToList());
var percentChanged = 1f / 60f;
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale);
@@ -316,11 +315,11 @@ namespace Unity.Netcode.RuntimeTests
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale);
@@ -333,11 +332,11 @@ namespace Unity.Netcode.RuntimeTests
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipePosition, serverSetPosition, percentChanged), testComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(anticipeScale, serverSetScale, percentChanged), testComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(anticipeRotation, serverSetRotation, percentChanged), testComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, testComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, testComponent.AuthoritativeState.Scale);
@@ -345,11 +344,11 @@ namespace Unity.Netcode.RuntimeTests
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.transform.position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.transform.localScale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.transform.rotation);
AssertVectorsAreEquivalent(Vector3.Lerp(startPosition, serverSetPosition, percentChanged), otherClientComponent.AnticipatedState.Position);
AssertVectorsAreEquivalent(Vector3.Lerp(startScale, serverSetScale, percentChanged), otherClientComponent.AnticipatedState.Scale);
AssertQuaternionsAreEquivalent(Quaternion.Slerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertQuaternionsAreEquivalent(Quaternion.Lerp(startRotation, serverSetRotation, percentChanged), otherClientComponent.AnticipatedState.Rotation);
AssertVectorsAreEquivalent(serverSetPosition, otherClientComponent.AuthoritativeState.Position);
AssertVectorsAreEquivalent(serverSetScale, otherClientComponent.AuthoritativeState.Scale);

View File

@@ -14,9 +14,11 @@ namespace Unity.Netcode.RuntimeTests
internal class NetworkVisibilityTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
protected override int NumberOfClients => 2;
private GameObject m_TestNetworkPrefab;
private bool m_SceneManagementEnabled;
private GameObject m_SpawnedObject;
private NetworkManager m_SessionOwner;
public NetworkVisibilityTests(SceneManagementState sceneManagementState, NetworkTopologyTypes networkTopologyType) : base(networkTopologyType)
{
@@ -27,7 +29,11 @@ namespace Unity.Netcode.RuntimeTests
{
m_TestNetworkPrefab = CreateNetworkObjectPrefab("Object");
m_TestNetworkPrefab.AddComponent<NetworkVisibilityComponent>();
if (!UseCMBService())
{
m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled;
}
foreach (var clientNetworkManager in m_ClientNetworkManagers)
{
clientNetworkManager.NetworkConfig.EnableSceneManagement = m_SceneManagementEnabled;
@@ -38,7 +44,8 @@ namespace Unity.Netcode.RuntimeTests
protected override IEnumerator OnServerAndClientsConnected()
{
SpawnObject(m_TestNetworkPrefab, m_ServerNetworkManager);
m_SessionOwner = UseCMBService() ? m_ClientNetworkManagers[0] : m_ServerNetworkManager;
m_SpawnedObject = SpawnObject(m_TestNetworkPrefab, m_SessionOwner);
yield return base.OnServerAndClientsConnected();
}
@@ -46,13 +53,49 @@ namespace Unity.Netcode.RuntimeTests
[UnityTest]
public IEnumerator HiddenObjectsTest()
{
var expectedCount = UseCMBService() ? 2 : 3;
#if UNITY_2023_1_OR_NEWER
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == 2);
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == expectedCount);
#else
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == 2);
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == expectedCount);
#endif
Assert.IsFalse(s_GlobalTimeoutHelper.TimedOut, "Timed out waiting for the visible object count to equal 2!");
}
[UnityTest]
public IEnumerator HideShowAndDeleteTest()
{
var expectedCount = UseCMBService() ? 2 : 3;
#if UNITY_2023_1_OR_NEWER
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == expectedCount);
#else
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == expectedCount);
#endif
AssertOnTimeout("Timed out waiting for the visible object count to equal 2!");
var sessionOwnerNetworkObject = m_SpawnedObject.GetComponent<NetworkObject>();
var clientIndex = UseCMBService() ? 1 : 0;
sessionOwnerNetworkObject.NetworkHide(m_ClientNetworkManagers[clientIndex].LocalClientId);
#if UNITY_2023_1_OR_NEWER
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsByType<NetworkVisibilityComponent>(FindObjectsSortMode.None).Where((c) => c.IsSpawned).Count() == expectedCount - 1);
#else
yield return WaitForConditionOrTimeOut(() => Object.FindObjectsOfType<NetworkVisibilityComponent>().Where((c) => c.IsSpawned).Count() == expectedCount - 1);
#endif
AssertOnTimeout($"Timed out waiting for {m_SpawnedObject.name} to be hidden from client!");
var networkObjectId = sessionOwnerNetworkObject.NetworkObjectId;
sessionOwnerNetworkObject.NetworkShow(m_ClientNetworkManagers[clientIndex].LocalClientId);
sessionOwnerNetworkObject.Despawn(true);
// Expect no exceptions
yield return s_DefaultWaitForTick;
// Now force a scenario where it normally would have caused an exception
m_SessionOwner.SpawnManager.ObjectsToShowToClient.Add(m_ClientNetworkManagers[clientIndex].LocalClientId, new System.Collections.Generic.List<NetworkObject>());
m_SessionOwner.SpawnManager.ObjectsToShowToClient[m_ClientNetworkManagers[clientIndex].LocalClientId].Add(null);
// Expect no exceptions
yield return s_DefaultWaitForTick;
}
}
}

View File

@@ -1,5 +1,7 @@
#if COM_UNITY_MODULES_PHYSICS
using System.Collections;
using System.Collections.Generic;
using System.Text;
using NUnit.Framework;
using Unity.Netcode.Components;
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!");
}
}
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

View File

@@ -11,7 +11,7 @@ namespace Unity.Netcode.RuntimeTests
[TestFixture(HostOrServer.Server)]
internal class PlayerObjectTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
protected override int NumberOfClients => 2;
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!");
}
}
/// <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]);
}
}
}
}
}

View File

@@ -63,14 +63,10 @@ namespace Unity.Netcode.RuntimeTests
private const int k_MaxThresholdFailures = 4;
private int m_ExceededThresholdCount;
private void Update()
public override void OnUpdate()
{
base.OnUpdate();
if (!IsSpawned || TestComplete)
{
return;
}
// Check the position of the nested object on the client
if (CheckPosition)
@@ -92,6 +88,17 @@ namespace Unity.Netcode.RuntimeTests
m_ExceededThresholdCount = 0;
}
}
}
private void Update()
{
base.OnUpdate();
if (!IsSpawned || !CanCommitToTransform || TestComplete)
{
return;
}
// Move the nested object on the server
if (IsMoving)
@@ -136,7 +143,6 @@ namespace Unity.Netcode.RuntimeTests
Assert.True(CanCommitToTransform, $"Using non-authority instance to update transform!");
transform.position = new Vector3(1000.0f, 1000.0f, 1000.0f);
}
}
}

View File

@@ -2,23 +2,23 @@
"name": "com.unity.netcode.gameobjects",
"displayName": "Netcode for GameObjects",
"description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.",
"version": "2.0.0-pre.4",
"version": "2.1.1",
"unity": "6000.0",
"dependencies": {
"com.unity.nuget.mono-cecil": "1.11.4",
"com.unity.transport": "2.3.0"
},
"_upm": {
"changelog": "### Added\n\n- Added `NetworkVariable.CheckDirtyState` that is to be used in tandem with collections in order to detect whether the collection or an item within the collection has changed. (#3004)\n\n### Fixed\n\n- Fixed issue where nested `NetworkTransform` components were not getting updated. (#3016)\n- Fixed issue by adding null checks in `NetworkVariableBase.CanClientRead` and `NetworkVariableBase.CanClientWrite` methods to ensure safe access to `NetworkBehaviour`. (#3012)\n- Fixed issue where `FixedStringSerializer<T>` was using `NetworkVariableSerialization<byte>.AreEqual` to determine if two bytes were equal causes an exception to be thrown due to no byte serializer having been defined. (#3009)\n- Fixed Issue where a state with dual triggers, inbound and outbound, could cause a false layer to layer state transition message to be sent to non-authority `NetworkAnimator` instances and cause a warning message to be logged. (#3008)\n- Fixed issue using collections within `NetworkVariable` where the collection would not detect changes to items or nested items. (#3004)\n- Fixed issue where `List`, `Dictionary`, and `HashSet` collections would not uniquely duplicate nested collections. (#3004)\n- Fixed issue where `NotAuthorityTarget` would include the service observer in the list of targets to send the RPC to as opposed to excluding the service observer as it should. (#3000)\n- Fixed issue where `ProxyRpcTargetGroup` could attempt to send a message if there were no targets to send to. (#3000)\n\n### Changed\n\n- Changed `NetworkAnimator` to automatically switch to owner authoritative mode when using a distributed authority network topology. (#3021)\n- Changed permissions exception thrown in `NetworkList` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004)\n- Changed permissions exception thrown in `NetworkVariable.Value` to exiting early with a logged error that is now a unified permissions message within `NetworkVariableBase`. (#3004)"
"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 owners 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": {
"footprint": "48286e9f7b0e053fe7f7b524bafc69a99c2906fc"
"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": {
"url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git",
"type": "git",
"revision": "2802dfcd13c3be1ac356191cc87d1559203d2db3"
"revision": "264b30d176dd71fcedd022a8d6f4d59a2e3922bc"
},
"samples": [
{