Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c6f0d4b
fix: reset static fields for Fast Enter Play Mode
noellie-velez Apr 24, 2026
702bb73
Remove RuntimeInitializeOnLoadMethod from non generic types
noellie-velez Apr 24, 2026
8dabded
Fix naming + if editor def on `using UnityEngine`
noellie-velez Apr 24, 2026
aa33ed6
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
noellie-velez Apr 24, 2026
5ab98dc
Styling, renaming and made a field readonly
noellie-velez Apr 24, 2026
69dce92
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
noellie-velez Apr 28, 2026
638855f
Fix code formatting issues (redundant UnityEngine)
noellie-velez Apr 28, 2026
4cbec9b
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
noellie-velez May 6, 2026
0ecf34c
Add AutoStaticsCleanup + fix typo
noellie-velez May 6, 2026
e65351c
Remove possible NullReferenceException
noellie-velez May 6, 2026
551b989
Unused parented children list cleanup
noellie-velez May 7, 2026
689ecd2
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
NoelStephensUnity May 8, 2026
25e456b
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
NoelStephensUnity May 12, 2026
70c90eb
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
NoelStephensUnity May 18, 2026
a889166
Review
noellie-velez May 18, 2026
9febb5e
Merge branch 'chore/fast-enter-playmode' of github.com:Unity-Technolo…
noellie-velez May 18, 2026
46e09ab
Finalize addressing review comments
noellie-velez May 18, 2026
ff2e26a
revert field removal
noellie-velez May 21, 2026
be40aa3
Readonly, missing MessageTypes, replace quat...
noellie-velez May 21, 2026
dbeccdf
Small fixes
noellie-velez May 21, 2026
f184559
Diable Domain Reload + remove warning as errors
noellie-velez May 21, 2026
99b4f18
Adding NetworkManager singleton reset test
noellie-velez May 21, 2026
935ebe1
Changelog update
noellie-velez May 21, 2026
e315369
Apply suggestion from @noellie-velez
noellie-velez May 21, 2026
2ac827f
fix indentation + naming convention
noellie-velez May 21, 2026
1575029
Fix test accesibility
noellie-velez May 21, 2026
9dc2b33
Small missed rename fix
noellie-velez May 21, 2026
ea56a62
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
EmandM May 25, 2026
d8c72f4
Remove unused import
EmandM May 25, 2026
df11388
Address review comments
noellie-velez May 28, 2026
1500b64
Merge branch 'develop-2.0.0' into chore/fast-enter-playmode
noellie-velez May 28, 2026
7e94529
Last fixes
noellie-velez May 28, 2026
08064c3
Fix typo in naming + fix comments
noellie-velez May 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Additional documentation and release notes are available at [Multiplayer Documen

### Added

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

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,6 @@ protected virtual bool OnIsServerAuthoritative()
private int[] m_TransitionHash;
private int[] m_AnimationHash;
private float[] m_LayerWeights;
private static byte[] s_EmptyArray = new byte[] { };
private List<int> m_ParametersToUpdate;
private RpcParams m_RpcParams;
private IGroupRpcTarget m_TargetGroup;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ public class NetworkTransform : NetworkBehaviour
[HideInInspector]
[SerializeField]
internal bool NetworkTransformExpanded;

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetStaticsOnLoad()
{
CurrentTick = 0;
TrackStateUpdateId = false;
AssignDefaultInterpolationType = false;
DefaultInterpolationType = default;
s_NetworkTickRegistration = new Dictionary<NetworkManager, NetworkTransformTickRegistration>();
InterpolationBufferTickOffset = 0;
s_TickSynchPosition = 0;
}
#endif

internal enum Axis { X, Y, Z }
Expand Down Expand Up @@ -1361,7 +1373,7 @@ public enum AuthorityModes
/// <summary>
/// When set each state update will contain a state identifier
/// </summary>
internal static bool TrackStateUpdateId = false;
internal static bool TrackStateUpdateId;

/// <summary>
/// Enabled by default.
Expand Down Expand Up @@ -2062,19 +2074,6 @@ private void TryCommitTransform(bool synchronize = false, bool settingState = fa
childNetworkTransform.OnNetworkTick(true);
}
}

// Synchronize any parented children with the parent's motion
foreach (var child in m_ParentedChildren)
{
// Synchronize any nested NetworkTransforms of the child with the parent's
foreach (var childNetworkTransform in child.NetworkTransforms)
{
if (childNetworkTransform.CanCommitToTransform)
{
childNetworkTransform.OnNetworkTick(true);
}
}
}
}
}
}
Expand Down Expand Up @@ -2221,13 +2220,11 @@ private bool CheckForStateChange(ref NetworkTransformState networkState, bool is
// values are applied.
var hasParentNetworkObject = false;

var parentNetworkObject = (NetworkObject)null;

// If the NetworkObject belonging to this NetworkTransform instance has a parent
// (i.e. this handles nested NetworkTransforms under a parent at some layer above)
if (NetworkObject.transform.parent != null)
{
parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();
var parentNetworkObject = NetworkObject.transform.parent.GetComponent<NetworkObject>();

// In-scene placed NetworkObjects parented under a GameObject with no
// NetworkObject preserve their lossyScale when synchronizing.
Expand Down Expand Up @@ -3363,19 +3360,6 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
childNetworkTransform.OnNetworkTick(true);
}
}

