Skip to content

Commit c48f54d

Browse files
noellie-velezNoelStephensUnityEmandM
authored
fix: reset static fields for Fast Enter Play Mode (#3956)
* fix: reset static fields for Fast Enter Play Mode support   - Added ResetStaticsOnLoad where possible   - Applied [AutoStaticsCleanup] to generic classes when applicable   - Promoted immutable statics to readonly or const   - Added OnDisable to deregister event subscriptions - chore: disable domain reload and remove treat warnings as errors in test project settings - test: add NetworkManager singleton reset test for Fast Enter Play Mode * fix: add missing message type registrations in MessageDelivery * chore: cleanup   - Remove unused m_ParentedChildren field and related loops in NetworkTransform   - Remove unused m_DespawnedInSceneObjects field in SceneEventData   - Replace static quaternion scratch buffer in QuaternionCompressor with local variables   - Consolidate LogSerializationOrder and EnableSerializationLogs into LogConfiguration - Update summary comments * docs: update CHANGELOG --------- Co-authored-by: Noel Stephens <noel.stephens@unity3d.com> Co-authored-by: Emma <emma.mcmillan@unity3d.com>
1 parent 7d03b41 commit c48f54d

32 files changed

Lines changed: 238 additions & 184 deletions

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1010

1111
### Added
1212

13+
- Added support for Unity's Fast Enter Play Mode with domain reload disabled. (#3956)
1314

1415
### Changed
1516

com.unity.netcode.gameobjects/Runtime/Components/NetworkAnimator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,6 @@ protected virtual bool OnIsServerAuthoritative()
706706
private int[] m_TransitionHash;
707707
private int[] m_AnimationHash;
708708
private float[] m_LayerWeights;
709-
private static byte[] s_EmptyArray = new byte[] { };
710709
private List<int> m_ParametersToUpdate;
711710
private RpcParams m_RpcParams;
712711
private IGroupRpcTarget m_TargetGroup;

com.unity.netcode.gameobjects/Runtime/Components/NetworkTransform.cs

Lines changed: 16 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ public class NetworkTransform : NetworkBehaviour
2424
[HideInInspector]
2525
[SerializeField]
2626
internal bool NetworkTransformExpanded;
27+
28+
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
29+
private static void ResetStaticsOnLoad()
30+
{
31+
CurrentTick = 0;
32+
TrackStateUpdateId = false;
33+
AssignDefaultInterpolationType = false;
34+
DefaultInterpolationType = default;
35+
s_NetworkTickRegistration = new Dictionary<NetworkManager, NetworkTransformTickRegistration>();
36+
InterpolationBufferTickOffset = 0;
37+
s_TickSynchPosition = 0;
38+
}
2739
#endif
2840

2941
internal enum Axis { X, Y, Z }
@@ -1361,7 +1373,7 @@ public enum AuthorityModes
13611373
/// <summary>
13621374
/// When set each state update will contain a state identifier
13631375
/// </summary>
1364-
internal static bool TrackStateUpdateId = false;
1376+
internal static bool TrackStateUpdateId;
13651377

13661378
/// <summary>
13671379
/// Enabled by default.
@@ -2062,19 +2074,6 @@ private void TryCommitTransform(bool synchronize = false, bool settingState = fa
20622074
childNetworkTransform.OnNetworkTick(true);
20632075
}
20642076
}
2065-
2066-
// Synchronize any parented children with the parent's motion
2067-
foreach (var child in m_ParentedChildren)
2068-
{
2069-
// Synchronize any nested NetworkTransforms of the child with the parent's
2070-
foreach (var childNetworkTransform in child.NetworkTransforms)
2071-
{
2072-
if (childNetworkTransform.CanCommitToTransform)
2073-
{
2074-
childNetworkTransform.OnNetworkTick(true);
2075-
}
2076-
}
2077-
}
20782077
}
20792078
}
20802079
}
@@ -2221,13 +2220,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is
22212220
// values are applied.
22222221
var hasParentNetworkObject = false;
22232222

