Skip to content

feat: Added support for client anticipation in NetworkVariables and NetworkTransform and support for throttling functionality in NetworkVariables #2820

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
merged 37 commits into from
Mar 27, 2024
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
e8e717c
feat: Better support for container types in NetworkVariable, includin…
ShadauxCat Jan 16, 2024
124690a
Reverted accidentally-committed files
ShadauxCat Jan 18, 2024
675e6c7
Fix compile errors after reverting changes
ShadauxCat Jan 18, 2024
a81e854
feat: AnticipatedNetworkVariable, AnticipatedNetworkTransform, and Ne…
ShadauxCat Jan 31, 2024
c923b86
standards
ShadauxCat Jan 31, 2024
e3a694a
Merge branch 'develop' into feat/NetworkVariable_Traits_And_Anticipation
ShadauxCat Feb 1, 2024
22722db
Added tests for NetworkVariableUpdateTraits... and fixed the function…
ShadauxCat Feb 2, 2024
c50967a
Merge branch 'develop' into feat/NetworkVariable_Traits_And_Anticipation
ShadauxCat Feb 2, 2024
24f35cd
standards
ShadauxCat Feb 2, 2024
629e4f9
Reverted bad change, fixed broken time integration tests.
ShadauxCat Feb 5, 2024
be6230e
Fixed Universal RPC tests
ShadauxCat Feb 5, 2024
df8f202
Changelog
ShadauxCat Feb 5, 2024
d4944fb
Small fixes
ShadauxCat Feb 9, 2024
109c732
- Fixed wrong initialization of AnticipatedNetworkTransform on clients
ShadauxCat Feb 9, 2024
c8da36c
Fixed initialization of anticipated value in AnticipatedNetworkVariable.
ShadauxCat Feb 10, 2024
5eca580
Fixed some timing issues with AnticipatedNetworkTransform - because i…
ShadauxCat Feb 12, 2024
0a4f836
Merge branch 'develop' into feat/NetworkVariable_Traits_And_Anticipation
ShadauxCat Feb 12, 2024
068f66e
- Fixed tests and ensured tests covered a wider variety of timing sce…
ShadauxCat Feb 13, 2024
0ca7d14
standards
ShadauxCat Feb 13, 2024
bfd34e8
Added AnticipatedNetworkTransform.AnticipateState() to more efficient…
ShadauxCat Feb 14, 2024
f7be25f
Adjusted the timing of OnReanticipate callbacks.
ShadauxCat Feb 15, 2024
76a3fce
standards
ShadauxCat Feb 15, 2024
a268bd2
Added authorityTime to global OnReanticipate callback
ShadauxCat Feb 16, 2024
a3e2425
Standards
ShadauxCat Feb 28, 2024
2f35b0d
Fixed issue where frequent updates to an anticipated variable would n…
ShadauxCat Feb 28, 2024
4683375
Handle calling smooth with duration <= 0
ShadauxCat Feb 28, 2024
49ace5c
Added support for using AnticipatedNetworkTransform.Smooth() on the s…
ShadauxCat Feb 28, 2024
481d97e
Fixed improper initialization when certain axes of synchronization ar…
ShadauxCat Feb 29, 2024
5618d67
Fix test failures.
ShadauxCat Mar 1, 2024
6eec963
Null ref guards.
ShadauxCat Mar 1, 2024
2d1615b
Don't send Anticipation Sync messages if there's no anticipation bein…
ShadauxCat Mar 4, 2024
052a334
Merge branch 'develop' into feat/NetworkVariable_Traits_And_Anticipation
ShadauxCat Mar 6, 2024
fc60e6c
Review feedback.
ShadauxCat Mar 12, 2024
ef792ef
Fixed some more initialization issues with AnticipatedNetworkTransform
ShadauxCat Mar 13, 2024
227a332
Renamed `AuthorityState` to `AuthoritativeState` for consistency.
ShadauxCat Mar 13, 2024
5a9eac6
Standards.
ShadauxCat Mar 18, 2024
c4d8aa7
Merge branch 'develop' into feat/NetworkVariable_Traits_And_Anticipation
ShadauxCat Mar 26, 2024
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
8 changes: 8 additions & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ Additional documentation and release notes are available at [Multiplayer Documen
## [Unreleased]

### Added
-Added AnticipatedNetworkVariable<T>, which adds support for client anticipation of NetworkVariable values, allowing for more responsive gameplay (#2820)
- Added AnticipatedNetworkTransform, which adds support for client anticipation of NetworkTransforms (#2820)
- 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) (#2820)
- 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. (#2820)
- Added virtual method NetworkVariableBase.OnInitialize() which can be used by NetworkVariable subclasses to add initialization code (#2820)
- Added virtual method NetworkVariableBase.Update(), which is called once per frame to support behaviors such as interpolation between an anticipated value and an authoritative one. (#2820)
- Added NetworkTime.TickWithPartial, which represents the current tick as a double that includes the fractional/partial tick value. (#2820)
- 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. (#2820)

### Fixed

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.

18 changes: 16 additions & 2 deletions com.unity.netcode.gameobjects/Components/NetworkTransform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2047,10 +2047,15 @@ private bool ApplyTransformToNetworkStateWithInfo(ref NetworkTransformState netw
return isDirty;
}

protected virtual void OnTransformUpdated()
{

}

/// <summary>
/// Applies the authoritative state to the transform
/// </summary>
private void ApplyAuthoritativeState()
protected internal void ApplyAuthoritativeState()
{
var networkState = m_LocalAuthoritativeNetworkState;
// The m_CurrentPosition, m_CurrentRotation, and m_CurrentScale values are continually updated
Expand Down Expand Up @@ -2221,6 +2226,7 @@ private void ApplyAuthoritativeState()
}
transform.localScale = m_CurrentScale;
}
OnTransformUpdated();
}

/// <summary>
Expand Down Expand Up @@ -2418,6 +2424,7 @@ private void ApplyTeleportingState(NetworkTransformState newState)
{
AddLogEntry(ref newState, NetworkObject.OwnerClientId);
}
OnTransformUpdated();
}

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

}

protected virtual void OnBeforeUpdateTransformState()
{

}

private NetworkTransformState m_OldState = new NetworkTransformState();

/// <summary>
Expand All @@ -2609,6 +2621,8 @@ private void OnNetworkStateChanged(NetworkTransformState oldState, NetworkTransf
// Get the time when this new state was sent
newState.SentTime = new NetworkTime(m_CachedNetworkManager.NetworkConfig.TickRate, newState.NetworkTick).Time;

OnBeforeUpdateTransformState();

// Apply the new state
ApplyUpdatedState(newState);

Expand Down Expand Up @@ -3315,7 +3329,7 @@ private static void RegisterForTickUpdate(NetworkTransform networkTransform)

/// <summary>
/// If a NetworkTransformTickRegistration exists for the NetworkManager instance, then this will
/// remove the NetworkTransform instance from the single tick update entry point.
/// remove the NetworkTransform instance from the single tick update entry point.
/// </summary>
/// <param name="networkTransform"></param>
private static void DeregisterForTickUpdate(NetworkTransform networkTransform)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using Unity.Netcode.Components;
using UnityEditor;

namespace Unity.Netcode.Editor
{
/// <summary>
/// The <see cref="CustomEditor"/> for <see cref="AnticipatedNetworkTransform"/>
/// </summary>
[CustomEditor(typeof(AnticipatedNetworkTransform), true)]
public class AnticipatedNetworkTransformEditor : NetworkTransformEditor
{
public override bool HideInterpolateValue => true;
}
}

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

Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,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 @@ -37,6 +37,8 @@ public class NetworkTransformEditor : UnityEditor.Editor
private static GUIContent s_RotationLabel = EditorGUIUtility.TrTextContent("Rotation");
private static GUIContent s_ScaleLabel = EditorGUIUtility.TrTextContent("Scale");

public virtual bool HideInterpolateValue => false;

/// <inheritdoc/>
public void OnEnable()
{
Expand Down Expand Up @@ -137,7 +139,11 @@ public override void OnInspectorGUI()
EditorGUILayout.Space();
EditorGUILayout.LabelField("Configurations", EditorStyles.boldLabel);
EditorGUILayout.PropertyField(m_InLocalSpaceProperty);
EditorGUILayout.PropertyField(m_InterpolateProperty);
if (!HideInterpolateValue)
{
EditorGUILayout.PropertyField(m_InterpolateProperty);
}

EditorGUILayout.PropertyField(m_SlerpPosition);
EditorGUILayout.PropertyField(m_UseQuaternionSynchronization);
if (m_UseQuaternionSynchronization.boolValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -510,6 +510,15 @@ internal void DisconnectEventHandler(ulong transportClientId)
// as the client ID is no longer valid.
NetworkManager.Shutdown(true);
}

if (NetworkManager.IsServer)
{
MessageManager.ClientDisconnected(clientId);
}
else
{
MessageManager.ClientDisconnected(NetworkManager.ServerClientId);
}
#if DEVELOPMENT_BUILD || UNITY_EDITOR
s_TransportDisconnect.End();
#endif
Expand Down
46 changes: 38 additions & 8 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkBehaviour.cs
Original file line number Diff line number Diff line change
Expand Up @@ -815,19 +815,35 @@ 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);
}
}
}
}

MarkVariablesDirty(false);
}