// Synchronize any parented children with the parent's motion
foreach (var child in m_ParentedChildren)
{
// Synchronize any nested NetworkTransforms of the child with the parent's
foreach (var childNetworkTransform in child.NetworkTransforms)
{
if (childNetworkTransform.CanCommitToTransform)
{
childNetworkTransform.OnNetworkTick(true);
}
}
}
}

// Provide notifications when the state has been updated
Expand Down Expand Up @@ -3634,8 +3618,6 @@ internal override void InternalOnNetworkPreSpawn(ref NetworkManager networkManag
/// <inheritdoc/>
public override void OnNetworkSpawn()
{
m_ParentedChildren.Clear();

Initialize();

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

private void CleanUpOnDestroyOrDespawn()
{
m_ParentedChildren.Clear();
#if COM_UNITY_MODULES_PHYSICS || COM_UNITY_MODULES_PHYSICS2D
var forUpdate = !m_UseRigidbodyForMotion;
#else
Expand Down Expand Up @@ -3861,7 +3842,6 @@ protected override void OnOwnershipChanged(ulong previous, ulong current)
}

internal bool IsNested;
private List<NetworkObject> m_ParentedChildren = new List<NetworkObject>();

private bool m_IsFirstNetworkTransform;

Expand Down Expand Up @@ -4694,7 +4674,7 @@ internal static void UpdateNetworkTick(NetworkManager networkManager)
/// The default value is 1 tick (plus the tick latency). When running on a local network, reducing this to 0 is recommended.<br />
/// <see cref="NetworkTimeSystem.TickLatency"/>
/// </remarks>
public static int InterpolationBufferTickOffset = 0;
public static int InterpolationBufferTickOffset;
internal static float GetTickLatency(NetworkManager networkManager)
{
if (networkManager.IsListening)
Expand Down Expand Up @@ -4799,6 +4779,7 @@ public NetworkTransformTickRegistration(NetworkManager networkManager)
}
}
}

