Skip to content

feat: up-port of network variable traits, anticipation, serialized null references, and removal of animator component requirement #2957

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
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
9 changes: 8 additions & 1 deletion com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,17 @@ 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).

## [2.0.0-pre.1] - 2024-06-17
## [2.0.0-pre.2] - 2024-06-17

### Added

- Added `AnticipatedNetworkVariable<T>`, which adds support for client anticipation of `NetworkVariable` values, allowing for more responsive gameplay. (#2957)
- Added `AnticipatedNetworkTransform`, which adds support for client anticipation of NetworkTransforms. (#2957)
- Added `NetworkVariableBase.ExceedsDirtinessThreshold` to allow network variables to throttle updates by only sending updates when the difference between the current and previous values exceeds a threshold. (This is exposed in `NetworkVariable<T>` with the callback `NetworkVariable<T>.CheckExceedsDirtinessThreshold`). (#2957)
- Added `NetworkVariableUpdateTraits`, which add additional throttling support: `MinSecondsBetweenUpdates` will prevent the `NetworkVariable` from sending updates more often than the specified time period (even if it exceeds the dirtiness threshold), while `MaxSecondsBetweenUpdates` will force a dirty `NetworkVariable` to send an update after the specified time period even if it has not yet exceeded the dirtiness threshold. (#2957)
- Added virtual method `NetworkVariableBase.OnInitialize` which can be used by `NetworkVariable` subclasses to add initialization code. (#2957)
- Added `NetworkTime.TickWithPartial`, which represents the current tick as a double that includes the fractional/partial tick value. (#2957)
- Added `NetworkTickSystem.AnticipationTick`, which can be helpful with implementation of client anticipation. This value represents the tick the current local client was at at the beginning of the most recent network round trip, which enables it to correlate server update ticks with the client tick that may have triggered them. (#2957)
- Added event `NetworkManager.OnSessionOwnerPromoted` that is invoked when a new session owner promotion occurs. (#2948)
- Added `NetworkRigidBodyBase.GetLinearVelocity` and `NetworkRigidBodyBase.SetLinearVelocity` convenience/helper methods. (#2948)
- Added `NetworkRigidBodyBase.GetAngularVelocity` and `NetworkRigidBodyBase.SetAngularVelocity` convenience/helper methods. (#2948)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly,
}
else
{
m_Diagnostics.AddError($"{type}: Managed type in NetworkVariable must implement IEquatable<{type}>");
equalityMethod = new GenericInstanceMethod(m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ public virtual void OnEnable()
/// <inheritdoc/>
public override void OnInspectorGUI()
{
var networkTransform = target as NetworkTransform;
EditorGUILayout.LabelField("Axis to Synchronize", EditorStyles.boldLabel);
{
GUILayout.BeginHorizontal();
Expand Down Expand Up @@ -144,7 +145,10 @@ public override void OnInspectorGUI()
EditorGUILayout.Space();
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty);
if (!networkTransform.HideInterpolateValue)
{
EditorGUILayout.PropertyField(m_InterpolateProperty);
}
EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
Expand Down

Large diffs are not rendered by default.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ namespace Unity.Netcode.Components
[AddComponentMenu("Netcode/Network Transform")]
public class NetworkTransform : NetworkBehaviour
{

#if UNITY_EDITOR
internal virtual bool HideInterpolateValue => false;
#endif

#region NETWORK TRANSFORM STATE
/// <summary>
/// Data structure used to synchronize the <see cref="NetworkTransform"/>
Expand Down Expand Up @@ -2184,10 +2189,15 @@ internal void UpdatePositionInterpolator(Vector3 position, double time, bool res

internal bool LogMotion;

protected virtual void OnTransformUpdated()
{

}

/// <summary>
/// Applies the authoritative state to the transform
/// </summary>
private void ApplyAuthoritativeState()
protected internal void ApplyAuthoritativeState()
{
#if COM_UNITY_MODULES_PHYSICS
// TODO: Make this an authority flag
Expand Down Expand Up @@ -2391,6 +2401,7 @@ private void ApplyAuthoritativeState()
}
transform.localScale = m_CurrentScale;
}
OnTransformUpdated();
}

/// <summary>
Expand Down Expand Up @@ -2602,6 +2613,8 @@ private void ApplyTeleportingState(NetworkTransformState newState)
{
AddLogEntry(ref newState, NetworkObject.OwnerClientId);
}

OnTransformUpdated();
}

/// <summary>
Expand Down Expand Up @@ -2769,6 +2782,11 @@ protected virtual void OnNetworkTransformStateUpdated(ref NetworkTransformState

}

protected virtual void OnBeforeUpdateTransformState()
{

}

internal bool LogStateUpdate;
/// <summary>
/// Only non-authoritative instances should invoke this method
Expand Down Expand Up @@ -2809,6 +2827,10 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
}
Debug.Log(builder);
}

// Notification prior to applying a state update
OnBeforeUpdateTransformState();

// Apply the new state
ApplyUpdatedState(newState);

Expand Down
43 changes: 38 additions & 5 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -950,15 +950,33 @@ internal void PostNetworkVariableWrite(bool forced = false)
// during OnNetworkSpawn has been sent and needs to be cleared
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
NetworkVariableFields[i].ResetDirty();
var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
{
networkVariable.UpdateLastSentTime();
networkVariable.ResetDirty();
networkVariable.SetDirty(false);
}
}
}
}
else
{
// mark any variables we wrote as no longer dirty
for (int i = 0; i < NetworkVariableIndexesToReset.Count; i++)
{
NetworkVariableFields[NetworkVariableIndexesToReset[i]].ResetDirty();
var networkVariable = NetworkVariableFields[NetworkVariableIndexesToReset[i]];
if (networkVariable.IsDirty())
{
if (networkVariable.CanSend())
{
networkVariable.UpdateLastSentTime();
networkVariable.ResetDirty();
networkVariable.SetDirty(false);
}
}
}
}

Expand Down Expand Up @@ -1001,7 +1019,10 @@ internal void NetworkVariableUpdate(ulong targetClientId)
networkVariable = NetworkVariableFields[k];
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
{
shouldSend = true;
if (networkVariable.CanSend())
{
shouldSend = true;
}
break;
}
}
Expand Down Expand Up @@ -1057,9 +1078,16 @@ private bool CouldHaveDirtyNetworkVariables()
// TODO: There should be a better way by reading one dirty variable vs. 'n'
for (int i = 0; i < NetworkVariableFields.Count; i++)
{
if (NetworkVariableFields[i].IsDirty())
var networkVariable = NetworkVariableFields[i];
if (networkVariable.IsDirty())
{
return true;
if (networkVariable.CanSend())
{
return true;
}
// If it's dirty but can't be sent yet, we have to keep monitoring it until one of the
// conditions blocking its send changes.
NetworkManager.BehaviourUpdater.AddForUpdate(NetworkObject);
}
}

Expand Down Expand Up @@ -1268,6 +1296,11 @@ protected virtual void OnSynchronize<T>(ref BufferSerializer<T> serializer) wher

}

public virtual void OnReanticipate(double lastRoundTripTime)
{

}

/// <summary>
/// The relative client identifier targeted for the serialization of this <see cref="NetworkBehaviour"/> instance.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,15 @@ public class NetworkBehaviourUpdater
private NetworkManager m_NetworkManager;
private NetworkConnectionManager m_ConnectionManager;
private HashSet<NetworkObject> m_DirtyNetworkObjects = new HashSet<NetworkObject>();
private HashSet<NetworkObject> m_PendingDirtyNetworkObjects = new HashSet<NetworkObject>();

#if DEVELOPMENT_BUILD || UNITY_EDITOR
private ProfilerMarker m_NetworkBehaviourUpdate = new ProfilerMarker($"{nameof(NetworkBehaviour)}.{nameof(NetworkBehaviourUpdate)}");
#endif

internal void AddForUpdate(NetworkObject networkObject)
{
m_DirtyNetworkObjects.Add(networkObject);
m_PendingDirtyNetworkObjects.Add(networkObject);
}

internal void NetworkBehaviourUpdate()
Expand All @@ -28,6 +29,9 @@ internal void NetworkBehaviourUpdate()
#endif
try
{
m_DirtyNetworkObjects.UnionWith(m_PendingDirtyNetworkObjects);
m_PendingDirtyNetworkObjects.Clear();

// NetworkObject references can become null, when hidden or despawned. Once NUll, there is no point
// trying to process them, even if they were previously marked as dirty.
m_DirtyNetworkObjects.RemoveWhere((sobj) => sobj == null);
Expand Down
32 changes: 32 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,8 +253,10 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)

DeferredMessageManager.ProcessTriggers(IDeferredNetworkMessageManager.TriggerType.OnNextFrame, 0);

AnticipationSystem.SetupForUpdate();
MessageManager.ProcessIncomingMessageQueue();
MessageManager.CleanupDisconnectedClients();
AnticipationSystem.ProcessReanticipation();
}
break;
#if COM_UNITY_MODULES_PHYSICS
Expand All @@ -273,6 +275,7 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
case NetworkUpdateStage.PreUpdate:
{
NetworkTimeSystem.UpdateTime();
AnticipationSystem.Update();
}
break;
case NetworkUpdateStage.PreLateUpdate:
Expand All @@ -287,6 +290,12 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)
}
}
break;
case NetworkUpdateStage.PostScriptLateUpdate:
{
AnticipationSystem.Sync();
AnticipationSystem.SetupForRender();
}
break;
case NetworkUpdateStage.PostLateUpdate:
{
// Handle deferred despawning
Expand Down Expand Up @@ -526,6 +535,25 @@ public event Action OnTransportFailure
remove => ConnectionManager.OnTransportFailure -= value;
}

