Skip to content
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

feat: NetworkTickManager - Calculating tick predictions and controlling physics ticks to synchronize the server with the client. #3943

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
3 changes: 3 additions & 0 deletions Assets/Mirror/Components/Experimental.meta

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
@@ -0,0 +1,102 @@
using UnityEngine;
using System;

namespace Mirror.Components.Experimental{
[DefaultExecutionOrder(-10)]
[DisallowMultipleComponent]
[AddComponentMenu("Network/Network Physics Controller")]
public class NetworkPhysicsController : MonoBehaviour{
// reconcile tick and request status
private static bool _pendingReconcile = false;
private static int _reconcileStartTick = 0;

/// <summary>
/// Callback action to handle tick-forwarding logic.
/// Allows external classes to define custom behavior when the tick advances.
/// </summary>
public Action<int> TickForwardCallback;

/// <summary>
/// Subscribable callback action to handle reset state logic.
/// Allows external classes to define custom behavior on reset state.
/// </summary>
public static event Action OnResetState;

/// <summary>
/// Advances the game state by a specified number of ticks.
/// Invokes the TickForwardCallback to allow external classes to handle tick-forwarding logic.
/// Typically called with `deltaTicks` = 1 from RunSimulate.
/// </summary>
/// <param name="deltaTicks">The number of ticks to forward.</param>
public virtual void TickForward(int deltaTicks) {
TickForwardCallback?.Invoke(deltaTicks);
}

/// <summary>
/// Called just before the reconcile process is performed. Invokes the <see cref="OnResetState"/> if it is not null.
/// </summary>
public virtual void ResetNetworkState() {
// First, call all callbacks to let non-networked items reset their states before networked items.
// Otherwise, networked items might rely on out-of-sync world data (e.g., positions) if non-networked items aren’t reset first.
OnResetState?.Invoke();
NetworkPhysicsEntity.RunResetNetworkState();
}

/// <summary>
/// Executes a single physics simulation step for the given delta time.
/// Uses Unity's Physics.Simulate to perform the physics tick.
/// Typically called with Time.fixedDeltaTime.
/// </summary>
/// <param name="deltaTime">The time interval to simulate physics for.</param>
public virtual void PhysicsTick(float deltaTime) {
Physics.Simulate(deltaTime); // Using Unity's built-in physics engine.
}

/// <summary>
/// Runs the simulation for the specified number of delta ticks.
/// This method performs multiple steps of entity updates and physics ticks
/// to bring the simulation in sync with the latest tick count.
/// </summary>
/// <param name="deltaTicks">The number of ticks to simulate forward.</param>
/// <param name="isReconciling">Is current simulation a reconciliation.</param>
public void RunSimulate(int deltaTicks, bool isReconciling = false) {
var deltaTime = Time.fixedDeltaTime;
for (var step = 0; step < deltaTicks; step++) {
TickForward(1);
if (isReconciling && step == 0) ResetNetworkState();
NetworkPhysicsEntity.RunBeforeNetworkUpdates(1, deltaTime);
NetworkPhysicsEntity.RunNetworkUpdates(1, deltaTime);
PhysicsTick(deltaTime);
NetworkPhysicsEntity.RunAfterNetworkUpdates(1, deltaTime);
}
if (isReconciling) NetworkPhysicsEntity.RunAfterReconcile();
}

/// <summary> Requests the reconciliation process to start from a specific tick (including the requested tick ) </summary>
/// <param name="reconcileStartTick">The tick from which to start reconciliation.</param>
public static void RequestReconcileFromTick(int reconcileStartTick) {
if (!_pendingReconcile || NetworkTick.SubtractTicks(_reconcileStartTick, reconcileStartTick) > 0) {
_pendingReconcile = true;
_reconcileStartTick = reconcileStartTick; // the +1 is important to include the faulty tick
}
}

/// <summary> Requests the reconciliation process to start from a specific tick on an instance. </summary>
/// <param name="reconcileStartTick">The tick from which to start reconciliation.</param>
public void ReconcileFromTick(int reconcileStartTick)
=> RequestReconcileFromTick(reconcileStartTick);

/// <summary> Retrieves the tick number from which reconciliation should start. </summary>
/// <returns>The tick number from which to start reconciliation.</returns>
public int GetReconcileStartTick() => _reconcileStartTick;

/// <summary> Is reconcile requested or not </summary>
public bool IsPEndingReconcile() => _pendingReconcile;

/// <summary> Resets the reconciliation counter and pending flag, marking the reconciliation process as complete. </summary>
public void ResetReconcile() {
_reconcileStartTick = 0;
_pendingReconcile = false;
}
}
}

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
@@ -0,0 +1,123 @@
using System.Collections.Generic;

