-
-
Notifications
You must be signed in to change notification settings - Fork 859
feat: NetworkTickManager (Experimental) - Sync, predict, reconcile using ticks #3996
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
base: master
Are you sure you want to change the base?
Changes from all commits
3568c62
640a11e
b0185aa
d440541
58351b6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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,4 @@ | ||
| namespace System.Runtime.CompilerServices{ | ||
| internal static class IsExternalInit{ | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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,139 @@ | ||
| using UnityEngine; | ||
| using System; | ||
|
|
||
| namespace Mirror.Components.Experimental{ | ||
| [DefaultExecutionOrder(-10)] | ||
| [DisallowMultipleComponent] | ||
| [AddComponentMenu("Network/Network Physics Controller")] | ||
| public class NetworkPhysicsController : MonoBehaviour{ | ||
| private readonly NetworkPhysicsEntity _physicsEntity = new NetworkPhysicsEntity(); | ||
|
|
||
| // 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> | ||
| /// Subscribable callback action to handle synchronized logic. | ||
| /// Allows external classes to define custom behavior on network synchronization. | ||
| /// </summary> | ||
| public static event Action OnSynchronized; | ||
|
|
||
| /// <summary> Ensure that auto physics simulations are disabled. </summary> | ||
| void Awake() => Physics.autoSimulation = false; | ||
|
|
||
| /// <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 when network is synchronized. Invokes the <see cref="OnSynchronized"/> if it is not null. | ||
| /// </summary> | ||
| public virtual void NetworkSynchronized() { | ||
| OnSynchronized?.Invoke(); | ||
| _physicsEntity.RunNetworkSynchronized(); | ||
| } | ||
|
|
||
| /// <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(); | ||
| _physicsEntity.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) { | ||
| // ensure that the are ticks to execute in the first place | ||
| if (deltaTicks < 1) return; | ||
|
|
||
| // execute the first tick | ||
| TickForward(1); | ||
|
|
||
| // If reconciling, reset the network state before simulating. | ||
| if (isReconciling) ResetNetworkState(); | ||
| SimulateTick(); | ||
|
|
||
| // run additional ticks iteration if any left skipping the first step since it was already executed | ||
| for (var step = 1; step < deltaTicks; step++) { | ||
| TickForward(1); | ||
| SimulateTick(); | ||
| } | ||
|
|
||
| // If reconciling, run post-reconciliation actions. | ||
| if (isReconciling) _physicsEntity.RunAfterReconcile(); | ||
| } | ||
|
|
||
| /// <summary> Performs a single simulation step by invoking pre-updates, network updates, physics simulation, and post-updates for one tick. </summary> | ||
| public void SimulateTick() { | ||
| var deltaTime = Time.fixedDeltaTime; | ||
| _physicsEntity.RunBeforeNetworkUpdates(1, deltaTime); | ||
| _physicsEntity.RunNetworkUpdates(1, deltaTime); | ||
| PhysicsTick(deltaTime); | ||
| _physicsEntity.RunAfterNetworkUpdates(1, deltaTime); | ||
| } | ||
|
|
||
| /// <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,140 @@ | ||
| 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 when client and server are synchronized. | ||
| /// </summary> | ||
| void OnNetworkSynchronized(); | ||
|
|
||
| /// <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 OnBeforeNetworkUpdate(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 OnAfterNetworkUpdate(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) { | ||
| // Add item to list of executables | ||
| NetworkItems.Add((priority, item)); | ||
|
|
||
| // 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] | ||
| NetworkItems.Sort((x, y) => y.priority.CompareTo(x.priority)); | ||
| } | ||
|
|
||
| /// <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 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 void RunResetNetworkState() { | ||
| foreach (var (_, item) in NetworkItems) { | ||
| item.OnResetNetworkState(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Runs the OnNetworkSynchronized method on each network item in priority order. | ||
| /// This method is intended to signal that the network state is synchronized. | ||
| /// </summary> | ||
| public void RunNetworkSynchronized() { | ||
| foreach (var (_, item) in NetworkItems) { | ||
| item.OnNetworkSynchronized(); | ||
| } | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Runs the OnBeforeNetworkUpdate 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 void RunBeforeNetworkUpdates(int deltaTicks, float deltaTime) { | ||
| foreach (var (_, item) in NetworkItems) { | ||
| item.OnBeforeNetworkUpdate(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 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 void RunAfterNetworkUpdates(int deltaTicks, float deltaTime) { | ||
| foreach (var (_, item) in NetworkItems) { | ||
| item.OnAfterNetworkUpdate(deltaTicks, deltaTime); | ||
| } | ||
| } | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IsP_E_nding typo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
true that :) will fix