public delegate void ReanticipateDelegate(double lastRoundTripTime);

/// <summary>
/// This callback is called after all individual OnReanticipate calls on AnticipatedNetworkVariable
/// and AnticipatedNetworkTransform values have been invoked. The first parameter is a hash set of
/// all the variables that have been changed on this frame (you can detect a particular variable by
/// checking if the set contains it), while the second parameter is a set of all anticipated network
/// transforms that have been changed. Both are passed as their base class type.
///
/// The third parameter is the local time corresponding to the current authoritative server state
/// (i.e., to determine the amount of time that needs to be re-simulated, you will use
/// NetworkManager.LocalTime.Time - authorityTime).
/// </summary>
public event ReanticipateDelegate OnReanticipate
{
add => AnticipationSystem.OnReanticipate += value;
remove => AnticipationSystem.OnReanticipate -= value;
}

/// <summary>
/// The callback to invoke during connection approval. Allows client code to decide whether or not to allow incoming client connection
/// </summary>
Expand Down Expand Up @@ -770,6 +798,8 @@ public NetworkPrefabHandler PrefabHandler
/// </summary>
public NetworkTickSystem NetworkTickSystem { get; private set; }

internal AnticipationSystem AnticipationSystem { get; private set; }

/// <summary>
/// Used for time mocking in tests
/// </summary>
Expand Down Expand Up @@ -1078,6 +1108,7 @@ internal void Initialize(bool server)
this.RegisterNetworkUpdate(NetworkUpdateStage.FixedUpdate);
#endif
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostScriptLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);