2224-
var parentNetworkObject = (NetworkObject)null;
2225-
22262223
// If the NetworkObject belonging to this NetworkTransform instance has a parent
22272224
// (i.e. this handles nested NetworkTransforms under a parent at some layer above)
22282225
if (NetworkObject.transform.parent != null)
22292226
{
2230-
parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();
2227+
var parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();
22312228

22322229
// In-scene placed NetworkObjects parented under a GameObject with no
22332230
// NetworkObject preserve their lossyScale when synchronizing.
@@ -3363,19 +3360,6 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
33633360
childNetworkTransform.OnNetworkTick(true);
33643361
}
33653362
}
3366-
3367-
// Synchronize any parented children with the parent's motion
3368-
foreach (var child in m_ParentedChildren)
3369-
{
3370-
// Synchronize any nested NetworkTransforms of the child with the parent's
3371-
foreach (var childNetworkTransform in child.NetworkTransforms)
3372-
{
3373-
if (childNetworkTransform.CanCommitToTransform)
3374-
{
3375-
childNetworkTransform.OnNetworkTick(true);
3376-
}
3377-
}
3378-
}
33793363
}
33803364

33813365
// Provide notifications when the state has been updated
@@ -3634,8 +3618,6 @@ internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManag
36343618
/// <inheritdoc/>
36353619
public override void OnNetworkSpawn()
36363620
{
3637-
m_ParentedChildren.Clear();
3638-
36393621
Initialize();
36403622

36413623
if (CanCommitToTransform && !SwitchTransformSpaceWhenParented)
@@ -3647,7 +3629,6 @@ public override void OnNetworkSpawn()
36473629

36483630
private void CleanUpOnDestroyOrDespawn()
36493631
{
3650-
m_ParentedChildren.Clear();
36513632
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
36523633
var forUpdate = !m_UseRigidbodyForMotion;
36533634
#else
@@ -3861,7 +3842,6 @@ protected override void OnOwnershipChanged(ulong previous, ulong current)
38613842
}
38623843

38633844
internal bool IsNested;
3864-
private List<NetworkObject> m_ParentedChildren = new List<NetworkObject>();
38653845

38663846
private bool m_IsFirstNetworkTransform;
38673847

@@ -4694,7 +4674,7 @@ internal static void UpdateNetworkTick(NetworkManager networkManager)
46944674
/// The default value is 1 tick (plus the tick latency). When running on a local network, reducing this to 0 is recommended.<br />
46954675
/// <see cref="NetworkTimeSystem.TickLatency"/>
46964676
/// </remarks>
4697-
public static int InterpolationBufferTickOffset = 0;
4677+
public static int InterpolationBufferTickOffset;
46984678
internal static float GetTickLatency(NetworkManager networkManager)
46994679
{
47004680
if (networkManager.IsListening)
@@ -4799,6 +4779,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager)
47994779
}
48004780
}
48014781
}
4782+
48024783
private static int s_TickSynchPosition;
48034784
private int m_NextTickSync;
48044785

com.unity.netcode.gameobjects/Runtime/Components/QuaternionCompressor.cs

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
namespace Unity.Netcode
55
{
66
/// <summary>
7-
/// A Smallest Three Quaternion Compressor Implementation
7+
/// The Smallest Three Quaternion Compressor Implementation
88
/// </summary>
99
/// <remarks>
1010
/// Explanation of why "The smallest three":
@@ -26,14 +26,14 @@ public static class QuaternionCompressor
2626

2727
// We can further improve the encoding compression by dividing k_SqrtTwoOverTwo into 1.0f and multiplying that
2828
// by the precision mask (minor reduction of runtime calculations)
29-
private const float k_CompressionEcodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask;
29+
private const float k_CompressionEncodingMask = (1.0f / k_SqrtTwoOverTwoEncoding) * k_PrecisionMask;
3030

3131
// Used to shift the negative bit to the 10th bit position when compressing and encoding
3232
private const ushort k_ShiftNegativeBit = 9;
3333

3434
// We can do the same for our decoding and decompression by dividing k_PrecisionMask into 1.0 and multiplying
3535
// that by k_SqrtTwoOverTwo (minor reduction of runtime calculations)
36-
private const float k_DcompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding;
36+
private const float k_DecompressionDecodingMask = (1.0f / k_PrecisionMask) * k_SqrtTwoOverTwoEncoding;
3737

3838
// The sign bit position (10th bit) used when decompressing and decoding
3939
private const ushort k_NegShortBit = 0x200;
@@ -42,9 +42,6 @@ public static class QuaternionCompressor
4242
private const ushort k_True = 1;
4343
private const ushort k_False = 0;
4444

45-
// Used to store the absolute value of the 4 quaternion elements
46-
private static Quaternion s_QuatAbsValues = Quaternion.identity;
47-
4845
/// <summary>
4946
/// Compresses a Quaternion into an unsigned integer
5047
/// </summary>
@@ -54,38 +51,31 @@ public static class QuaternionCompressor
5451
public static uint CompressQuaternion(ref Quaternion quaternion)
5552
{
5653
// Store off the absolute value for each Quaternion element
57-
s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]);
58-
s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]);
59-
s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]);
60-
s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]);
54+
var quatAbsValue0 = Mathf.Abs(quaternion[0]);
55+
var quatAbsValue1 = Mathf.Abs(quaternion[1]);
56+
var quatAbsValue2 = Mathf.Abs(quaternion[2]);
57+
var quatAbsValue3 = Mathf.Abs(quaternion[3]);
6158

