diff --git a/com.unity.netcode.gameobjects/Editor/Icons.meta b/com.unity.netcode.gameobjects/Editor/Icons.meta
new file mode 100644
index 0000000000..47f083a506
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Editor/Icons.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 09ac49ca8d1df6347814a8a4760eb02c
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png b/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png
new file mode 100644
index 0000000000..c94cfb2e9b
Binary files /dev/null and b/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png differ
diff --git a/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png.meta b/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png.meta
new file mode 100644
index 0000000000..2a11d94057
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Editor/Icons/NOBridgeIcon.png.meta
@@ -0,0 +1,156 @@
+fileFormatVersion: 2
+guid: 04d779b185ebc8d488b5d25eeb21c611
+TextureImporter:
+ internalIDToNameTable: []
+ externalObjects: {}
+ serializedVersion: 13
+ mipmaps:
+ mipMapMode: 0
+ enableMipMap: 1
+ sRGBTexture: 1
+ linearTexture: 0
+ fadeOut: 0
+ borderMipMap: 0
+ mipMapsPreserveCoverage: 0
+ alphaTestReferenceValue: 0.5
+ mipMapFadeDistanceStart: 1
+ mipMapFadeDistanceEnd: 3
+ bumpmap:
+ convertToNormalMap: 0
+ externalNormalMap: 0
+ heightScale: 0.25
+ normalMapFilter: 0
+ flipGreenChannel: 0
+ isReadable: 0
+ streamingMipmaps: 0
+ streamingMipmapsPriority: 0
+ vTOnly: 0
+ ignoreMipmapLimit: 0
+ grayScaleToAlpha: 0
+ generateCubemap: 6
+ cubemapConvolution: 0
+ seamlessCubemap: 0
+ textureFormat: 1
+ maxTextureSize: 2048
+ textureSettings:
+ serializedVersion: 2
+ filterMode: 1
+ aniso: 1
+ mipBias: 0
+ wrapU: 0
+ wrapV: 0
+ wrapW: 0
+ nPOTScale: 1
+ lightmap: 0
+ compressionQuality: 50
+ spriteMode: 0
+ spriteExtrude: 1
+ spriteMeshType: 1
+ alignment: 0
+ spritePivot: {x: 0.5, y: 0.5}
+ spritePixelsToUnits: 100
+ spriteBorder: {x: 0, y: 0, z: 0, w: 0}
+ spriteGenerateFallbackPhysicsShape: 1
+ alphaUsage: 1
+ alphaIsTransparency: 1
+ spriteTessellationDetail: -1
+ textureType: 0
+ textureShape: 1
+ singleChannelComponent: 0
+ flipbookRows: 1
+ flipbookColumns: 1
+ maxTextureSizeSet: 0
+ compressionQualitySet: 0
+ textureFormatSet: 0
+ ignorePngGamma: 0
+ applyGammaDecoding: 0
+ swizzle: 50462976
+ cookieLightType: 0
+ platformSettings:
+ - serializedVersion: 4
+ buildTarget: DefaultTexturePlatform
+ maxTextureSize: 64
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Standalone
+ maxTextureSize: 8192
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: iOS
+ maxTextureSize: 8192
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: Android
+ maxTextureSize: 8192
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ - serializedVersion: 4
+ buildTarget: WindowsStoreApps
+ maxTextureSize: 8192
+ resizeAlgorithm: 0
+ textureFormat: -1
+ textureCompression: 1
+ compressionQuality: 50
+ crunchedCompression: 0
+ allowsAlphaSplitting: 0
+ overridden: 0
+ ignorePlatformSupport: 0
+ androidETC2FallbackOverride: 0
+ forceMaximumCompressionQuality_BC6H_BC7: 0
+ spriteSheet:
+ serializedVersion: 2
+ sprites: []
+ outline: []
+ customData:
+ physicsShape: []
+ bones: []
+ spriteID:
+ internalID: 0
+ vertices: []
+ indices:
+ edges: []
+ weights: []
+ secondaryTextures: []
+ spriteCustomMetadata:
+ entries: []
+ nameFileIdTable: {}
+ mipmapLimitGroupName:
+ pSDRemoveMatte: 0
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
index b0f0987eec..bd0aff1bea 100644
--- a/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
+++ b/com.unity.netcode.gameobjects/Editor/NetworkBehaviourEditor.cs
@@ -321,20 +321,6 @@ private void OnEnable()
CheckForNetworkObject((target as NetworkBehaviour).gameObject);
}
- ///
- /// Recursively finds the root parent of a
- ///
- /// The current we are inspecting for a parent
- /// the root parent for the first passed into the method
- public static Transform GetRootParentTransform(Transform transform)
- {
- if (transform.parent == null || transform.parent == transform)
- {
- return transform;
- }
- return GetRootParentTransform(transform.parent);
- }
-
///
/// Used to determine if a GameObject has one or more NetworkBehaviours but
/// does not already have a NetworkObject component. If not it will notify
@@ -358,7 +344,7 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje
}
// Now get the root parent transform to the current GameObject (or itself)
- var rootTransform = GetRootParentTransform(gameObject.transform);
+ var rootTransform = gameObject.transform.root;
if (!rootTransform.TryGetComponent(out var networkManager))
{
networkManager = rootTransform.GetComponentInChildren();
diff --git a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
index ce9d9744cb..924e519f27 100644
--- a/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
+++ b/com.unity.netcode.gameobjects/Editor/NetworkObjectEditor.cs
@@ -32,6 +32,9 @@ private void Initialize()
m_Initialized = true;
m_NetworkObject = (NetworkObject)target;
+#if UNIFIED_NETCODE
+ m_NetworkObject.UnifiedValidation();
+#endif
}
///
diff --git a/com.unity.netcode.gameobjects/Editor/Unity.Netcode.Editor.asmdef b/com.unity.netcode.gameobjects/Editor/Unity.Netcode.Editor.asmdef
index 16ac846643..e4d210bd4d 100644
--- a/com.unity.netcode.gameobjects/Editor/Unity.Netcode.Editor.asmdef
+++ b/com.unity.netcode.gameobjects/Editor/Unity.Netcode.Editor.asmdef
@@ -7,7 +7,8 @@
"Unity.Services.Relay",
"Unity.Networking.Transport",
"Unity.Services.Core",
- "Unity.Services.Authentication"
+ "Unity.Services.Authentication",
+ "Unity.NetCode"
],
"includePlatforms": [
"Editor"
@@ -43,6 +44,16 @@
"name": "com.unity.services.multiplayer",
"expression": "0.2.0",
"define": "MULTIPLAYER_SERVICES_SDK_INSTALLED"
+ },
+ {
+ "name": "com.unity.netcode",
+ "expression": "1.10.1",
+ "define": "UNIFIED_NETCODE"
+ },
+ {
+ "name": "com.unity.multiplayer.playmode",
+ "expression": "0.1.0",
+ "define": "UNITY_MULTIPLAYER_PLAYMODE"
}
],
"noEngineReferences": false
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs
new file mode 100644
index 0000000000..fc0b35f36d
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs
@@ -0,0 +1,54 @@
+#if UNIFIED_NETCODE
+using Unity.NetCode;
+using UnityEditor;
+
+namespace Unity.Netcode
+{
+ ///
+ /// TODO-UNIFIED: Needs further peer review and exploring alternate ways of handling this.
+ ///
+ ///
+ /// If used, we most likely would make this internal
+ ///
+ public partial class NetworkObjectBridge : GhostBehaviour
+ {
+
+#if UNITY_EDITOR
+ [UnityEngine.HideInInspector]
+ [UnityEngine.SerializeField]
+ private bool m_Sorted = false;
+ private void OnValidate()
+ {
+ // Sort only once when we have first been added.
+ if (!m_Sorted && !EditorApplication.isPlaying)
+ {
+ while (UnityEditorInternal.ComponentUtility.MoveComponentUp(this))
+ {
+ // Keep moving until it can't go higher
+ }
+ var ghostAdapter = gameObject.GetComponent();
+ // Now move the GhostAdapter to the top so it is above NetworkObjectBridge
+ while (ghostAdapter != null && UnityEditorInternal.ComponentUtility.MoveComponentUp(ghostAdapter))
+ {
+ // Keep moving until it can't go higher
+ }
+
+ m_Sorted = true;
+ }
+ }
+#endif
+
+
+ ///
+ /// This is used to link data to
+ /// N4E-spawned hybrid prefab instances.
+ ///
+ internal GhostField NetworkObjectId = new GhostField();
+ public void SetNetworkObjectId(ulong networkObjectId)
+ {
+ NetworkObjectId.PresetValue(networkObjectId);
+ NetworkObjectId.Value = networkObjectId;
+ }
+ }
+}
+#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs.meta
new file mode 100644
index 0000000000..0e4dd44b97
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/NetworkObjectBridge.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 510c5bb08d2f5724e85aa4fb66a8a4ff
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {fileID: 2800000, guid: 04d779b185ebc8d488b5d25eeb21c611, type: 3}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs
new file mode 100644
index 0000000000..4c9f9ad0a9
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs
@@ -0,0 +1,94 @@
+#if UNIFIED_NETCODE
+using System;
+using Unity.Entities;
+using Unity.NetCode;
+using UnityEngine;
+
+namespace Unity.Netcode
+{
+ ///
+ /// TODO-UNIFIED: Would need to be reviewed for alternate ways of handling this.
+ /// Creates the hosted world and provides a means to configuring
+ /// the 2nd port for unified netcode connection.
+ ///
+ internal class UnifiedBootstrap : ClientServerBootstrap
+ {
+ public static UnifiedBootstrap Instance { get; private set; }
+ public static Action OnInitialized;
+ public static ushort Port = 7979;
+ public static NetworkManager CurrentNetworkManagerForInitialization;
+
+ public static World LastCreatedWorld { get; private set; }
+
+ private static int s_WorldCounter = 0;
+
+ public override bool Initialize(string defaultWorldName)
+ {
+ var networkManager = CurrentNetworkManagerForInitialization;
+ if (networkManager == NetworkManager.Singleton)
+ {
+ Instance = this;
+ }
+
+ AutoConnectPort = Port;
+ if (base.Initialize(defaultWorldName))
+ {
+ Debug.LogError($"[{nameof(UnifiedBootstrap)}] Auto-bootstrap is enabled!!! This will break the POC!");
+ return true;
+ }
+
+ if (networkManager != null)
+ {
+ Debug.Log($"Starting a world for {(networkManager.IsServer ? "Host" : "Client")}");
+ s_WorldCounter++;
+ LastCreatedWorld = networkManager.IsServer ? CreateSingleWorldHost($"HostSingleWorld-{s_WorldCounter}")
+ : CreateClientWorld($"ClientWorld-{s_WorldCounter}");
+
+ if (LastCreatedWorld == null)
+ {
+ s_WorldCounter--;
+ Debug.LogError($"[{nameof(UnifiedBootstrap)}] World is null!");
+ return false;
+ }
+
+ if (!LastCreatedWorld.IsCreated)
+ {
+ s_WorldCounter--;
+ Debug.LogError($"[{nameof(UnifiedBootstrap)}] World was not created!");
+ return false;
+ }
+
+ //if (networkManager.LogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo($"[{nameof(UnifiedBootstrap)}] Created world: {LastCreatedWorld.Name} / {LastCreatedWorld.SequenceNumber}");
+ }
+
+ networkManager.NetcodeWorld = (NetcodeWorld)LastCreatedWorld;
+ if (networkManager.NetworkConfig.Prefabs.HasPendingGhostPrefabs)
+ {
+ if (networkManager.LogLevel <= LogLevel.Developer)
+ {
+ NetworkLog.LogInfo($"[{nameof(UnifiedBootstrap)}] Registering hybrid prefabs...");
+ }
+
+ networkManager.NetworkConfig.Prefabs.RegisterGhostPrefabs(networkManager);
+ }
+ }
+ else
+ {
+ LastCreatedWorld = CreateLocalWorld("LocalWorld");
+ }
+
+ OnInitialized?.Invoke();
+
+ return true;
+ }
+
+ ~UnifiedBootstrap()
+ {
+ LastCreatedWorld = null;
+ Instance = null;
+ }
+ }
+}
+#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs.meta
new file mode 100644
index 0000000000..a9a3d5ee88
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedBootstrap.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: 0671ce785ad022344a6721d1be9559ad
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs
new file mode 100644
index 0000000000..e9b66ba877
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs
@@ -0,0 +1,129 @@
+#if UNIFIED_NETCODE
+using System.Collections.Generic;
+using Unity.Collections;
+using Unity.Entities;
+using Unity.NetCode;
+using UnityEngine;
+
+namespace Unity.Netcode.Components
+{
+
+ public struct NetcodeConnection
+ {
+ internal World World;
+ internal Entity Entity;
+ public int NetworkId;
+
+ public bool IsServer => World.IsServer();
+ public void GoInGame()
+ {
+ World.EntityManager.AddComponentData(Entity, default(NetworkStreamInGame));
+ }
+ public void SendMessage(T message) where T : unmanaged, IRpcCommand
+ {
+ var req = World.EntityManager.CreateEntity();
+ World.EntityManager.AddComponentData(req, new SendRpcCommandRequest { TargetConnection = Entity });
+ World.EntityManager.AddComponentData(req, message);
+ }
+ }
+
+ internal partial class UnifiedUpdateConnections : SystemBase
+ {
+ private List m_TempConnections = new List();
+
+ private Dictionary m_NewConnections = new Dictionary();
+
+ protected override void OnUpdate()
+ {
+ var isServer = World.IsServer();
+ var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
+ foreach (var networkManager in Object.FindObjectsByType())
+ {
+ foreach (var (networkId, connectionState, entity) in SystemAPI.Query().WithNone().WithEntityAccess())
+ {
+ commandBuffer.RemoveComponent(entity);
+ m_TempConnections.Add(new NetcodeConnection
+ {
+ World = World,
+ Entity = entity,
+ NetworkId = networkId.Value
+ });
+ }
+
+ foreach (var con in m_TempConnections)
+ {
+ NetworkManager.OnNetCodeDisconnect?.Invoke(con);
+ }
+
+ m_TempConnections.Clear();
+
+ // TODO: We should figure out how to associate the N4E NetworkId with the NGO ClientId
+ foreach (var (networkId, entity) in SystemAPI.Query().WithAll()
+ .WithNone().WithEntityAccess())
+ {
+ if (!m_NewConnections.ContainsKey(networkId.Value))
+ {
+ var newConnection = new NetcodeConnection
+ { World = World, Entity = entity, NetworkId = networkId.Value };
+ m_NewConnections.Add(networkId.Value, newConnection);
+ }
+ }
+
+ // If we have any pending connections
+ if (m_NewConnections.Count > 0)
+ {
+ foreach (var entry in m_NewConnections)
+ {
+ // Server: always connect
+ // Client: wait until we have synchronized before announcing we are ready to receive snapshots
+ if (networkManager.IsServer || (!networkManager.IsServer && networkManager.IsConnectedClient))
+ {
+ // Set the connection in-game
+ commandBuffer.AddComponent(entry.Value.Entity);
+ commandBuffer.AddComponent(entry.Value.Entity, default(ConnectionState));
+ NetworkManager.OnNetCodeConnect?.Invoke(entry.Value);
+ m_TempConnections.Add(entry.Value);
+ }
+ }
+
+ // Remove any connections that have "gone in-game".
+ foreach (var connection in m_TempConnections)
+ {
+ m_NewConnections.Remove(connection.NetworkId);
+ }
+ }
+
+ m_TempConnections.Clear();
+
+ // If the local NetworkManager is shutting down or no longer connected, then
+ // make sure we have disconnected all known connections.
+ if (networkManager.ShutdownInProgress || !networkManager.IsListening)
+ {
+ foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess())
+ {
+ commandBuffer.RemoveComponent(entity);
+ NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection
+ { World = World, Entity = entity, NetworkId = networkId.Value });
+ }
+ }
+ }
+ commandBuffer.Playback(EntityManager);
+ }
+
+ ///
+ /// Always disconnect all known connections when being destroyed.
+ ///
+ protected override void OnDestroy()
+ {
+ var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
+ foreach (var (networkId, entity) in SystemAPI.Query().WithEntityAccess())
+ {
+ commandBuffer.RemoveComponent(entity);
+ NetworkManager.OnNetCodeDisconnect?.Invoke(new NetcodeConnection { World = World, Entity = entity, NetworkId = networkId.Value });
+ }
+ commandBuffer.Playback(EntityManager);
+ base.OnDestroy();
+ }
+ }
+}
+#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs.meta b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs.meta
new file mode 100644
index 0000000000..01feb655c4
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Components/Helpers/UnifiedUpdateConnections.cs.meta
@@ -0,0 +1,2 @@
+fileFormatVersion: 2
+guid: d5f2f5fd179c39f43b68ec502cdec9c4
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
index 0d0de9afa8..842c289a1a 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkRigidBodyBase.cs
@@ -194,6 +194,11 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network
#endif
#if COM_UNITY_MODULES_PHYSICS && COM_UNITY_MODULES_PHYSICS2D
+#if UNIFIED_NETCODE
+ // Used to keep track of the original kinematic state upon awake.
+ // (see OnDestroy below)
+ private bool m_OriginalKinematicState;
+#endif
///
/// Initializes the networked Rigidbody based on the
/// passed in as a parameter.
@@ -247,9 +252,31 @@ protected void Initialize(RigidbodyTypes rigidbodyType, NetworkTransform network
if (AutoUpdateKinematicState)
{
+#if UNIFIED_NETCODE
+ // Keep track of the original kinematic state. (see OnDestroy)
+ m_OriginalKinematicState = IsKinematic();
+#endif
SetIsKinematic(true);
}
}
+
+#if UNIFIED_NETCODE
+ public override void OnDestroy()
+ {
+ base.OnDestroy();
+ // If the user has left this component on their prefab and this is a hybrid prefab,
+ // then we want to set the rigid body back to its original kinematic settings since
+ // we are automatically destroying these components at runtime when it is a hybrid
+ // prefab that is spawned.
+ if (NetworkObject && NetworkObject.HasGhost)
+ {
+ if (m_InternalRigidbody || m_InternalRigidbody2D)
+ {
+ SetIsKinematic(m_OriginalKinematicState);
+ }
+ }
+ }
+#endif
#endif
internal Vector3 GetAdjustedPositionThreshold()
{
diff --git a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
index 3ca9093b34..5a9e7c08bc 100644
--- a/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs
@@ -1793,6 +1793,17 @@ internal void RegisterRigidbody(NetworkRigidbodyBase networkRigidbody)
m_UseRigidbodyForMotion = m_NetworkRigidbodyInternal.UseRigidBodyForMotion;
}
}
+
+#if UNIFIED_NETCODE
+ internal void UnregisterRigidbody()
+ {
+ if (m_NetworkRigidbodyInternal)
+ {
+ m_NetworkRigidbodyInternal = null;
+ m_UseRigidbodyForMotion = false;
+ }
+ }
+#endif
#endif
#if DEBUG_NETWORKTRANSFORM || UNITY_INCLUDE_TESTS
@@ -3617,6 +3628,8 @@ protected virtual void Awake()
}
CachedTransform = transform;
+
+
}
private NetworkObject m_CachedNetworkObject;
@@ -3634,6 +3647,12 @@ internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManag
///
public override void OnNetworkSpawn()
{
+#if UNIFIED_NETCODE
+ if (NetworkObject.HasGhost)
+ {
+ return;
+ }
+#endif
m_ParentedChildren.Clear();
Initialize();
@@ -3726,8 +3745,15 @@ private void ResetInterpolatedStateToCurrentAuthoritativeState()
/// The internal initialization method to allow for internal API adjustments
///
///
- private void InternalInitialization(bool isOwnershipChange = false)
+ internal virtual void InternalInitialization(bool isOwnershipChange = false)
{
+
+#if UNIFIED_NETCODE
+ if (NetworkObject.HasGhost)
+ {
+ return;
+ }
+#endif
if (!IsSpawned)
{
return;
diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
index c6f30d2835..25d75adf26 100644
--- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefab.cs
@@ -57,6 +57,14 @@ public class NetworkPrefab
///
public GameObject OverridingTargetPrefab;
+#if UNIFIED_NETCODE
+ ///
+ /// Used to determine if this prefab needs to be registered
+ /// via the unified API.
+ ///
+ internal bool HasGhost { get; private set; }
+#endif
+
///
/// Compares this NetworkPrefab with another to determine equality
///
@@ -166,6 +174,11 @@ public bool Validate(int index = -1)
return false;
}
+#if UNIFIED_NETCODE
+ // Mark this network prefab as having to be registered via the unified API
+ HasGhost = networkObject.HasGhost;
+#endif
+
return true;
}
@@ -183,7 +196,6 @@ public bool Validate(int index = -1)
return false;
}
-
break;
}
case NetworkPrefabOverride.Prefab:
diff --git a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs
index 45d93ad9d7..a0321a7ac4 100644
--- a/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Configuration/NetworkPrefabs.cs
@@ -47,6 +47,11 @@ public class NetworkPrefabs
[NonSerialized]
private List m_Prefabs = new List();
+#if UNIFIED_NETCODE
+ [NonSerialized]
+ internal Dictionary PrefabTable = new Dictionary();
+#endif
+
[NonSerialized]
private List m_RuntimeAddedPrefabs = new List();
@@ -57,12 +62,21 @@ private void AddTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
// Don't add this to m_RuntimeAddedPrefabs
// This prefab is now in the PrefabList, so if we shutdown and initialize again, we'll pick it up from there.
m_Prefabs.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (!PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
+ }
+#endif
}
}
private void RemoveTriggeredByNetworkPrefabList(NetworkPrefab networkPrefab)
{
m_Prefabs.Remove(networkPrefab);
+#if UNIFIED_NETCODE
+ PrefabTable.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
+#endif
}
///
@@ -95,6 +109,9 @@ public void Initialize(bool warnInvalid = true)
{
m_Prefabs.Clear();
NetworkPrefabsLists.RemoveAll(x => x == null);
+#if UNIFIED_NETCODE
+ PrefabTable.Clear();
+#endif
foreach (var list in NetworkPrefabsLists)
{
list.OnAdd += AddTriggeredByNetworkPrefabList;
@@ -127,10 +144,22 @@ public void Initialize(bool warnInvalid = true)
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (!PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
+ }
+#endif
}
else
{
removeList?.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
+ }
+#endif
}
}
@@ -139,10 +168,22 @@ public void Initialize(bool warnInvalid = true)
if (AddPrefabRegistration(networkPrefab))
{
m_Prefabs.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (!PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
+ }
+#endif
}
else
{
removeList?.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Remove(networkPrefab.SourcePrefabGlobalObjectIdHash);
+ }
+#endif
}
}
@@ -175,6 +216,12 @@ public bool Add(NetworkPrefab networkPrefab)
{
m_Prefabs.Add(networkPrefab);
m_RuntimeAddedPrefabs.Add(networkPrefab);
+#if UNIFIED_NETCODE
+ if (!PrefabTable.ContainsKey(networkPrefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Add(networkPrefab.SourcePrefabGlobalObjectIdHash, networkPrefab);
+ }
+#endif
return true;
}
@@ -202,6 +249,12 @@ public void Remove(NetworkPrefab prefab)
m_RuntimeAddedPrefabs.Remove(prefab);
OverrideToNetworkPrefab.Remove(prefab.TargetPrefabGlobalObjectIdHash);
NetworkPrefabOverrideLinks.Remove(prefab.SourcePrefabGlobalObjectIdHash);
+#if UNIFIED_NETCODE
+ if (PrefabTable.ContainsKey(prefab.SourcePrefabGlobalObjectIdHash))
+ {
+ PrefabTable.Remove(prefab.SourcePrefabGlobalObjectIdHash);
+ }
+#endif
}
///
@@ -277,6 +330,53 @@ public bool Contains(NetworkPrefab prefab)
return false;
}
+#if UNIFIED_NETCODE
+ ///
+ /// TODO: Either keep or remove prior to freeze.
+ /// Leaving this here in case we have to control when things get registered.
+ ///
+ internal bool HasPendingGhostPrefabs { get; private set; }
+ internal bool HasGhostPrefabs { get; private set; }
+ private List m_PendingGhostRegistration = new List();
+
+ ///
+ /// UNIFIED-POC
+ /// Hybrid NetworkObject-Ghost Prefab Registration
+ ///
+ ///
+ /// When is true, s
+ /// will mark themselves as having a ghost during .
+ /// After validation, if a network prefab's value is
+ /// set, then it is added to .
+ /// Within during the ,
+ /// if is true then will be invoked.
+ /// This will repeat until the hosted single world instance is created.
+ ///
+ ///
+ internal void RegisterGhostPrefabs(NetworkManager networkManager)
+ {
+ if (!HasPendingGhostPrefabs)
+ {
+ Debug.LogWarning($"Should not be invoking!");
+ return;
+ }
+ var isHost = networkManager.IsHost;
+ for (int i = m_PendingGhostRegistration.Count - 1; i >= 0; i--)
+ {
+ var networkPrefab = m_PendingGhostRegistration[i];
+
+ // Returns false if the single world is not available yet
+ if (NetCode.Netcode.RegisterPrefabSingleWorld(networkPrefab.Prefab, isHost, networkManager.NetcodeWorld))
+ {
+ Debug.Log($"[{nameof(NetworkPrefabs)}][{nameof(RegisterGhostPrefabs)}] Registered hybrid spawned object: {networkPrefab.Prefab.name}");
+ m_PendingGhostRegistration.RemoveAt(i);
+ }
+ }
+ HasPendingGhostPrefabs = m_PendingGhostRegistration.Count > 0;
+ }
+#endif
+
+
///
/// Configures for the given
///
@@ -295,6 +395,15 @@ private bool AddPrefabRegistration(NetworkPrefab networkPrefab)
uint source = networkPrefab.SourcePrefabGlobalObjectIdHash;
uint target = networkPrefab.TargetPrefabGlobalObjectIdHash;
+#if UNIFIED_NETCODE
+ if (networkPrefab.HasGhost)
+ {
+ //HasPendingGhostPrefabs = true;
+ HasGhostPrefabs = true;
+ //m_PendingGhostRegistration.Add(networkPrefab);
+ }
+#endif
+
// Make sure the prefab isn't already registered.
if (NetworkPrefabOverrideLinks.ContainsKey(source))
{
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
index 59f9e637b6..696bef2234 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
@@ -1,8 +1,10 @@
#pragma warning disable IDE0005
using System;
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
using Unity.Collections;
+#if MULTIPLAYER_TOOLS && (DEVELOPMENT_BUILD || UNITY_EDITOR || UNITY_MP_TOOLS_NET_STATS_MONITOR_ENABLED_IN_RELEASE)
+using System.Runtime.CompilerServices;
+#endif
using UnityEngine;
#pragma warning restore IDE0005
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
index b9a2c15789..6130d16005 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
@@ -1,10 +1,17 @@
using System;
using System.Collections.Generic;
-using Unity.Collections;
using System.Linq;
+using Unity.Collections;
+#if UNIFIED_NETCODE
+using Unity.Entities;
+using Unity.NetCode;
+#endif
using Unity.Netcode.Components;
using Unity.Netcode.Logging;
using Unity.Netcode.Runtime;
+#if UNIFIED_NETCODE && OUT_OF_BAND_RPC
+using Unity.Netcode.Unified;
+#endif
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
@@ -12,6 +19,8 @@
#endif
using UnityEngine.SceneManagement;
+
+
namespace Unity.Netcode
{
///
@@ -1009,11 +1018,13 @@ internal bool NetworkManagerCheckForParent(bool ignoreNetworkManagerCache = fals
{
#if UNITY_EDITOR
var isParented = NetworkManagerHelper.NotifyUserOfNestedNetworkManager(this, ignoreNetworkManagerCache);
+
+
#else
- var isParented = transform.root != transform;
+ var isParented = transform.parent != null;
if (isParented)
{
- throw new Exception(GenerateNestedNetworkManagerMessage(transform));
+ Log.Error(new Context(LogLevel.Error, GenerateNestedNetworkManagerMessage(transform)));
}
#endif
return isParented;
@@ -1029,7 +1040,17 @@ internal static string GenerateNestedNetworkManagerMessage(Transform transform)
///
private void OnTransformParentChanged()
{
+#if UNITY_EDITOR
+ // During editor playmode, we log the message as a dialog box
+ // and that script sets the parent back to root/null.
NetworkManagerCheckForParent();
+#else
+ if (NetworkManagerCheckForParent())
+ {
+ // During runtime, we log the message and set our parent back to root/null.
+ transform.parent = null;
+ }
+#endif
}
///
@@ -1216,6 +1237,23 @@ internal void Initialize(bool server)
// UnityTransport dependencies are then initialized
RealTimeProvider = ComponentFactory.Create(this);
+
+#if UNIFIED_NETCODE && OUT_OF_BAND_RPC
+ // TODO-FixMe:
+ // We assign transport at this point to preceed the NetworkConnectionManager
+ // being initialized. However, HasGhostPrefabs might not be set at this point
+ // if the prefabs list was set after instantiating the NetworkManager.
+ // Integration tests do this, but user code could do this too.
+ // To-Investigate:
+ // Determine if this really impacts anything having prefabs initialze/register
+ // at this point versus last.
+ NetworkConfig.InitializePrefabs();
+ if (NetworkConfig.Prefabs.HasGhostPrefabs)
+ {
+ NetworkConfig.NetworkTransport = gameObject.AddComponent();
+ }
+#endif
+
MetricsManager.Initialize(this);
{
@@ -1262,8 +1300,9 @@ internal void Initialize(bool server)
BehaviourUpdater = new NetworkBehaviourUpdater();
BehaviourUpdater.Initialize(this);
-
+#if !UNIFIED_NETCODE
NetworkConfig.InitializePrefabs();
+#endif
PrefabHandler.RegisterPlayerPrefab();
#if UNITY_EDITOR
BeginNetworkSession();
@@ -1310,6 +1349,52 @@ private bool CanStart(StartType type)
return true;
}
+#if UNIFIED_NETCODE
+ ///
+ /// The world instance assigned to this NetworkManager instance.
+ ///
+ public NetcodeWorld NetcodeWorld { get; internal set; }
+
+ internal void InitializeNetcodeWorld()
+ {
+ if (NetcodeWorld != null)
+ {
+ return;
+ }
+
+ if (this == Singleton)
+ {
+ if (NetCode.Netcode.IsActive)
+ {
+ NetworkLog.LogInfo($"[{nameof(InitializeNetcodeWorld)}] Netcode is not active but has an instance at this point.");
+ }
+ /// !! Important !!
+ /// Clear out any pre-existing configuration in the event this applicatioin instance has already been connected to a session.
+ NetCode.Netcode.Reset();
+ }
+
+ /// !! Initialize worlds here !!
+ /// Worlds are created here:
+ UnifiedBootstrap.CurrentNetworkManagerForInitialization = this;
+ DefaultWorldInitialization.Initialize("Default World", false);
+ }
+
+ private bool UnifiedIsConfiguredCorrectly()
+ {
+ if (NetCodeConfig.Global == null)
+ {
+ Debug.LogError($"[{nameof(NetworkManager)}][Unified] You must create a {nameof(NetCodeConfig)} and set it to a single world in order to run in hybrid mode!");
+ return false;
+ }
+ if (NetCodeConfig.Global.HostWorldModeSelection != NetCodeConfig.HostWorldMode.SingleWorld)
+ {
+ Debug.LogError($"[{nameof(NetworkManager)}][Unified] You must configure {nameof(NetCodeConfig)} to only use a single world in order to run in hybrid mode!");
+ return false;
+ }
+ return true;
+ }
+#endif
+
///
/// Starts a server
///
@@ -1341,6 +1426,34 @@ public bool StartServer()
return false;
}
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: Review and align on this being a way to handle knowing if the world should be created.
+ if (NetworkConfig.Prefabs.HasGhostPrefabs)
+ {
+ if (!UnifiedIsConfiguredCorrectly())
+ {
+ m_ShuttingDown = true;
+ ShutdownInternal();
+ return false;
+ }
+ if (LogLevel <= LogLevel.Developer)
+ {
+ Debug.Log("Creating world: Default world");
+ }
+ InitializeNetcodeWorld();
+ return InternalStartServer();
+ }
+ else
+ {
+ return InternalStartServer();
+ }
+#else
+ return InternalStartServer();
+#endif
+ }
+
+ internal bool InternalStartServer()
+ {
try
{
IsListening = NetworkConfig.NetworkTransport.StartServer();
@@ -1366,7 +1479,6 @@ public bool StartServer()
ShutdownInternal();
IsListening = false;
}
-
return IsListening;
}
@@ -1399,6 +1511,35 @@ public bool StartClient()
return false;
}
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: Review and align on this being a way to handle knowing if the world should be created.
+ if (NetworkConfig.Prefabs.HasGhostPrefabs)
+ {
+ if (!UnifiedIsConfiguredCorrectly())
+ {
+ m_ShuttingDown = true;
+ ShutdownInternal();
+ return false;
+ }
+ if (LogLevel <= LogLevel.Developer)
+ {
+ Debug.Log("Creating world: Default world");
+ }
+ InitializeNetcodeWorld();
+ return InternalStartClient();
+ }
+ else
+ {
+ return InternalStartClient();
+ }
+#else
+ return InternalStartClient();
+#endif
+
+ }
+
+ internal bool InternalStartClient()
+ {
try
{
IsListening = NetworkConfig.NetworkTransport.StartClient();
@@ -1423,6 +1564,7 @@ public bool StartClient()
return IsListening;
}
+
///
/// Starts a Host
///
@@ -1453,6 +1595,35 @@ public bool StartHost()
return false;
}
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: Review and align on this being a way to handle knowing if the world should be created.
+ if (NetworkConfig.Prefabs.HasGhostPrefabs)
+ {
+ if (!UnifiedIsConfiguredCorrectly())
+ {
+ m_ShuttingDown = true;
+ ShutdownInternal();
+ return false;
+ }
+ if (LogLevel <= LogLevel.Developer)
+ {
+ Debug.Log("Creating world: Default world");
+ }
+ InitializeNetcodeWorld();
+ return InternalStartHost();
+ }
+ else
+ {
+ return InternalStartHost();
+ }
+#else
+ return InternalStartHost();
+#endif
+
+ }
+
+ internal bool InternalStartHost()
+ {
try
{
IsListening = NetworkConfig.NetworkTransport.StartServer();
@@ -1661,6 +1832,25 @@ internal void ShutdownInternal()
IsListening = false;
m_ShuttingDown = false;
+
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: Review and align on this being a way to handle knowing if the world should be created.
+ if (NetworkConfig != null && NetworkConfig.Prefabs != null && NetworkConfig.Prefabs.HasGhostPrefabs)
+ {
+ try
+ {
+ // Dispose of all worlds
+ World.DisposeAllWorlds();
+ // Clear the world assigned from previous session
+ NetcodeWorld = null;
+ }
+ catch (Exception ex)
+ {
+ Debug.LogException(ex);
+ }
+ }
+#endif
+
// Generate a local notification that the host client is disconnected
if (IsHost)
{
@@ -1689,7 +1879,6 @@ internal void ShutdownInternal()
NetworkTimeSystem?.Shutdown();
NetworkTickSystem = null;
-
if (localClient.IsClient)
{
// If we were a client, we want to know if we were a host
@@ -1965,5 +2154,12 @@ internal static void OnOneTimeTearDown()
}
#endif
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: We might not need all of this (i.e. UnifiedUpdateConnections might be handled differently in unified)
+ public delegate void OnConnectDelegate(NetcodeConnection connection);
+ public delegate void OnDisconnectDelegate(NetcodeConnection connection);
+ public static OnConnectDelegate OnNetCodeConnect;
+ public static OnDisconnectDelegate OnNetCodeDisconnect;
+#endif
}
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
index d2f5aa5869..0463d9e89c 100644
--- a/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Core/NetworkObject.cs
@@ -5,13 +5,13 @@
using System.Text;
using Unity.Netcode.Components;
using Unity.Netcode.Runtime;
+#if UNIFIED_NETCODE
+using Unity.NetCode;
+#endif
+
#if UNITY_EDITOR
using UnityEditor;
-#if UNITY_2021_2_OR_NEWER
using UnityEditor.SceneManagement;
-#else
-using UnityEditor.Experimental.SceneManagement;
-#endif
#endif
using UnityEngine;
using UnityEngine.SceneManagement;
@@ -287,6 +287,10 @@ internal void OnValidate()
// Always check for in-scene placed to assure any previous version scene assets with in-scene place NetworkObjects gets updated.
CheckForInScenePlaced();
+#if UNIFIED_NETCODE
+ UnifiedValidation();
+#endif
+
// If the GlobalObjectIdHash value changed, then mark the asset dirty.
if (GlobalObjectIdHash != oldValue)
{
@@ -348,6 +352,39 @@ private void CheckForInScenePlaced()
}
#endif // UNITY_EDITOR
+#if UNIFIED_NETCODE
+ [HideInInspector]
+ [SerializeField]
+ internal GhostAdapter GhostAdapter;
+
+ [HideInInspector]
+ [SerializeField]
+ internal bool HasGhost;
+
+ [HideInInspector]
+ [SerializeField]
+ internal bool HadBridge;
+#if UNITY_EDITOR
+ internal void UnifiedValidation()
+ {
+ NetworkObjectBridge = GetComponent();
+ GhostAdapter = GetComponent();
+ HasGhost = GhostAdapter != null;
+ if (HasGhost && NetworkObjectBridge == null)
+ {
+ NetworkObjectBridge = gameObject.AddComponent();
+ HadBridge = true;
+ // Transform synchronization is handled by unified netcode
+ SynchronizeTransform = false;
+ }
+ else if (HadBridge && !HasGhost && !NetworkObjectBridge)
+ {
+ HadBridge = false;
+ SynchronizeTransform = true;
+ }
+ }
+#endif
+#endif
///
/// Gets the NetworkManager that owns this NetworkObject instance
///
@@ -1737,10 +1774,19 @@ private void OnDestroy()
return;
}
+ var spawnManager = NetworkManager.SpawnManager;
+
// Always attempt to remove from scene changed updates
- networkManager.SpawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this);
+ spawnManager?.RemoveNetworkObjectFromSceneChangedUpdates(this);
+#if UNIFIED_NETCODE
+ spawnManager?.GhostsPendingSpawn.Remove(NetworkObjectId);
+ spawnManager?.GhostsPendingSynchronization.Remove(NetworkObjectId);
+ // N4E controls this on the client, allow this if there is a ghost
+ if (IsSpawned && !HasGhost && !networkManager.ShutdownInProgress)
+#else
if (IsSpawned && !networkManager.ShutdownInProgress)
+#endif
{
// An authorized destroy is when done by the authority instance or done due to a scene event and the NetworkObject
// was marked as destroy pending scene event (which means the destroy with scene property was set).
@@ -1768,11 +1814,11 @@ private void OnDestroy()
}
}
- if (networkManager.SpawnManager != null && networkManager.SpawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
+ if (spawnManager != null && spawnManager.SpawnedObjects.TryGetValue(NetworkObjectId, out var networkObject))
{
if (this == networkObject)
{
- networkManager.SpawnManager.OnDespawnObject(networkObject, false);
+ spawnManager.OnDespawnObject(networkObject, false);
}
}
}
@@ -2026,6 +2072,12 @@ public void SpawnAsPlayerObject(ulong clientId, bool destroyWithScene = false)
/// (true) the will be destroyed (false) the will persist after being despawned
public void Despawn(bool destroy = true)
{
+#if UNIFIED_NETCODE
+ if (HasGhost && destroy == false)
+ {
+ throw new NotSupportedException("Despawn without destroy is not supported for hybrid objects.");
+ }
+#endif
if (!IsSpawned)
{
if (NetworkManager.LogLevel <= LogLevel.Error)
@@ -2634,6 +2686,7 @@ internal static void CheckOrphanChildren()
internal void InvokeBehaviourNetworkPreSpawn()
{
+
var networkManager = NetworkManager;
InitializeChildNetworkBehaviours();
foreach (var childBehaviour in ChildNetworkBehaviours.Values)
@@ -2665,7 +2718,7 @@ internal void InvokeBehaviourNetworkSpawn()
childBehaviour.InternalOnNetworkSpawn();
}
- // After initialization, we can then invoke OnNetworkSpawn on each child NetworkBehaviour.
+ // After internally spawning, we can then invoke OnNetworkSpawn on each child NetworkBehaviour.
foreach (var childBehaviour in ChildNetworkBehaviours.Values)
{
if (!childBehaviour.gameObject.activeInHierarchy)
@@ -2740,6 +2793,7 @@ internal string GenerateDisabledNetworkBehaviourWarning(NetworkBehaviour network
}
internal Dictionary ChildNetworkBehaviours;
+
internal bool InitializeChildNetworkBehaviours()
{
ChildNetworkBehaviours = new Dictionary();
@@ -2770,9 +2824,7 @@ internal bool InitializeChildNetworkBehaviours()
networkTransform.IsNested = networkTransform.gameObject != gameObject;
NetworkTransforms.Add(networkTransform);
}
-
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
-
var rigidbodyBase = behaviour as NetworkRigidbodyBase;
if (rigidbodyBase != null)
{
@@ -2780,7 +2832,42 @@ internal bool InitializeChildNetworkBehaviours()
}
#endif
}
+#if UNIFIED_NETCODE
+ // For now, cycle through all known NetworkTransform and NetworkRigidbodyBase derived components
+ // and destroy them all if this is a hybrid prefab instance.
+ // This allows a user to not have to make direct adjustments until trying out their NGO prefab
+ // as a hybrid spawned prefab (optional to completely remove, will eventually become obsolete and
+ // automatically removed later).
+ if (HasGhost)
+ {
+
+ if (NetworkRigidbodies != null)
+ {
+ for (int i = NetworkRigidbodies.Count - 1; i >= 0; i--)
+ {
+ ChildNetworkBehaviours.Remove(NetworkRigidbodies[i].NetworkBehaviourId);
+ Destroy(NetworkRigidbodies[i]);
+ }
+ NetworkRigidbodies.Clear();
+ }
+ // When hybrid spawning, the transform is synchronized by the GhostObject.
+ // As a convenience, we remove and destroy all NetworkTransforms.
+ // TODO-Parenting-Related-Area: We need to replicate this functionality in a GhostAdapter
+ // Possibly use a "Synchronize" property and display only on children of a root parent GhostAdapter.
+ if (NetworkTransforms != null)
+ {
+ NetworkManager.Log.Warning(new Logging.Context(LogLevel.Developer, $"[]{name} Hybrid spawned objects do not support {nameof(NetworkTransform)} and " +
+ $"are removed at runtime. If hybrid spawning is intended, then remove it from the network prefab to avoid allocating and de-allocating at runtime."));
+ for (int i = NetworkTransforms.Count - 1; i >= 0; i--)
+ {
+ ChildNetworkBehaviours.Remove(NetworkTransforms[i].NetworkBehaviourId);
+ Destroy(NetworkTransforms[i]);
+ }
+ NetworkTransforms.Clear();
+ }
+ }
+#endif
return true;
}
@@ -2914,18 +3001,21 @@ internal struct SerializedObject
public ulong OwnerClientId;
public ushort OwnershipFlags;
- private const ushort k_IsPlayerObject = 0x001;
- private const ushort k_HasParent = 0x002;
- private const ushort k_IsSceneObject = 0x004;
- private const ushort k_HasTransform = 0x008;
- private const ushort k_IsLatestParentSet = 0x010;
- private const ushort k_WorldPositionStays = 0x020;
- private const ushort k_DestroyWithScene = 0x040;
- private const ushort k_DontDestroyWithOwner = 0x080;
- private const ushort k_HasOwnershipFlags = 0x100;
- private const ushort k_SyncObservers = 0x200;
- private const ushort k_SpawnWithObservers = 0x400;
- private const ushort k_HasInstantiationData = 0x800;
+ private const ushort k_IsPlayerObject = 0x0001;
+ private const ushort k_HasParent = 0x0002;
+ private const ushort k_IsSceneObject = 0x0004;
+ private const ushort k_HasTransform = 0x0008;
+ private const ushort k_IsLatestParentSet = 0x0010;
+ private const ushort k_WorldPositionStays = 0x0020;
+ private const ushort k_DestroyWithScene = 0x0040;
+ private const ushort k_DontDestroyWithOwner = 0x0080;
+ private const ushort k_HasOwnershipFlags = 0x0100;
+ private const ushort k_SyncObservers = 0x0200;
+ private const ushort k_SpawnWithObservers = 0x0400;
+ private const ushort k_HasInstantiationData = 0x0800;
+#if UNIFIED_NETCODE
+ private const ushort k_HasGhost = 0x1000;
+#endif
public bool IsPlayerObject;
public bool HasParent;
@@ -2946,6 +3036,9 @@ internal struct SerializedObject
public bool SyncObservers;
public bool SpawnWithObservers;
public bool HasInstantiationData;
+#if UNIFIED_NETCODE
+ public bool HasGhost;
+#endif
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal ushort GetBitsetRepresentation()
@@ -2995,10 +3088,19 @@ internal ushort GetBitsetRepresentation()
{
bitset |= k_SpawnWithObservers;
}
+
if (HasInstantiationData)
{
bitset |= k_HasInstantiationData;
}
+
+#if UNIFIED_NETCODE
+ if (HasGhost)
+ {
+ bitset |= k_HasGhost;
+ }
+
+#endif
return bitset;
}
@@ -3017,6 +3119,9 @@ internal void SetStateFromBitset(ushort bitset)
SyncObservers = (bitset & k_SyncObservers) != 0;
SpawnWithObservers = (bitset & k_SpawnWithObservers) != 0;
HasInstantiationData = (bitset & k_HasInstantiationData) != 0;
+#if UNIFIED_NETCODE
+ HasGhost = (bitset & k_HasGhost) != 0;
+#endif
}
// When handling the initial synchronization of NetworkObjects,
@@ -3282,7 +3387,10 @@ internal SerializedObject Serialize(ulong targetClientId = NetworkManager.Server
Hash = CheckForGlobalObjectIdHashOverride(),
OwnerObject = this,
TargetClientId = targetClientId,
- HasInstantiationData = InstantiationData != null && InstantiationData.Length > 0
+ HasInstantiationData = InstantiationData != null && InstantiationData.Length > 0,
+#if UNIFIED_NETCODE
+ HasGhost = HasGhost,
+#endif
};
// Handle Parenting
@@ -3552,7 +3660,91 @@ private void Awake()
{
SetCachedParent(transform.parent);
SceneOrigin = gameObject.scene;
+
+ }
+
+#if UNIFIED_NETCODE
+
+#if DEBUG_ENABLE_DISABLE
+ private void OnEnable()
+ {
+ Debug.Log("Enabled!");
+ }
+
+ private void OnDisable()
+ {
+ Debug.Log("Disabled!");
+ if (IsSpawned || HasGhost)
+ {
+ if (HasGhost && GhostAdapter.IsPrefab())
+ {
+ return;
+ }
+ gameObject.SetActive(true);
+ }
+
+ try
+ {
+ throw new Exception("Disabled trap!");
+ }
+ catch (Exception ex)
+ {
+ Debug.LogWarning($"[{name}][{ex.Message}] Callstack:\n{ex.StackTrace}");
+ }
+ }
+#endif
+
+ private void Start()
+ {
+ InitGhost();
}
+ [SerializeField]
+ [HideInInspector]
+ internal NetworkObjectBridge NetworkObjectBridge;
+
+ private void InitGhost()
+ {
+ // All instances with Ghosts are automatically registered
+ if (HasGhost && NetworkObjectBridge && !GhostAdapter.IsPrefab())
+ {
+ if (NetworkManager.LogLevel == LogLevel.Developer)
+ {
+ Debug.Log($"[{nameof(NetworkObject)}] GhostBridge {name} detected and instantiated.");
+ }
+ if (GhostAdapter.WasInitialized && NetworkObjectBridge.NetworkObjectId.Value != 0)
+ {
+ RegisterGhostBridge();
+ }
+ }
+ }
+
+ internal void RegisterGhostBridge()
+ {
+ if (NetworkManager.LogLevel == LogLevel.Developer)
+ {
+ Debug.Log($"[{nameof(NetworkObject)}][{nameof(NetworkObjectId)}] NetworkObjectBridge notified instance exists with assigned ID of: {NetworkObjectBridge.NetworkObjectId.Value}");
+ if (!NetworkManager.IsListening)
+ {
+ Debug.LogWarning($"[{nameof(NetworkObject)}] Did not register because there is no session in progress!");
+ return;
+ }
+ }
+
+ // Set when running through integration tests in order to initially bypass the
+ // normal registration. This is because at this point in the instantiation process,
+ // NetworkObject's NetworkManager is pointing to the singleton which means all instances
+ // (even if intended to be for a specific client) will end up registering with whichever
+ // NetworkManager instance is being pointed to by the singleton.
+ if (NetworkSpawnManager.RegisterPendingGhost != null)
+ {
+ NetworkSpawnManager.RegisterPendingGhost(this, NetworkObjectBridge.NetworkObjectId.Value);
+ }
+ else if (!NetworkManager.IsServer)
+ {
+ NetworkManager.SpawnManager.RegisterGhostPendingSpawn(this, NetworkObjectBridge.NetworkObjectId.Value);
+ }
+ }
+#endif
///
/// Update
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs
index f05d000fec..c68173e60d 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/DeferredMessageManager.cs
@@ -125,6 +125,16 @@ protected virtual void PurgeTrigger(IDeferredNetworkMessageManager.TriggerType t
triggerInfo.TriggerData.Dispose();
}
+ public bool HasAnyOfTrigger(IDeferredNetworkMessageManager.TriggerType trigger)
+ {
+ if (m_Triggers.TryGetValue(trigger, out var triggers))
+ {
+ return triggers.Count != 0;
+ }
+
+ return false;
+ }
+
public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType trigger, ulong key)
{
if (m_Triggers.TryGetValue(trigger, out var triggers))
@@ -143,6 +153,11 @@ public virtual void ProcessTriggers(IDeferredNetworkMessageManager.TriggerType t
triggerInfo.TriggerData.Dispose();
}
}
+
+ if (trigger != IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing)
+ {
+ ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing, (ulong)trigger);
+ }
}
///
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs
index 910021f606..569970b22f 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/IDeferredNetworkMessageManager.cs
@@ -8,6 +8,10 @@ internal enum TriggerType
OnSpawn,
OnAddPrefab,
OnNextFrame,
+#if UNIFIED_NETCODE
+ OnGhostSpawned,
+#endif
+ OnOtherTriggerFinishedProcessing,
}
///
@@ -28,6 +32,8 @@ internal enum TriggerType
public void ProcessTriggers(TriggerType trigger, ulong key);
+ public bool HasAnyOfTrigger(TriggerType trigger);
+
///
/// Cleans up any trigger that's existed for more than a second.
/// These triggers were probably for situations where a request was received after a despawn rather than before a spawn.
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs
index cf518a6216..f00c7cb0f3 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/CreateObjectMessage.cs
@@ -120,6 +120,25 @@ public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int
ByteUnpacker.ReadValuePacked(reader, out NetworkObjectId);
}
+#if UNIFIED_NETCODE
+ // Leaving for debugging purposes
+ //if (networkManager.LogLevel == LogLevel.Developer)
+ //{
+ // UnityEngine.Debug.Log($"Received {nameof(CreateObjectMessage)} for NetworkObjectId-{ObjectInfo.NetworkObjectId}.");
+ //}
+
+ // For now, we will defer the create object message until the associated Ghost is spawned
+ if (ObjectInfo.HasGhost && !networkManager.SpawnManager.GhostsPendingSpawn.ContainsKey(ObjectInfo.NetworkObjectId))
+ {
+ if (networkManager.LogLevel == LogLevel.Developer)
+ {
+ UnityEngine.Debug.Log($"[{nameof(NetworkObject)}-{ObjectInfo.NetworkObjectId}] Deferring {nameof(CreateObjectMessage)} to wait for Ghost.");
+ }
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, ObjectInfo.NetworkObjectId, reader, ref context, k_Name);
+ return false;
+ }
+#endif
+
if (!networkManager.NetworkConfig.ForceSamePrefabs && !networkManager.SpawnManager.HasPrefab(ObjectInfo))
{
networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, ObjectInfo.Hash, reader, ref context, k_Name);
diff --git a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs
index 25c53a2786..4a3376d912 100644
--- a/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Messaging/Messages/SceneEventMessage.cs
@@ -1,4 +1,3 @@
-
namespace Unity.Netcode
{
// Todo: Would be lovely to get this one nicely formatted with all the data it sends in the struct
@@ -9,6 +8,7 @@ internal struct SceneEventMessage : INetworkMessage
public SceneEventData EventData;
+ private const string k_Name = "SceneEventMessage";
private FastBufferReader m_ReceivedData;
@@ -19,6 +19,15 @@ public void Serialize(FastBufferWriter writer, int targetVersion)
public bool Deserialize(FastBufferReader reader, ref NetworkContext context, int receivedMessageVersion)
{
+ var networkManager = (NetworkManager)context.SystemOwner;
+#if UNIFIED_NETCODE
+ if (networkManager.DeferredMessageManager.HasAnyOfTrigger(IDeferredNetworkMessageManager.TriggerType
+ .OnGhostSpawned))
+ {
+ networkManager.DeferredMessageManager.DeferMessage(IDeferredNetworkMessageManager.TriggerType.OnOtherTriggerFinishedProcessing, (ulong)IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, reader, ref context, k_Name);
+ return false;
+ }
+#endif
m_ReceivedData = reader;
return true;
}
diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs
index 2fbdf1fbe9..641fed9b8e 100644
--- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/NetworkSceneManager.cs
@@ -2756,8 +2756,14 @@ internal void PopulateScenePlacedObjects(Scene sceneToFilterBy, bool clearSceneP
var globalObjectIdHash = networkObjectInstance.GlobalObjectIdHash;
var sceneHandle = networkObjectInstance.gameObject.scene.handle;
// We check to make sure the NetworkManager instance is the same one to be "NetcodeIntegrationTestHelpers" compatible and filter the list on a per scene basis (for additive scenes)
+#if UNIFIED_NETCODE
+ if (!networkObjectInstance.HasGhost && networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager ||
+ networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle)
+#else
if (networkObjectInstance.IsSceneObject != false && (networkObjectInstance.NetworkManager == NetworkManager ||
networkObjectInstance.NetworkManagerOwner == null) && sceneHandle == sceneToFilterBy.handle)
+
+#endif
{
if (!ScenePlacedObjects.ContainsKey(globalObjectIdHash))
{
diff --git a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs
index 0fff313574..e5ae1aafbb 100644
--- a/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs
+++ b/com.unity.netcode.gameobjects/Runtime/SceneManagement/SceneEventData.cs
@@ -5,6 +5,7 @@
using Unity.Collections;
using UnityEngine.SceneManagement;
+
namespace Unity.Netcode
{
///
@@ -1106,6 +1107,10 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
{
builder.AppendLine($"[Read][Synchronize Objects][WPos: {InternalBuffer.Position}][NO-Count: {newObjectsCount}] Begin:");
}
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: This is a temporary POC fix to handle hybrid spawning where the Ghost instance might not yet exist.
+ var spawnManager = m_NetworkManager.SpawnManager;
+#endif
for (int i = 0; i < newObjectsCount; i++)
{
@@ -1113,6 +1118,42 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
var serializedObject = new NetworkObject.SerializedObject();
serializedObject.Deserialize(InternalBuffer);
+#if UNIFIED_NETCODE
+ // TODO-UNIFIED: This is a temporary POC fix to handle synchronizing hybrid spawned objects where the Ghost instance might not yet exist.
+ if (serializedObject.HasGhost)
+ {
+ if (!networkManager.SpawnManager.GhostsPendingSpawn.ContainsKey(serializedObject.NetworkObjectId))
+ {
+ if (networkManager.LogLevel == LogLevel.Developer)
+ {
+ UnityEngine.Debug.Log($"[{nameof(SceneEventData)}][{nameof(SynchronizeSceneNetworkObjects)}] Deferring creation of NetworkObjectId-{serializedObject.NetworkObjectId} to wait for Ghost.");
+ }
+
+ var newEntry = new PendingGhostSpawnEntry()
+ {
+ RegistrationTime = UnityEngine.Time.realtimeSinceStartup,
+ SerializedObject = serializedObject,
+ Buffer = new FastBufferReader(InternalBuffer, Allocator.Persistent, serializedObject.SynchronizationDataSize, InternalBuffer.Position)
+ };
+
+ spawnManager.RegisterGhostPendingSynchronization(newEntry);
+ InternalBuffer.Seek(InternalBuffer.Position + serializedObject.SynchronizationDataSize);
+ continue;
+ }
+ else if (networkManager.SpawnManager.GhostsPendingSpawn[serializedObject.NetworkObjectId] == null)
+ {
+ if (networkManager.LogLevel == LogLevel.Developer)
+ {
+ UnityEngine.Debug.Log($"[{nameof(SceneEventData)}][{nameof(SynchronizeSceneNetworkObjects)}] Dropping creation of NetworkObjectId-{serializedObject.NetworkObjectId} as it has an entry but no longer exists!");
+ }
+ // If it no longer exists, then just remove the entry and skip it.
+ InternalBuffer.Seek(InternalBuffer.Position + serializedObject.SynchronizationDataSize);
+ networkManager.SpawnManager.GhostsPendingSpawn.Remove(serializedObject.NetworkObjectId);
+ continue;
+ }
+ }
+#endif
+
// If the sceneObject is in-scene placed, then set the scene being synchronized
if (serializedObject.IsSceneObject)
{
@@ -1120,9 +1161,9 @@ internal void SynchronizeSceneNetworkObjects(NetworkManager networkManager)
}
var spawnedNetworkObject = NetworkObject.Deserialize(serializedObject, InternalBuffer, networkManager);
- var noStop = InternalBuffer.Position;
if (EnableSerializationLogs)
{
+ var noStop = InternalBuffer.Position;
builder.AppendLine($"[Head: {noStart}][Tail: {noStop}][Size: {noStop - noStart}][{spawnedNetworkObject.name}][NID-{spawnedNetworkObject.NetworkObjectId}][Children: {spawnedNetworkObject.ChildNetworkBehaviours.Count}]");
LogArray(InternalBuffer.ToArray(), noStart, noStop, builder);
}
@@ -1399,4 +1440,14 @@ internal SceneEventData(NetworkManager networkManager)
SceneEventId = XXHash.Hash32(Guid.NewGuid().ToString());
}
}
+
+#if UNIFIED_NETCODE
+ internal struct PendingGhostSpawnEntry
+ {
+ public float RegistrationTime;
+ public FastBufferReader Buffer;
+ public NetworkObject.SerializedObject SerializedObject;
+
+ }
+#endif
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
index c8aab939f4..34cf77d959 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkPrefabHandler.cs
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
+#if UNIFIED_NETCODE
+using Unity.NetCode;
+#endif
using UnityEngine;
namespace Unity.Netcode
@@ -418,6 +421,19 @@ public void AddNetworkPrefab(GameObject prefab)
{
m_NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnAddPrefab, networkObject.GlobalObjectIdHash);
}
+
+#if UNIFIED_NETCODE
+ if (m_NetworkManager.IsListening)
+ {
+ var ghost = prefab.GetComponent();
+ if (ghost)
+ {
+ m_NetworkManager.InitializeNetcodeWorld();
+ NetCode.Netcode.RegisterPrefabSingleWorld(prefab, m_NetworkManager.IsHost,
+ m_NetworkManager.NetcodeWorld);
+ }
+ }
+#endif
}
///
diff --git a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
index 52e891f8e1..6bae4d5c2f 100644
--- a/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
+++ b/com.unity.netcode.gameobjects/Runtime/Spawning/NetworkSpawnManager.cs
@@ -33,6 +33,167 @@ public class NetworkSpawnManager
///
public readonly HashSet SpawnedObjectsList = new HashSet();
+#if UNIFIED_NETCODE
+
+ internal readonly Dictionary GhostsPendingSpawn = new Dictionary();
+
+ // TODO: We might want to make this a mock interfacebut temporary solution to validate
+ // the need to assure we are registering with the right NetworkManager instance when testing (everything
+ // will use the singleton during Awake and Start when we need to register).
+ internal delegate void RegisterPendingGhostDelegateHandler(NetworkObject networkObject, ulong networkObjectId);
+
+ internal static RegisterPendingGhostDelegateHandler RegisterPendingGhost;
+
+ internal void RegisterGhostPendingSpawn(NetworkObject networkObject, ulong networkObjectId)
+ {
+ if (NetworkManager.LogLevel == LogLevel.Developer)
+ {
+ Debug.Log($"[{nameof(RegisterGhostPendingSpawn)}] Registering {networkObject.name} with a {nameof(NetworkObject.NetworkObjectId)} of {networkObjectId}.");
+ }
+ if (GhostsPendingSpawn.TryAdd(networkObjectId, networkObject))
+ {
+ // TODO-REVIEW-BELOW: *** This is very likely no longer an issue with the new connection sequence ***
+ // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn.
+ // Edge-Case scenario: During initial client synchronization (i.e. !NetworkManager.IsConnectedClient).
+ //
+ // Description: A client can receive snapshots before finishing the NGO synchronization process.
+ // This is when an edge case scenario can happen where the initial NGO synchronization information
+ // can include new scenes to load. If one of those scenes is configured to load in SingleMode, then
+ // any instantiated ghosts pending synchronization would be instantiated in whatever the currently
+ // active scene was when the client was processing the synchronization data. If the ghosts pending
+ // synchrpnization are in the currently active scene when the new scene is loaded in SingleMode, then
+ // they would be destroyed.
+ //
+ // Current Fix:
+ // If the client is not yet synchronized, then any ghost pending spawn get migrated into the DDOL.
+ //
+ // Further review:
+ // We need to make sure that we are migrating NetworkObjects into their assigned scene (if scene
+ // management is enabled). Currently, we assume all instances were in the DDOL and just migrate
+ // them into the currently active scene upon spawn.
+ if (!NetworkManager.IsConnectedClient && !GhostsPendingSynchronization.ContainsKey(networkObjectId))
+ {
+ Object.DontDestroyOnLoad(networkObject.gameObject);
+ }
+ else // There is matching spawn data for this pending Ghost, process the pending spawn for this hybrid instance.
+ {
+ NetworkManager.DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnGhostSpawned, networkObjectId);
+ if (GhostsPendingSynchronization.ContainsKey(networkObjectId))
+ {
+ ProcessGhostPendingSynchronization(networkObjectId);
+ }
+ }
+ }
+ else
+ {
+ Debug.LogError($"[{networkObject.name}-{networkObjectId}] Has already been registered as a pending ghost!");
+ }
+ }
+
+ internal NetworkObject GetGhostNetworkObjectForSpawn(ulong networkObjectId)
+ {
+ if (!GhostsPendingSpawn.ContainsKey(networkObjectId))
+ {
+ Debug.LogError($"[{nameof(GetGhostNetworkObjectForSpawn)}] Attempting to spawn NetworkObject-{networkObjectId} with no instance to spawn!");
+ return null;
+ }
+ var networkObject = GhostsPendingSpawn[networkObjectId];
+
+ GhostsPendingSpawn.Remove(networkObjectId);
+ if (networkObject != null)
+ {
+ // TODO-UNIFIED: We need a better way to preserve any hybrid instances pending NGO spawn.
+ // NOTE: We might be able to use the NetworkSceneHandle to get the associated local scene handle to which we can use to get the targeted scene.
+ UnityEngine.SceneManagement.SceneManager.MoveGameObjectToScene(networkObject.gameObject, UnityEngine.SceneManagement.SceneManager.GetActiveScene());
+ }
+ return networkObject;
+ }
+
+ internal bool GhostsArePendingSynchronization;
+ internal readonly Dictionary GhostsPendingSynchronization = new Dictionary();
+ internal void RegisterGhostPendingSynchronization(PendingGhostSpawnEntry pendingGhostSpawnEntry)
+ {
+ var networkObjectId = pendingGhostSpawnEntry.SerializedObject.NetworkObjectId;
+ if (NetworkManager.LogLevel == LogLevel.Developer)
+ {
+ Debug.Log($"[{nameof(RegisterGhostPendingSpawn)}] Registering {nameof(NetworkObject)}-{networkObjectId} for pending synchronization.");
+ }
+ GhostsPendingSynchronization.TryAdd(networkObjectId, pendingGhostSpawnEntry);
+ GhostsArePendingSynchronization = true;
+ }
+
+ internal void ProcessGhostPendingSynchronization(ulong networkObjectId, bool removeUponSpawn = true)
+ {
+ var ghostPendingSynch = GhostsPendingSynchronization[networkObjectId];
+ var serializedObject = ghostPendingSynch.SerializedObject;
+ var reader = ghostPendingSynch.Buffer;
+ if (removeUponSpawn)
+ {
+ GhostsPendingSynchronization.Remove(networkObjectId);
+ }
+
+ if (serializedObject.IsSceneObject)
+ {
+ NetworkManager.SceneManager.SetTheSceneBeingSynchronized(serializedObject.NetworkSceneHandle);
+ }
+ var networkObject = NetworkObject.Deserialize(serializedObject, reader, NetworkManager);
+ // TODO-UNIFIED: How do we handle the "all in-scene placed objects are spawned notification"?
+ //if (serializedObject.IsSceneObject)
+ //{
+ // networkObject.InternalInSceneNetworkObjectsSpawned();
+ //}
+ if (removeUponSpawn)
+ {
+ GhostsPendingSynchronization.Remove(networkObjectId);
+ GhostsArePendingSynchronization = GhostsPendingSynchronization.Count > 0;
+ ghostPendingSynch.Buffer.Dispose();
+ }
+ }
+
+
+ private HashSet m_GhostSynchronizationPendingRemoval = new HashSet();
+
+ internal void ProcessAllGhostsPendingSynchronization()
+ {
+ var spawnTimeout = NetworkManager.NetworkConfig.SpawnTimeout;
+ var logLevel = NetworkManager.LogLevel;
+ if (!GhostsArePendingSynchronization)
+ {
+ return;
+ }
+ foreach (var ghost in GhostsPendingSynchronization)
+ {
+ var networkObjectId = ghost.Value.SerializedObject.NetworkObjectId;
+ if (GhostsPendingSpawn.ContainsKey(networkObjectId))
+ {
+ // Process it, but don't remove it as we handle that a little later
+ ProcessGhostPendingSynchronization(ghost.Value.SerializedObject.NetworkObjectId, false);
+ m_GhostSynchronizationPendingRemoval.Add(networkObjectId);
+ }
+ else
+ if ((ghost.Value.RegistrationTime + spawnTimeout) < Time.realtimeSinceStartup)
+ {
+ if (logLevel == LogLevel.Developer)
+ {
+ Debug.LogWarning($"[{nameof(NetworkSpawnManager)}][{nameof(ProcessAllGhostsPendingSynchronization)}] NetworkObject-{networkObjectId} pending Ghost spawn timed out wiating for the Ghost instance to spawn!");
+ }
+ // Timed out entries are removed too
+ m_GhostSynchronizationPendingRemoval.Add(ghost.Key);
+ }
+ }
+
+ foreach (var networkObjectId in m_GhostSynchronizationPendingRemoval)
+ {
+ var entry = GhostsPendingSynchronization[networkObjectId];
+ GhostsPendingSynchronization.Remove(networkObjectId);
+ entry.Buffer.Dispose();
+ }
+ m_GhostSynchronizationPendingRemoval.Clear();
+ GhostsArePendingSynchronization = GhostsPendingSynchronization.Count > 0;
+ }
+
+#endif
+
///
/// Use to get all NetworkObjects owned by a client
/// Ownership to Objects Table Format:
@@ -966,32 +1127,49 @@ internal NetworkObject CreateLocalNetworkObject(NetworkObject.SerializedObject s
var parentNetworkId = serializedObject.HasParent ? serializedObject.ParentObjectId : default;
var worldPositionStays = (!serializedObject.HasParent) || serializedObject.WorldPositionStays;
- // If scene management is disabled or the NetworkObject was dynamically spawned
- if (!NetworkManager.NetworkConfig.EnableSceneManagement || !serializedObject.IsSceneObject)
+#if UNIFIED_NETCODE
+ if (serializedObject.HasGhost)
{
- networkObject = GetNetworkObjectToSpawn(serializedObject.Hash, serializedObject.OwnerClientId, position, rotation, serializedObject.IsSceneObject, instantiationData);
+ // TODO-UNIFIED: Get this working somehow (or if not possible prevent this from happening prior to getting to this point)
+ if (serializedObject.HasInstantiationData)
+ {
+ Debug.LogError($"[{nameof(NetworkObject)}] Pre-spawn instantiation data does not work in this version!");
+ }
+ networkObject = GetGhostNetworkObjectForSpawn(serializedObject.NetworkObjectId);
+ if (networkObject == null)
+ {
+ throw new Exception($"Failed to get spawned Ghost object!");
+ }
}
- else // Get the in-scene placed NetworkObject
+ else
+#endif
{
- networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle);
- if (networkObject == null)
+ // If scene management is disabled or the NetworkObject was dynamically spawned
+ if (!NetworkManager.NetworkConfig.EnableSceneManagement || !serializedObject.IsSceneObject)
{
- if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
+ networkObject = GetNetworkObjectToSpawn(serializedObject.Hash, serializedObject.OwnerClientId, position, rotation, serializedObject.IsSceneObject, instantiationData);
+ }
+ else // Get the in-scene placed NetworkObject
+ {
+ networkObject = NetworkManager.SceneManager.GetSceneRelativeInSceneNetworkObject(globalObjectIdHash, serializedObject.NetworkSceneHandle);
+ if (networkObject == null)
{
- NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
- }
+ if (NetworkLog.CurrentLogLevel <= LogLevel.Error)
+ {
+ NetworkLog.LogError($"{nameof(NetworkPrefab)} hash was not found! In-Scene placed {nameof(NetworkObject)} soft synchronization failure for Hash: {globalObjectIdHash}!");
+ }
- return null;
- }
+ return null;
+ }
- // Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
- // NetworkBehaviours will have their OnNetworkSpawn method invoked
- if (!networkObject.gameObject.activeInHierarchy)
- {
- networkObject.gameObject.SetActive(true);
+ // Since this NetworkObject is an in-scene placed NetworkObject, if it is disabled then enable it so
+ // NetworkBehaviours will have their OnNetworkSpawn method invoked
+ if (!networkObject.gameObject.activeInHierarchy)
+ {
+ networkObject.gameObject.SetActive(true);
+ }
}
}
-
if (networkObject == null)
{
return null;
@@ -1189,6 +1367,13 @@ internal bool AuthorityLocalSpawn([NotNull] NetworkObject networkObject, ulong n
return false;
}
+#if UNIFIED_NETCODE
+ if (networkObject.HasGhost)
+ {
+ networkObject.NetworkObjectBridge.SetNetworkObjectId(networkObject.NetworkObjectId);
+ }
+#endif
+
// When done spawning invoke post spawn
networkObject.InvokeBehaviourNetworkPostSpawn();
@@ -1914,7 +2099,14 @@ internal void OnDespawnObject([NotNull] NetworkObject networkObject, bool destro
{
RemovePlayerObject(networkObject, destroyGameObject);
}
-
+#if UNIFIED_NETCODE
+ // Let unified netcode handle destroying
+ if (destroyGameObject && networkObject.HasGhost && !NetworkManager.IsServer)
+ {
+ // exit early
+ return;
+ }
+#endif
var gobj = networkObject.gameObject;
if (destroyGameObject && gobj != null)
{
@@ -2057,6 +2249,10 @@ internal NetworkSpawnManager(NetworkManager networkManager)
internal void Shutdown()
{
+#if UNIFIED_NETCODE
+ GhostsPendingSpawn.Clear();
+ GhostsPendingSynchronization.Clear();
+#endif
NetworkObjectsToSynchronizeSceneChanges?.Clear();
CleanUpDisposedObjects?.Clear();
}
diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified.meta b/com.unity.netcode.gameobjects/Runtime/Transports/Unified.meta
new file mode 100644
index 0000000000..b4340f3a18
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 5ce5c8d0dc8948aab8482e617eccabcc
+timeCreated: 1772129529
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs
new file mode 100644
index 0000000000..c7c813b872
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs
@@ -0,0 +1,474 @@
+#if UNIFIED_NETCODE && OUT_OF_BAND_RPC
+using System;
+using System.Collections.Generic;
+using Unity.Burst;
+using Unity.Burst.Intrinsics;
+using Unity.Collections;
+using Unity.Collections.LowLevel.Unsafe;
+using Unity.Entities;
+using Unity.NetCode;
+using Unity.Netcode.Transports.UTP;
+using UnityEngine;
+
+namespace Unity.Netcode.Unified
+{
+ [BurstCompile]
+ internal unsafe struct FixedBytes1280
+ {
+ public fixed byte Buffer[1280];
+ public int Length;
+
+ // Returns a direct pointer to the data in the buffer.
+ // Implemented as a static with an in-parameter to avoid the buffer being copied while keeping its memory allocation fixed/non-heap
+ // Note that the buffer MUST outlive the returned pointer, as it is an alias.
+ public static byte* GetUnsafePtr(in FixedBytes1280 data)
+ {
+ fixed (byte* buffer = data.Buffer)
+ {
+ return buffer;
+ }
+ }
+
+ // Returns a native array that is an alias of the existing data without copying it
+ // Implemented as a static with an in-parameter to avoid the buffer being copied while keeping its memory allocation fixed/non-heap
+ // Note that the buffer MUST outlive the returned array, as it is an alias.
+ public static NativeArray ToNativeArray(in FixedBytes1280 data)
+ {
+ var array = NativeArrayUnsafeUtility.ConvertExistingDataToNativeArray(GetUnsafePtr(data), data.Length, Allocator.None);
+#if ENABLE_UNITY_COLLECTIONS_CHECKS
+ var safety = CollectionHelper.CreateSafetyHandle(Allocator.None);
+ NativeArrayUnsafeUtility.SetAtomicSafetyHandle(ref array, safety);
+#endif
+ return array;
+ }
+ }
+
+ [BurstCompile]
+ internal struct TransportRpc : IOutOfBandRpcCommand, IRpcCommandSerializer
+ {
+ public FixedBytes1280 Buffer;
+ public ulong Order;
+
+ public unsafe void Serialize(ref DataStreamWriter writer, in RpcSerializerState state, in TransportRpc data)
+ {
+ writer.WriteULong(data.Order);
+ writer.WriteInt(data.Buffer.Length);
+ var span = new Span(FixedBytes1280.GetUnsafePtr(data.Buffer), data.Buffer.Length);
+ writer.WriteBytes(span);
+ }
+
+ public unsafe void Deserialize(ref DataStreamReader reader, in RpcDeserializerState state, ref TransportRpc data)
+ {
+ data.Order = reader.ReadULong();
+ var length = reader.ReadInt();
+ data.Buffer = new FixedBytes1280
+ {
+ Length = length
+ };
+
+ var span = new Span(FixedBytes1280.GetUnsafePtr(data.Buffer), length);
+ reader.ReadBytes(span);
+ }
+
+ [BurstCompile(DisableDirectCall = true)]
+ private static void InvokeExecute(ref RpcExecutor.Parameters parameters)
+ {
+ RpcExecutor.ExecuteCreateRequestComponent(ref parameters);
+ }
+
+ private static readonly PortableFunctionPointer k_InvokeExecuteFunctionPointer = new PortableFunctionPointer(InvokeExecute);
+
+ public PortableFunctionPointer CompileExecute()
+ {
+ return k_InvokeExecuteFunctionPointer;
+ }
+ }
+
+ [UpdateInGroup(typeof(RpcCommandRequestSystemGroup))]
+ [CreateAfter(typeof(RpcSystem))]
+ [BurstCompile]
+ internal partial struct TransportRpcCommandRequestSystem : ISystem
+ {
+ private RpcCommandRequest m_Request;
+
+ [BurstCompile]
+ internal struct SendRpc : IJobChunk
+ {
+ public RpcCommandRequest.SendRpcData Data;
+
+ public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
+ {
+ Data.Execute(chunk, unfilteredChunkIndex);
+ }
+ }
+
+ public void OnCreate(ref SystemState state)
+ {
+ m_Request.OnCreate(ref state);
+ }
+
+ [BurstCompile]
+ public void OnUpdate(ref SystemState state)
+ {
+ var sendJob = new SendRpc { Data = m_Request.InitJobData(ref state) };
+ state.Dependency = sendJob.Schedule(m_Request.Query, state.Dependency);
+ }
+ }
+
+ internal partial class UnifiedNetcodeUpdateSystem : SystemBase
+ {
+ public UnifiedNetcodeTransport Transport;
+
+ public List DisconnectQueue = new List();
+
+ public void Disconnect(Connection connection)
+ {
+ DisconnectQueue.Add(connection);
+ }
+
+ protected override void OnUpdate()
+ {
+ using var commandBuffer = new EntityCommandBuffer(Allocator.Temp);
+ foreach (var (request, rpc, entity) in SystemAPI.Query, RefRO>().WithEntityAccess())
+ {
+ var connectionId = SystemAPI.GetComponent(request.ValueRO.SourceConnection).Value;
+
+ var buffer = rpc.ValueRO.Buffer;
+ try
+ {
+ Transport.DispatchMessage(connectionId, buffer, rpc.ValueRO.Order);
+ }
+ finally
+ {
+ commandBuffer.DestroyEntity(entity);
+ }
+ }
+
+ foreach (var connection in DisconnectQueue)
+ {
+ commandBuffer.AddComponent(connection.ConnectionEntity);
+ }
+ DisconnectQueue.Clear();
+
+ commandBuffer.Playback(EntityManager);
+
+ }
+ }
+
+ internal class UnifiedNetcodeTransport : NetworkTransport
+ {
+ private const int k_MaxPacketSize = 1280;
+
+ private int m_ServerClientId = -1;
+ public override ulong ServerClientId => (ulong)m_ServerClientId;
+
+ private NetworkManager m_NetworkManager;
+
+ private IRealTimeProvider m_RealTimeProvider;
+
+ private class ConnectionInfo
+ {
+ public BatchedSendQueue SendQueue;
+ public BatchedReceiveQueue ReceiveQueue;
+ public Connection Connection;
+ public ulong LastSent;
+ public ulong LastReceived;
+ public Dictionary DeferredMessages;
+ }
+
+ private Dictionary m_Connections;
+
+ internal void DispatchMessage(int connectionId, in FixedBytes1280 buffer, ulong order)
+ {
+ var connectionInfo = m_Connections[connectionId];
+
+ if (order <= connectionInfo.LastReceived)
+ {
+ Debug.LogWarning("Received duplicate message, ignoring.");
+ return;
+ }
+
+ if (order != connectionInfo.LastReceived + 1)
+ {
+ if (connectionInfo.DeferredMessages == null)
+ {
+ connectionInfo.DeferredMessages = new Dictionary();
+ }
+
+ connectionInfo.DeferredMessages[order] = buffer;
+ return;
+ }
+
+ using var arr = FixedBytes1280.ToNativeArray(buffer);
+ var reader = new DataStreamReader(arr);
+ if (connectionInfo.ReceiveQueue == null)
+ {
+ connectionInfo.ReceiveQueue = new BatchedReceiveQueue(reader);
+ }
+ else
+ {
+ connectionInfo.ReceiveQueue.PushReader(reader);
+ }
+
+ connectionInfo.LastReceived = order;
+ if (connectionInfo.DeferredMessages != null)
+ {
+ var next = order + 1;
+ while (connectionInfo.DeferredMessages.Remove(next, out var nextBuffer))
+ {
+ reader = new DataStreamReader(FixedBytes1280.ToNativeArray(nextBuffer));
+ connectionInfo.ReceiveQueue.PushReader(reader);
+ connectionInfo.LastReceived = next;
+ ++next;
+ }
+ }
+
+ var message = connectionInfo.ReceiveQueue.PopMessage();
+ while (message.Count != 0)
+ {
+ InvokeOnTransportEvent(NetworkEvent.Data, (ulong)connectionId, message,
+ m_RealTimeProvider.RealTimeSinceStartup);
+ message = connectionInfo.ReceiveQueue.PopMessage();
+ }
+ }
+
+ public override unsafe void Send(ulong clientId, ArraySegment payload, NetworkDelivery networkDelivery)
+ {
+ if (!m_Connections.TryGetValue((int)clientId, out ConnectionInfo connectionInfo))
+ {
+ return;
+ }
+
+ connectionInfo.SendQueue.PushMessage(payload);
+
+ while (!connectionInfo.SendQueue.IsEmpty)
+ {
+ var rpc = new TransportRpc
+ {
+ Buffer = new FixedBytes1280(),
+ };
+
+ var writer = new DataStreamWriter(FixedBytes1280.GetUnsafePtr(rpc.Buffer), k_MaxPacketSize);
+
+ var amount = connectionInfo.SendQueue.FillWriterWithBytes(ref writer, k_MaxPacketSize);
+ rpc.Buffer.Length = amount;
+ rpc.Order = ++connectionInfo.LastSent;
+
+ var req = m_NetworkManager.NetcodeWorld.EntityManager.CreateEntity(ComponentType.ReadWrite(), ComponentType.ReadWrite());
+ m_NetworkManager.NetcodeWorld.EntityManager.SetComponentData(req, new SendRpcCommandRequest{TargetConnection = connectionInfo.Connection.ConnectionEntity});
+ m_NetworkManager.NetcodeWorld.EntityManager.SetComponentData(req, rpc);
+
+ connectionInfo.SendQueue.Consume(amount);
+ }
+ }
+
+ public override NetworkEvent PollEvent(out ulong clientId, out ArraySegment payload, out float receiveTime)
+ {
+ clientId = 0;
+ payload = default;
+ receiveTime = 0;
+ return NetworkEvent.Nothing;
+ }
+
+ private void OnClientConnectedToServer(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ m_Connections[connection.NetworkId.Value] = new ConnectionInfo
+ {
+ ReceiveQueue = null,
+ SendQueue = new BatchedSendQueue(BatchedSendQueue.MaximumMaximumCapacity),
+ Connection = connection
+ };
+ m_ServerClientId = connection.NetworkId.Value;
+ InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
+ }
+
+ private void OnServerNewClientConnection(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ m_Connections[connection.NetworkId.Value] = new ConnectionInfo
+ {
+ ReceiveQueue = null,
+ SendQueue = new BatchedSendQueue(BatchedSendQueue.MaximumMaximumCapacity),
+ Connection = connection
+ }; ;
+ InvokeOnTransportEvent(NetworkEvent.Connect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
+ }
+
+ private const string k_InvalidRpcMessage = "An invalid RPC was received";
+ private const string k_HandshakeTimeoutMessage = "The connection was closed because the handshake timed out.";
+ private const string k_ApprovalFailureMessage = "The connection was closed because the connection was not approved by the server.";
+ private const string k_ApprovalTimeoutMessage = "The connection was closed because the connection approval process timed out.";
+
+ private string GetDisconnectMessageFromNetworkStreamDisconnectReason(NetworkStreamDisconnectReason reason)
+ {
+ switch (reason)
+ {
+ case NetworkStreamDisconnectReason.ConnectionClose:
+ return UnityTransportNotificationHandler.DisconnectedMessage;
+ case NetworkStreamDisconnectReason.Timeout:
+ return UnityTransportNotificationHandler.TimeoutMessage;
+ case NetworkStreamDisconnectReason.MaxConnectionAttempts:
+ return UnityTransportNotificationHandler.MaxConnectionAttemptsMessage;
+ case NetworkStreamDisconnectReason.ClosedByRemote:
+ return UnityTransportNotificationHandler.ClosedRemoteConnectionMessage;
+ case NetworkStreamDisconnectReason.BadProtocolVersion:
+ return UnityTransportNotificationHandler.ProtocolErrorMessage;
+ case NetworkStreamDisconnectReason.InvalidRpc:
+ return k_InvalidRpcMessage;
+ case NetworkStreamDisconnectReason.AuthenticationFailure:
+ return UnityTransportNotificationHandler.AuthenticationFailureMessage;
+ case NetworkStreamDisconnectReason.ProtocolError:
+ return UnityTransportNotificationHandler.ProtocolErrorMessage;
+ case NetworkStreamDisconnectReason.HandshakeTimeout:
+ return k_HandshakeTimeoutMessage;
+ case NetworkStreamDisconnectReason.ApprovalFailure:
+ return k_ApprovalFailureMessage;
+ case NetworkStreamDisconnectReason.ApprovalTimeout:
+ return k_ApprovalTimeoutMessage;
+ }
+ return "Unknown reason";
+ }
+
+ private DisconnectEvents GetDisconnectEventFromNetworkStreamDisconnectReason(NetworkStreamDisconnectReason reason)
+ {
+ switch (reason)
+ {
+ case NetworkStreamDisconnectReason.ConnectionClose:
+ return DisconnectEvents.Disconnected;
+ case NetworkStreamDisconnectReason.Timeout:
+ return DisconnectEvents.ProtocolTimeout;
+ case NetworkStreamDisconnectReason.MaxConnectionAttempts:
+ return DisconnectEvents.MaxConnectionAttempts;
+ case NetworkStreamDisconnectReason.ClosedByRemote:
+ return DisconnectEvents.ClosedByRemote;
+ case NetworkStreamDisconnectReason.BadProtocolVersion:
+ return DisconnectEvents.ProtocolError;
+ case NetworkStreamDisconnectReason.InvalidRpc:
+ return DisconnectEvents.ProtocolError;
+ case NetworkStreamDisconnectReason.AuthenticationFailure:
+ return DisconnectEvents.AuthenticationFailure;
+ case NetworkStreamDisconnectReason.ProtocolError:
+ return DisconnectEvents.ProtocolError;
+ case NetworkStreamDisconnectReason.HandshakeTimeout:
+ return DisconnectEvents.ProtocolError;
+ case NetworkStreamDisconnectReason.ApprovalFailure:
+ return DisconnectEvents.AuthenticationFailure;
+ case NetworkStreamDisconnectReason.ApprovalTimeout:
+ return DisconnectEvents.ProtocolTimeout;
+ }
+ return DisconnectEvents.Disconnected;
+ }
+
+ private void OnClientDisconnectFromServer(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ SetDisconnectEvent(
+ GetDisconnectEventFromNetworkStreamDisconnectReason(connectionEvent.DisconnectReason),
+ GetDisconnectMessageFromNetworkStreamDisconnectReason(connectionEvent.DisconnectReason)
+ );
+ InvokeOnTransportEvent(NetworkEvent.Disconnect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
+ }
+
+ private void OnServerClientDisconnected(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ InvokeOnTransportEvent(NetworkEvent.Disconnect, (ulong)connection.NetworkId.Value, default, m_RealTimeProvider.RealTimeSinceStartup);
+ }
+
+ private void OnClientConnectionEvent(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent.State)
+ {
+ case ConnectionState.State.Connected:
+ OnClientConnectedToServer(connection, connectionEvent);
+ break;
+ case ConnectionState.State.Disconnected:
+ OnClientDisconnectFromServer(connection, connectionEvent);
+ break;
+ }
+ }
+
+ private void OnServerConnectionEvent(Connection connection, NetCodeConnectionEvent connectionEvent)
+ {
+ switch (connectionEvent.State)
+ {
+ case ConnectionState.State.Connected:
+ OnServerNewClientConnection(connection, connectionEvent);
+ break;
+ case ConnectionState.State.Disconnected:
+ OnServerClientDisconnected(connection, connectionEvent);
+ break;
+ }
+ }
+
+ public override bool StartClient()
+ {
+ m_NetworkManager.NetcodeWorld.OnConnectionEvent += OnClientConnectionEvent;
+ var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged();
+ updateSystem.Transport = this;
+ return true;
+ }
+
+ public override bool StartServer()
+ {
+ foreach (var connection in m_NetworkManager.NetcodeWorld.AllConnections)
+ {
+ OnServerNewClientConnection(connection, default);
+ }
+
+ m_NetworkManager.NetcodeWorld.OnConnectionEvent += OnServerConnectionEvent;
+ var updateSystem = m_NetworkManager.NetcodeWorld.GetExistingSystemManaged();
+ updateSystem.Transport = this;
+ return true;
+ }
+
+ public override void DisconnectRemoteClient(ulong clientId)
+ {
+ m_NetworkManager.NetcodeWorld.DisconnectAClient(m_Connections[(int)clientId].Connection);
+ m_Connections.Remove((int)clientId);
+ }
+
+ public override void DisconnectLocalClient()
+ {
+ // Remove the connection 1st (the world might not be available)
+ m_Connections.Remove((int)ServerClientId);
+
+ // TODO-FIX-REVIEW-ME:
+ // This was causing errors to occur upon shutdown during an integration test.
+ // The cases being trapped for below yield no errors, but there might be some
+ // form of other underlying issue here:
+
+ if (m_NetworkManager.NetcodeWorld == null || !m_NetworkManager.NetcodeWorld.IsCreated)
+ {
+ return;
+ }
+
+ if (m_NetworkManager.IsServer || m_NetworkManager.NetcodeWorld.IsHost())
+ {
+ if (m_NetworkManager.LogLevel <= LogLevel.Developer)
+ {
+ Debug.LogWarning("Host is attempting to shutdown the local client which is not required with a single world host.");
+ }
+ return;
+ }
+ m_NetworkManager.NetcodeWorld.RequestDisconnectFromServer();
+
+ }
+
+ public override ulong GetCurrentRtt(ulong clientId)
+ {
+ var (transportId, _) = m_NetworkManager.ConnectionManager.ClientIdToTransportId(clientId);
+ return (ulong)m_Connections[(int)transportId].Connection.RTT;
+ }
+
+ public override void Initialize(NetworkManager networkManager = null)
+ {
+ m_Connections = new Dictionary();
+ m_RealTimeProvider = networkManager.RealTimeProvider;
+ m_NetworkManager = networkManager;
+ }
+
+ public override void Shutdown()
+ {
+
+ }
+ }
+}
+#endif
diff --git a/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs.meta b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs.meta
new file mode 100644
index 0000000000..ace36beccc
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Runtime/Transports/Unified/UnifiedNetcodeTransport.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 9980b4e2027240ceb731e395dc270359
+timeCreated: 1772129541
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef b/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef
index 3d0e9a58bf..98a2cd6a29 100644
--- a/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef
+++ b/com.unity.netcode.gameobjects/Runtime/Unity.Netcode.Runtime.asmdef
@@ -13,7 +13,9 @@
"Unity.Networking.Transport",
"Unity.Collections",
"Unity.Burst",
- "Unity.Mathematics"
+ "Unity.Mathematics",
+ "Unity.NetCode",
+ "Unity.Entities"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -88,6 +90,16 @@
"expression": "6000.5.0a1",
"define": "SCENE_MANAGEMENT_SCENE_HANDLE_MUST_USE_ULONG"
},
+ {
+ "name": "com.unity.netcode",
+ "expression": "1.10.1",
+ "define": "UNIFIED_NETCODE"
+ },
+ {
+ "name": "com.unity.multiplayer.playmode",
+ "expression": "0.1.0",
+ "define": "UNITY_MULTIPLAYER_PLAYMODE"
+ },
{
"name": "Unity",
"expression": "[6000.4.0b5,6000.5.0a1)",
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs
new file mode 100644
index 0000000000..2c61af684b
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs
@@ -0,0 +1,91 @@
+#if UNIFIED_NETCODE
+using System.Collections;
+using NUnit.Framework;
+using Unity.Netcode.Components;
+using Unity.Netcode.TestHelpers.Runtime;
+using UnityEngine;
+using UnityEngine.TestTools;
+
+
+namespace Unity.Netcode.RuntimeTests
+{
+ ///
+ /// Test class that deliberately removes some functionality from NetworkTransform that is conditionally disabled
+ /// by the presence of ghost objects in the base class. This is to help be certain that the network transform
+ /// is not doing the work, but that the work is being done by N4E's snapshots.
+ ///
+ internal class DoNothingNetworkTransform : NetworkTransform
+ {
+ public override void OnNetworkSpawn()
+ {
+ // Deliberately left empty
+ }
+
+ internal override void InternalInitialization(bool isOwnershipChange = false)
+ {
+ // Deliberately left empty
+ }
+ }
+
+ [TestFixture(HostOrServer.UnifiedHost)]
+ internal class UnifiedNetworkTransformTest : IntegrationTestWithApproximation
+ {
+ protected override int NumberOfClients => 2;
+
+ private GameObject m_Prefab;
+ private NetworkObject m_Instance;
+
+ public UnifiedNetworkTransformTest(HostOrServer hostOrServer) : base(hostOrServer)
+ {
+ }
+
+ protected override bool OnSetVerboseDebug()
+ {
+ return true;
+ }
+
+ protected override IEnumerator OnSetup()
+ {
+ // Creates the hybrid prefab
+ m_Prefab = CreateNetworkObjectPrefab("HybridPrefab");
+ m_Prefab.AddComponent();
+ return base.OnSetup();
+ }
+
+ [UnityTest]
+ public IEnumerator BasicMovementTest()
+ {
+ var authority = GetAuthorityNetworkManager();
+ m_Instance = SpawnObject(m_Prefab, m_ServerNetworkManager).GetComponent();
+
+ // Wait 5 seconds so we will dump any deferred messages if it failed on clients
+ // when checking to see if it spawned or not on the clients next.
+ // Enable this to debug deferred
+ //yield return new WaitForSeconds(5);
+
+ yield return WaitForSpawnedOnAllOrTimeOut(m_Instance);
+ AssertOnTimeout($"Failed to spawn {m_Instance.name} on all clients!");
+
+ VerboseDebug("All clients spawned instance!");
+
+ var originalPos = authority.LocalClient.PlayerObject.transform.position;
+ var newPos = originalPos + new Vector3(1, 1, 1);
+
+ m_Instance.transform.position = newPos;
+
+ foreach (var client in m_ClientNetworkManagers)
+ {
+ Assert.IsTrue(Approximately(originalPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position));
+ }
+
+ yield return new WaitForSeconds(1);
+
+ foreach (var client in m_ClientNetworkManagers)
+ {
+ Assert.IsTrue(Approximately(newPos, s_GlobalNetworkObjects[client.LocalClientId][m_Instance.NetworkObjectId].transform.position));
+ }
+ VerboseDebug("Test Passed!");
+ }
+ }
+}
+#endif
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta
new file mode 100644
index 0000000000..10d990cfa3
--- /dev/null
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkTransform/UnifiedNetworkTransformTest.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 0f26fa7bd5474b3f9947e0813374b50f
+timeCreated: 1775078549
\ No newline at end of file
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs
index ecaf3ec792..7f4dba5f16 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkUpdateLoopTests.cs
@@ -1,6 +1,6 @@
using System;
using System.Collections;
-using System.Linq;
+using System.Collections.Generic;
using NUnit.Framework;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
@@ -41,9 +41,10 @@ public void RegisterCustomLoopInTheMiddle()
PlayerLoop.SetPlayerLoop(curPlayerLoop);
NetworkUpdateLoop.UnregisterLoopSystems();
-
+ var subSystemArray = PlayerLoop.GetCurrentPlayerLoop().subSystemList[0].subSystemList;
+ var lastType = subSystemArray[subSystemArray.Length - 1].type;
// our custom `PlayerLoopSystem` with the type of `NetworkUpdateLoopTests` should still exist
- Assert.AreEqual(typeof(NetworkUpdateLoopTests), PlayerLoop.GetCurrentPlayerLoop().subSystemList[0].subSystemList.Last().type);
+ Assert.AreEqual(typeof(NetworkUpdateLoopTests), lastType);
}
// replace the current PlayerLoop with the cached PlayerLoop after the test
PlayerLoop.SetPlayerLoop(cachedPlayerLoop);
@@ -94,7 +95,14 @@ public void UpdateStageSystems()
for (int i = 0; i < currentPlayerLoop.subSystemList.Length; i++)
{
var playerLoopSystem = currentPlayerLoop.subSystemList[i];
- var subsystems = playerLoopSystem.subSystemList.ToList();
+ // New behaviour (6000.6.x)
+ // Some PlayerLoopSystems can evidently now have no sub-system lists.
+ if (playerLoopSystem.subSystemList == null)
+ {
+ // Ignore any PlayerLoopSystem with no sub-system lists.
+ continue;
+ }
+ var subsystems = new List(playerLoopSystem.subSystemList);
if (playerLoopSystem.type == typeof(Initialization))
{
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs
index 5f3be93f57..daceeee8b9 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/NetworkVariable/NetworkListTests.cs
@@ -15,6 +15,10 @@ namespace Unity.Netcode.RuntimeTests
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.DAHost)]
[TestFixture(HostOrServer.Server)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+ [TestFixture(HostOrServer.UnifiedServer)]
+#endif
internal class NetworkListTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 3;
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
index a6f4a83275..570646a17a 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTest.cs
@@ -6,6 +6,9 @@
using System.Runtime.CompilerServices;
using System.Text;
using NUnit.Framework;
+#if UNIFIED_NETCODE
+using Unity.NetCode;
+#endif
using Unity.Netcode.RuntimeTests;
using Unity.Netcode.Transports.UTP;
using UnityEngine;
@@ -223,7 +226,17 @@ public enum HostOrServer
///
/// Denotes that distributed authority is being used.
///
- DAHost
+ DAHost,
+#if UNIFIED_NETCODE
+ ///
+ /// Use N4E-backed hybrid spawning in server mode
+ ///
+ UnifiedServer,
+ ///
+ /// Use N4E-backed hybrid spawning in host mode
+ ///
+ UnifiedHost
+#endif
}
///
@@ -626,6 +639,12 @@ private void InternalOnOneTimeSetup()
///
protected virtual IEnumerator OnSetup()
{
+#if UNIFIED_NETCODE
+ if (m_AllPrefabsAsHybrid)
+ {
+ NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost;
+ }
+#endif
yield return null;
}
@@ -639,6 +658,12 @@ protected virtual IEnumerator OnSetup()
///
protected virtual void OnInlineSetup()
{
+#if UNIFIED_NETCODE
+ if (m_AllPrefabsAsHybrid)
+ {
+ NetworkSpawnManager.RegisterPendingGhost = RegisterPendingGhost;
+ }
+#endif
}
///
@@ -705,6 +730,24 @@ public IEnumerator SetUp()
VerboseDebug($"Exiting {nameof(SetUp)}");
}
+#if UNIFIED_NETCODE
+ private void RegisterPendingGhost(NetworkObject networkObject, ulong networkObjectId)
+ {
+ var ghost = networkObject.GetComponent();
+ Assert.IsNotNull(ghost, $"[RegisterPendingGhost][NetworkObject-{networkObjectId}] Has no {nameof(GhostAdapter)}!");
+ foreach (var networkManager in m_NetworkManagers)
+ {
+ // If the world matches, then register the instance with this NetworkManager's spawn manager.
+ if (networkManager.NetcodeWorld == ghost.World)
+ {
+ networkManager.SpawnManager.RegisterGhostPendingSpawn(networkObject, networkObjectId);
+ return;
+ }
+ }
+ Debug.LogError($"Did not find a world for NetworkObject-{networkObjectId}!!");
+ }
+#endif
+
///
/// Override this to add components or adjustments to the default player prefab
///
@@ -824,7 +867,20 @@ protected void CreateServerAndClients(int numberOfClients)
{
manager.NetworkConfig.PlayerPrefab = m_PlayerPrefab;
SetDistributedAuthorityProperties(manager);
+#if UNIFIED_NETCODE
+ foreach (var pendingPrefab in m_PendingPrefabs)
+ {
+ var prefab = new NetworkPrefab()
+ {
+ Prefab = pendingPrefab
+ };
+ manager.NetworkConfig.Prefabs.Add(prefab);
+ }
+#endif
}
+#if UNIFIED_NETCODE
+ m_PendingPrefabs.Clear();
+#endif
// Provides opportunity to allow child derived classes to
// modify the NetworkManager's configuration before starting.
@@ -1613,6 +1669,21 @@ protected IEnumerator CoroutineShutdownAndCleanUp()
DestroyNetworkManagers();
}
+ ///
+ /// When using hybrid spawning, this handles clean up.
+ ///
+ protected void UnifiedCleanup()
+ {
+#if UNIFIED_NETCODE
+ if (m_AllPrefabsAsHybrid)
+ {
+ m_PendingPrefabs.Clear();
+ NetworkSpawnManager.RegisterPendingGhost = null;
+ CleanupPrefabReferences();
+ }
+#endif
+ }
+
///
/// Note: For mode
/// this is called before ShutdownAndCleanUp.
@@ -1620,6 +1691,7 @@ protected IEnumerator CoroutineShutdownAndCleanUp()
///
protected virtual IEnumerator OnTearDown()
{
+ UnifiedCleanup();
yield return null;
}
@@ -1628,6 +1700,7 @@ protected virtual IEnumerator OnTearDown()
///
protected virtual void OnInlineTearDown()
{
+ UnifiedCleanup();
}
///
@@ -1763,6 +1836,24 @@ protected void DestroySceneNetworkObjects()
if (CanDestroyNetworkObject(networkObject))
{
+#if UNIFIED_NETCODE
+ // Handle removing the prefab reference and destroying it
+ // and then destroying the ghostAdapter prior to destroying
+ // a hybrid prefab.
+ var ghostAdapter = networkObject.GetComponent();
+ if (ghostAdapter != null)
+ {
+ if (ghostAdapter.prefabReference != null)
+ {
+ var prefabReference = ghostAdapter.prefabReference;
+ prefabReference.Prefab = null;
+ ghostAdapter.prefabReference = null;
+ Object.Destroy(prefabReference);
+ }
+ Object.Destroy(networkObject.gameObject);
+ continue;
+ }
+#endif
// Destroy the GameObject that holds the NetworkObject component
Object.DestroyImmediate(networkObject.gameObject);
}
@@ -2189,6 +2280,10 @@ internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder,
Assert.True(WaitForConditionOrTimeOutWithTimeTravel(hooks), $"[Messages Not Recieved] {hooks.GetHooksStillWaiting()}");
}
+#if UNIFIED_NETCODE
+ protected bool m_AllPrefabsAsHybrid = false;
+#endif
+
///
/// Creates a basic NetworkObject test prefab, assigns it to a new
/// NetworkPrefab entry, and then adds it to the server and client(s)
@@ -2198,6 +2293,12 @@ internal void WaitForMessagesReceivedWithTimeTravel(List messagesInOrder,
/// The assigned to the new NetworkPrefab entry
protected GameObject CreateNetworkObjectPrefab(string baseName)
{
+#if UNIFIED_NETCODE
+ if (m_AllPrefabsAsHybrid)
+ {
+ return CreateHybridPrefab(baseName, true);
+ }
+#endif
var prefabCreateAssertError = $"You can only invoke this method during {nameof(OnServerAndClientsCreated)} " +
$"but before {nameof(OnStartedServerAndClients)}!";
var authorityNetworkManager = GetAuthorityNetworkManager();
@@ -2210,6 +2311,100 @@ protected GameObject CreateNetworkObjectPrefab(string baseName)
return prefabObject;
}
+#if UNIFIED_NETCODE
+ // Pending prefabs declared before NetworkManagers instantiated
+ private List m_PendingPrefabs = new List();
+ protected void CleanupPrefabReferences()
+ {
+ foreach (var reference in Object.FindObjectsByType())
+ {
+ Object.Destroy(reference);
+ }
+ }
+ protected GameObject CreateHybridPrefab(string baseName, bool moveToDDOL = true)
+ {
+ // Prevent from trying to register/spawn when creating this hybrid prefab
+ var gameObject = new GameObject
+ {
+ name = baseName
+ };
+
+ // Order of operations in how these execute is actually important.
+ // GhostObject should execute 1st.
+ // NetworkObjectBridge 2nd.
+ // NetworkObject 3rd.
+ // NetworkBehaviours will execute in the order they are arranged unless otherwise specified.
+
+ // When adding a Hybrid/Ghost prefab:
+ // - We disabled the GameObject prior to adding the GhostPrefabReference (so IsPrefab() == true).
+ // - Add the GhostAdapter and GhostPrefabReference
+ // - Then set it back to active.
+ gameObject.SetActive(false);
+ var adapter = gameObject.AddComponent();
+ // Mark the reference as post processing to avoid registering this instance automatically.
+ GhostPrefabReference.s_IsPostProcessing = true;
+ adapter.prefabReference = ScriptableObject.CreateInstance();
+ adapter.prefabReference.name = "GhostPrefabReference";
+ adapter.prefabReference.Prefab = gameObject;
+
+ GhostPrefabReference.s_IsPostProcessing = false;
+
+ // TODO: This might be part of the CreateHybridPrefab parameters
+ // For now, just use normal interpolation until we get integration
+ // tests running.
+ // Once we have validated prediction works and have a working manual
+ // test, we can circle back to this (possibly make that a sub-task
+ // with the dependency to prediction manual test).
+ adapter.SupportedGhostModes = GhostModeMask.Interpolated;
+
+ // Once done with setting up the GhostAdapter, we can set it back to active in the hierarchy
+ gameObject.SetActive(true);
+
+ // GhostBehaviours that are part of a prefab will not invoke Ghost.InternalAcquireEntityReference
+ // Add the bridge
+ var bridge = gameObject.AddComponent();
+
+ // Now add NGO components
+ var no = gameObject.AddComponent();
+
+ // NetworkObject Ghost specific settings
+ no.HasGhost = true;
+ no.GhostAdapter = adapter;
+ no.HadBridge = true;
+ no.NetworkObjectBridge = bridge;
+
+ // Disable transform synchronization for NetworkObject serialization
+ // since that is handled by the GhostAdapter.
+ no.SynchronizeTransform = false;
+
+ // Turn it into a test prefab
+ NetcodeIntegrationTestHelpers.MakeNetworkObjectTestPrefab(no);
+ if (moveToDDOL)
+ {
+ Object.DontDestroyOnLoad(gameObject);
+ }
+ var authorityNetworkManager = GetAuthorityNetworkManager();
+ if (authorityNetworkManager == null)
+ {
+ m_PendingPrefabs.Add(gameObject);
+ }
+ else
+ {
+ authorityNetworkManager.AddNetworkPrefab(gameObject);
+ foreach (var clientNetworkManager in m_ClientNetworkManagers)
+ {
+ if (clientNetworkManager == authorityNetworkManager)
+ {
+ continue;
+ }
+ clientNetworkManager.AddNetworkPrefab(gameObject);
+ }
+ }
+ return gameObject;
+ }
+#endif
+
+
///
/// Overloaded method
///
@@ -2255,6 +2450,14 @@ internal void SpawnInstanceWithOwnership(NetworkObject networkObjectToSpawn, Net
}
else
{
+#if UNIFIED_NETCODE
+ // TODO-FixMe: NetCode.Netcode.Instance is a singleton and might cause issues
+ // assigning this.
+ if (networkObjectToSpawn.HasGhost)
+ {
+ NetCode.Netcode.Instance.m_ActiveWorld = m_ServerNetworkManager.NetcodeWorld;
+ }
+#endif
networkObjectToSpawn.NetworkManagerOwner = m_ServerNetworkManager; // Required to assure the server does the spawning
if (spawnAuthority == m_ServerNetworkManager)
{
@@ -2417,8 +2620,12 @@ private void InitializeTestConfiguration(NetworkTopologyTypes networkTopologyTyp
// Note: For m_DistributedAuthority to be true, the m_NetworkTopologyType must be set to NetworkTopologyTypes.DistributedAuthority
hostOrServer = m_DistributedAuthority ? HostOrServer.DAHost : HostOrServer.Host;
}
+#if UNIFIED_NETCODE
+ m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost || hostOrServer == HostOrServer.UnifiedHost;
+ m_AllPrefabsAsHybrid = (hostOrServer == HostOrServer.UnifiedServer || hostOrServer == HostOrServer.UnifiedHost);
+#else
m_UseHost = hostOrServer == HostOrServer.Host || hostOrServer == HostOrServer.DAHost;
-
+#endif
// If we are using a distributed authority network topology and the environment variable
// to use the CMBService is set, then perform the m_UseCmbService check.
if (m_DistributedAuthority && GetServiceEnvironmentVariable())
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs
index 32a0f734d7..6e031c6866 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/TestHelpers/NetcodeIntegrationTestHelpers.cs
@@ -745,18 +745,17 @@ public static GameObject CreateNetworkObjectPrefab(string baseName, NetworkManag
Assert.IsFalse(authorityNetworkManager.IsListening, prefabCreateAssertError);
var gameObject = CreateNetworkObject(baseName);
- var networkPrefab = new NetworkPrefab() { Prefab = gameObject };
// We could refactor this test framework to share a NetworkPrefabList instance, but at this point it's
// probably more trouble than it's worth to verify these lists stay in sync across all tests...
- authorityNetworkManager.NetworkConfig.Prefabs.Add(networkPrefab);
+ authorityNetworkManager.AddNetworkPrefab(gameObject);
foreach (var clientNetworkManager in clients)
{
if (clientNetworkManager == authorityNetworkManager)
{
continue;
}
- clientNetworkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab() { Prefab = gameObject });
+ clientNetworkManager.AddNetworkPrefab(gameObject);
}
return gameObject;
}
diff --git a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef
index cde139b07f..86b6bae95f 100644
--- a/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef
+++ b/com.unity.netcode.gameobjects/Tests/Runtime/Unity.Netcode.Runtime.Tests.asmdef
@@ -13,7 +13,9 @@
"Unity.Netcode.TestHelpers.Runtime",
"Unity.Mathematics",
"UnityEngine.TestRunner",
- "UnityEditor.TestRunner"
+ "UnityEditor.TestRunner",
+ "Unity.NetCode",
+ "Unity.Entities"
],
"includePlatforms": [],
"excludePlatforms": [],
@@ -47,6 +49,11 @@
"name": "Unity",
"expression": "6000.1.0a1",
"define": "HOSTNAME_RESOLUTION_AVAILABLE"
+ },
+ {
+ "name": "com.unity.netcode",
+ "expression": "1.10.1",
+ "define": "UNIFIED_NETCODE"
}
],
"noEngineReferences": false
diff --git a/pvpExceptions.json b/pvpExceptions.json
index c5bd307e4d..e92be2a8a5 100644
--- a/pvpExceptions.json
+++ b/pvpExceptions.json
@@ -15,7 +15,8 @@
},
"PVP-132-2": {
"errors": [
- "Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef: name of editor assembly should end with '.Editor'"
+ "Editor/CodeGen/Unity.Netcode.Editor.CodeGen.asmdef: name of editor assembly should end with '.Editor'",
+ "Editor/PackageChecker/Unity.Netcode.PackageChecker.Editor.asmdef: name of editor assembly should end with '.Editor'"
]
}
}
diff --git a/testproject/Assets/NetCodeConfig.asset b/testproject/Assets/NetCodeConfig.asset
new file mode 100644
index 0000000000..f6b1280970
--- /dev/null
+++ b/testproject/Assets/NetCodeConfig.asset
@@ -0,0 +1,76 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!114 &11400000
+MonoBehaviour:
+ m_ObjectHideFlags: 0
+ m_CorrespondingSourceObject: {fileID: 0}
+ m_PrefabInstance: {fileID: 0}
+ m_PrefabAsset: {fileID: 0}
+ m_GameObject: {fileID: 0}
+ m_Enabled: 1
+ m_EditorHideFlags: 0
+ m_Script: {fileID: 11500000, guid: abd30ee0214cf6a45b2d76765a4615b1, type: 3}
+ m_Name: NetCodeConfig
+ m_EditorClassIdentifier: Unity.NetCode::Unity.NetCode.NetCodeConfig
+ IsGlobalConfig: 1
+ EnableClientServerBootstrap: 0
+ HostWorldModeSelection: 1
+ ClientServerTickRate:
+ SimulationTickRate: 30
+ PredictedFixedStepSimulationTickRatio: 1
+ NetworkTickRate: 0
+ MaxSimulationStepsPerFrame: 1
+ MaxSimulationStepBatchSize: 4
+ TargetFrameRateMode: 0
+ m_SendSnapshotsForCatchUpTicks: 0
+ SnapshotAckMaskCapacity: 4096
+ m_ClampPartialTicksThreshold: 5
+ HandshakeApprovalTimeoutMS: 5000
+ ClientTickRate:
+ InterpolationTimeNetTicks: 2
+ InterpolationTimeMS: 0
+ MaxExtrapolationTimeSimTicks: 20
+ ForcedInputLatencyTicks: 0
+ MaxPredictAheadTimeMS: 500
+ NumAdditionalClientPredictedGhostLifetimeTicks: 0
+ DefaultClassificationAllowableTickPeriod: 5
+ TargetCommandSlack: 2
+ NumAdditionalCommandsToSend: 2
+ MaxPredictionStepBatchSizeRepeatedTick: 0
+ MaxPredictionStepBatchSizeFirstTimeTick: 0
+ PredictionLoopUpdateMode: 0
+ InterpolationDelayJitterScale: 1.25
+ InterpolationDelayMaxDeltaTicksFraction: 0.1
+ InterpolationDelayCorrectionFraction: 0.1
+ InterpolationTimeScaleMin: 0.85
+ InterpolationTimeScaleMax: 1.1
+ CommandAgeCorrectionFraction: 0.1
+ PredictionTimeScaleMin: 0.9
+ PredictionTimeScaleMax: 1.1
+ GhostSendSystemData:
+ DefaultSnapshotPacketSize: 0
+ PercentReservedForDespawnMessages: 0.33
+ MinSendImportance: 0
+ MinDistanceScaledSendImportance: 0
+ MaxIterateChunks: 0
+ MaxSendChunks: 0
+ MaxSendEntities: 0
+ m_ForceSingleBaseline: 0
+ m_ForcePreSerialize: 0
+ m_KeepSnapshotHistoryOnStructuralChange: 1
+ m_EnablePerComponentProfiling: 0
+ CleanupConnectionStatePerTick: 1
+ m_FirstSendImportanceMultiplier: 1
+ m_IrrelevantImportanceDownScale: 1
+ m_TempStreamSize: 8192
+ m_UseCustomSerializer: 0
+ ConnectTimeoutMS: 1000
+ MaxConnectAttempts: 60
+ DisconnectTimeoutMS: 30000
+ HeartbeatTimeoutMS: 500
+ ReconnectionTimeoutMS: 2000
+ ClientSendQueueCapacity: 64
+ ClientReceiveQueueCapacity: 64
+ ServerSendQueueCapacity: 512
+ ServerReceiveQueueCapacity: 512
+ MaxMessageSize: 1400
diff --git a/testproject/Assets/NetCodeConfig.asset.meta b/testproject/Assets/NetCodeConfig.asset.meta
new file mode 100644
index 0000000000..222ccfadf4
--- /dev/null
+++ b/testproject/Assets/NetCodeConfig.asset.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c547acbddd81d32a0ba5e62ddfc4f4e3
+NativeFormatImporter:
+ externalObjects: {}
+ mainObjectFileID: 11400000
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs
index 0ab1566882..4cb30cb930 100644
--- a/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs
+++ b/testproject/Assets/Tests/Manual/DontDestroyOnLoad/ObjectToNotDestroyBehaviour.cs
@@ -9,9 +9,12 @@ namespace TestProject.ManualTests
///
public class ObjectToNotDestroyBehaviour : NetworkBehaviour
{
+ public static bool VerboseDebug;
+
private bool m_ContinueSendingPing;
private uint m_PingCounter;
+
public uint CurrentPing
{
get
@@ -20,12 +23,19 @@ public uint CurrentPing
}
}
- ///
- /// When enabled, we move ourself to the DontDestroyOnLoad scene
- ///
- private void OnEnable()
+ private void Log(string msg)
+ {
+ if (VerboseDebug)
+ {
+ Debug.Log(msg);
+ }
+ }
+
+ // Migrate into DDOL during pre-spawn
+ protected override void OnNetworkPreSpawn(ref NetworkManager networkManager)
{
DontDestroyOnLoad(this);
+ base.OnNetworkPreSpawn(ref networkManager);
}
///
@@ -38,11 +48,11 @@ private void PingUpdateClientRpc(uint pingNumber)
{
if (IsHost)
{
- Debug.Log($"Sent ping number ({pingNumber}).");
+ Log($"Sent ping number ({pingNumber}).");
}
else if (IsClient)
{
- Debug.Log($"Receiving ping number ({pingNumber}) from server");
+ Log($"Receiving ping number ({pingNumber}) from server");
m_PingCounter = pingNumber;
}
}
diff --git a/testproject/Assets/Tests/Runtime/AddressablesTests.cs b/testproject/Assets/Tests/Runtime/AddressablesTests.cs
index 4c9151c0f3..fb492b9540 100644
--- a/testproject/Assets/Tests/Runtime/AddressablesTests.cs
+++ b/testproject/Assets/Tests/Runtime/AddressablesTests.cs
@@ -15,6 +15,10 @@ namespace TestProject.RuntimeTests
[TestFixture(HostOrServer.DAHost)]
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.Server)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+ [TestFixture(HostOrServer.UnifiedServer)]
+#endif
public class AddressablesTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
diff --git a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs
index 105265b08f..2d841049ca 100644
--- a/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs
+++ b/testproject/Assets/Tests/Runtime/DontDestroyOnLoadTests.cs
@@ -11,6 +11,9 @@ namespace TestProject.RuntimeTests
{
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.DAHost)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+#endif
public class DontDestroyOnLoadTests : NetcodeIntegrationTest
{
private const int k_ClientsToConnect = 4;
diff --git a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs
index 771a90b9b8..0582211d0f 100644
--- a/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkBehaviourSessionSynchronized.cs
@@ -9,6 +9,9 @@ namespace TestProject.RuntimeTests
{
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.DAHost)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+#endif
public class NetworkBehaviourSessionSynchronized : NetcodeIntegrationTest
{
private const string k_SceneToLoad = "SessionSynchronize";
diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs b/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs
index ad5fe5e1b0..57a2660601 100644
--- a/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkObjectDestroyWithSceneTests.cs
@@ -10,6 +10,9 @@ namespace TestProject.RuntimeTests
{
[TestFixture(HostOrServer.DAHost)]
[TestFixture(HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+#endif
internal class NetworkObjectDestroyWithSceneTests : NetcodeIntegrationTest
{
private const string k_SceneToLoad = "EmptyScene";
@@ -98,7 +101,7 @@ public IEnumerator DestroyWithScene()
// Depending on network topology, spawn the object with the appropriate owner.
var owner = m_DistributedAuthority ? m_NotSessionOwner : m_SessionOwner;
- m_SpawnedInstance = SpawnObject(m_TestPrefab.gameObject, m_NotSessionOwner, true).GetComponent();
+ m_SpawnedInstance = SpawnObject(m_TestPrefab.gameObject, owner, true).GetComponent();
var instanceName = m_SpawnedInstance.name;
yield return WaitForConditionOrTimeOut(() => ObjectSpawnedOnAllNetworkManagers(true));
diff --git a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs
index 6ed56b04ed..1b1f0e8c32 100644
--- a/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkObjectSpawning.cs
@@ -9,8 +9,11 @@
namespace TestProject.RuntimeTests
{
- [TestFixture(NetworkTopologyTypes.ClientServer)]
- [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)]
+#endif
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
internal class NetworkObjectSpawning : NetcodeIntegrationTest
{
private const string k_SceneToLoad = "NetworkObjectSpawnerTest";
@@ -27,7 +30,7 @@ protected override bool UseCMBService()
return false;
}
- public NetworkObjectSpawning(NetworkTopologyTypes networkTopology) : base(networkTopology) { }
+ public NetworkObjectSpawning(NetworkTopologyTypes networkTopology, HostOrServer hostOrServer) : base(networkTopology, hostOrServer) { }
protected override IEnumerator OnSetup()
diff --git a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs
index a37a3244d2..f1bdc67c66 100644
--- a/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs
+++ b/testproject/Assets/Tests/Runtime/NetworkSceneManager/SceneEventDataTests.cs
@@ -38,12 +38,10 @@ public IEnumerator FastReaderAllocationTest()
var networkManager = networkManagerGameObject.AddComponent();
var unityTransport = networkManagerGameObject.AddComponent();
- var prefabs = ScriptableObject.CreateInstance();
- prefabs.Add(new NetworkPrefab());
networkManager.NetworkConfig = new NetworkConfig()
{
ConnectionApproval = false,
- Prefabs = new NetworkPrefabs { NetworkPrefabsLists = new List { prefabs } },
+ Prefabs = new NetworkPrefabs { NetworkPrefabsLists = new List { } },
NetworkTransport = unityTransport
};
diff --git a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs
index ea81ac8409..224460e8c8 100644
--- a/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs
+++ b/testproject/Assets/Tests/Runtime/OnNetworkSpawnExceptionTests.cs
@@ -2,11 +2,12 @@
using System.Collections;
using System.Collections.Generic;
using System.Text.RegularExpressions;
+using NUnit.Framework;
using Unity.Netcode;
using Unity.Netcode.TestHelpers.Runtime;
using UnityEngine;
-using UnityEngine.Assertions;
using UnityEngine.TestTools;
+using Assert = UnityEngine.Assertions.Assert;
using Random = UnityEngine.Random;
namespace TestProject.RuntimeTests
@@ -82,6 +83,10 @@ public override void OnNetworkDespawn()
}
}
+ [TestFixture(HostOrServer.Server)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedServer)]
+#endif
public class OnNetworkSpawnExceptionTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
@@ -101,6 +106,11 @@ protected override bool UseCMBService()
return false;
}
+ public OnNetworkSpawnExceptionTests(HostOrServer hostOrServer) : base(hostOrServer)
+ {
+
+ }
+
[UnityTest]
public IEnumerator WhenOnNetworkSpawnThrowsException_FutureOnNetworkSpawnsAreNotPrevented()
{
@@ -238,7 +248,6 @@ public IEnumerator WhenOnNetworkDespawnThrowsException_FutureOnNetworkDespawnsAr
protected override IEnumerator OnSetup()
{
- m_UseHost = false;
OnNetworkSpawnThrowsExceptionComponent.NumClientSpawns = 0;
OnNetworkSpawnThrowsExceptionComponent.NumServerSpawns = 0;
OnNetworkSpawnNoExceptionComponent.NumClientSpawns = 0;
diff --git a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs
index d0f7e294cb..9fb4c7a603 100644
--- a/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs
+++ b/testproject/Assets/Tests/Runtime/PrefabExtendedTests.cs
@@ -13,8 +13,12 @@
namespace TestProject.RuntimeTests
{
// DAMODE-TODO: When scene management is working in distributed authority mode we need to update this test
- [TestFixture(SceneManagementTypes.SceneManagementEnabled)]
- [TestFixture(SceneManagementTypes.SceneManagementDisabled)]
+ [TestFixture(SceneManagementTypes.SceneManagementEnabled, HostOrServer.Host)]
+ [TestFixture(SceneManagementTypes.SceneManagementDisabled, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(SceneManagementTypes.SceneManagementEnabled, HostOrServer.UnifiedHost)]
+ [TestFixture(SceneManagementTypes.SceneManagementDisabled, HostOrServer.UnifiedHost)]
+#endif
public class PrefabExtendedTests : NetcodeIntegrationTest
{
private const string k_PrefabTestScene = "PrefabTestScene";
@@ -42,7 +46,7 @@ protected override bool UseCMBService()
return false;
}
- public PrefabExtendedTests(SceneManagementTypes sceneManagementType)
+ public PrefabExtendedTests(SceneManagementTypes sceneManagementType, HostOrServer hostOrServer) : base(hostOrServer)
{
m_SceneManagementEnabled = sceneManagementType == SceneManagementTypes.SceneManagementEnabled;
}
diff --git a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs
index 7dbdc2635f..857c14ee6f 100644
--- a/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs
+++ b/testproject/Assets/Tests/Runtime/RespawnInSceneObjectsAfterShutdown.cs
@@ -8,8 +8,11 @@
namespace TestProject.RuntimeTests
{
- [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
- [TestFixture(NetworkTopologyTypes.ClientServer)]
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)]
+#endif
public class RespawnInSceneObjectsAfterShutdown : NetcodeIntegrationTest
{
public const string SceneToLoad = "InSceneNetworkObject";
@@ -23,7 +26,7 @@ protected override bool UseCMBService()
return false;
}
- public RespawnInSceneObjectsAfterShutdown(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { }
+ public RespawnInSceneObjectsAfterShutdown(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }
protected override void OnOneTimeSetup()
{
diff --git a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
index b71c7e03dd..3ce2642a6c 100644
--- a/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
+++ b/testproject/Assets/Tests/Runtime/RpcObserverTests.cs
@@ -18,6 +18,10 @@ namespace TestProject.RuntimeTests
[TestFixture(HostOrServer.DAHost)]
[TestFixture(HostOrServer.Host)]
[TestFixture(HostOrServer.Server)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+ [TestFixture(HostOrServer.UnifiedServer)]
+#endif
public class RpcObserverTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 9;
@@ -155,6 +159,12 @@ private IEnumerator RunRpcObserverTest(List nonObservers)
[UnityTest]
public IEnumerator DespawnRespawnObserverTest()
{
+#if UNIFIED_NETCODE
+ if (m_AllPrefabsAsHybrid)
+ {
+ Assert.Ignore("Hybrid spawning does not support despawn-without-destroy.");
+ }
+#endif
var nonObservers = new List();
m_ServerRpcObserverObject.ResetTest();
// Wait for all clients to report they have spawned an instance of our test prefab
diff --git a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs
index 228bfbfdd5..d37a3379ad 100644
--- a/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs
+++ b/testproject/Assets/Tests/Runtime/RpcTestsAutomated.cs
@@ -10,6 +10,10 @@
namespace TestProject.RuntimeTests
{
+ [TestFixture(HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+#endif
public class RpcTestsAutomated : NetcodeIntegrationTest
{
private bool m_TimedOut;
@@ -23,6 +27,11 @@ protected override bool UseCMBService()
return false;
}
+ public RpcTestsAutomated(HostOrServer hostOrServer) : base(hostOrServer)
+ {
+
+ }
+
protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode()
{
return NetworkManagerInstatiationMode.DoNotCreate;
diff --git a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
index ae10ea60c9..5a92112f32 100644
--- a/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
+++ b/testproject/Assets/Tests/Runtime/RpcUserSerializableTypesTest.cs
@@ -49,8 +49,11 @@ public void NetworkSerialize(BufferSerializer
}
- [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
- [TestFixture(NetworkTopologyTypes.ClientServer)]
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)]
+#endif
public class RpcUserSerializableTypesTest : NetcodeIntegrationTest
{
private UserSerializableClass m_UserSerializableClass;
@@ -88,7 +91,7 @@ protected override bool UseCMBService()
return false;
}
- public RpcUserSerializableTypesTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { }
+ public RpcUserSerializableTypesTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }
protected override NetworkManagerInstatiationMode OnSetIntegrationTestMode()
{
diff --git a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs
index f82b7ed400..3fcc9feae6 100644
--- a/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs
+++ b/testproject/Assets/Tests/Runtime/SceneObjectsNotDestroyedOnShutdownTest.cs
@@ -9,8 +9,11 @@
namespace TestProject.RuntimeTests
{
- [TestFixture(NetworkTopologyTypes.ClientServer)]
- [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)]
+#endif
public class SceneObjectsNotDestroyedOnShutdownTest : NetcodeIntegrationTest
{
protected override int NumberOfClients => 0;
@@ -20,7 +23,7 @@ public class SceneObjectsNotDestroyedOnShutdownTest : NetcodeIntegrationTest
private Scene m_TestScene;
private WaitForSeconds m_DefaultWaitForTick = new(1.0f / 30);
- public SceneObjectsNotDestroyedOnShutdownTest(NetworkTopologyTypes topology) : base(topology) { }
+ public SceneObjectsNotDestroyedOnShutdownTest(NetworkTopologyTypes topology, HostOrServer hostOrServer) : base(topology, hostOrServer) { }
[UnityTest]
public IEnumerator SceneObjectsNotDestroyedOnShutdown()
diff --git a/testproject/Assets/Tests/Runtime/SenderIdTests.cs b/testproject/Assets/Tests/Runtime/SenderIdTests.cs
index 92ebf2f4a1..768a51ae53 100644
--- a/testproject/Assets/Tests/Runtime/SenderIdTests.cs
+++ b/testproject/Assets/Tests/Runtime/SenderIdTests.cs
@@ -10,6 +10,10 @@
namespace TestProject.RuntimeTests
{
+ [TestFixture(HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(HostOrServer.UnifiedHost)]
+#endif
public class SenderIdTests : NetcodeIntegrationTest
{
protected override int NumberOfClients => 2;
@@ -17,6 +21,8 @@ public class SenderIdTests : NetcodeIntegrationTest
private NetworkManager FirstClient => m_ClientNetworkManagers[0];
private NetworkManager SecondClient => m_ClientNetworkManagers[1];
+ public SenderIdTests(HostOrServer hostOrServer) : base(hostOrServer) { }
+
[UnityTest]
public IEnumerator WhenSendingMessageFromServerToClient_SenderIdIsCorrect()
{
diff --git a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
index c16cceda66..cd730b2099 100644
--- a/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
+++ b/testproject/Assets/Tests/Runtime/ServerDisconnectsClientTest.cs
@@ -9,8 +9,11 @@
namespace TestProject.RuntimeTests
{
- [TestFixture(NetworkTopologyTypes.DistributedAuthority)]
- [TestFixture(NetworkTopologyTypes.ClientServer)]
+ [TestFixture(NetworkTopologyTypes.DistributedAuthority, HostOrServer.DAHost)]
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.Host)]
+#if UNIFIED_NETCODE
+ [TestFixture(NetworkTopologyTypes.ClientServer, HostOrServer.UnifiedHost)]
+#endif
public class ServerDisconnectsClientTest : NetcodeIntegrationTest
{
protected override int NumberOfClients => 1;
@@ -21,7 +24,7 @@ protected override bool UseCMBService()
return false;
}
- public ServerDisconnectsClientTest(NetworkTopologyTypes networkTopologyType) : base(networkTopologyType) { }
+ public ServerDisconnectsClientTest(NetworkTopologyTypes networkTopologyType, HostOrServer hostOrServer) : base(networkTopologyType, hostOrServer) { }
protected override void OnCreatePlayerPrefab()
{
diff --git a/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef b/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef
index 38af444a61..51b61982a5 100644
--- a/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef
+++ b/testproject/Assets/Tests/Runtime/TestProject.Runtime.Tests.asmdef
@@ -33,6 +33,11 @@
"name": "com.unity.addressables",
"expression": "",
"define": "TESTPROJECT_USE_ADDRESSABLES"
+ },
+ {
+ "name": "com.unity.netcode",
+ "expression": "1.10.1",
+ "define": "UNIFIED_NETCODE"
}
],
"noEngineReferences": false
diff --git a/testproject/ProjectSettings/PackageManagerSettings.asset b/testproject/ProjectSettings/PackageManagerSettings.asset
index b01b2f8da9..6e57db2799 100644
--- a/testproject/ProjectSettings/PackageManagerSettings.asset
+++ b/testproject/ProjectSettings/PackageManagerSettings.asset
@@ -12,32 +12,33 @@ MonoBehaviour:
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
m_Name:
m_EditorClassIdentifier:
- m_EnablePreviewPackages: 1
- m_EnablePackageDependencies: 1
+ m_EnablePreReleasePackages: 0
m_AdvancedSettingsExpanded: 1
m_ScopedRegistriesSettingsExpanded: 1
+ m_SeeAllPackageVersions: 0
+ m_DismissPreviewPackagesInUse: 0
oneTimeWarningShown: 1
- m_Registries:
- - m_Id: main
+ oneTimePackageErrorsPopUpShown: 0
+ m_MainRegistry:
+ m_Id: main
m_Name:
m_Url: https://packages.unity.com
m_Scopes: []
m_IsDefault: 1
+ m_IsUnityRegistry: 1
m_Capabilities: 7
+ m_ConfigSource: 0
+ m_Compliance:
+ m_Status: 0
+ m_Violations: []
+ m_ScopedRegistries: []
m_UserSelectedRegistryName:
m_UserAddingNewScopedRegistry: 0
m_RegistryInfoDraft:
- m_ErrorMessage:
- m_Original:
- m_Id:
- m_Name:
- m_Url:
- m_Scopes: []
- m_IsDefault: 0
- m_Capabilities: 0
m_Modified: 0
- m_Name:
- m_Url:
- m_Scopes:
- -
- m_SelectedScopeIndex: 0
+ m_ErrorMessage:
+ m_UserModificationsEntityId:
+ m_rawData: 568105584918791443
+ m_OriginalEntityId:
+ m_rawData: 568105584918791444
+ m_LoadAssets: 0
diff --git a/testproject/ProjectSettings/ProjectSettings.asset b/testproject/ProjectSettings/ProjectSettings.asset
index 3bb16ba357..06561ea008 100644
--- a/testproject/ProjectSettings/ProjectSettings.asset
+++ b/testproject/ProjectSettings/ProjectSettings.asset
@@ -3,7 +3,7 @@
--- !u!129 &1
PlayerSettings:
m_ObjectHideFlags: 0
- serializedVersion: 28
+ serializedVersion: 30
productGUID: bba99b16607b94720b7d04f7f1a82989
AndroidProfiler: 0
AndroidFilterTouchesWhenObscured: 0
@@ -41,7 +41,6 @@ PlayerSettings:
height: 1
m_SplashScreenLogos: []
m_VirtualRealitySplashScreen: {fileID: 0}
- m_HolographicTrackingLossScreen: {fileID: 0}
defaultScreenWidth: 1280
defaultScreenHeight: 720
defaultScreenWidthWeb: 960
@@ -66,10 +65,15 @@ PlayerSettings:
useOSAutorotation: 1
use32BitDisplayBuffer: 1
preserveFramebufferAlpha: 0
+ adjustIOSFPSUsingThermalState: 1
+ thermalStateSeriousIOSFPS: 30
+ thermalStateCriticalIOSFPS: 15
disableDepthAndStencilBuffers: 0
androidStartInFullscreen: 1
androidRenderOutsideSafeArea: 1
androidUseSwappy: 1
+ androidRequestedVisibleInsets: 0
+ androidSystemBarsBehavior: 2
androidDisplayOptions: 1
androidBlitType: 0
androidResizeableActivity: 0
@@ -84,6 +88,7 @@ PlayerSettings:
defaultIsNativeResolution: 1
macRetinaSupport: 1
runInBackground: 1
+ callOnDisableOnAssetBundleUnload: 1
muteOtherAudioSources: 0
Prepare IOS For Recording: 0
Force IOS Speakers When Recording: 0
@@ -114,6 +119,7 @@ PlayerSettings:
xboxEnableGuest: 0
xboxEnablePIXSampling: 0
metalFramebufferOnly: 0
+ metalUseMetalDisplayLink: 0
xboxOneResolution: 0
xboxOneSResolution: 0
xboxOneXResolution: 3
@@ -147,12 +153,10 @@ PlayerSettings:
preloadedAssets: []
metroInputSource: 0
wsaTransparentSwapchain: 0
- m_HolographicPauseOnTrackingLoss: 1
xboxOneDisableKinectGpuReservation: 1
xboxOneEnable7thCore: 1
vrSettings:
enable360StereoCapture: 0
- isWsaHolographicRemotingEnabled: 0
enableFrameTimingStats: 0
enableOpenGLProfilerGPURecorders: 1
allowHDRDisplaySupport: 0
@@ -174,9 +178,10 @@ PlayerSettings:
tvOS: 0
overrideDefaultApplicationIdentifier: 0
AndroidBundleVersionCode: 1
- AndroidMinSdkVersion: 23
+ AndroidMinSdkVersion: 26
AndroidTargetSdkVersion: 0
AndroidPreferredInstallLocation: 1
+ AndroidPreferredDataLocation: 1
aotOptions: nimt-trampolines=1024
stripEngineCode: 1
iPhoneStrippingLevel: 0
@@ -191,13 +196,14 @@ PlayerSettings:
VertexChannelCompressionMask: 4054
iPhoneSdkVersion: 988
iOSSimulatorArchitecture: 0
- iOSTargetOSVersionString: 13.0
+ iOSTargetOSVersionString: 15.0
tvOSSdkVersion: 0
tvOSSimulatorArchitecture: 0
tvOSRequireExtendedGameController: 0
- tvOSTargetOSVersionString: 13.0
+ tvOSTargetOSVersionString: 15.0
VisionOSSdkVersion: 0
VisionOSTargetOSVersionString: 1.0
+ xcodeProjectType: 0
uIPrerenderedIcon: 0
uIRequiresPersistentWiFi: 0
uIRequiresFullScreen: 1
@@ -451,12 +457,6 @@ PlayerSettings:
- m_BuildTarget: WindowsStandaloneSupport
m_APIs: 0200000012000000
m_Automatic: 0
- m_BuildTargetVRSettings:
- - m_BuildTarget: Standalone
- m_Enabled: 0
- m_Devices:
- - Oculus
- - OpenVR
m_DefaultShaderChunkSizeInMB: 16
m_DefaultShaderChunkCount: 0
openGLRequireES31: 0
@@ -484,7 +484,7 @@ PlayerSettings:
locationUsageDescription:
microphoneUsageDescription:
bluetoothUsageDescription:
- macOSTargetOSVersion: 11.0
+ macOSTargetOSVersion: 12.0
switchNMETAOverride:
switchNetLibKey:
switchSocketMemoryPoolSize: 6144
@@ -630,6 +630,8 @@ PlayerSettings:
switchMicroSleepForYieldTime: 25
switchRamDiskSpaceSize: 12
switchUpgradedPlayerSettingsToNMETA: 0
+ switchCaStoreSource: 0
+ switchCaStoreFilePath:
ps4NPAgeRating: 12
ps4NPTitleSecret:
ps4NPTrophyPackPath:
@@ -739,7 +741,7 @@ PlayerSettings:
webWasm2023: 0
webEnableSubmoduleStrippingCompatibility: 0
scriptingDefineSymbols:
- Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT
+ Standalone: UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT;NETCODE_GAMEOBJECT_BRIDGE_EXPERIMENTAL;NETCODE_EXPERIMENTAL_SINGLE_WORLD_HOST;OUT_OF_BAND_RPC
additionalCompilerArguments:
Standalone:
- -warnaserror
@@ -747,6 +749,7 @@ PlayerSettings:
scriptingBackend: {}
il2cppCompilerConfiguration: {}
il2cppCodeGeneration: {}
+ il2cppLTOMode: {}
il2cppStacktraceInformation: {}
managedStrippingLevel:
EmbeddedLinux: 1
@@ -794,8 +797,7 @@ PlayerSettings:
metroDefaultTileSize: 1
metroTileForegroundText: 2
metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0}
- metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628,
- a: 1}
+ metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1}
metroSplashScreenUseBackgroundColor: 0
syncCapabilities: 0
platformCapabilities: {}
@@ -831,7 +833,6 @@ PlayerSettings:
XboxOneXTitleMemory: 8
XboxOneOverrideIdentityName:
XboxOneOverrideIdentityPublisher:
- vrEditorSettings: {}
cloudServicesEnabled:
Analytics: 0
Build: 0
@@ -862,6 +863,7 @@ PlayerSettings:
captureStartupLogs: {}
activeInputHandler: 0
windowsGamepadBackendHint: 0
+ enableDirectStorage: 0
cloudProjectId:
framebufferDepthMemorylessMode: 0
qualitySettingsNames: []
@@ -877,3 +879,4 @@ PlayerSettings:
androidVulkanAllowFilterList: []
androidVulkanDeviceFilterListAsset: {fileID: 0}
d3d12DeviceFilterListAsset: {fileID: 0}
+ allowedHttpConnections: 3