private static int s_TickSynchPosition;
private int m_NextTickSync;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
namespace Unity.Netcode
{
/// <summary>
/// A Smallest Three Quaternion Compressor Implementation
/// The Smallest Three Quaternion Compressor Implementation
/// </summary>
/// <remarks>
/// Explanation of why "The smallest three":
Expand All @@ -26,14 +26,14 @@ public static class QuaternionCompressor

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

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

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

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

// Used to store the absolute value of the 4 quaternion elements
private static Quaternion s_QuatAbsValues = Quaternion.identity;

/// <summary>
/// Compresses a Quaternion into an unsigned integer
/// </summary>
Expand All @@ -54,38 +51,31 @@ public static class QuaternionCompressor
public static uint CompressQuaternion(ref Quaternion quaternion)
{
// Store off the absolute value for each Quaternion element
s_QuatAbsValues[0] = Mathf.Abs(quaternion[0]);
s_QuatAbsValues[1] = Mathf.Abs(quaternion[1]);
s_QuatAbsValues[2] = Mathf.Abs(quaternion[2]);
s_QuatAbsValues[3] = Mathf.Abs(quaternion[3]);
var quatAbsValue0 = Mathf.Abs(quaternion[0]);
var quatAbsValue1 = Mathf.Abs(quaternion[1]);
var quatAbsValue2 = Mathf.Abs(quaternion[2]);
var quatAbsValue3 = Mathf.Abs(quaternion[3]);

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

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

// Get the sign of the largest element which is all that is needed when calculating the sum of squares of a normalized quaternion.

var quatMaxSign = (quaternion[indexToSkip] < 0 ? k_True : k_False);

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

// Step 1: Start with the first element
var currentIndex = 0;

// Step 2: If we are on the index to skip preserve the current compressed value, otherwise proceed to step 3 and 4
// 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
// 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
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;
currentIndex++;
// Repeat the last 3 steps for the remaining elements
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;
currentIndex++;
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;
currentIndex++;
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;
// Step 1: If we are on the index to skip, preserve the current compressed value, otherwise proceed to step 2 and 3
// 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
// 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
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;
// Repeat the 3 steps for the remaining elements
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;
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;
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;

// Return the compress quaternion
return compressed;
Expand All @@ -111,7 +101,7 @@ public static void DecompressQuaternion(ref Quaternion quaternion, uint compress
continue;
}
// Check the negative bit and multiply that result with the decompressed and decoded value
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DcompressionDecodingMask);
quaternion[i] = ((compressed & k_NegShortBit) > 0 ? -1.0f : 1.0f) * ((compressed & k_PrecisionMask) * k_DecompressionDecodingMask);
sumOfSquaredMagnitudes += quaternion[i] * quaternion[i];
compressed = compressed >> 10;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public struct ContactEventHandlerInfo
/// </summary>
public bool ProvideNonRigidBodyContactEvents;
/// <summary>
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br /></br>
/// When set to true, the <see cref="RigidbodyContactEventManager"/> will prioritize invoking <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> <br />
/// if it is the 2nd colliding body in the contact pair being processed. With distributed authority, setting this value to true when a <see cref="NetworkObject"/> is owned by the local client <br />
/// will assure <see cref="IContactEventHandler.ContactEvent(ulong, Vector3, Rigidbody, Vector3, bool, Vector3)"/> is only invoked on the authoritative side.
/// </summary>
Expand Down Expand Up @@ -109,7 +109,7 @@ private void OnEnable()
{
m_ResultsArray = new NativeArray<JobResultStruct>(16, Allocator.Persistent);
Physics.ContactEvent += Physics_ContactEvent;
if (Instance != null)
if (Instance != null && Instance != this)
{
NetworkLog.LogError($"[Invalid][Multiple Instances] Found more than one instance of {nameof(RigidbodyContactEventManager)}: {name} and {Instance.name}");
NetworkLog.LogError($"[Disable][Additional Instance] Disabling {name} instance!");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using UnityEngine;

namespace Unity.Netcode
{
Expand All @@ -13,6 +12,7 @@ public class CommandLineOptions
/// <summary>
/// Command-line options singleton
/// </summary>
[Obsolete("Not used anymore replaced by TryGetArg")]
public static CommandLineOptions Instance
Comment thread
noellie-velez marked this conversation as resolved.
{
get
Expand All @@ -30,34 +30,41 @@ private set
}
private static CommandLineOptions s_Instance;

[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RuntimeInitializeOnLoad() => Instance = new CommandLineOptions();

// Contains the current application instance domain's command line arguments
internal static List<string> CommandLineArguments = new List<string>();

// Invoked upon application start, after scene load
[RuntimeInitializeOnLoadMethod]
private static void ParseCommandLineArguments()
{
// Get all the command line arguments to be parsed later and/or modified
// prior to being parsed (for testing purposes).
CommandLineArguments = new List<string>(Environment.GetCommandLineArgs());
}
private static readonly List<string> k_CommandLineArguments = new List<string>(Environment.GetCommandLineArgs());

/// <summary>
/// Returns the value of an argument or null if there the argument is not present
/// Returns the value of an argument or null if the argument is not present
/// </summary>
/// <param name="arg">The name of the argument</param>
/// <returns><see cref="string"/>Value of the command line argument passed in.</returns>
[Obsolete("Not used anymore replaced by TryGetArg")]
public string GetArg(string arg)
{
var argIndex = CommandLineArguments.IndexOf(arg);
if (argIndex >= 0 && argIndex < CommandLineArguments.Count - 1)
var argIndex = k_CommandLineArguments.IndexOf(arg);
if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1)
{
return CommandLineArguments[argIndex + 1];
return k_CommandLineArguments[argIndex + 1];
}
return null;
}

/// <summary>
/// Returns true if the argument was found.
/// </summary>
/// <param name="arg">The name of the argument to look up.</param>
/// <param name="argValue">The argument's value, or <see langword="null"/> if not found.</param>
/// <returns><c>true</c> if the argument was found; otherwise <c>false</c>.</returns>
public static bool TryGetArg(string arg, out string argValue)
{
var argIndex = k_CommandLineArguments.IndexOf(arg);
if (argIndex >= 0 && argIndex < k_CommandLineArguments.Count - 1)
{
argValue = k_CommandLineArguments[argIndex + 1];
return true;
}
argValue = null;
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ internal static class ComponentFactory
internal delegate object CreateObjectDelegate(NetworkManager networkManager);

private static Dictionary<Type, CreateObjectDelegate> s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
#if UNITY_EDITOR
[UnityEngine.RuntimeInitializeOnLoadMethod(UnityEngine.RuntimeInitializeLoadType.SubsystemRegistration)]
private static void ResetStaticsOnLoad() => s_Delegates = new Dictionary<Type, CreateObjectDelegate>();
#endif

/// <summary>
/// Instantiates an instance of a given interface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1299,8 +1299,6 @@ internal void NetworkVariableUpdate(ulong targetClientId, bool forceSend = false
}
}

internal static bool LogSentVariableUpdateMessage;

private bool CouldHaveDirtyNetworkVariables()
{
// TODO: There should be a better way by reading one dirty variable vs. 'n'
Expand Down
Loading
Loading