namespace Mirror.Components.Experimental{
/// <summary>
/// Interface representing a network item that requires updates at various stages of the network tick cycle.
/// Each method in this interface is intended to handle specific stages of the update process.
/// </summary>
public interface INetworkedItem{
/// <summary>
/// Called before the network reconciliation process begins, allowing the item to properly reset state to the last known good state.
/// </summary>
void OnResetNetworkState();

/// <summary>
/// Called after the network reconciliation process ends.
/// </summary>
void AfterNetworkReconcile() {
}

/// <summary>
/// Called before the main network update, allowing the item to perform any necessary preparation or pre-update logic.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
void BeforeNetworkUpdate(int deltaTicks, float deltaTime) {
}

/// <summary>
/// Called during the main network update, allowing the item to handle core updates related to network state, physics, or entity positioning.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
void OnNetworkUpdate(int deltaTicks, float deltaTime);

/// <summary>
/// Called after the main network update, allowing the item to perform any necessary cleanup or post-update logic.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
void AfterNetworkUpdate(int deltaTicks, float deltaTime) {
}
}

/// <summary>
/// Manages network update sequences for entities requiring tick-based adjustments.
/// </summary>
public class NetworkPhysicsEntity{
/// <summary> Stores items requiring updates on each tick, as a list of tuples with priority and item. </summary>
private static readonly List<(int priority, INetworkedItem item)> NetworkItems = new List<(int, INetworkedItem)>();

/// <summary> Adds a network entity to the collection for updates and sorts by priority. </summary>
/// <param name="item">The network item implementing <see cref="INetworkedItem"/> that requires tick updates.</param>
/// <param name="priority">The priority for the entity, with lower numbers indicating higher priority.</param>
public static void AddNetworkEntity(INetworkedItem item, int priority = 0) {
NetworkItems.Add((priority, item));
NetworkItems.Sort((x, y) => y.priority.CompareTo(x.priority));
// Fortunately, List.Sort() in C# uses a stable sorting algorithm so same priority remains in the same order and new items are added to the end
// [2-a, 1-a, 1-b, 0-a, 0-b, 0-c] + [1-c] => [2-a, 1-a, 1-b, 1-c, 0-a, 0-b, 0-c]
}

/// <summary> Removes a network entity from the collection based on the item reference only. </summary>
/// <param name="item">The network item to remove.</param>
public static void RemoveNetworkEntity(INetworkedItem item) {
NetworkItems.RemoveAll(entry => entry.item.Equals(item));
}

/// <summary>
/// Runs the AfterReconcile method on each network item in priority order.
/// This method is intended to signal reconcile complete.
/// </summary>
public static void RunAfterReconcile() {
foreach (var (_, item) in NetworkItems) {
item.AfterNetworkReconcile();
}
}

/// <summary>
/// Runs the OnResetNetworkState method on each network item in priority order.
/// This method is intended to reset the network state before any updates are processed.
/// </summary>
public static void RunResetNetworkState() {
foreach (var (_, item) in NetworkItems) {
item.OnResetNetworkState();
}
}

/// <summary>
/// Runs the BeforeNetworkUpdate method on each network item in priority order.
/// This method is intended to perform any necessary setup or pre-update logic before the main network updates are processed.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
public static void RunBeforeNetworkUpdates(int deltaTicks, float deltaTime) {
foreach (var (_, item) in NetworkItems) {
item.BeforeNetworkUpdate(deltaTicks, deltaTime);
}
}

/// <summary>
/// Runs the OnNetworkUpdate method on each network item in priority order.
/// This method executes the main network update logic for each item, handling any core updates needed for the network state or entity positions.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
public static void RunNetworkUpdates(int deltaTicks, float deltaTime) {
foreach (var (_, item) in NetworkItems) {
item.OnNetworkUpdate(deltaTicks, deltaTime);
}
}

/// <summary>
/// Runs the AfterNetworkUpdate method on each network item in priority order.
/// This method is intended for any necessary cleanup or post-update logic following the main network updates.
/// </summary>
/// <param name="deltaTicks">The number of ticks since the last update.</param>
/// <param name="deltaTime">The time elapsed since the last update in seconds.</param>
public static void RunAfterNetworkUpdates(int deltaTicks, float deltaTime) {
foreach (var (_, item) in NetworkItems) {
item.AfterNetworkUpdate(deltaTicks, deltaTime);
}
}
}
}

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

Loading