6259
// Get the largest element value of the quaternion to know what the remaining "Smallest Three" values are
63-
var quatMax = Mathf.Max(s_QuatAbsValues[0], s_QuatAbsValues[1], s_QuatAbsValues[2], s_QuatAbsValues[3]);
60+
var quatMax = Mathf.Max(quatAbsValue0, quatAbsValue1, quatAbsValue2, quatAbsValue3);
6461

65-
// Find the index of the largest element so we can skip that element while compressing and decompressing
66-
var indexToSkip = (ushort)(s_QuatAbsValues[0] == quatMax ? 0 : s_QuatAbsValues[1] == quatMax ? 1 : s_QuatAbsValues[2] == quatMax ? 2 : 3);
62+
// Find the index of the largest element, so we can skip that element while compressing and decompressing
63+
var indexToSkip = (ushort)(quatAbsValue0 == quatMax ? 0 : quatAbsValue1 == quatMax ? 1 : quatAbsValue2 == quatMax ? 2 : 3);
6764

6865
// Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion.
69-
7066
var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False);
7167

7268
// Start with the index to skip which will be shifted to the highest two bits
7369
var compressed = (uint)indexToSkip;
7470

75-
// Step 1: Start with the first element
76-
var currentIndex = 0;
77-
78-
// Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4
79-
// Step 3: Get the sign of the element we are processing. If it is the not the same as the largest value's sign bit then we set the bit
80-
// Step 4: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEcodingMask and round that result up
81-
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
82-
currentIndex++;
83-
// Repeat the last 3 steps for the remaining elements
84-
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
85-
currentIndex++;
86-
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
87-
currentIndex++;
88-
compressed = currentIndex != indexToSkip ? (compressed << 10) | (uint)((quaternion[currentIndex] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEcodingMask * s_QuatAbsValues[currentIndex]) : compressed;
71+
// Step 1: If we are on the index to skip, preserve the current compressed value, otherwise proceed to step 2 and 3
72+
// Step 2: Get the sign of the element we are processing. If it is not the same as the largest value's sign bit then we set the bit
73+
// Step 3: Get the compressed and encoded value by multiplying the absolute value of the current element by k_CompressionEncodingMask and round that result up
74+
compressed = 0 != indexToSkip ? (compressed << 10) | (uint)((quaternion[0] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue0) : compressed;
75+
// Repeat the 3 steps for the remaining elements
76+
compressed = 1 != indexToSkip ? (compressed << 10) | (uint)((quaternion[1] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue1) : compressed;
77+
compressed = 2 != indexToSkip ? (compressed << 10) | (uint)((quaternion[2] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue2) : compressed;
78+
compressed = 3 != indexToSkip ? (compressed << 10) | (uint)((quaternion[3] < 0 ? k_True : k_False) != quatMaxSign ? k_True : k_False) << k_ShiftNegativeBit | (ushort)Mathf.Round(k_CompressionEncodingMask * quatAbsValue3) : compressed;
8979

9080
// Return the compress quaternion
9181
return compressed;
@@ -111,7 +101,7 @@ public static void DecompressQuaternion(ref Quaternion quaternion, uint compress
111101
continue;
112102
}
113103
// Check the negative bit and multiply that result with the decompressed and decoded value
114-
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask);
104+
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DecompressionDecodingMask);
115105
sumOfSquaredMagnitudes += quaternion[i] * quaternion[i];
116106
compressed = compressed >> 10;
117107
}

com.unity.netcode.gameobjects/Runtime/Components/RigidbodyContactEventManager.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ public struct ContactEventHandlerInfo
2121
/// </summary>
2222
public bool ProvideNonRigidBodyContactEvents;
2323
/// <summary>
24-
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br /></br>
24+
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br />
2525
/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a <see cref="NetworkObject"/> is owned by the local client <br />
2626
/// will assure <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> is only invoked on the authoritative side.
2727
/// </summary>
@@ -112,7 +112,7 @@ private void OnEnable()
112112
m_Log = new ContextualLogger(this);
113113
m_ResultsArray = new NativeArray<JobResultStruct>(16, Allocator.Persistent);
114114
Physics.ContactEvent += Physics_ContactEvent;
115-
if (Instance != null)
115+
if (Instance != null && Instance != this)
116116
{
117117
m_Log.Error(new Context(LogLevel.Error, $"Found more than one instance of {nameof(RigidbodyContactEventManager)}").AddTag("Invalid").AddTag("Multiple Instances").AddInfo("Instance 1", Instance.name).AddInfo("Instance 2", name));
118118
m_Log.Error(new Context(LogLevel.Error, $"Disabling instance: ").AddTag("Disable").AddTag("Additional Instance").AddInfo("Instance", name));
Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using UnityEngine;
43

54
namespace Unity.Netcode
65
{
@@ -13,6 +12,7 @@ public class CommandLineOptions
1312
/// <summary>
1413
/// Command-line options singleton
1514
/// </summary>
15+
[Obsolete("Not used anymore replaced by TryGetArg")]
1616
public static CommandLineOptions Instance
1717
{
1818
get
@@ -30,34 +30,41 @@ private set
3030
}
3131
private static CommandLineOptions s_Instance;
3232

33-
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
34-
private static void RuntimeInitializeOnLoad() => Instance = new CommandLineOptions();
35-
3633
// Contains the current application instance domain's command line arguments
37-
internal static List<string> CommandLineArguments = new List<string>();
38-
39-
// Invoked upon application start, after scene load
40-
[RuntimeInitializeOnLoadMethod]
41-
private static void ParseCommandLineArguments()
42-
{
43-
// Get all the command line arguments to be parsed later and/or modified
44-
// prior to being parsed (for testing purposes).
45-
CommandLineArguments = new List<string>(Environment.GetCommandLineArgs());
46-
}
34+
private static readonly List<string> k_CommandLineArguments = new List<string>(Environment.GetCommandLineArgs());
4735

4836
/// <summary>
49-
/// Returns the value of an argument or null if there the argument is not present
37+
/// Returns the value of an argument or null if the argument is not present
5038
/// </summary>
5139
/// <param name="arg">The name of the argument</param>
5240
/// <returns><see cref="string"/>Value of the command line argument passed in.</returns>
41+
[Obsolete("Not used anymore replaced by TryGetArg")]
5342
public string GetArg(string arg)
5443
{
55-
var argIndex = CommandLineArguments.IndexOf(arg);
56-
if (argIndex >= 0 && argIndex < CommandLineArguments.Count - 1)
44+
var argIndex = k_CommandLineArguments.IndexOf(arg);
45+
if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1)
5746
{
58-
return CommandLineArguments[argIndex + 1];
47+
return k_CommandLineArguments[argIndex + 1];
5948
}
6049
return null;
6150
}
51+
52+
/// <summary>
53+
/// Returns true if the argument was found.
54+
/// </summary>
55+
/// <param name="arg">The name of the argument to look up.</param>
56+
/// <param name="argValue">The argument's value, or <see langword="null"/> if not found.</param>
57+
/// <returns><c>true</c> if the argument was found; otherwise <c>false</c>.</returns>
58+
public static bool TryGetArg(string arg, out string argValue)
59+
{
60+
var argIndex = k_CommandLineArguments.IndexOf(arg);
61+
if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1)
62+
{
63+
argValue = k_CommandLineArguments[argIndex + 1];
64+
return true;
65+
}
66+
argValue = null;
67+
return false;
68+
}
6269
}
6370
}

com.unity.netcode.gameobjects/Runtime/Core/ComponentFactory.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ internal static class ComponentFactory
1414
internal delegate object CreateObjectDelegate(NetworkManager networkManager);
1515

1616
private static Dictionary<Type, CreateObjectDelegate> s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
17+
#if UNITY_EDITOR
18+
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
19+
private static void ResetStaticsOnLoad() => s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
20+
#endif
1721

1822
/// <summary>
1923
/// Instantiates an instance of a given interface

com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,8 +1299,6 @@ internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false
12991299
}
13001300
}
13011301

1302-
internal static bool LogSentVariableUpdateMessage;
1303-
13041302
private bool CouldHaveDirtyNetworkVariables()
13051303
{
13061304
// TODO: There should be a better way by reading one dirty variable vs. 'n'

0 commit comments

Comments
 (0)