diff --git a/CHANGELOG.md b/CHANGELOG.md index 80902b8..ab0fd6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) Additional documentation and release notes are available at [Multiplayer Documentation](https://docs-multiplayer.unity3d.com). +## [1.7.0] - 2023-10-11 + +### Added + +- exposed NetworkObject.GetNetworkBehaviourAtOrderIndex as a public API (#2724) +- Added context menu tool that provides users with the ability to quickly update the GlobalObjectIdHash value for all in-scene placed prefab instances that were created prior to adding a NetworkObject component to it. (#2707) +- Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676) +- Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694) +- Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694) +- Exposed `NetworkVariableSerialization.Read`, `NetworkVariableSerialization.Write`, `NetworkVariableSerialization.AreEqual`, and `NetworkVariableSerialization.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694) +- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694) + +### Fixed + +- Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723) +- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720) +- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720) +- Errors are no longer thrown when entering play mode with domain reload disabled (#2720) +- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720) +- NetworkVariables of non-integer types will no longer break the inspector (#2714) +- NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714) +- Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695) +- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685) +- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682) +- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674) +- Fixed "writing past the end of the buffer" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670) +- Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662) +- Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662) +- Fixed issue where the `GlobalObjectIdHash` value of a (network) prefab asset could be assigned an incorrect value when editing the prefab in a temporary scene. (#2662) +- Fixed issue where the `GlobalObjectIdHash` value generated after creating a (network) prefab from an object constructed within the scene would not be the correct final value in a stand alone build. (#2662) + +### Changed + +- Updated dependency on `com.unity.transport` to version 1.4.0. (#2716) + ## [1.6.0] - 2023-08-09 ### Added @@ -14,7 +49,8 @@ Additional documentation and release notes are available at [Multiplayer Documen ### Fixed -- Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661) +- Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661) +- Fixed issue where ARMv7 Android builds would crash when trying to validate the batch header. (#2654) - Fixed issue with client synchronization of position when using half precision and the delta position reaches the maximum value and is collapsed on the host prior to being forwarded to the non-owner clients. (#2636) - Fixed issue with scale not synchronizing properly depending upon the spawn order of NetworkObjects. (#2636) - Fixed issue position was not properly transitioning between ownership changes with an owner authoritative NetworkTransform. (#2636) diff --git a/Components/NetworkAnimator.cs b/Components/NetworkAnimator.cs index 64f6017..538b4b9 100644 --- a/Components/NetworkAnimator.cs +++ b/Components/NetworkAnimator.cs @@ -1137,6 +1137,7 @@ internal void UpdateAnimationState(AnimationState animationState) if (m_LayerWeights[animationState.Layer] != animationState.Weight) { m_Animator.SetLayerWeight(animationState.Layer, animationState.Weight); + m_LayerWeights[animationState.Layer] = animationState.Weight; } } diff --git a/Components/NetworkTransform.cs b/Components/NetworkTransform.cs index ab88dde..2e0b8d8 100644 --- a/Components/NetworkTransform.cs +++ b/Components/NetworkTransform.cs @@ -499,6 +499,10 @@ public Quaternion GetRotation() /// /// When there is no change in an updated state's position then there are no values to return. /// Checking for is one way to detect this. + /// When used with half precision it returns the half precision delta position state update + /// which will not be the full position. + /// To get a NettworkTransform's full position, use and + /// pass true as the parameter. /// /// public Vector3 GetPosition() @@ -1110,7 +1114,16 @@ public Vector3 GetSpaceRelativePosition(bool getCurrentState = false) } else { - return m_CurrentPosition; + // When half float precision is enabled, get the NetworkDeltaPosition's full position + if (UseHalfFloatPrecision) + { + return m_HalfPositionState.GetFullPosition(); + } + else + { + // Otherwise, just get the current position + return m_CurrentPosition; + } } } @@ -1393,6 +1406,8 @@ protected virtual void OnAuthorityPushTransformState(ref NetworkTransformState n { } + // Tracks the last tick a state update was sent (see further below) + private int m_LastTick; /// /// Authoritative side only /// If there are any transform delta states, this method will synchronize the @@ -1411,11 +1426,27 @@ private void TryCommitTransform(ref Transform transformToCommit, bool synchroniz if (ApplyTransformToNetworkStateWithInfo(ref m_LocalAuthoritativeNetworkState, ref transformToCommit, synchronize)) { m_LocalAuthoritativeNetworkState.LastSerializedSize = m_OldState.LastSerializedSize; - OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState); + // Make sure our network tick is incremented + if (m_LastTick == m_LocalAuthoritativeNetworkState.NetworkTick && !m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame) + { + // When running in authority and a remote client is the owner, the client can hit a perfect window of time where + // it is still on the previous network tick (as a count) but still have had the tick event triggered. + // (This is cheaper than calculating the exact tick each time and only can occur on clients) + if (!IsServer) + { + m_LocalAuthoritativeNetworkState.NetworkTick = m_LocalAuthoritativeNetworkState.NetworkTick + 1; + } + else + { + NetworkLog.LogError($"[NT TICK DUPLICATE] Server already sent an update on tick {m_LastTick} and is attempting to send again on the same network tick!"); + } + } + m_LastTick = m_LocalAuthoritativeNetworkState.NetworkTick; // Update the state UpdateTransformState(); + OnAuthorityPushTransformState(ref m_LocalAuthoritativeNetworkState); m_LocalAuthoritativeNetworkState.IsTeleportingNextFrame = false; } } @@ -2209,6 +2240,7 @@ private void ApplyUpdatedState(NetworkTransformState newState) m_HalfPositionState.HalfVector3.Axis = m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.HalfVector3.Axis; // and update our target position m_TargetPosition = m_HalfPositionState.ToVector3(newState.NetworkTick); + m_LocalAuthoritativeNetworkState.NetworkDeltaPosition.CurrentBasePosition = m_HalfPositionState.CurrentBasePosition; m_LocalAuthoritativeNetworkState.CurrentPosition = m_TargetPosition; } @@ -2455,6 +2487,9 @@ private void NetworkTickSystem_Tick() // Update any changes to the transform var transformSource = transform; OnUpdateAuthoritativeState(ref transformSource); + + m_CurrentPosition = GetSpaceRelativePosition(); + m_TargetPosition = GetSpaceRelativePosition(); } else { @@ -2466,9 +2501,6 @@ private void NetworkTickSystem_Tick() } } - - - /// public override void OnNetworkSpawn() { @@ -2510,24 +2542,25 @@ public override void OnDestroy() } /// - public override void OnGainedOwnership() + public override void OnLostOwnership() { - // Only initialize if we gained ownership - if (OwnerClientId == NetworkManager.LocalClientId) - { - Initialize(); - } + base.OnLostOwnership(); } /// - public override void OnLostOwnership() + public override void OnGainedOwnership() { - // Only initialize if we are not authority and lost - // ownership - if (OwnerClientId != NetworkManager.LocalClientId) + base.OnGainedOwnership(); + } + + protected override void OnOwnershipChanged(ulong previous, ulong current) + { + // If we were the previous owner or the newly assigned owner then reinitialize + if (current == NetworkManager.LocalClientId || previous == NetworkManager.LocalClientId) { Initialize(); } + base.OnOwnershipChanged(previous, current); } /// @@ -2552,6 +2585,7 @@ protected virtual void OnInitialize(ref NetworkVariable r } + /// /// Initializes NetworkTransform when spawned and ownership changes. /// @@ -2572,7 +2606,8 @@ protected void Initialize() { m_HalfPositionState = new NetworkDeltaPosition(currentPosition, NetworkManager.NetworkTickSystem.ServerTime.Tick, math.bool3(SyncPositionX, SyncPositionY, SyncPositionZ)); } - + m_CurrentPosition = currentPosition; + m_TargetPosition = currentPosition; // Authority only updates once per network tick NetworkManager.NetworkTickSystem.Tick -= NetworkTickSystem_Tick; NetworkManager.NetworkTickSystem.Tick += NetworkTickSystem_Tick; @@ -2835,6 +2870,13 @@ public bool IsServerAuthoritative() /// serialzied private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayload) { + if (!OnIsServerAuthoritative() && IsServer && OwnerClientId == NetworkManager.ServerClientId) + { + // Ownership must have changed, ignore any additional pending messages that might have + // come from a previous owner client. + return; + } + // Forward owner authoritative messages before doing anything else if (IsServer && !OnIsServerAuthoritative()) { @@ -2856,6 +2898,7 @@ private void TransformStateUpdate(ulong senderId, FastBufferReader messagePayloa /// the owner state message payload private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) { + var serverAuthoritative = OnIsServerAuthoritative(); var currentPosition = messagePayload.Position; var messageSize = messagePayload.Length - currentPosition; var writer = new FastBufferWriter(messageSize, Allocator.Temp); @@ -2867,7 +2910,7 @@ private unsafe void ForwardStateUpdateMessage(FastBufferReader messagePayload) for (int i = 0; i < clientCount; i++) { var clientId = NetworkManager.ConnectionManager.ConnectedClientsList[i].ClientId; - if (!OnIsServerAuthoritative() && (NetworkManager.ServerClientId == clientId || clientId == OwnerClientId)) + if (NetworkManager.ServerClientId == clientId || (!serverAuthoritative && clientId == OwnerClientId)) { continue; } diff --git a/Editor/CodeGen/NetworkBehaviourILPP.cs b/Editor/CodeGen/NetworkBehaviourILPP.cs index 03999db..c15ea8a 100644 --- a/Editor/CodeGen/NetworkBehaviourILPP.cs +++ b/Editor/CodeGen/NetworkBehaviourILPP.cs @@ -8,6 +8,9 @@ using Mono.Cecil.Rocks; using Unity.CompilationPipeline.Common.Diagnostics; using Unity.CompilationPipeline.Common.ILPostProcessing; +#if UNITY_EDITOR +using UnityEditor; +#endif using UnityEngine; using ILPPInterface = Unity.CompilationPipeline.Common.ILPostProcessing.ILPostProcessor; using MethodAttributes = Mono.Cecil.MethodAttributes; @@ -66,7 +69,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) { m_MainModule = mainModule; - if (ImportReferences(mainModule)) + if (ImportReferences(mainModule, compiledAssembly.Defines)) { // process `NetworkBehaviour` types try @@ -76,7 +79,38 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) .ToList() .ForEach(b => ProcessNetworkBehaviour(b, compiledAssembly.Defines)); - CreateNetworkVariableTypeInitializers(assemblyDefinition); + foreach (var type in mainModule.GetTypes()) + { + var resolved = type.Resolve(); + foreach (var attribute in resolved.CustomAttributes) + { + if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute)) + { + var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value); + if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + { + m_WrappedNetworkVariableTypes.Add(wrappedType); + } + } + } + + foreach (var method in resolved.Methods) + { + foreach (var attribute in method.CustomAttributes) + { + if (attribute.AttributeType.Name == nameof(GenerateSerializationForTypeAttribute)) + { + var wrappedType = mainModule.ImportReference((TypeReference)attribute.ConstructorArguments[0].Value); + if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + { + m_WrappedNetworkVariableTypes.Add(wrappedType); + } + } + } + } + } + + CreateNetworkVariableTypeInitializers(assemblyDefinition, compiledAssembly.Defines); } catch (Exception e) { @@ -135,7 +169,7 @@ private bool IsSpecialCaseType(TypeReference type) return false; } - private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) + private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly, string[] assemblyDefines) { var typeDefinition = new TypeDefinition("__GEN", "NetworkVariableSerializationHelper", TypeAttributes.NotPublic | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit, assembly.MainModule.TypeSystem.Object); @@ -145,7 +179,15 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) MethodAttributes.Static, assembly.MainModule.TypeSystem.Void); staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor)); + bool isEditor = assemblyDefines.Contains("UNITY_EDITOR"); + if (isEditor) + { + staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_InitializeOnLoadAttribute_Ctor)); + } + else + { + staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor)); + } typeDefinition.Methods.Add(staticCtorMethodDef); @@ -196,10 +238,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEqualsArray_MethodRef); } - if (serializeMethod != null) - { - serializeMethod.GenericArguments.Add(wrappedType); - } + serializeMethod?.GenericArguments.Add(wrappedType); equalityMethod.GenericArguments.Add(wrappedType); } #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT @@ -259,10 +298,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef); } - if (serializeMethod != null) - { - serializeMethod.GenericArguments.Add(type); - } + serializeMethod?.GenericArguments.Add(type); equalityMethod.GenericArguments.Add(type); } else @@ -296,10 +332,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef); } - if (serializeMethod != null) - { - serializeMethod.GenericArguments.Add(type); - } + serializeMethod?.GenericArguments.Add(type); equalityMethod.GenericArguments.Add(type); } @@ -328,10 +361,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private MethodReference m_NetworkManager_getIsServer_MethodRef; private MethodReference m_NetworkManager_getIsClient_MethodRef; private FieldReference m_NetworkManager_LogLevel_FieldRef; - private FieldReference m_NetworkManager_rpc_func_table_FieldRef; - private MethodReference m_NetworkManager_rpc_func_table_Add_MethodRef; - private FieldReference m_NetworkManager_rpc_name_table_FieldRef; - private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef; + private MethodReference m_NetworkBehaviour___registerRpc_MethodRef; private TypeReference m_NetworkBehaviour_TypeRef; private TypeReference m_NetworkVariableBase_TypeRef; private MethodReference m_NetworkVariableBase_Initialize_MethodRef; @@ -382,6 +412,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef; private MethodReference m_RuntimeInitializeOnLoadAttribute_Ctor; + private MethodReference m_InitializeOnLoadAttribute_Ctor; private MethodReference m_ExceptionCtorMethodReference; private MethodReference m_List_NetworkVariableBase_Add; @@ -457,9 +488,9 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private const string k_NetworkManager_IsServer = nameof(NetworkManager.IsServer); private const string k_NetworkManager_IsClient = nameof(NetworkManager.IsClient); private const string k_NetworkManager_LogLevel = nameof(NetworkManager.LogLevel); - private const string k_NetworkManager_rpc_func_table = nameof(NetworkManager.__rpc_func_table); - private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table); + private const string k_NetworkBehaviour_rpc_func_table = nameof(NetworkBehaviour.__rpc_func_table); + private const string k_NetworkBehaviour_rpc_name_table = nameof(NetworkBehaviour.__rpc_name_table); private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage); private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields); private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc); @@ -467,10 +498,12 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc); private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc); private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables); + private const string k_NetworkBehaviour___initializeRpcs = nameof(NetworkBehaviour.__initializeRpcs); private const string k_NetworkBehaviour_createNativeList = nameof(NetworkBehaviour.__createNativeList); private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager); private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId); private const string k_NetworkBehaviour___nameNetworkVariable = nameof(NetworkBehaviour.__nameNetworkVariable); + private const string k_NetworkBehaviour___registerRpc = nameof(NetworkBehaviour.__registerRpc); private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize); @@ -484,7 +517,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly) // CodeGen cannot reference the collections assembly to do a typeof() on it due to a bug that causes that to crash. private const string k_INativeListBool_FullName = "Unity.Collections.INativeList`1"; - private bool ImportReferences(ModuleDefinition moduleDefinition) + private bool ImportReferences(ModuleDefinition moduleDefinition, string[] assemblyDefines) { TypeDefinition debugTypeDef = null; foreach (var unityTypeDef in m_UnityModule.GetAllTypes()) @@ -496,6 +529,13 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) } } + + bool isEditor = assemblyDefines.Contains("UNITY_EDITOR"); + if (isEditor) + { + m_InitializeOnLoadAttribute_Ctor = moduleDefinition.ImportReference(typeof(InitializeOnLoadMethodAttribute).GetConstructor(new Type[] { })); + } + m_RuntimeInitializeOnLoadAttribute_Ctor = moduleDefinition.ImportReference(typeof(RuntimeInitializeOnLoadMethodAttribute).GetConstructor(new Type[] { })); TypeDefinition networkManagerTypeDef = null; @@ -530,7 +570,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) continue; } - if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler)) + if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkBehaviour.RpcReceiveHandler)) { networkHandlerDelegateTypeDef = netcodeTypeDef; continue; @@ -629,20 +669,6 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case k_NetworkManager_LogLevel: m_NetworkManager_LogLevel_FieldRef = moduleDefinition.ImportReference(fieldDef); break; - case k_NetworkManager_rpc_func_table: - m_NetworkManager_rpc_func_table_FieldRef = moduleDefinition.ImportReference(fieldDef); - - m_NetworkManager_rpc_func_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add"); - m_NetworkManager_rpc_func_table_Add_MethodRef.DeclaringType = fieldDef.FieldType; - m_NetworkManager_rpc_func_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_func_table_Add_MethodRef); - break; - case k_NetworkManager_rpc_name_table: - m_NetworkManager_rpc_name_table_FieldRef = moduleDefinition.ImportReference(fieldDef); - - m_NetworkManager_rpc_name_table_Add_MethodRef = fieldDef.FieldType.Resolve().Methods.First(m => m.Name == "Add"); - m_NetworkManager_rpc_name_table_Add_MethodRef.DeclaringType = fieldDef.FieldType; - m_NetworkManager_rpc_name_table_Add_MethodRef = moduleDefinition.ImportReference(m_NetworkManager_rpc_name_table_Add_MethodRef); - break; } } @@ -682,6 +708,10 @@ private bool ImportReferences(ModuleDefinition moduleDefinition) case k_NetworkBehaviour___nameNetworkVariable: m_NetworkBehaviour___nameNetworkVariable_MethodRef = moduleDefinition.ImportReference(methodDef); break; + case k_NetworkBehaviour___registerRpc: + m_NetworkBehaviour___registerRpc_MethodRef = moduleDefinition.ImportReference(methodDef); + break; + } } @@ -1095,8 +1125,15 @@ private void GetAllBaseTypesAndResolveGenerics(TypeDefinition type, ref List(); - var rpcNames = new List<(uint RpcMethodId, string RpcMethodName)>(); + foreach (var methodDefinition in typeDefinition.Methods) + { + if (methodDefinition.Name == k_NetworkBehaviour___initializeRpcs) + { + // If this hits, we've already generated the method for this class because a child class got processed first. + return; + } + } + var rpcHandlers = new List<(uint RpcMethodId, MethodDefinition RpcHandler, string RpcMethodName)>(); bool isEditorOrDevelopment = assemblyDefines.Contains("UNITY_EDITOR") || assemblyDefines.Contains("DEVELOPMENT_BUILD"); @@ -1127,12 +1164,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass InjectWriteAndCallBlocks(methodDefinition, rpcAttribute, rpcMethodId); - rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId))); - - if (isEditorOrDevelopment) - { - rpcNames.Add((rpcMethodId, methodDefinition.Name)); - } + rpcHandlers.Add((rpcMethodId, GenerateStaticHandler(methodDefinition, rpcAttribute, rpcMethodId), methodDefinition.Name)); } GenerateVariableInitialization(typeDefinition); @@ -1146,13 +1178,22 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass //var type = field.FieldType; if (type.IsGenericInstance) { - if (type.Resolve().Name == typeof(NetworkVariable<>).Name || type.Resolve().Name == typeof(NetworkList<>).Name) + foreach (var attribute in type.Resolve().CustomAttributes) { - var genericInstanceType = (GenericInstanceType)type; - var wrappedType = genericInstanceType.GenericArguments[0]; - if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + if (attribute.AttributeType.Name == nameof(GenerateSerializationForGenericParameterAttribute)) { - m_WrappedNetworkVariableTypes.Add(wrappedType); + var idx = (int)attribute.ConstructorArguments[0].Value; + var genericInstanceType = (GenericInstanceType)type; + if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count) + { + m_Diagnostics.AddError($"{type} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); + continue; + } + var wrappedType = genericInstanceType.GenericArguments[idx]; + if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + { + m_WrappedNetworkVariableTypes.Add(wrappedType); + } } } } @@ -1173,13 +1214,22 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass GetAllBaseTypesAndResolveGenerics(type.Resolve(), ref baseTypes, genericParams); foreach (var baseType in baseTypes) { - if (baseType.Resolve().Name == typeof(NetworkVariable<>).Name || baseType.Resolve().Name == typeof(NetworkList<>).Name) + foreach (var attribute in baseType.Resolve().CustomAttributes) { - var genericInstanceType = (GenericInstanceType)baseType; - var wrappedType = genericInstanceType.GenericArguments[0]; - if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + if (attribute.AttributeType.Name == nameof(GenerateSerializationForGenericParameterAttribute)) { - m_WrappedNetworkVariableTypes.Add(wrappedType); + var idx = (int)attribute.ConstructorArguments[0].Value; + var genericInstanceType = (GenericInstanceType)baseType; + if (idx < 0 || idx >= genericInstanceType.GenericArguments.Count) + { + m_Diagnostics.AddError($"{baseType} has a {nameof(GenerateSerializationForGenericParameterAttribute)} referencing a parameter index outside the valid range (0-{genericInstanceType.GenericArguments.Count - 1}"); + continue; + } + var wrappedType = genericInstanceType.GenericArguments[idx]; + if (!m_WrappedNetworkVariableTypes.Contains(wrappedType)) + { + m_WrappedNetworkVariableTypes.Add(wrappedType); + } } } } @@ -1187,42 +1237,84 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass } } - if (rpcHandlers.Count > 0 || rpcNames.Count > 0) + if (rpcHandlers.Count > 0) { - var staticCtorMethodDef = new MethodDefinition( - $"InitializeRPCS_{typeDefinition.Name}", - MethodAttributes.Assembly | - MethodAttributes.Static, + var initializeRpcsMethodDef = new MethodDefinition( + k_NetworkBehaviour___initializeRpcs, + MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig, typeDefinition.Module.TypeSystem.Void); - staticCtorMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - staticCtorMethodDef.CustomAttributes.Add(new CustomAttribute(m_RuntimeInitializeOnLoadAttribute_Ctor)); - typeDefinition.Methods.Add(staticCtorMethodDef); + initializeRpcsMethodDef.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); + + typeDefinition.Methods.Add(initializeRpcsMethodDef); var instructions = new List(); - var processor = staticCtorMethodDef.Body.GetILProcessor(); + var processor = initializeRpcsMethodDef.Body.GetILProcessor(); - foreach (var (rpcMethodId, rpcHandler) in rpcHandlers) + foreach (var (rpcMethodId, rpcHandler, rpcMethodName) in rpcHandlers) { typeDefinition.Methods.Add(rpcHandler); - // NetworkManager.__rpc_func_table.Add(RpcMethodId, HandleFunc); - instructions.Add(processor.Create(OpCodes.Ldsfld, m_NetworkManager_rpc_func_table_FieldRef)); + MethodReference callMethod = rpcHandler; + if (typeDefinition.HasGenericParameters) + { + var genericTypes = new List(); + foreach (var parameter in typeDefinition.GenericParameters) + { + genericTypes.Add(parameter); + } + callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); + } + + // __registerRpc(RpcMethodId, HandleFunc, methodName); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); instructions.Add(processor.Create(OpCodes.Ldnull)); - instructions.Add(processor.Create(OpCodes.Ldftn, rpcHandler)); + instructions.Add(processor.Create(OpCodes.Ldftn, callMethod)); instructions.Add(processor.Create(OpCodes.Newobj, m_NetworkHandlerDelegateCtor_MethodRef)); - instructions.Add(processor.Create(OpCodes.Call, m_NetworkManager_rpc_func_table_Add_MethodRef)); + instructions.Add(processor.Create(OpCodes.Ldstr, rpcMethodName)); + instructions.Add(processor.Create(OpCodes.Call, m_NetworkBehaviour___registerRpc_MethodRef)); } - foreach (var (rpcMethodId, rpcMethodName) in rpcNames) + // Find the base method... + MethodReference initializeRpcsBaseReference = null; + foreach (var methodDefinition in typeDefinition.BaseType.Resolve().Methods) { - // NetworkManager.__rpc_name_table.Add(RpcMethodId, RpcMethodName); - instructions.Add(processor.Create(OpCodes.Ldsfld, m_NetworkManager_rpc_name_table_FieldRef)); - instructions.Add(processor.Create(OpCodes.Ldc_I4, unchecked((int)rpcMethodId))); - instructions.Add(processor.Create(OpCodes.Ldstr, rpcMethodName)); - instructions.Add(processor.Create(OpCodes.Call, m_NetworkManager_rpc_name_table_Add_MethodRef)); + if (methodDefinition.Name == k_NetworkBehaviour___initializeRpcs) + { + initializeRpcsBaseReference = m_MainModule.ImportReference(methodDefinition); + break; + } + } + + if (initializeRpcsBaseReference == null) + { + // If we couldn't find it, we have to go ahead and add it. + // The base class could be in another assembly... that's ok, this won't + // actually save but it'll generate the same method the same way later, + // so this at least allows us to reference it. + ProcessNetworkBehaviour(typeDefinition.BaseType.Resolve(), assemblyDefines); + foreach (var methodDefinition in typeDefinition.BaseType.Resolve().Methods) + { + if (methodDefinition.Name == k_NetworkBehaviour___initializeRpcs) + { + initializeRpcsBaseReference = m_MainModule.ImportReference(methodDefinition); + break; + } + } } + if (typeDefinition.BaseType.Resolve().HasGenericParameters) + { + var baseTypeInstance = (GenericInstanceType)typeDefinition.BaseType; + initializeRpcsBaseReference = initializeRpcsBaseReference.MakeGeneric(baseTypeInstance.GenericArguments.ToArray()); + } + + // base.__initializeRpcs(); + instructions.Add(processor.Create(OpCodes.Nop)); + instructions.Add(processor.Create(OpCodes.Ldarg_0)); + instructions.Add(processor.Create(OpCodes.Call, initializeRpcsBaseReference)); + instructions.Add(processor.Create(OpCodes.Nop)); + instructions.Reverse(); instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction)); } @@ -1453,6 +1545,27 @@ private MethodReference GetFastBufferWriterWriteMethod(string name, TypeReferenc private bool GetWriteMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.Resolve() == null) + { + // Handle generic types by passing them to RpcFallbackSerialization + // This just passes directly to NetworkVariableSerialization, but I could not figure out how to + // get ILPP to generate valid code for calling a method of the format + // `GenericClass.StaticMethod(ref T value)` - it would either complain about T being + // defined in another module, or it would end up generating a completely invalid call to a + // random method on another random class. + var serializationHelperType = m_MainModule.ImportReference(typeof(RpcFallbackSerialization)); + + foreach (var method in serializationHelperType.Resolve().Methods) + { + if (method.Name == nameof(NetworkVariableSerialization.Write)) + { + var reference = new GenericInstanceMethod(m_MainModule.ImportReference(method)); + reference.GenericArguments.Add(paramType); + methodRef = reference; + return true; + } + } + } if (paramType.FullName == typeof(short).FullName) { methodRef = m_BytePacker_WriteValueBitPacked_Short_MethodRef; @@ -1669,6 +1782,27 @@ private MethodReference GetFastBufferReaderReadMethod(string name, TypeReference private bool GetReadMethodForParameter(TypeReference paramType, out MethodReference methodRef) { + if (paramType.Resolve() == null) + { + // Handle generic types by passing them to RpcFallbackSerialization + // This just passes directly to NetworkVariableSerialization, but I could not figure out how to + // get ILPP to generate valid code for calling a method of the format + // `GenericClass.StaticMethod(ref T value)` - it would either complain about T being + // defined in another module, or it would end up generating a completely invalid call to a + // random method on another random class. + var serializationHelperType = m_MainModule.ImportReference(typeof(RpcFallbackSerialization)); + + foreach (var method in serializationHelperType.Resolve().Methods) + { + if (method.Name == nameof(NetworkVariableSerialization.Read)) + { + var reference = new GenericInstanceMethod(m_MainModule.ImportReference(method)); + reference.GenericArguments.Add(paramType); + methodRef = reference; + return true; + } + } + } if (paramType.FullName == typeof(short).FullName) { methodRef = m_ByteUnpacker_ReadValueBitPacked_Short_MethodRef; @@ -1959,7 +2093,7 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA Instruction jumpInstruction = null; - if (!paramType.IsValueType) + if (!paramType.IsValueType && paramType.Resolve() != null) { if (!GetWriteMethodForParameter(typeSystem.Boolean, out var boolMethodRef)) { @@ -2432,7 +2566,7 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition Instruction jumpInstruction = null; - if (!paramType.IsValueType) + if (!paramType.IsValueType && paramType.Resolve() != null) { if (!GetReadMethodForParameter(typeSystem.Boolean, out var boolMethodRef)) { @@ -2559,9 +2693,21 @@ private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition // NetworkBehaviour.XXXRpc(...); processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Castclass, methodDefinition.DeclaringType); + var castType = (TypeReference)methodDefinition.DeclaringType; + var callMethod = (MethodReference)methodDefinition; + if (castType.HasGenericParameters) + { + var genericTypes = new List(); + foreach (var parameter in castType.GenericParameters) + { + genericTypes.Add(parameter); + } + castType = castType.MakeGenericInstanceType(genericTypes.ToArray()); + callMethod = callMethod.MakeGeneric(genericTypes.ToArray()); + } + processor.Emit(OpCodes.Castclass, castType); Enumerable.Range(0, paramCount).ToList().ForEach(paramIndex => processor.Emit(OpCodes.Ldloc, paramLocalMap[paramIndex])); - processor.Emit(OpCodes.Callvirt, methodDefinition); + processor.Emit(OpCodes.Callvirt, callMethod); // NetworkBehaviour.__rpc_exec_stage = __RpcExecStage.None; processor.Emit(OpCodes.Ldarg_0); diff --git a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs index 51cf65a..0ef4f2c 100644 --- a/Editor/CodeGen/RuntimeAccessModifiersILPP.cs +++ b/Editor/CodeGen/RuntimeAccessModifiersILPP.cs @@ -53,6 +53,7 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) ProcessNetworkBehaviour(typeDefinition); break; case nameof(__RpcParams): + case nameof(RpcFallbackSerialization): typeDefinition.IsPublic = true; break; } @@ -79,6 +80,9 @@ public override ILPostProcessResult Process(ICompiledAssembly compiledAssembly) return new ILPostProcessResult(new InMemoryAssembly(pe.ToArray(), pdb.ToArray()), m_Diagnostics); } + // TODO: Deprecate... + // This is changing accessibility for values that are no longer used, but since our validator runs + // after ILPP and sees those values as public, they cannot be removed until a major version change. private void ProcessNetworkManager(TypeDefinition typeDefinition, string[] assemblyDefines) { foreach (var fieldDefinition in typeDefinition.Fields) @@ -116,6 +120,10 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { nestedType.IsNestedFamily = true; } + if (nestedType.Name == nameof(NetworkBehaviour.RpcReceiveHandler)) + { + nestedType.IsNestedPublic = true; + } } foreach (var fieldDefinition in typeDefinition.Fields) @@ -124,6 +132,20 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) { fieldDefinition.IsFamilyOrAssembly = true; } + if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_func_table)) + { + fieldDefinition.IsFamilyOrAssembly = true; + } + + if (fieldDefinition.Name == nameof(NetworkBehaviour.RpcReceiveHandler)) + { + fieldDefinition.IsFamilyOrAssembly = true; + } + + if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_name_table)) + { + fieldDefinition.IsFamilyOrAssembly = true; + } } foreach (var methodDefinition in typeDefinition.Methods) @@ -133,6 +155,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition) methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || + methodDefinition.Name == nameof(NetworkBehaviour.__initializeRpcs) || + methodDefinition.Name == nameof(NetworkBehaviour.__registerRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable) || methodDefinition.Name == nameof(NetworkBehaviour.__createNativeList)) { diff --git a/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs index b8f3b7f..e5d18b6 100644 --- a/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs +++ b/Editor/Configuration/NetcodeForGameObjectsProjectSettings.cs @@ -20,7 +20,7 @@ private void OnEnable() } [SerializeField] - public bool GenerateDefaultNetworkPrefabs; + public bool GenerateDefaultNetworkPrefabs = true; internal void SaveSettings() { diff --git a/Editor/NetworkBehaviourEditor.cs b/Editor/NetworkBehaviourEditor.cs index 0697449..7d57afb 100644 --- a/Editor/NetworkBehaviourEditor.cs +++ b/Editor/NetworkBehaviourEditor.cs @@ -37,12 +37,12 @@ private void Init(MonoScript script) for (int i = 0; i < fields.Length; i++) { var ft = fields[i].FieldType; - if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true)) + if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkVariable<>) && !fields[i].IsDefined(typeof(HideInInspector), true) && !fields[i].IsDefined(typeof(NonSerializedAttribute), true)) { m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name)); m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]); } - if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true)) + if (ft.IsGenericType && ft.GetGenericTypeDefinition() == typeof(NetworkList<>) && !fields[i].IsDefined(typeof(HideInInspector), true) && !fields[i].IsDefined(typeof(NonSerializedAttribute), true)) { m_NetworkVariableNames.Add(ObjectNames.NicifyVariableName(fields[i].Name)); m_NetworkVariableFields.Add(ObjectNames.NicifyVariableName(fields[i].Name), fields[i]); @@ -417,6 +417,48 @@ public static void CheckForNetworkObject(GameObject gameObject, bool networkObje } } } + + if (networkObject != null) + { + OrderNetworkObject(networkObject); + } + } + + // Assures the NetworkObject precedes any NetworkBehaviour on the same GameObject as the NetworkObject + private static void OrderNetworkObject(NetworkObject networkObject) + { + var monoBehaviours = networkObject.gameObject.GetComponents(); + var networkObjectIndex = 0; + var firstNetworkBehaviourIndex = -1; + for (int i = 0; i < monoBehaviours.Length; i++) + { + if (monoBehaviours[i] == networkObject) + { + networkObjectIndex = i; + break; + } + + var networkBehaviour = monoBehaviours[i] as NetworkBehaviour; + if (networkBehaviour != null) + { + // Get the index of the first NetworkBehaviour Component + if (firstNetworkBehaviourIndex == -1) + { + firstNetworkBehaviourIndex = i; + } + } + } + + if (firstNetworkBehaviourIndex != -1 && networkObjectIndex > firstNetworkBehaviourIndex) + { + var positionsToMove = networkObjectIndex - firstNetworkBehaviourIndex; + for (int i = 0; i < positionsToMove; i++) + { + UnityEditorInternal.ComponentUtility.MoveComponentUp(networkObject); + } + + EditorUtility.SetDirty(networkObject.gameObject); + } } } } diff --git a/Runtime/Core/NetworkBehaviour.cs b/Runtime/Core/NetworkBehaviour.cs index 7a79bab..e0d0f21 100644 --- a/Runtime/Core/NetworkBehaviour.cs +++ b/Runtime/Core/NetworkBehaviour.cs @@ -3,6 +3,7 @@ using Unity.Collections; using UnityEngine; + namespace Unity.Netcode { /// @@ -11,6 +12,18 @@ namespace Unity.Netcode public abstract class NetworkBehaviour : MonoBehaviour { #pragma warning disable IDE1006 // disable naming rule violation check + + // RuntimeAccessModifiersILPP will make this `public` + internal delegate void RpcReceiveHandler(NetworkBehaviour behaviour, FastBufferReader reader, __RpcParams parameters); + + // RuntimeAccessModifiersILPP will make this `public` + internal static readonly Dictionary> __rpc_func_table = new Dictionary>(); + +#if DEVELOPMENT_BUILD || UNITY_EDITOR + // RuntimeAccessModifiersILPP will make this `public` + internal static readonly Dictionary> __rpc_name_table = new Dictionary>(); +#endif + // RuntimeAccessModifiersILPP will make this `protected` internal enum __RpcExecStage { @@ -97,7 +110,7 @@ internal void __endSendServerRpc(ref FastBufferWriter bufferWriter, uint rpcMeth bufferWriter.Dispose(); #if DEVELOPMENT_BUILD || UNITY_EDITOR - if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) + if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) { NetworkManager.NetworkMetrics.TrackRpcSent( NetworkManager.ServerClientId, @@ -228,7 +241,7 @@ internal void __endSendClientRpc(ref FastBufferWriter bufferWriter, uint rpcMeth bufferWriter.Dispose(); #if DEVELOPMENT_BUILD || UNITY_EDITOR - if (NetworkManager.__rpc_name_table.TryGetValue(rpcMethodId, out var rpcMethodName)) + if (__rpc_name_table[GetType()].TryGetValue(rpcMethodId, out var rpcMethodName)) { if (clientRpcParams.Send.TargetClientIds != null) { @@ -532,6 +545,23 @@ internal void InternalOnGainedOwnership() OnGainedOwnership(); } + /// + /// Invoked on all clients, override this method to be notified of any + /// ownership changes (even if the instance was niether the previous or + /// newly assigned current owner). + /// + /// the previous owner + /// the current owner + protected virtual void OnOwnershipChanged(ulong previous, ulong current) + { + + } + + internal void InternalOnOwnershipChanged(ulong previous, ulong current) + { + OnOwnershipChanged(previous, current); + } + /// /// Gets called when we loose ownership of this object /// @@ -565,6 +595,25 @@ internal virtual void __initializeVariables() // ILPP generates code for all NetworkBehaviour subtypes to initialize each type's network variables. } +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal virtual void __initializeRpcs() +#pragma warning restore IDE1006 // restore naming rule violation check + { + // ILPP generates code for all NetworkBehaviour subtypes to initialize each type's RPCs. + } + +#pragma warning disable IDE1006 // disable naming rule violation check + // RuntimeAccessModifiersILPP will make this `protected` + internal void __registerRpc(uint hash, RpcReceiveHandler handler, string rpcMethodName) +#pragma warning restore IDE1006 // restore naming rule violation check + { + __rpc_func_table[GetType()][hash] = handler; +#if DEVELOPMENT_BUILD || UNITY_EDITOR + __rpc_name_table[GetType()][hash] = rpcMethodName; +#endif + } + #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `protected` // Using this method here because ILPP doesn't seem to let us do visibility modification on properties. @@ -583,6 +632,14 @@ internal void InitializeVariables() m_VarInit = true; + if (!__rpc_func_table.ContainsKey(GetType())) + { + __rpc_func_table[GetType()] = new Dictionary(); +#if UNITY_EDITOR || DEVELOPMENT_BUILD + __rpc_name_table[GetType()] = new Dictionary(); +#endif + __initializeRpcs(); + } __initializeVariables(); { diff --git a/Runtime/Core/NetworkManager.cs b/Runtime/Core/NetworkManager.cs index 7bcedfd..4270331 100644 --- a/Runtime/Core/NetworkManager.cs +++ b/Runtime/Core/NetworkManager.cs @@ -15,6 +15,9 @@ namespace Unity.Netcode [AddComponentMenu("Netcode/Network Manager", -100)] public class NetworkManager : MonoBehaviour, INetworkUpdateSystem { + // TODO: Deprecate... + // The following internal values are not used, but because ILPP makes them public in the assembly, they cannot + // be removed thanks to our semver validation. #pragma warning disable IDE1006 // disable naming rule violation check // RuntimeAccessModifiersILPP will make this `public` @@ -491,6 +494,15 @@ internal void OnValidate() } } } + + private void ModeChanged(PlayModeStateChange change) + { + if (IsListening && change == PlayModeStateChange.ExitingPlayMode) + { + // Make sure we are not holding onto anything in case domain reload is disabled + ShutdownInternal(); + } + } #endif /// @@ -539,6 +551,9 @@ private void Awake() NetworkConfig?.InitializePrefabs(); UnityEngine.SceneManagement.SceneManager.sceneUnloaded += OnSceneUnloaded; +#if UNITY_EDITOR + EditorApplication.playModeStateChanged += ModeChanged; +#endif } private void OnEnable() @@ -581,7 +596,12 @@ private void OnEnable() /// /// Sets the maximum size of a single non-fragmented message (or message batch) passed through the transport. - /// This should represent the transport's MTU size, minus any transport-level overhead. + /// This should represent the transport's default MTU size, minus any transport-level overhead. + /// This value will be used for any remote endpoints that haven't had per-endpoint MTUs set. + /// This value is also used as the size of the temporary buffer used when serializing + /// a single message (to avoid serializing multiple times when sending to multiple endpoints), + /// and thus should be large enough to ensure it can hold each message type. + /// This value defaults to 1296. /// /// public int MaximumTransmissionUnitSize @@ -590,6 +610,34 @@ public int MaximumTransmissionUnitSize get => MessageManager.NonFragmentedMessageMaxSize; } + /// + /// Set the maximum transmission unit for a specific peer. + /// This determines the maximum size of a message batch that can be sent to that client. + /// If not set for any given client, will be used instead. + /// + /// + /// + public void SetPeerMTU(ulong clientId, int size) + { + MessageManager.PeerMTUSizes[clientId] = size; + } + + /// + /// Queries the current MTU size for a client. + /// If no MTU has been set for that client, will return + /// + /// + /// + public int GetPeerMTU(ulong clientId) + { + if (MessageManager.PeerMTUSizes.TryGetValue(clientId, out var ret)) + { + return ret; + } + + return MessageManager.NonFragmentedMessageMaxSize; + } + /// /// Sets the maximum size of a message (or message batch) passed through the transport with the ReliableFragmented delivery. /// Warning: setting this value too low may result in the SDK becoming non-functional with projects that have a large number of NetworkBehaviours or NetworkVariables, as the SDK relies on the transport's ability to fragment some messages when they grow beyond the MTU size. diff --git a/Runtime/Core/NetworkObject.cs b/Runtime/Core/NetworkObject.cs index 81542e3..f7d6217 100644 --- a/Runtime/Core/NetworkObject.cs +++ b/Runtime/Core/NetworkObject.cs @@ -1,9 +1,18 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +#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; + namespace Unity.Netcode { /// @@ -37,30 +46,171 @@ public uint PrefabIdHash } } - private bool m_IsPrefab; - #if UNITY_EDITOR - private void OnValidate() + private const string k_GlobalIdTemplate = "GlobalObjectId_V1-{0}-{1}-{2}-{3}"; + + /// + /// Object Types + /// Parameter 0 of + /// + // 0 = Null (when considered a null object type we can ignore) + // 1 = Imported Asset + // 2 = Scene Object + // 3 = Source Asset. + private const int k_NullObjectType = 0; + private const int k_ImportedAssetObjectType = 1; + private const int k_SceneObjectType = 2; + private const int k_SourceAssetObjectType = 3; + + [ContextMenu("Refresh In-Scene Prefab Instances")] + internal void RefreshAllPrefabInstances() { - GenerateGlobalObjectIdHash(); + var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this); + if (!PrefabUtility.IsPartOfAnyPrefab(this) || instanceGlobalId.identifierType != k_ImportedAssetObjectType) + { + EditorUtility.DisplayDialog("Network Prefab Assets Only", "This action can only be performed on a network prefab asset.", "Ok"); + return; + } + + // Handle updating the currently active scene + var networkObjects = FindObjectsByType(FindObjectsInactive.Include, FindObjectsSortMode.None); + foreach (var networkObject in networkObjects) + { + networkObject.OnValidate(); + } + NetworkObjectRefreshTool.ProcessActiveScene(); + + // Refresh all build settings scenes + var activeScene = SceneManager.GetActiveScene(); + foreach (var editorScene in EditorBuildSettings.scenes) + { + // skip disabled scenes and the currently active scene + if (!editorScene.enabled || activeScene.path == editorScene.path) + { + continue; + } + // Add the scene to be processed + NetworkObjectRefreshTool.ProcessScene(editorScene.path, false); + } + + // Process all added scenes + NetworkObjectRefreshTool.ProcessScenes(); } - internal void GenerateGlobalObjectIdHash() + private void OnValidate() { // do NOT regenerate GlobalObjectIdHash for NetworkPrefabs while Editor is in PlayMode - if (UnityEditor.EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name)) + if (EditorApplication.isPlaying && !string.IsNullOrEmpty(gameObject.scene.name)) { return; } // do NOT regenerate GlobalObjectIdHash if Editor is transitioning into or out of PlayMode - if (!UnityEditor.EditorApplication.isPlaying && UnityEditor.EditorApplication.isPlayingOrWillChangePlaymode) + if (!EditorApplication.isPlaying && EditorApplication.isPlayingOrWillChangePlaymode) { return; } - var globalObjectIdString = UnityEditor.GlobalObjectId.GetGlobalObjectIdSlow(this).ToString(); - GlobalObjectIdHash = XXHash.Hash32(globalObjectIdString); + // Get a global object identifier for this network prefab + var globalId = GetGlobalId(); + + + // if the identifier type is 0, then don't update the GlobalObjectIdHash + if (globalId.identifierType == k_NullObjectType) + { + return; + } + + var oldValue = GlobalObjectIdHash; + GlobalObjectIdHash = globalId.ToString().Hash32(); + + // If the GlobalObjectIdHash value changed, then mark the asset dirty + if (GlobalObjectIdHash != oldValue) + { + // Check if this is an in-scnee placed NetworkObject (Special Case for In-Scene Placed) + if (!IsEditingPrefab() && gameObject.scene.name != null && gameObject.scene.name != gameObject.name) + { + // Sanity check to make sure this is a scene placed object + if (globalId.identifierType != k_SceneObjectType) + { + // This should never happen, but in the event it does throw and error + Debug.LogError($"[{gameObject.name}] is detected as an in-scene placed object but its identifier is of type {globalId.identifierType}! **Report this error**"); + } + + // If this is a prefab instance + if (PrefabUtility.IsPartOfAnyPrefab(this)) + { + // We must invoke this in order for the modifications to get saved with the scene (does not mark scene as dirty) + PrefabUtility.RecordPrefabInstancePropertyModifications(this); + } + } + else // Otherwise, this is a standard network prefab asset so we just mark it dirty for the AssetDatabase to update it + { + EditorUtility.SetDirty(this); + } + } + } + + private bool IsEditingPrefab() + { + // Check if we are directly editing the prefab + var stage = PrefabStageUtility.GetPrefabStage(gameObject); + + // if we are not editing the prefab directly (or a sub-prefab), then return the object identifier + if (stage == null || stage.assetPath == null) + { + return false; + } + return true; + } + + private GlobalObjectId GetGlobalId() + { + var instanceGlobalId = GlobalObjectId.GetGlobalObjectIdSlow(this); + + // If not editing a prefab, then just use the generated id + if (!IsEditingPrefab()) + { + return instanceGlobalId; + } + + // If the asset doesn't exist at the given path, then return the object identifier + var prefabStageAssetPath = PrefabStageUtility.GetPrefabStage(gameObject).assetPath; + // If (for some reason) the asset path is null return the generated id + if (prefabStageAssetPath == null) + { + return instanceGlobalId; + } + + var theAsset = AssetDatabase.LoadAssetAtPath(prefabStageAssetPath); + // If there is no asset at that path (for some odd/edge case reason), return the generated id + if (theAsset == null) + { + return instanceGlobalId; + } + + // If we can't get the asset GUID and/or the file identifier, then return the object identifier + if (!AssetDatabase.TryGetGUIDAndLocalFileIdentifier(theAsset, out var guid, out long localFileId)) + { + return instanceGlobalId; + } + + // Note: If we reached this point, then we are most likely opening a prefab to edit. + // The instanceGlobalId will be constructed as if it is a scene object, however when it + // is serialized its value will be treated as a file asset (the "why" to the below code). + + // Construct an imported asset identifier with the type being a source asset object type + var prefabGlobalIdText = string.Format(k_GlobalIdTemplate, k_SourceAssetObjectType, guid, (ulong)localFileId, 0); + + // If we can't parse the result log an error and return the instanceGlobalId + if (!GlobalObjectId.TryParse(prefabGlobalIdText, out var prefabGlobalId)) + { + Debug.LogError($"[GlobalObjectId Gen] Failed to parse ({prefabGlobalIdText}) returning default ({instanceGlobalId})! ** Please Report This Error **"); + return instanceGlobalId; + } + + // Otherwise, return the constructed identifier for the source prefab asset + return prefabGlobalId; } #endif // UNITY_EDITOR @@ -684,6 +834,21 @@ internal void InvokeBehaviourOnGainedOwnership() } } + internal void InvokeOwnershipChanged(ulong previous, ulong next) + { + for (int i = 0; i < ChildNetworkBehaviours.Count; i++) + { + if (ChildNetworkBehaviours[i].gameObject.activeInHierarchy) + { + ChildNetworkBehaviours[i].InternalOnOwnershipChanged(previous, next); + } + else + { + Debug.LogWarning($"{ChildNetworkBehaviours[i].gameObject.name} is disabled! Netcode for GameObjects does not support disabled NetworkBehaviours! The {ChildNetworkBehaviours[i].GetType().Name} component was skipped during ownership assignment!"); + } + } + } + internal void InvokeBehaviourOnNetworkObjectParentChanged(NetworkObject parentNetworkObject) { for (int i = 0; i < ChildNetworkBehaviours.Count; i++) @@ -1190,7 +1355,7 @@ internal ushort GetNetworkBehaviourOrderIndex(NetworkBehaviour instance) return 0; } - internal NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) + public NetworkBehaviour GetNetworkBehaviourAtOrderIndex(ushort index) { if (index >= ChildNetworkBehaviours.Count) { diff --git a/Runtime/Core/NetworkObjectRefreshTool.cs b/Runtime/Core/NetworkObjectRefreshTool.cs new file mode 100644 index 0000000..b9c6db0 --- /dev/null +++ b/Runtime/Core/NetworkObjectRefreshTool.cs @@ -0,0 +1,118 @@ +#if UNITY_EDITOR +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEditor.SceneManagement; +using UnityEngine; +using UnityEngine.SceneManagement; + +namespace Unity.Netcode +{ + /// + /// This is a helper tool to update all in-scene placed instances of a prefab that + /// originally did not have a NetworkObject component but one was added to the prefab + /// later. + /// + internal class NetworkObjectRefreshTool + { + private static List s_ScenesToUpdate = new List(); + private static bool s_ProcessScenes; + private static bool s_CloseScenes; + + internal static Action AllScenesProcessed; + + internal static void ProcessScene(string scenePath, bool processScenes = true) + { + if (!s_ScenesToUpdate.Contains(scenePath)) + { + if (s_ScenesToUpdate.Count == 0) + { + EditorSceneManager.sceneOpened += EditorSceneManager_sceneOpened; + EditorSceneManager.sceneSaved += EditorSceneManager_sceneSaved; + } + s_ScenesToUpdate.Add(scenePath); + } + s_ProcessScenes = processScenes; + } + + internal static void ProcessActiveScene() + { + var activeScene = SceneManager.GetActiveScene(); + if (s_ScenesToUpdate.Contains(activeScene.path) && s_ProcessScenes) + { + SceneOpened(activeScene); + } + } + + internal static void ProcessScenes() + { + if (s_ScenesToUpdate.Count != 0) + { + s_CloseScenes = true; + var scenePath = s_ScenesToUpdate.First(); + EditorSceneManager.OpenScene(scenePath, OpenSceneMode.Additive); + } + else + { + s_CloseScenes = false; + EditorSceneManager.sceneSaved -= EditorSceneManager_sceneSaved; + EditorSceneManager.sceneOpened -= EditorSceneManager_sceneOpened; + AllScenesProcessed?.Invoke(); + } + } + + private static void FinishedProcessingScene(Scene scene, bool refreshed = false) + { + if (s_ScenesToUpdate.Contains(scene.path)) + { + // Provide a log of all scenes that were modified to the user + if (refreshed) + { + Debug.Log($"Refreshed and saved updates to scene: {scene.name}"); + } + s_ProcessScenes = false; + s_ScenesToUpdate.Remove(scene.path); + + if (scene != SceneManager.GetActiveScene()) + { + EditorSceneManager.CloseScene(scene, s_CloseScenes); + } + ProcessScenes(); + } + } + + private static void EditorSceneManager_sceneSaved(Scene scene) + { + FinishedProcessingScene(scene, true); + } + + private static void SceneOpened(Scene scene) + { + if (s_ScenesToUpdate.Contains(scene.path)) + { + if (s_ProcessScenes) + { + if (!EditorSceneManager.MarkSceneDirty(scene)) + { + Debug.Log($"Scene {scene.name} did not get marked as dirty!"); + FinishedProcessingScene(scene); + } + else + { + EditorSceneManager.SaveScene(scene); + } + } + else + { + FinishedProcessingScene(scene); + } + } + } + + private static void EditorSceneManager_sceneOpened(Scene scene, OpenSceneMode mode) + { + SceneOpened(scene); + } + } +} +#endif // UNITY_EDITOR diff --git a/Runtime/Core/NetworkObjectRefreshTool.cs.meta b/Runtime/Core/NetworkObjectRefreshTool.cs.meta new file mode 100644 index 0000000..58a3178 --- /dev/null +++ b/Runtime/Core/NetworkObjectRefreshTool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d24d5e8371c3cca4890e2713bdeda288 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs b/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs new file mode 100644 index 0000000..6b26e53 --- /dev/null +++ b/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs @@ -0,0 +1,82 @@ +using System; + +namespace Unity.Netcode +{ + /// + /// Marks a generic parameter in this class as a type that should be serialized through + /// . This enables the use of the following methods to support + /// serialization within a Network Variable type: + ///
+ ///
+ /// . + ///
+ /// . + ///
+ /// . + ///
+ /// . + ///
+ ///
+ /// The parameter is indicated by index (and is 0-indexed); for example: + ///
+ /// + /// [SerializesGenericParameter(1)] + /// public class MyClass<TTypeOne, TTypeTwo> + /// { + /// } + /// + ///
+ /// This tells the code generation for to generate + /// serialized code for TTypeTwo (generic parameter 1). + ///
+ ///
+ /// Note that this is primarily intended to support subtypes of , + /// and as such, the type resolution is done by examining fields of + /// subclasses. If your type is not used in a , the codegen will + /// not find the types, even with this attribute. + ///
+ ///
+ /// This attribute is properly inherited by subclasses. For example: + ///
+ /// + /// [SerializesGenericParameter(0)] + /// public class MyClass<T> + /// { + /// } + ///
+ /// public class MySubclass1 : MyClass<Foo> + /// { + /// } + ///
+ /// public class MySubclass2<T> : MyClass<T> + /// { + /// } + ///
+ /// [SerializesGenericParameter(1)] + /// public class MySubclass3<TTypeOne, TTypeTwo> : MyClass<TTypeOne> + /// { + /// } + ///
+ /// public class MyBehaviour : NetworkBehaviour + /// { + /// public MySubclass1 TheValue; + /// public MySubclass2<Bar> TheValue; + /// public MySubclass3<Baz, Qux> TheValue; + /// } + ///
+ ///
+ /// The above code will trigger generation of serialization code for Foo (passed directly to the + /// base class), Bar (passed indirectly to the base class), Baz (passed indirectly to the base class), + /// and Qux (marked as serializable in the subclass). + ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = true)] + public class GenerateSerializationForGenericParameterAttribute : Attribute + { + internal int ParameterIndex; + + public GenerateSerializationForGenericParameterAttribute(int parameterIndex) + { + ParameterIndex = parameterIndex; + } + } +} diff --git a/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs.meta b/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs.meta new file mode 100644 index 0000000..e81a5a6 --- /dev/null +++ b/Runtime/Messaging/GenerateSerializationForGenericParameterAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 18cdaa9c2f6446279b0c5948fcd34eec +timeCreated: 1694029524 \ No newline at end of file diff --git a/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs b/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs new file mode 100644 index 0000000..81e55c0 --- /dev/null +++ b/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs @@ -0,0 +1,26 @@ +using System; + +namespace Unity.Netcode +{ + /// + /// Specifies a specific type that needs serialization to be generated by codegen. + /// This is only needed in special circumstances where manual serialization is being done. + /// If you are making a generic network variable-style class, use . + ///
+ ///
+ /// This attribute can be attached to any class or method anywhere in the codebase and + /// will trigger codegen to generate serialization code for the provided type. It only needs + /// to be included once type per codebase, but including it multiple times for the same type + /// is safe. + ///
+ [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method, AllowMultiple = true)] + public class GenerateSerializationForTypeAttribute : Attribute + { + internal Type Type; + + public GenerateSerializationForTypeAttribute(Type type) + { + Type = type; + } + } +} diff --git a/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs.meta b/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs.meta new file mode 100644 index 0000000..f0701d5 --- /dev/null +++ b/Runtime/Messaging/GenerateSerializationForTypeAttribute.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1bd80306706f4054b9ba514a72076df5 +timeCreated: 1694103021 \ No newline at end of file diff --git a/Runtime/Messaging/ILPPMessageProvider.cs b/Runtime/Messaging/ILPPMessageProvider.cs index 620ad97..4424550 100644 --- a/Runtime/Messaging/ILPPMessageProvider.cs +++ b/Runtime/Messaging/ILPPMessageProvider.cs @@ -1,4 +1,7 @@ using System.Collections.Generic; +#if UNITY_EDITOR +using UnityEditor; +#endif namespace Unity.Netcode { @@ -13,5 +16,24 @@ internal struct ILPPMessageProvider : INetworkMessageProvider { return __network_message_types; } + +#if UNITY_EDITOR + [InitializeOnLoadMethod] + public static void NotifyOnPlayStateChange() + { + EditorApplication.playModeStateChanged += OnPlayModeStateChanged; + } + + public static void OnPlayModeStateChanged(PlayModeStateChange change) + { + if (change == PlayModeStateChange.ExitingPlayMode) + { + // Clear out the network message types, because ILPP-generated RuntimeInitializeOnLoad code will + // run again and add more messages to it. + __network_message_types.Clear(); + } + } + +#endif } } diff --git a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs index d52de3b..b0d1ca7 100644 --- a/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs +++ b/Runtime/Messaging/Messages/ChangeOwnershipMessage.cs @@ -60,6 +60,8 @@ public void Handle(ref NetworkContext context) } } + networkObject.InvokeOwnershipChanged(originalOwner, OwnerClientId); + networkManager.NetworkMetrics.TrackOwnershipChangeReceived(context.SenderId, networkObject, context.MessageSize); } } diff --git a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs index 648573e..0a0b398 100644 --- a/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs +++ b/Runtime/Messaging/Messages/ConnectionApprovedMessage.cs @@ -37,6 +37,9 @@ public void Serialize(FastBufferWriter writer, int targetVersion) BytePacker.WriteValueBitPacked(writer, NetworkTick); uint sceneObjectCount = 0; + + // When SpawnedObjectsList is not null then scene management is disabled. Provide a list of + // all observed and spawned NetworkObjects that the approved client needs to synchronize. if (SpawnedObjectsList != null) { var pos = writer.Position; @@ -45,7 +48,7 @@ public void Serialize(FastBufferWriter writer, int targetVersion) // Serialize NetworkVariable data foreach (var sobj in SpawnedObjectsList) { - if (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId)) + if (sobj.SpawnWithObservers && (sobj.CheckObjectVisibility == null || sobj.CheckObjectVisibility(OwnerClientId))) { sobj.Observers.Add(OwnerClientId); var sceneObject = sobj.GetMessageSceneObject(OwnerClientId); diff --git a/Runtime/Messaging/Messages/RpcMessages.cs b/Runtime/Messaging/Messages/RpcMessages.cs index 59ecb9f..1798c5c 100644 --- a/Runtime/Messaging/Messages/RpcMessages.cs +++ b/Runtime/Messaging/Messages/RpcMessages.cs @@ -34,7 +34,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo return false; } - if (!NetworkManager.__rpc_func_table.ContainsKey(metadata.NetworkRpcMethodId)) + if (!NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()].ContainsKey(metadata.NetworkRpcMethodId)) { return false; } @@ -42,7 +42,7 @@ public static unsafe bool Deserialize(ref FastBufferReader reader, ref NetworkCo payload = new FastBufferReader(reader.GetUnsafePtrAtCurrentPosition(), Allocator.None, reader.Length - reader.Position); #if DEVELOPMENT_BUILD || UNITY_EDITOR - if (NetworkManager.__rpc_name_table.TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) + if (NetworkBehaviour.__rpc_name_table[networkBehaviour.GetType()].TryGetValue(metadata.NetworkRpcMethodId, out var rpcMethodName)) { networkManager.NetworkMetrics.TrackRpcReceived( context.SenderId, @@ -67,7 +67,7 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, try { - NetworkManager.__rpc_func_table[metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); + NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()][metadata.NetworkRpcMethodId](networkBehaviour, payload, rpcParams); } catch (Exception ex) { @@ -75,7 +75,7 @@ public static void Handle(ref NetworkContext context, ref RpcMetadata metadata, if (networkManager.LogLevel == LogLevel.Developer) { Debug.Log($"RPC Table Contents"); - foreach (var entry in NetworkManager.__rpc_func_table) + foreach (var entry in NetworkBehaviour.__rpc_func_table[networkBehaviour.GetType()]) { Debug.Log($"{entry.Key} | {entry.Value.Method.Name}"); } diff --git a/Runtime/Messaging/NetworkMessageManager.cs b/Runtime/Messaging/NetworkMessageManager.cs index 6caf731..e66b54e 100644 --- a/Runtime/Messaging/NetworkMessageManager.cs +++ b/Runtime/Messaging/NetworkMessageManager.cs @@ -99,6 +99,8 @@ internal uint GetMessageType(Type t) public int NonFragmentedMessageMaxSize = DefaultNonFragmentedMessageMaxSize; public int FragmentedMessageMaxSize = int.MaxValue; + public Dictionary PeerMTUSizes = new Dictionary(); + internal struct MessageWithHandler { public Type MessageType; @@ -497,6 +499,7 @@ private void CleanupDisconnectedClient(ulong clientId) m_SendQueues.Remove(clientId); m_PerClientMessageVersions.Remove(clientId); + PeerMTUSizes.Remove(clientId); } internal void CleanupDisconnectedClients() @@ -678,6 +681,21 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t continue; } + var startSize = NonFragmentedMessageMaxSize; + if (delivery != NetworkDelivery.ReliableFragmentedSequenced) + { + if (PeerMTUSizes.TryGetValue(clientId, out var clientMaxSize)) + { + maxSize = clientMaxSize; + } + startSize = maxSize; + if (tmpSerializer.Position >= maxSize) + { + Debug.LogError($"MTU size for {clientId} is too small to contain a message of type {typeof(TMessageType).FullName}"); + continue; + } + } + for (var hookIdx = 0; hookIdx < m_Hooks.Count; ++hookIdx) { m_Hooks[hookIdx].OnBeforeSendMessage(clientId, ref message, delivery); @@ -686,7 +704,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t var sendQueueItem = m_SendQueues[clientId]; if (sendQueueItem.Length == 0) { - sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize)); + sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize)); sendQueueItem.ElementAt(0).Writer.Seek(sizeof(NetworkBatchHeader)); } else @@ -694,7 +712,7 @@ internal unsafe int SendPreSerializedMessage(in FastBufferWriter t ref var lastQueueItem = ref sendQueueItem.ElementAt(sendQueueItem.Length - 1); if (lastQueueItem.NetworkDelivery != delivery || lastQueueItem.Writer.MaxCapacity - lastQueueItem.Writer.Position < tmpSerializer.Length + headerSerializer.Length) { - sendQueueItem.Add(new SendQueueItem(delivery, NonFragmentedMessageMaxSize, Allocator.TempJob, maxSize)); + sendQueueItem.Add(new SendQueueItem(delivery, startSize, Allocator.TempJob, maxSize)); sendQueueItem.ElementAt(sendQueueItem.Length - 1).Writer.Seek(sizeof(NetworkBatchHeader)); } } diff --git a/Runtime/NetworkVariable/Collections/NetworkList.cs b/Runtime/NetworkVariable/Collections/NetworkList.cs index aee004f..a082b4e 100644 --- a/Runtime/NetworkVariable/Collections/NetworkList.cs +++ b/Runtime/NetworkVariable/Collections/NetworkList.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using Unity.Collections; -using UnityEngine; namespace Unity.Netcode { @@ -9,6 +8,7 @@ namespace Unity.Netcode /// Event based NetworkVariable container for syncing Lists ///
/// The type for the list + [GenerateSerializationForGenericParameter(0)] public class NetworkList : NetworkVariableBase where T : unmanaged, IEquatable { private NativeList m_List = new NativeList(64, Allocator.Persistent); @@ -68,14 +68,7 @@ public override bool IsDirty() internal void MarkNetworkObjectDirty() { - if (m_NetworkBehaviour == null) - { - Debug.LogWarning($"NetworkList is written to, but doesn't know its NetworkBehaviour yet. " + - "Are you modifying a NetworkList before the NetworkObject is spawned?"); - return; - } - - m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject); + MarkNetworkBehaviourDirty(); } /// diff --git a/Runtime/NetworkVariable/NetworkVariable.cs b/Runtime/NetworkVariable/NetworkVariable.cs index 505df39..db33b7b 100644 --- a/Runtime/NetworkVariable/NetworkVariable.cs +++ b/Runtime/NetworkVariable/NetworkVariable.cs @@ -8,6 +8,7 @@ namespace Unity.Netcode ///
/// the unmanaged type for [Serializable] + [GenerateSerializationForGenericParameter(0)] public class NetworkVariable : NetworkVariableBase { /// @@ -146,8 +147,11 @@ public override void ResetDirty() // Therefore, we set the m_PreviousValue field to a duplicate of the current // field, so that our next dirty check is made against the current "not dirty" // value. - m_HasPreviousValue = true; - NetworkVariableSerialization.Serializer.Duplicate(m_InternalValue, ref m_PreviousValue); + if (!m_HasPreviousValue || !NetworkVariableSerialization.AreEqual(ref m_InternalValue, ref m_PreviousValue)) + { + m_HasPreviousValue = true; + NetworkVariableSerialization.Duplicate(m_InternalValue, ref m_PreviousValue); + } } /// diff --git a/Runtime/NetworkVariable/NetworkVariableBase.cs b/Runtime/NetworkVariable/NetworkVariableBase.cs index ece07f1..c72813e 100644 --- a/Runtime/NetworkVariable/NetworkVariableBase.cs +++ b/Runtime/NetworkVariable/NetworkVariableBase.cs @@ -88,17 +88,22 @@ public virtual void SetDirty(bool isDirty) if (m_IsDirty) { - if (m_NetworkBehaviour == null) - { - Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + - "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); - return; - } - - m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject); + MarkNetworkBehaviourDirty(); } } + protected void MarkNetworkBehaviourDirty() + { + if (m_NetworkBehaviour == null) + { + Debug.LogWarning($"NetworkVariable is written to, but doesn't know its NetworkBehaviour yet. " + + "Are you modifying a NetworkVariable before the NetworkObject is spawned?"); + return; + } + + m_NetworkBehaviour.NetworkManager.BehaviourUpdater.AddForUpdate(m_NetworkBehaviour.NetworkObject); + } + /// /// Resets the dirty state and marks the variable as synced / clean /// diff --git a/Runtime/NetworkVariable/NetworkVariableSerialization.cs b/Runtime/NetworkVariable/NetworkVariableSerialization.cs index 6ade408..fc900e6 100644 --- a/Runtime/NetworkVariable/NetworkVariableSerialization.cs +++ b/Runtime/NetworkVariable/NetworkVariableSerialization.cs @@ -514,7 +514,7 @@ void INetworkVariableSerializer.ReadWithAllocator(FastBufferReader reader, ou public void Duplicate(in T value, ref T duplicatedValue) { - using var writer = new FastBufferWriter(256, Allocator.Temp); + using var writer = new FastBufferWriter(256, Allocator.Temp, int.MaxValue); var refValue = value; Write(writer, ref refValue); @@ -580,11 +580,16 @@ public class UserNetworkVariableSerialization /// internal class FallbackSerializer : INetworkVariableSerializer { + private void ThrowArgumentError() + { + throw new ArgumentException($"Serialization has not been generated for type {typeof(T).FullName}. This can be addressed by adding a [{nameof(GenerateSerializationForGenericParameterAttribute)}] to your generic class that serializes this value (if you are using one), adding [{nameof(GenerateSerializationForTypeAttribute)}(typeof({typeof(T).FullName})] to the class or method that is attempting to serialize it, or creating a field on a {nameof(NetworkBehaviour)} of type {nameof(NetworkVariable)}. If this error continues to appear after doing one of those things and this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list to enable automatic serialization generation. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + } + public void Write(FastBufferWriter writer, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { - throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + ThrowArgumentError(); } UserNetworkVariableSerialization.WriteValue(writer, value); } @@ -592,7 +597,7 @@ public void Read(FastBufferReader reader, ref T value) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { - throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + ThrowArgumentError(); } UserNetworkVariableSerialization.ReadValue(reader, out value); } @@ -606,7 +611,7 @@ public void Duplicate(in T value, ref T duplicatedValue) { if (UserNetworkVariableSerialization.ReadValue == null || UserNetworkVariableSerialization.WriteValue == null || UserNetworkVariableSerialization.DuplicateValue == null) { - throw new ArgumentException($"Type {typeof(T).FullName} is not supported by {typeof(NetworkVariable<>).Name}. If this is a type you can change, then either implement {nameof(INetworkSerializable)} or mark it as serializable by memcpy by adding {nameof(INetworkSerializeByMemcpy)} to its interface list. If not, assign serialization code to {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.WriteValue)}, {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.ReadValue)}, and {nameof(UserNetworkVariableSerialization)}.{nameof(UserNetworkVariableSerialization.DuplicateValue)}, or if it's serializable by memcpy (contains no pointers), wrap it in {typeof(ForceNetworkSerializeByMemcpy<>).Name}."); + ThrowArgumentError(); } UserNetworkVariableSerialization.DuplicateValue(value, ref duplicatedValue); } @@ -841,8 +846,95 @@ public static class NetworkVariableSerialization { internal static INetworkVariableSerializer Serializer = new FallbackSerializer(); - internal delegate bool EqualsDelegate(ref T a, ref T b); - internal static EqualsDelegate AreEqual; + /// + /// A callback to check if two values are equal. + /// + public delegate bool EqualsDelegate(ref T a, ref T b); + + /// + /// Uses the most efficient mechanism for a given type to determine if two values are equal. + /// For types that implement , it will call the Equals() method. + /// For unmanaged types, it will do a bytewise memory comparison. + /// For other types, it will call the == operator. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to check their equality yourself. + ///
+ public static EqualsDelegate AreEqual { get; internal set; } + + /// + /// Serialize a value using the best-known serialization method for a generic value. + /// Will reliably serialize any value that is passed to it correctly with no boxing. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferWriter directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user serialization callback to it at runtime. + ///
+ /// + /// + public static void Write(FastBufferWriter writer, ref T value) + { + Serializer.Write(writer, ref value); + } + + /// + /// Deserialize a value using the best-known serialization method for a generic value. + /// Will reliably deserialize any value that is passed to it correctly with no boxing. + /// For types whose deserialization can be determined by codegen (which is most types), + /// GC will only be incurred if the type is a managed type and the ref value passed in is `null`, + /// in which case a new value is created; otherwise, it will be deserialized in-place. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to use FastBufferReader directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user deserialization callback to it at runtime. + ///
+ /// + /// + public static void Read(FastBufferReader reader, ref T value) + { + Serializer.Read(reader, ref value); + } + + /// + /// Duplicates a value using the most efficient means of creating a complete copy. + /// For most types this is a simple assignment or memcpy. + /// For managed types, this is will serialize and then deserialize the value to ensure + /// a correct copy. + ///
+ ///
+ /// Note: If you are using this in a custom generic class, please make sure your class is + /// decorated with so that codegen can + /// initialize the serialization mechanisms correctly. If your class is NOT + /// generic, it is better to duplicate it directly. + ///
+ ///
+ /// If the codegen is unable to determine a serializer for a type, + /// . is called, which, by default, + /// will throw an exception, unless you have assigned a user duplication callback to it at runtime. + ///
+ /// + /// + public static void Duplicate(in T value, ref T duplicatedValue) + { + Serializer.Duplicate(value, ref duplicatedValue); + } // Compares two values of the same unmanaged type by underlying memory // Ignoring any overridden value checks @@ -1001,15 +1093,21 @@ internal static bool ClassEquals(ref TValueType a, ref TValueType b) { return a == b; } + } - internal static void Write(FastBufferWriter writer, ref T value) + // RuntimeAccessModifiersILPP will make this `public` + // This is just pass-through to NetworkVariableSerialization but is here becaues I could not get ILPP + // to generate code that would successfully call Type.Method(T), but it has no problem calling Type.Method(T) + internal class RpcFallbackSerialization + { + public static void Write(FastBufferWriter writer, ref T value) { - Serializer.Write(writer, ref value); + NetworkVariableSerialization.Write(writer, ref value); } - internal static void Read(FastBufferReader reader, ref T value) + public static void Read(FastBufferReader reader, ref T value) { - Serializer.Read(reader, ref value); + NetworkVariableSerialization.Read(reader, ref value); } } } diff --git a/Runtime/SceneManagement/NetworkSceneManager.cs b/Runtime/SceneManagement/NetworkSceneManager.cs index f7a2f13..ffb244e 100644 --- a/Runtime/SceneManagement/NetworkSceneManager.cs +++ b/Runtime/SceneManagement/NetworkSceneManager.cs @@ -740,6 +740,14 @@ internal NetworkSceneManager(NetworkManager networkManager) // Since NetworkManager is now always migrated to the DDOL we will use this to get the DDOL scene DontDestroyOnLoadScene = networkManager.gameObject.scene; + // Since the server tracks loaded scenes, we need to add the currently active scene + // to the list of scenes that can be unloaded. + if (networkManager.IsServer) + { + var activeScene = SceneManager.GetActiveScene(); + ScenesLoaded.Add(activeScene.handle, activeScene); + } + // Add to the server to client scene handle table UpdateServerClientSceneHandle(DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene.handle, DontDestroyOnLoadScene); } diff --git a/Runtime/Spawning/NetworkSpawnManager.cs b/Runtime/Spawning/NetworkSpawnManager.cs index c7ddc60..c11b487 100644 --- a/Runtime/Spawning/NetworkSpawnManager.cs +++ b/Runtime/Spawning/NetworkSpawnManager.cs @@ -257,6 +257,7 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) throw new SpawnStateException("Object is not spawned"); } + var previous = networkObject.OwnerClientId; // Assign the new owner networkObject.OwnerClientId = clientId; @@ -286,6 +287,12 @@ internal void ChangeOwnership(NetworkObject networkObject, ulong clientId) NetworkManager.NetworkMetrics.TrackOwnershipChangeSent(client.Key, networkObject, size); } } + + // After we have sent the change ownership message to all client observers, invoke the ownership changed notification. + /// !!Important!! + /// This gets called specifically *after* sending the ownership message so any additional messages that need to proceed an ownership + /// change can be sent from NetworkBehaviours that override the + networkObject.InvokeOwnershipChanged(previous, clientId); } internal bool HasPrefab(NetworkObject.SceneObject sceneObject) diff --git a/Runtime/Transports/UTP/BatchedSendQueue.cs b/Runtime/Transports/UTP/BatchedSendQueue.cs index 6f81eea..6bf3e49 100644 --- a/Runtime/Transports/UTP/BatchedSendQueue.cs +++ b/Runtime/Transports/UTP/BatchedSendQueue.cs @@ -242,7 +242,7 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) /// Fill the given with as many bytes from the queue as /// possible, disregarding message boundaries. ///
- /// + /// /// This does NOT actually consume anything from the queue. That is, calling this method /// does not reduce the length of the queue. Callers are expected to call /// with the value returned by this method afterwards if the data can @@ -252,15 +252,17 @@ public int FillWriterWithMessages(ref DataStreamWriter writer) /// this could lead to reading messages from a corrupted queue. /// /// The to write to. + /// Max number of bytes to copy (0 means writer capacity). /// How many bytes were written to the writer. - public int FillWriterWithBytes(ref DataStreamWriter writer) + public int FillWriterWithBytes(ref DataStreamWriter writer, int maxBytes = 0) { if (!IsCreated || Length == 0) { return 0; } - var copyLength = Math.Min(writer.Capacity, Length); + var maxLength = maxBytes == 0 ? writer.Capacity : Math.Min(maxBytes, writer.Capacity); + var copyLength = Math.Min(maxLength, Length); unsafe { diff --git a/Runtime/Transports/UTP/UnityTransport.cs b/Runtime/Transports/UTP/UnityTransport.cs index dc2e143..4576404 100644 --- a/Runtime/Transports/UTP/UnityTransport.cs +++ b/Runtime/Transports/UTP/UnityTransport.cs @@ -727,6 +727,7 @@ private struct SendBatchedMessagesJob : IJob public SendTarget Target; public BatchedSendQueue Queue; public NetworkPipeline ReliablePipeline; + public int MTU; public void Execute() { @@ -749,7 +750,7 @@ public void Execute() // in the stream (the send queue does that automatically) we are sure they'll be // reassembled properly at the other end. This allows us to lift the limit of ~44KB // on reliable payloads (because of the reliable window size). - var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer) : Queue.FillWriterWithMessages(ref writer); + var written = pipeline == ReliablePipeline ? Queue.FillWriterWithBytes(ref writer, MTU) : Queue.FillWriterWithMessages(ref writer); result = Driver.EndSend(writer); if (result == written) @@ -783,12 +784,21 @@ private void SendBatchedMessages(SendTarget sendTarget, BatchedSendQueue queue) { return; } + + var mtu = 0; + if (NetworkManager) + { + var ngoClientId = NetworkManager.ConnectionManager.TransportIdToClientId(sendTarget.ClientId); + mtu = NetworkManager.GetPeerMTU(ngoClientId); + } + new SendBatchedMessagesJob { Driver = m_Driver.ToConcurrent(), Target = sendTarget, Queue = queue, - ReliablePipeline = m_ReliableSequencedPipeline + ReliablePipeline = m_ReliableSequencedPipeline, + MTU = mtu, }.Run(); } @@ -1560,6 +1570,21 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } #endif +#if UTP_TRANSPORT_2_1_ABOVE + if (m_ProtocolType == ProtocolType.RelayUnityTransport) + { + if (m_UseWebSockets && m_RelayServerData.IsWebSocket == 0) + { + Debug.LogError("Transport is configured to use WebSockets, but Relay server data isn't. Be sure to use \"wss\" as the connection type when creating the server data (instead of \"dtls\" or \"udp\")."); + } + + if (!m_UseWebSockets && m_RelayServerData.IsWebSocket != 0) + { + Debug.LogError("Relay server data indicates usage of WebSockets, but \"Use WebSockets\" checkbox isn't checked under \"Unity Transport\" component."); + } + } +#endif + #if UTP_TRANSPORT_2_0_ABOVE if (m_UseWebSockets) { @@ -1567,7 +1592,7 @@ public void CreateDriver(UnityTransport transport, out NetworkDriver driver, } else { -#if UNITY_WEBGL +#if UNITY_WEBGL && !UNITY_EDITOR Debug.LogWarning($"WebSockets were used even though they're not selected in NetworkManager. You should check {nameof(UseWebSockets)}', on the Unity Transport component, to silence this warning."); driver = NetworkDriver.Create(new WebSocketNetworkInterface(), m_NetworkSettings); #else diff --git a/Runtime/com.unity.netcode.runtime.asmdef b/Runtime/com.unity.netcode.runtime.asmdef index 43433ab..beeb62a 100644 --- a/Runtime/com.unity.netcode.runtime.asmdef +++ b/Runtime/com.unity.netcode.runtime.asmdef @@ -36,6 +36,11 @@ "name": "com.unity.transport", "expression": "2.0.0-exp", "define": "UTP_TRANSPORT_2_0_ABOVE" + }, + { + "name": "com.unity.transport", + "expression": "2.1.0", + "define": "UTP_TRANSPORT_2_1_ABOVE" } ] } diff --git a/TestHelpers/Runtime/IntegrationTestSceneHandler.cs b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs index c5b5595..d979c74 100644 --- a/TestHelpers/Runtime/IntegrationTestSceneHandler.cs +++ b/TestHelpers/Runtime/IntegrationTestSceneHandler.cs @@ -913,6 +913,9 @@ public IntegrationTestSceneHandler(NetworkManager networkManager) if (CoroutineRunner == null) { CoroutineRunner = new GameObject("UnitTestSceneHandlerCoroutine").AddComponent(); + // Move the CoroutineRunner into the DDOL in case we unload the scene it was instantiated in. + // (which if that gets destroyed then it basically stops all integration test queue processing) + Object.DontDestroyOnLoad(CoroutineRunner); } } diff --git a/TestHelpers/Runtime/NetcodeIntegrationTest.cs b/TestHelpers/Runtime/NetcodeIntegrationTest.cs index 63ea3f4..b30d935 100644 --- a/TestHelpers/Runtime/NetcodeIntegrationTest.cs +++ b/TestHelpers/Runtime/NetcodeIntegrationTest.cs @@ -886,7 +886,6 @@ protected void RegisterSceneManagerHandler() { IntegrationTestSceneHandler.CanClientsLoad += ClientSceneHandler_CanClientsLoad; IntegrationTestSceneHandler.CanClientsUnload += ClientSceneHandler_CanClientsUnload; - NetcodeIntegrationTestHelpers.RegisterSceneManagerHandler(m_ServerNetworkManager, true); } private bool ClientSceneHandler_CanClientsUnload() diff --git a/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs b/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs index e98590d..bd20732 100644 --- a/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs +++ b/TestHelpers/Runtime/NetcodeIntegrationTestHelpers.cs @@ -165,7 +165,8 @@ public static void RegisterHandlers(NetworkManager networkManager, bool serverSi if (!networkManager.IsServer || networkManager.IsServer && serverSideSceneManager) { - RegisterSceneManagerHandler(networkManager); + // Pass along the serverSideSceneManager property (otherwise the server won't register properly) + RegisterSceneManagerHandler(networkManager, serverSideSceneManager); } } @@ -405,7 +406,10 @@ private static void SceneManagerValidationAndTestRunnerInitialization(NetworkMan // scene to synchronize NetworkObjects. Next, add the currently active test runner scene to the scenes // loaded and register the server to client scene handle since host-server shares the test runner scene // with the clients. - networkManager.SceneManager.GetAndAddNewlyLoadedSceneByName(scene.name); + if (!networkManager.SceneManager.ScenesLoaded.ContainsKey(scene.handle)) + { + networkManager.SceneManager.ScenesLoaded.Add(scene.handle, scene); + } networkManager.SceneManager.ServerSceneHandleToClientSceneHandle.Add(scene.handle, scene.handle); } } @@ -443,8 +447,8 @@ public static bool Start(bool host, NetworkManager server, NetworkManager[] clie server.ConnectionManager.MessageManager.Hook(hooks); s_Hooks[server] = hooks; - // if set, then invoke this for the server - RegisterHandlers(server); + // Register the server side handler (always pass true for server) + RegisterHandlers(server, true); callback?.Invoke(); @@ -919,6 +923,24 @@ private static IEnumerator ExecuteWaitForHook(MessageHandleCheckWithResult check var res = check.Result; result.Result = res; } + + public static uint GetGlobalObjectIdHash(NetworkObject networkObject) + { + return networkObject.GlobalObjectIdHash; + } + +#if UNITY_EDITOR + public static void SetRefreshAllPrefabsCallback(Action scenesProcessed) + { + NetworkObjectRefreshTool.AllScenesProcessed = scenesProcessed; + } + + public static void RefreshAllPrefabInstances(NetworkObject networkObject, Action scenesProcessed) + { + NetworkObjectRefreshTool.AllScenesProcessed = scenesProcessed; + networkObject.RefreshAllPrefabInstances(); + } +#endif } // Empty MonoBehaviour that is a holder of coroutine diff --git a/Tests/Editor/Messaging/MessageSendingTests.cs b/Tests/Editor/Messaging/MessageSendingTests.cs index a7492bc..81cc7e7 100644 --- a/Tests/Editor/Messaging/MessageSendingTests.cs +++ b/Tests/Editor/Messaging/MessageSendingTests.cs @@ -181,6 +181,64 @@ public void WhenExceedingBatchSize_NewBatchesAreCreated([Values(500, 1000, 1300, Assert.AreEqual(2, m_MessageSender.MessageQueue.Count); } + [Test] + public void WhenExceedingPerClientBatchSizeLessThanDefault_NewBatchesAreCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) + { + var message = GetMessage(); + m_MessageManager.NonFragmentedMessageMaxSize = maxMessageSize * 5; + var clients = new ulong[] { 0, 1, 2 }; + m_MessageManager.ClientConnected(1); + m_MessageManager.ClientConnected(2); + m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0); + m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0); + + for (var i = 0; i < clients.Length; ++i) + { + m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1); + } + + var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes + for (var i = 0; i < clients.Length; ++i) + { + for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf()) / size) + 1; ++j) + { + m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]); + } + } + + m_MessageManager.ProcessSendQueues(); + Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count); + } + + [Test] + public void WhenExceedingPerClientBatchSizeGreaterThanDefault_OnlyOneNewBatcheIsCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) + { + var message = GetMessage(); + m_MessageManager.NonFragmentedMessageMaxSize = 128; + var clients = new ulong[] { 0, 1, 2 }; + m_MessageManager.ClientConnected(1); + m_MessageManager.ClientConnected(2); + m_MessageManager.SetVersion(1, XXHash.Hash32(typeof(TestMessage).FullName), 0); + m_MessageManager.SetVersion(2, XXHash.Hash32(typeof(TestMessage).FullName), 0); + + for (var i = 0; i < clients.Length; ++i) + { + m_MessageManager.PeerMTUSizes[clients[i]] = maxMessageSize * (i + 1); + } + + var size = UnsafeUtility.SizeOf() + 2; // MessageHeader packed with this message will be 2 bytes + for (var i = 0; i < clients.Length; ++i) + { + for (var j = 0; j < ((m_MessageManager.PeerMTUSizes[clients[i]] - UnsafeUtility.SizeOf()) / size) + 1; ++j) + { + m_MessageManager.SendMessage(ref message, NetworkDelivery.Reliable, clients[i]); + } + } + + m_MessageManager.ProcessSendQueues(); + Assert.AreEqual(2 * clients.Length, m_MessageSender.MessageQueue.Count); + } + [Test] public void WhenExceedingMTUSizeWithFragmentedDelivery_NewBatchesAreNotCreated([Values(500, 1000, 1300, 2000)] int maxMessageSize) { diff --git a/Tests/Editor/NetworkManagerConfigurationTests.cs b/Tests/Editor/NetworkManagerConfigurationTests.cs index cac5430..45ac91c 100644 --- a/Tests/Editor/NetworkManagerConfigurationTests.cs +++ b/Tests/Editor/NetworkManagerConfigurationTests.cs @@ -173,37 +173,50 @@ public void WhenModifyingPrefabListUsingNetworkManagerAPI_ModificationIsLocal() NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); + try + { + var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager.Initialize(true); - networkManager2.Initialize(false); + networkManager.Initialize(true); + networkManager2.Initialize(false); - networkManager.AddNetworkPrefab(object2.gameObject); - networkManager2.AddNetworkPrefab(object3.gameObject); + networkManager.AddNetworkPrefab(object2.gameObject); + networkManager2.AddNetworkPrefab(object3.gameObject); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsFalse(sharedList.Contains(object2.gameObject)); - Assert.IsFalse(sharedList.Contains(object3.gameObject)); + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsFalse(sharedList.Contains(object2.gameObject)); + Assert.IsFalse(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } [Test] @@ -224,36 +237,49 @@ public void WhenModifyingPrefabListUsingPrefabsAPI_ModificationIsLocal() NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); + try + { + var object1 = new GameObject("Object 1").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager.Initialize(true); - networkManager2.Initialize(false); + networkManager.Initialize(true); + networkManager2.Initialize(false); - networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); - networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); + networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); + networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsFalse(sharedList.Contains(object2.gameObject)); - Assert.IsFalse(sharedList.Contains(object3.gameObject)); + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsFalse(sharedList.Contains(object2.gameObject)); + Assert.IsFalse(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } [Test] @@ -274,36 +300,49 @@ public void WhenModifyingPrefabListUsingPrefabsListAPI_ModificationIsShared() NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); + try + { + var object1 = new GameObject("Object 1").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager.Initialize(true); - networkManager2.Initialize(false); + networkManager.Initialize(true); + networkManager2.Initialize(false); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object2.gameObject }); - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object3.gameObject }); + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object2.gameObject }); + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists[0].Add(new NetworkPrefab { Prefab = object3.gameObject }); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsTrue(sharedList.Contains(object2.gameObject)); - Assert.IsTrue(sharedList.Contains(object3.gameObject)); + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsTrue(sharedList.Contains(object2.gameObject)); + Assert.IsTrue(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } [Test] @@ -324,36 +363,49 @@ public void WhenCallingInitializeAfterAddingAPrefabUsingPrefabsAPI_ThePrefabStil NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); + try + { + var object1 = new GameObject("Object 1").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); - networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); + networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); + networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); - networkManager.Initialize(true); - networkManager2.Initialize(false); + networkManager.Initialize(true); + networkManager2.Initialize(false); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsFalse(sharedList.Contains(object2.gameObject)); - Assert.IsFalse(sharedList.Contains(object3.gameObject)); + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsFalse(sharedList.Contains(object2.gameObject)); + Assert.IsFalse(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } [Test] @@ -374,42 +426,60 @@ public void WhenShuttingDownAndReinitializingPrefabs_RuntimeAddedPrefabsStillExi NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); - - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; - - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - - networkManager.Initialize(true); - networkManager2.Initialize(false); - - networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); - networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); - - networkManager.ShutdownInternal(); - networkManager2.ShutdownInternal(); - - networkManager.Initialize(true); - networkManager2.Initialize(false); - - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsFalse(sharedList.Contains(object2.gameObject)); - Assert.IsFalse(sharedList.Contains(object3.gameObject)); + try + { + var object1 = new GameObject("Object 1").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); + + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; + + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + + networkManager.Initialize(true); + networkManager2.Initialize(false); + + networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); + networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); + + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + + networkManager.Initialize(true); + networkManager2.Initialize(false); + + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsFalse(sharedList.Contains(object2.gameObject)); + Assert.IsFalse(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } [Test] @@ -430,39 +500,52 @@ public void WhenCallingInitializeMultipleTimes_NothingBreaks() NetworkTransport = networkManager.gameObject.AddComponent() }; - var object1 = new GameObject("Object 1").AddComponent(); - var object2 = new GameObject("Object 2").AddComponent(); - var object3 = new GameObject("Object 3").AddComponent(); + try + { + var object1 = new GameObject("Object 1").AddComponent(); + var object2 = new GameObject("Object 2").AddComponent(); + var object3 = new GameObject("Object 3").AddComponent(); - object1.GlobalObjectIdHash = 1; - object2.GlobalObjectIdHash = 2; - object3.GlobalObjectIdHash = 3; + object1.GlobalObjectIdHash = 1; + object2.GlobalObjectIdHash = 2; + object3.GlobalObjectIdHash = 3; - var sharedList = ScriptableObject.CreateInstance(); - sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); + var sharedList = ScriptableObject.CreateInstance(); + sharedList.List.Add(new NetworkPrefab { Prefab = object1.gameObject }); - networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; + networkManager2.NetworkConfig.Prefabs.NetworkPrefabsLists = new List { sharedList }; - networkManager.Initialize(true); - networkManager2.Initialize(false); + networkManager.Initialize(true); + networkManager2.Initialize(false); - networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); - networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); + networkManager.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object2.gameObject }); + networkManager2.NetworkConfig.Prefabs.Add(new NetworkPrefab { Prefab = object3.gameObject }); - networkManager.NetworkConfig.Prefabs.Initialize(); - networkManager2.NetworkConfig.Prefabs.Initialize(); + networkManager.NetworkConfig.Prefabs.Initialize(); + networkManager2.NetworkConfig.Prefabs.Initialize(); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); - Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); - Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object1.gameObject)); + Assert.IsTrue(networkManager.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsFalse(networkManager2.NetworkConfig.Prefabs.Contains(object2.gameObject)); + Assert.IsTrue(networkManager2.NetworkConfig.Prefabs.Contains(object3.gameObject)); + Assert.IsFalse(networkManager.NetworkConfig.Prefabs.Contains(object3.gameObject)); - Assert.IsTrue(sharedList.Contains(object1.gameObject)); - Assert.IsFalse(sharedList.Contains(object2.gameObject)); - Assert.IsFalse(sharedList.Contains(object3.gameObject)); + Assert.IsTrue(sharedList.Contains(object1.gameObject)); + Assert.IsFalse(sharedList.Contains(object2.gameObject)); + Assert.IsFalse(sharedList.Contains(object3.gameObject)); + } + finally + { + networkManager.ShutdownInternal(); + networkManager2.ShutdownInternal(); + // Shutdown doesn't get called correctly because we called Initialize() + // instead of calling StartHost/StartClient/StartServer. See MTT-860 for + // why. + networkManager.NetworkConfig?.NetworkTransport.Shutdown(); + networkManager2.NetworkConfig?.NetworkTransport.Shutdown(); + } } } } diff --git a/Tests/Editor/NetworkObjectTests.cs b/Tests/Editor/NetworkObjectTests.cs index cb14dbf..dd0d930 100644 --- a/Tests/Editor/NetworkObjectTests.cs +++ b/Tests/Editor/NetworkObjectTests.cs @@ -1,5 +1,6 @@ using System.Text.RegularExpressions; using NUnit.Framework; +using Unity.Netcode.Editor; using UnityEngine; using UnityEngine.TestTools; @@ -64,9 +65,95 @@ public void GetBehaviourIndexOne() Object.DestroyImmediate(gameObject); } + /// + /// Verifies that a NetworkObject component that is positioned after a NetworkBehaviour component will + /// be migrated to a component index value that is before the lowest NetworkBehaviour component index value. + /// (The lowest NetworkBehaviour component's index value will also change when this happens) + /// + [Test] + public void NetworkObjectComponentOrder() + { + var gameObject = new GameObject(nameof(GetBehaviourIndexOne)); + // Add the Networkbehaviour first + var networkBehaviour = gameObject.AddComponent(); + // Add an empty MonoBehaviour inbetween the NetworkBehaviour and NetworkObject + gameObject.AddComponent(); + // Add the NetworkObject + var networkObject = gameObject.AddComponent(); + var componentIndices = GetIndices(gameObject); + + // Verify the NetworkObject procedes the NetworkBehaviour + Assert.True(componentIndices.NetworkObjectIndex > componentIndices.NetworkBehaviourIndex, $"[Initial Setup] NetworkObject index ({componentIndices.NetworkObjectIndex}) is not greater than the NetworkBehaviour index ({componentIndices.NetworkBehaviourIndex})!"); + + // Force-Invoke the CheckForNetworkObject method in order to verify the NetworkObject is moved + NetworkBehaviourEditor.CheckForNetworkObject(gameObject); + var adjustedIndices = GetIndices(gameObject); + + Assert.True(ValidateComponentIndices(componentIndices, GetIndices(gameObject)), "NetworkObject did not get migrated below the NetworkBehaviour!"); + + // Cleanup + Object.DestroyImmediate(gameObject); + } + + private bool ValidateComponentIndices(ComponentIndices previous, ComponentIndices current) + { + if (previous.NetworkObjectIndex != current.NetworkObjectIndex && previous.NetworkBehaviourIndex != current.NetworkBehaviourIndex) + { + if (current.NetworkObjectIndex < previous.NetworkObjectIndex && current.NetworkObjectIndex < current.NetworkBehaviourIndex) + { + return true; + } + } + return false; + } + + private ComponentIndices GetIndices(GameObject gameObject) + { + // Get the index/order values for the added NetworkBehaviour and NetworkObject + var components = gameObject.GetComponents(); + var componentIndices = new ComponentIndices() + { + NetworkObjectIndex = -1, + NetworkBehaviourIndex = -1 + }; + for (int i = 0; i < components.Length; i++) + { + if (componentIndices.NetworkObjectIndex != -1 && componentIndices.NetworkBehaviourIndex != -1) + { + break; + } + var component = components[i]; + var networkObjectComponent = component as NetworkObject; + if (networkObjectComponent != null) + { + componentIndices.NetworkObjectIndex = i; + continue; + } + var networkBehaviourComponent = component as EmptyNetworkBehaviour; + if (networkBehaviourComponent != null) + { + componentIndices.NetworkBehaviourIndex = i; + continue; + } + } + + return componentIndices; + } + + private struct ComponentIndices + { + public int NetworkObjectIndex; + public int NetworkBehaviourIndex; + } + public class EmptyNetworkBehaviour : NetworkBehaviour { } + + public class EmptyMonoBehaviour : MonoBehaviour + { + + } } } diff --git a/Tests/Editor/Transports/BatchedSendQueueTests.cs b/Tests/Editor/Transports/BatchedSendQueueTests.cs index 3438c60..3481480 100644 --- a/Tests/Editor/Transports/BatchedSendQueueTests.cs +++ b/Tests/Editor/Transports/BatchedSendQueueTests.cs @@ -293,6 +293,23 @@ public void BatchedSendQueue_FillWriterWithBytes_WriterCapacityEqualToLength() AssertIsTestMessage(data); } + [Test] + public void BatchedSendQueue_FillWriterWithBytes_MaxBytesGreaterThanCapacity() + { + var dataLength = k_TestMessageSize + BatchedSendQueue.PerMessageOverhead; + + using var q = new BatchedSendQueue(k_TestQueueCapacity); + using var data = new NativeArray(dataLength, Allocator.Temp); + + q.PushMessage(m_TestMessage); + q.PushMessage(m_TestMessage); + + var writer = new DataStreamWriter(data); + Assert.AreEqual(dataLength, q.FillWriterWithBytes(ref writer, dataLength * 2)); + AssertIsTestMessage(data); + Assert.False(writer.HasFailedWrites); + } + [Test] public void BatchedSendQueue_Consume_LessThanLength() { diff --git a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs index ee01794..17b618a 100644 --- a/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs +++ b/Tests/Runtime/NetworkObject/NetworkObjectOnSpawnTests.cs @@ -74,6 +74,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) networkManager.NetworkConfig.Prefabs.Add(networkPrefab); } } + networkManager.NetworkConfig.EnableSceneManagement = m_ServerNetworkManager.NetworkConfig.EnableSceneManagement; base.OnNewClientCreated(networkManager); } @@ -82,8 +83,46 @@ protected override void OnNewClientCreated(NetworkManager networkManager) ///
/// whether to spawn with or without observers [UnityTest] - public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes) + public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTypes, [Values] bool sceneManagement) { + if (!sceneManagement) + { + // Disable prefabs to prevent them from being destroyed + foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) + { + networkPrefab.Prefab.SetActive(false); + } + + // Shutdown and clean up the current client NetworkManager instances + foreach (var networkManager in m_ClientNetworkManagers) + { + m_PlayerNetworkObjects[networkManager.LocalClientId].Clear(); + m_PlayerNetworkObjects.Remove(networkManager.LocalClientId); + yield return StopOneClient(networkManager, true); + } + + // Shutdown and clean up the server NetworkManager instance + m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId].Clear(); + yield return StopOneClient(m_ServerNetworkManager); + + // Set the prefabs to active again + foreach (var networkPrefab in m_ServerNetworkManager.NetworkConfig.Prefabs.Prefabs) + { + networkPrefab.Prefab.SetActive(true); + } + + // Disable scene management and start the host + m_ServerNetworkManager.NetworkConfig.EnableSceneManagement = false; + m_ServerNetworkManager.StartHost(); + yield return s_DefaultWaitForTick; + + // Create 2 new clients and connect them + for (int i = 0; i < NumberOfClients; i++) + { + yield return CreateAndStartNewClient(); + } + } + m_ObserverTestType = observerTestTypes; var prefabNetworkObject = m_ObserverPrefab.GetComponent(); prefabNetworkObject.SpawnWithObservers = observerTestTypes == ObserverTestTypes.WithObservers; @@ -129,6 +168,7 @@ public IEnumerator ObserverSpawnTests([Values] ObserverTestTypes observerTestTyp AssertOnTimeout($"{k_WithObserversError} {k_ObserverTestObjName} object!"); } } + /// /// Tests that instantiating a and destroying without spawning it /// does not run or . diff --git a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs index 321f913..a29c963 100644 --- a/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs +++ b/Tests/Runtime/NetworkTransform/NetworkTransformTests.cs @@ -588,6 +588,7 @@ public void ParentedNetworkTransformTest([Values] Precision precision, [Values] success = WaitForConditionOrTimeOutWithTimeTravel(AllChildObjectInstancesHaveChild); Assert.True(success, "Timed out waiting for all instances to have parented a child!"); + TimeTravelToNextTick(); // This validates each child instance has preserved their local space values AllChildrenLocalTransformValuesMatch(false); @@ -689,7 +690,7 @@ protected override void OnNewClientCreated(NetworkManager networkManager) private Precision m_Precision = Precision.Full; private float m_CurrentHalfPrecision = 0.0f; - private const float k_HalfPrecisionPosScale = 0.03f; + private const float k_HalfPrecisionPosScale = 0.041f; private const float k_HalfPrecisionRot = 0.725f; protected override float GetDeltaVarianceThreshold() diff --git a/Tests/Runtime/NetworkVariableTests.cs b/Tests/Runtime/NetworkVariableTests.cs index 1dec25d..02afdc8 100644 --- a/Tests/Runtime/NetworkVariableTests.cs +++ b/Tests/Runtime/NetworkVariableTests.cs @@ -50,6 +50,85 @@ private void Awake() } } + internal struct TypeReferencedOnlyInCustomSerialization1 : INetworkSerializeByMemcpy + { + public int I; + } + + internal struct TypeReferencedOnlyInCustomSerialization2 : INetworkSerializeByMemcpy + { + public int I; + } + + internal struct TypeReferencedOnlyInCustomSerialization3 : INetworkSerializeByMemcpy + { + public int I; + } + + internal struct TypeReferencedOnlyInCustomSerialization4 : INetworkSerializeByMemcpy + { + public int I; + } + + internal struct TypeReferencedOnlyInCustomSerialization5 : INetworkSerializeByMemcpy + { + public int I; + } + + internal struct TypeReferencedOnlyInCustomSerialization6 : INetworkSerializeByMemcpy + { + public int I; + } + + // Both T and U are serializable + [GenerateSerializationForGenericParameter(0)] + [GenerateSerializationForGenericParameter(1)] + internal class CustomSerializableClass + { + + } + + // Only U is serializable + [GenerateSerializationForGenericParameter(1)] + internal class CustomSerializableBaseClass + { + + } + + // T is serializable, passes TypeReferencedOnlyInCustomSerialization3 as U to the subclass, making it serializable + [GenerateSerializationForGenericParameter(0)] + internal class CustomSerializableSubclass : CustomSerializableBaseClass + { + + } + + // T is serializable, passes TypeReferencedOnlyInCustomSerialization3 as U to the subclass, making it serializable + [GenerateSerializationForGenericParameter(0)] + internal class CustomSerializableSubclassWithNativeArray : CustomSerializableBaseClass> + { + + } + + internal class CustomGenericSerializationTestBehaviour : NetworkBehaviour + { + public CustomSerializableClass Value1; + public CustomSerializableClass, NativeArray> Value2; + public CustomSerializableSubclass Value3; + public CustomSerializableSubclassWithNativeArray> Value4; + } + + [GenerateSerializationForType(typeof(TypeReferencedOnlyInCustomSerialization5))] + [GenerateSerializationForType(typeof(NativeArray))] + internal struct SomeRandomStruct + { + [GenerateSerializationForType(typeof(TypeReferencedOnlyInCustomSerialization6))] + [GenerateSerializationForType(typeof(NativeArray))] + public void Foo() + { + + } + } + public struct TemplatedValueOnlyReferencedByNetworkVariableSubclass : INetworkSerializeByMemcpy where T : unmanaged { @@ -1329,6 +1408,39 @@ public void TestINetworkSerializableStructCallsNetworkSerialize([Values] HostOrS Assert.True(WaitForConditionOrTimeOutWithTimeTravel(VerifyCallback)); } + [Test] + public void TestCustomGenericSerialization() + { + // Just verifies that the ILPP codegen initialized these values for this type. + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedTypeSerializer), NetworkVariableSerialization.Serializer.GetType()); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization.AreEqual); + + // Verify no issues with generic values... + + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.AreEqual(typeof(UnmanagedArraySerializer), NetworkVariableSerialization>.Serializer.GetType()); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + Assert.IsNotNull(NetworkVariableSerialization>.AreEqual); + } + [Test] public void TestUnsupportedManagedTypesThrowExceptions() { diff --git a/Tests/Runtime/RpcTests.cs b/Tests/Runtime/RpcTests.cs index 19f3bb0..d676a14 100644 --- a/Tests/Runtime/RpcTests.cs +++ b/Tests/Runtime/RpcTests.cs @@ -12,9 +12,23 @@ namespace Unity.Netcode.RuntimeTests { public class RpcTests : NetcodeIntegrationTest { - public class RpcTestNB : NetworkBehaviour + public class GenericRpcTestNB : NetworkBehaviour where T : unmanaged + { + public event Action OnServer_Rpc; + + [ServerRpc] + public void MyServerRpc(T clientId, ServerRpcParams param = default) + { + OnServer_Rpc(clientId, param); + } + } + + public class RpcTestNBFloat : GenericRpcTestNB + { + } + + public class RpcTestNB : GenericRpcTestNB { - public event Action OnServer_Rpc; #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT public event Action, ServerRpcParams> OnNativeListServer_Rpc; #endif @@ -26,12 +40,6 @@ public class RpcTestNB : NetworkBehaviour public event Action OnClient_Rpc; - [ServerRpc] - public void MyServerRpc(ulong clientId, ServerRpcParams param = default) - { - OnServer_Rpc(clientId, param); - } - #if UNITY_NETCODE_NATIVE_COLLECTION_SUPPORT [ServerRpc] public void MyNativeListServerRpc(NativeList clientId, ServerRpcParams param = default) @@ -67,6 +75,7 @@ public void MyTypedServerRpc(Vector3 param1, Vector3[] param2, protected override void OnCreatePlayerPrefab() { m_PlayerPrefab.AddComponent(); + m_PlayerPrefab.AddComponent(); } [UnityTest] @@ -74,12 +83,15 @@ public IEnumerator TestRpcs() { // This is the *SERVER VERSION* of the *CLIENT PLAYER* RpcTestNB component var serverClientRpcTestNB = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + var serverClientRpcTestNBFloat = m_PlayerNetworkObjects[m_ServerNetworkManager.LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); // This is the *CLIENT VERSION* of the *CLIENT PLAYER* RpcTestNB component var localClienRpcTestNB = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); + var localClienRpcTestNBFloat = m_PlayerNetworkObjects[m_ClientNetworkManagers[0].LocalClientId][m_ClientNetworkManagers[0].LocalClientId].GetComponent(); // Setup state bool hasReceivedServerRpc = false; + bool hasReceivedFloatServerRpc = false; bool hasReceivedTypedServerRpc = false; bool hasReceivedClientRpcRemotely = false; bool hasReceivedClientRpcLocally = false; @@ -106,6 +118,12 @@ public IEnumerator TestRpcs() Assert.Fail("ServerRpc invoked locally. Weaver failure?"); }; + localClienRpcTestNBFloat.OnServer_Rpc += (clientId, param) => + { + // The RPC invoked locally. (Weaver failure?) + Assert.Fail("ServerRpc (float) invoked locally. Weaver failure?"); + }; + serverClientRpcTestNB.OnServer_Rpc += (clientId, param) => { Debug.Log("ServerRpc received on server object"); @@ -113,6 +131,13 @@ public IEnumerator TestRpcs() hasReceivedServerRpc = true; }; + serverClientRpcTestNBFloat.OnServer_Rpc += (clientId, param) => + { + Debug.Log("ServerRpc (float) received on server object"); + Assert.True(param.Receive.SenderClientId == clientId); + hasReceivedFloatServerRpc = true; + }; + serverClientRpcTestNB.OnClient_Rpc += () => { // The RPC invoked locally. (Weaver failure?) @@ -145,6 +170,7 @@ public IEnumerator TestRpcs() // Send ServerRpc localClienRpcTestNB.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId); + localClienRpcTestNBFloat.MyServerRpc(m_ClientNetworkManagers[0].LocalClientId); // Send TypedServerRpc localClienRpcTestNB.MyTypedServerRpc(vector3, vector3s, @@ -181,6 +207,7 @@ public IEnumerator TestRpcs() yield return WaitForConditionOrTimeOut(() => hasReceivedServerRpc && hasReceivedClientRpcLocally && hasReceivedClientRpcRemotely && hasReceivedTypedServerRpc); Assert.True(hasReceivedServerRpc, "ServerRpc was not received"); + Assert.True(hasReceivedFloatServerRpc, "ServerRpc was not received"); Assert.True(hasReceivedTypedServerRpc, "TypedServerRpc was not received"); Assert.True(hasReceivedClientRpcLocally, "ClientRpc was not locally received on the server"); Assert.True(hasReceivedClientRpcRemotely, "ClientRpc was not remotely received on the client"); diff --git a/Tests/Runtime/TransformInterpolationTests.cs b/Tests/Runtime/TransformInterpolationTests.cs index 9ef656c..48f0472 100644 --- a/Tests/Runtime/TransformInterpolationTests.cs +++ b/Tests/Runtime/TransformInterpolationTests.cs @@ -59,6 +59,9 @@ public void StopMoving() IsMoving = false; } + private const int k_MaxThresholdFailures = 4; + private int m_ExceededThresholdCount; + protected override void Update() { base.Update(); @@ -73,7 +76,19 @@ protected override void Update() { if (transform.position.y < -MinThreshold || transform.position.y > Application.targetFrameRate + MinThreshold) { - Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}]."); + // Temporary work around for this test. + // Really, this test needs to be completely re-written. + m_ExceededThresholdCount++; + // If we haven't corrected ourselves within the maximum number of updates then throw an error. + if (m_ExceededThresholdCount > k_MaxThresholdFailures) + { + Debug.LogError($"Interpolation failure. transform.position.y is {transform.position.y}. Should be between 0.0 and 100.0. Current threshold is [+/- {MinThreshold}]."); + } + } + else + { + // If corrected, then reset our count + m_ExceededThresholdCount = 0; } } diff --git a/package.json b/package.json index a031098..4db8def 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,23 @@ "name": "com.unity.netcode.gameobjects", "displayName": "Netcode for GameObjects", "description": "Netcode for GameObjects is a high-level netcode SDK that provides networking capabilities to GameObject/MonoBehaviour workflows within Unity and sits on top of underlying transport layer.", - "version": "1.6.0", + "version": "1.7.0", "unity": "2020.3", "dependencies": { "com.unity.nuget.mono-cecil": "1.10.1", - "com.unity.transport": "1.3.4" + "com.unity.transport": "1.4.0" }, "_upm": { - "changelog": "### Added\n\n- Added a protected virtual method `NetworkTransform.OnInitialize(ref NetworkTransformState replicatedState)` that just returns the replicated state reference.\n \n### Fixed\n\n- Fixed issue where invoking `NetworkManager.Shutdown` within `NetworkManager.OnClientStopped` or `NetworkManager.OnServerStopped` would force `NetworkManager.ShutdownInProgress` to remain true after completing the shutdown process. (#2661)\n- Fixed issue with client synchronization of position when using half precision and the delta position reaches the maximum value and is collapsed on the host prior to being forwarded to the non-owner clients. (#2636)\n- Fixed issue with scale not synchronizing properly depending upon the spawn order of NetworkObjects. (#2636)\n- Fixed issue position was not properly transitioning between ownership changes with an owner authoritative NetworkTransform. (#2636)\n- Fixed issue where a late joining non-owner client could update an owner authoritative NetworkTransform if ownership changed without any updates to position prior to the non-owner client joining. (#2636)\n\n### Changed" + "changelog": "### Added\n\n- exposed NetworkObject.GetNetworkBehaviourAtOrderIndex as a public API (#2724)\n- Added context menu tool that provides users with the ability to quickly update the GlobalObjectIdHash value for all in-scene placed prefab instances that were created prior to adding a NetworkObject component to it. (#2707)\n- Added methods NetworkManager.SetPeerMTU and NetworkManager.GetPeerMTU to be able to set MTU sizes per-peer (#2676)\n- Added `GenerateSerializationForGenericParameterAttribute`, which can be applied to user-created Network Variable types to ensure the codegen generates serialization for the generic types they wrap. (#2694)\n- Added `GenerateSerializationForTypeAttribute`, which can be applied to any class or method to ensure the codegen generates serialization for the specific provided type. (#2694)\n- Exposed `NetworkVariableSerialization.Read`, `NetworkVariableSerialization.Write`, `NetworkVariableSerialization.AreEqual`, and `NetworkVariableSerialization.Duplicate` to further support the creation of user-created network variables by allowing users to access the generated serialization methods and serialize generic types efficiently without boxing. (#2694)\n- Added `NetworkVariableBase.MarkNetworkBehaviourDirty` so that user-created network variable types can mark their containing `NetworkBehaviour` to be processed by the update loop. (#2694)\n\n### Fixed\n\n- Fixed issue where the server side `NetworkSceneManager` instance was not adding the currently active scene to its list of scenes loaded. (#2723)\n- Generic NetworkBehaviour types no longer result in compile errors or runtime errors (#2720)\n- Rpcs within Generic NetworkBehaviour types can now serialize parameters of the class's generic types (but may not have generic types of their own) (#2720)\n- Errors are no longer thrown when entering play mode with domain reload disabled (#2720)\n- NetworkSpawn is now correctly called each time when entering play mode with scene reload disabled (#2720)\n- NetworkVariables of non-integer types will no longer break the inspector (#2714)\n- NetworkVariables with NonSerializedAttribute will not appear in the inspector (#2714)\n- Fixed issue where `UnityTransport` would attempt to establish WebSocket connections even if using UDP/DTLS Relay allocations when the build target was WebGL. This only applied to working in the editor since UDP/DTLS can't work in the browser. (#2695)\n- Fixed issue where a `NetworkBehaviour` component's `OnNetworkDespawn` was not being invoked on the host-server side for an in-scene placed `NetworkObject` when a scene was unloaded (during a scene transition) and the `NetworkBehaviour` component was positioned/ordered before the `NetworkObject` component. (#2685)\n- Fixed issue where `SpawnWithObservers` was not being honored when `NetworkConfig.EnableSceneManagement` was disabled. (#2682)\n- Fixed issue where `NetworkAnimator` was not internally tracking changes to layer weights which prevented proper layer weight synchronization back to the original layer weight value. (#2674)\n- Fixed \"writing past the end of the buffer\" error when calling ResetDirty() on managed network variables that are larger than 256 bytes when serialized. (#2670)\n- Fixed issue where generation of the `DefaultNetworkPrefabs` asset was not enabled by default. (#2662)\n- Fixed issue where the `GlobalObjectIdHash` value could be updated but the asset not marked as dirty. (#2662)\n- Fixed issue where the `GlobalObjectIdHash` value of a (network) prefab asset could be assigned an incorrect value when editing the prefab in a temporary scene. (#2662)\n- Fixed issue where the `GlobalObjectIdHash` value generated after creating a (network) prefab from an object constructed within the scene would not be the correct final value in a stand alone build. (#2662)\n\n### Changed\n\n- Updated dependency on `com.unity.transport` to version 1.4.0. (#2716)" }, "upmCi": { - "footprint": "58b37aee2ff0caec7a160473c56e62119d3b760a" + "footprint": "87fd22da62ed4e055a6eead5cea85b4b449eded0" }, - "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.6/manual/index.html", + "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.netcode.gameobjects@1.7/manual/index.html", "repository": { "url": "https://github.com/Unity-Technologies/com.unity.netcode.gameobjects.git", "type": "git", - "revision": "4ee8da05b439a5b8f76ee6d008221c6d58b7965a" + "revision": "f4004c72eb46bf2aac62bc71b599017bd0570fdb" }, "samples": [ {