Expand Down Expand Up @@ -1117,6 +1148,7 @@ internal void Initialize(bool server)
// The remaining systems can then be initialized
NetworkTimeSystem = server ? NetworkTimeSystem.ServerTimeSystem() : new NetworkTimeSystem(1.0 / NetworkConfig.TickRate);
NetworkTickSystem = NetworkTimeSystem.Initialize(this);
AnticipationSystem = new AnticipationSystem(this);

// Create spawn manager instance
SpawnManager = new NetworkSpawnManager(this);
Expand Down
20 changes: 20 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ public enum NetworkUpdateStage : byte
/// </summary>
PreLateUpdate = 6,
/// <summary>
/// Updated after Monobehaviour.LateUpdate, but BEFORE rendering
/// </summary>
// Yes, these numbers are out of order due to backward compatibility requirements.
// The enum values are listed in the order they will be called.
PostScriptLateUpdate = 8,
/// <summary>
/// Updated after the Monobehaviour.LateUpdate for all components is invoked
/// </summary>
PostLateUpdate = 7
Expand Down Expand Up @@ -258,6 +264,18 @@ public static PlayerLoopSystem CreateLoopSystem()
}
}

internal struct NetworkPostScriptLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
{
return new PlayerLoopSystem
{
type = typeof(NetworkPostScriptLateUpdate),
updateDelegate = () => RunNetworkUpdateStage(NetworkUpdateStage.PostScriptLateUpdate)
};
}
}

internal struct NetworkPostLateUpdate
{
public static PlayerLoopSystem CreateLoopSystem()
Expand Down Expand Up @@ -399,6 +417,7 @@ internal static void RegisterLoopSystems()
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryAddLoopSystem(ref currentSystem, NetworkPreLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.Before);
TryAddLoopSystem(ref currentSystem, NetworkPostScriptLateUpdate.CreateLoopSystem(), typeof(PreLateUpdate.ScriptRunBehaviourLateUpdate), LoopSystemPosition.After);
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
Expand Down Expand Up @@ -440,6 +459,7 @@ internal static void UnregisterLoopSystems()
else if (currentSystem.type == typeof(PreLateUpdate))
{
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPreLateUpdate));
TryRemoveLoopSystem(ref currentSystem, typeof(NetworkPostScriptLateUpdate));
}
else if (currentSystem.type == typeof(PostLateUpdate))
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ internal enum NetworkMessageTypes : uint
SessionOwner = 20,
TimeSync = 21,
Unnamed = 22,
AnticipationCounterSyncPingMessage = 23,
AnticipationCounterSyncPongMessage = 24,
}


Expand Down Expand Up @@ -103,7 +105,9 @@ internal enum NetworkMessageTypes : uint
{ typeof(ServerRpcMessage), NetworkMessageTypes.ServerRpc },
{ typeof(TimeSyncMessage), NetworkMessageTypes.TimeSync },
{ typeof(UnnamedMessage), NetworkMessageTypes.Unnamed },
{ typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner }
{ typeof(SessionOwnerMessage), NetworkMessageTypes.SessionOwner },
{ typeof(AnticipationCounterSyncPingMessage), NetworkMessageTypes.AnticipationCounterSyncPingMessage},
{ typeof(AnticipationCounterSyncPongMessage), NetworkMessageTypes.AnticipationCounterSyncPongMessage},
};

// Assure the type to lookup table count and NetworkMessageType enum count matches (i.e. to catch human error when adding new messages)
Expand Down
Loading