internal void PreVariableUpdate()
Expand All @@ -836,7 +852,6 @@ internal void PreVariableUpdate()
{
InitializeVariables();
}

PreNetworkVariableWrite();
}

Expand All @@ -863,7 +878,10 @@ private void NetworkVariableUpdate(ulong targetClientId, int behaviourIndex)
var networkVariable = NetworkVariableFields[k];
if (networkVariable.IsDirty() && networkVariable.CanClientRead(targetClientId))
{
shouldSend = true;
if (networkVariable.CanSend())
{
shouldSend = true;
}
break;
}
}
Expand Down Expand Up @@ -904,9 +922,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 @@ -1063,6 +1088,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
33 changes: 33 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,25 @@ public void NetworkUpdate(NetworkUpdateStage updateStage)

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

AnticipationSystem.SetupForUpdate();
MessageManager.ProcessIncomingMessageQueue();
MessageManager.CleanupDisconnectedClients();

AnticipationSystem.ProcessReanticipation();
}
break;
case NetworkUpdateStage.PreUpdate:
{
NetworkTimeSystem.UpdateTime();
AnticipationSystem.Update();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh... you pulled out from the NetworkObject.Update!
:godmode:

}
break;
case NetworkUpdateStage.PostScriptLateUpdate:

AnticipationSystem.Sync();
AnticipationSystem.SetupForRender();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this concept but would like to know more about it for the DA stuff.

break;

case NetworkUpdateStage.PostLateUpdate:
{
// This should be invoked just prior to the MessageManager processes its outbound queue.
Expand Down Expand Up @@ -274,6 +284,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 @@ -518,6 +547,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 @@ -813,6 +844,7 @@ internal void Initialize(bool server)

this.RegisterNetworkUpdate(NetworkUpdateStage.EarlyUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PreUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostScriptLateUpdate);
this.RegisterNetworkUpdate(NetworkUpdateStage.PostLateUpdate);

// ComponentFactory needs to set its defaults next
Expand Down Expand Up @@ -845,6 +877,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
21 changes: 21 additions & 0 deletions com.unity.netcode.gameobjects/Runtime/Core/NetworkUpdateLoop.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,14 @@ 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
/// and all rendering is complete
/// </summary>
PostLateUpdate = 7
}
Expand Down Expand Up @@ -258,6 +265,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 +418,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 +460,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
Loading