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 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
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,96 @@
using UnityEngine;
using System;

namespace Mirror.Components.Experimental{
[DefaultExecutionOrder(-10)]
[DisallowMultipleComponent]
[AddComponentMenu("Network/Network Physics Controller")]
public class NetworkPhysicsController : MonoBehaviour{
/// <summary>
/// Callback action to handle tick-forwarding logic.
/// Allows external classes to define custom behavior when the tick advances.
/// </summary>
public Action<int> TickForwardCallback;

private static int _reconcileStartTick = 0;

/// <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>
/// 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>
public void RunSimulate(int deltaTicks) {
var deltaTime = Time.fixedDeltaTime;
for (var step = 0; step < deltaTicks; step++) {
TickForward(1);
NetworkPhysicsEntity.RunBeforeNetworkUpdates(1, deltaTime);
NetworkPhysicsEntity.RunNetworkUpdates(1, deltaTime);
PhysicsTick(deltaTime);
NetworkPhysicsEntity.RunAfterNetworkUpdates(1, deltaTime);
}
}

/// <summary>
/// Runs the simulation for the specified number of delta ticks as a single batch.
/// This method performs a single set of entity updates and a single physics tick
/// scaled to account for the total number of ticks, useful for batching simulations.
/// </summary>
/// <param name="deltaTicks">The number of ticks to simulate forward in one batch.</param>
public void RunBatchSimulate(int deltaTicks) {
var deltaTime = Time.fixedDeltaTime * deltaTicks;
TickForward(deltaTicks);
NetworkPhysicsEntity.RunBeforeNetworkUpdates(deltaTicks, deltaTime);
NetworkPhysicsEntity.RunNetworkUpdates(deltaTicks, deltaTime);
PhysicsTick(deltaTime); // Uses scaled deltaTime for batch processing
NetworkPhysicsEntity.RunAfterNetworkUpdates(deltaTicks, deltaTime);
}

/// <summary>
/// Requests the reconciliation process to start from a specific tick.
/// Stores the earliest requested tick before reconciliation is executed.
/// </summary>
/// <param name="reconcileStartTick">The tick from which to start reconciliation.</param>
public static void RequestReconcileFromTick(int reconcileStartTick) {
if (_reconcileStartTick > reconcileStartTick || _reconcileStartTick == 0) {
_reconcileStartTick = reconcileStartTick; // the +1 is important to include the faulty tick
}
}

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

/// <summary>
/// Resets the reconciliation counter, marking the reconciliation process as complete.
/// Sets _ticksToReconcile to 0, indicating no further reconciliation is required.
/// </summary>
public void ResetReconcile() {
_reconcileStartTick = 0;
}
}
}

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,93 @@
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 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 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 (priority, 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 (priority, 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 (priority, 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