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