123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192 |
- // our ideal update looks like this:
- // transport.process_incoming()
- // update_world()
- // transport.process_outgoing()
- //
- // this way we avoid unnecessary latency for low-ish server tick rates.
- // for example, if we were to use this tick:
- // transport.process_incoming/outgoing()
- // update_world()
- //
- // then anything sent in update_world wouldn't be actually sent out by the
- // transport until the next frame. if server runs at 60Hz, then this can add
- // 16ms latency for every single packet.
- //
- // => instead we process incoming, update world, process_outgoing in the same
- // frame. it's more clear (no race conditions) and lower latency.
- // => we need to add custom Update functions to the Unity engine:
- // NetworkEarlyUpdate before Update()/FixedUpdate()
- // NetworkLateUpdate after LateUpdate()
- // this way the user can update the world in Update/FixedUpdate/LateUpdate
- // and networking still runs before/after those functions no matter what!
- // => see also: https://docs.unity3d.com/Manual/ExecutionOrder.html
- // => update order:
- // * we add to the end of EarlyUpdate so it runs after any Unity initializations
- // * we add to the end of PreLateUpdate so it runs after LateUpdate(). adding
- // to the beginning of PostLateUpdate doesn't actually work.
- using System;
- using UnityEngine;
- // PlayerLoop and LowLevel were in the Experimental namespace until 2019.3
- // https://docs.unity3d.com/2019.2/Documentation/ScriptReference/Experimental.LowLevel.PlayerLoop.html
- // https://docs.unity3d.com/2019.3/Documentation/ScriptReference/LowLevel.PlayerLoop.html
- #if UNITY_2019_3_OR_NEWER
- using UnityEngine.LowLevel;
- using UnityEngine.PlayerLoop;
- #else
- using UnityEngine.Experimental.LowLevel;
- using UnityEngine.Experimental.PlayerLoop;
- #endif
- namespace Mirror
- {
- internal static class NetworkLoop
- {
- // helper enum to add loop to begin/end of subSystemList
- internal enum AddMode { Beginning, End }
- // helper function to find an update function's index in a player loop
- // type. this is used for testing to guarantee our functions are added
- // at the beginning/end properly.
- internal static int FindPlayerLoopEntryIndex(PlayerLoopSystem.UpdateFunction function, PlayerLoopSystem playerLoop, Type playerLoopSystemType)
- {
- // did we find the type? e.g. EarlyUpdate/PreLateUpdate/etc.
- if (playerLoop.type == playerLoopSystemType)
- return Array.FindIndex(playerLoop.subSystemList, (elem => elem.updateDelegate == function));
- // recursively keep looking
- if (playerLoop.subSystemList != null)
- {
- for(int i = 0; i < playerLoop.subSystemList.Length; ++i)
- {
- int index = FindPlayerLoopEntryIndex(function, playerLoop.subSystemList[i], playerLoopSystemType);
- if (index != -1) return index;
- }
- }
- return -1;
- }
- // MODIFIED AddSystemToPlayerLoopList from Unity.Entities.ScriptBehaviourUpdateOrder (ECS)
- //
- // => adds an update function to the Unity internal update type.
- // => Unity has different update loops:
- // https://medium.com/@thebeardphantom/unity-2018-and-playerloop-5c46a12a677
- // EarlyUpdate
- // FixedUpdate
- // PreUpdate
- // Update
- // PreLateUpdate
- // PostLateUpdate
- //
- // function: the custom update function to add
- // IMPORTANT: according to a comment in Unity.Entities.ScriptBehaviourUpdateOrder,
- // the UpdateFunction can not be virtual because
- // Mono 4.6 has problems invoking virtual methods
- // as delegates from native!
- // ownerType: the .type to fill in so it's obvious who the new function
- // belongs to. seems to be mostly for debugging. pass any.
- // addMode: prepend or append to update list
- internal static bool AddToPlayerLoop(PlayerLoopSystem.UpdateFunction function, Type ownerType, ref PlayerLoopSystem playerLoop, Type playerLoopSystemType, AddMode addMode)
- {
- // did we find the type? e.g. EarlyUpdate/PreLateUpdate/etc.
- if (playerLoop.type == playerLoopSystemType)
- {
- // debugging
- //Debug.Log($"Found playerLoop of type {playerLoop.type} with {playerLoop.subSystemList.Length} Functions:");
- //foreach (PlayerLoopSystem sys in playerLoop.subSystemList)
- // Debug.Log($" ->{sys.type}");
- // resize & expand subSystemList to fit one more entry
- int oldListLength = (playerLoop.subSystemList != null) ? playerLoop.subSystemList.Length : 0;
- Array.Resize(ref playerLoop.subSystemList, oldListLength + 1);
- // IMPORTANT: always insert a FRESH PlayerLoopSystem!
- // We CAN NOT resize and then OVERWRITE an entry's type/loop.
- // => PlayerLoopSystem has native IntPtr loop members
- // => forgetting to clear those would cause undefined behaviour!
- // see also: https://github.com/vis2k/Mirror/pull/2652
- PlayerLoopSystem system = new PlayerLoopSystem {
- type = ownerType,
- updateDelegate = function
- };
- // prepend our custom loop to the beginning
- if (addMode == AddMode.Beginning)
- {
- // shift to the right, write into first array element
- Array.Copy(playerLoop.subSystemList, 0, playerLoop.subSystemList, 1, playerLoop.subSystemList.Length - 1);
- playerLoop.subSystemList[0] = system;
- }
- // append our custom loop to the end
- else if (addMode == AddMode.End)
- {
- // simply write into last array element
- playerLoop.subSystemList[oldListLength] = system;
- }
- // debugging
- //Debug.Log($"New playerLoop of type {playerLoop.type} with {playerLoop.subSystemList.Length} Functions:");
- //foreach (PlayerLoopSystem sys in playerLoop.subSystemList)
- // Debug.Log($" ->{sys.type}");
- return true;
- }
- // recursively keep looking
- if (playerLoop.subSystemList != null)
- {
- for(int i = 0; i < playerLoop.subSystemList.Length; ++i)
- {
- if (AddToPlayerLoop(function, ownerType, ref playerLoop.subSystemList[i], playerLoopSystemType, addMode))
- return true;
- }
- }
- return false;
- }
- // hook into Unity runtime to actually add our custom functions
- [RuntimeInitializeOnLoadMethod]
- static void RuntimeInitializeOnLoad()
- {
- //Debug.Log("Mirror: adding Network[Early/Late]Update to Unity...");
- // get loop
- // 2019 has GetCURRENTPlayerLoop which is safe to use without
- // breaking other custom system's custom loops.
- // see also: https://github.com/vis2k/Mirror/pull/2627/files
- PlayerLoopSystem playerLoop =
- #if UNITY_2019_3_OR_NEWER
- PlayerLoop.GetCurrentPlayerLoop();
- #else
- PlayerLoop.GetDefaultPlayerLoop();
- #endif
- // add NetworkEarlyUpdate to the end of EarlyUpdate so it runs after
- // any Unity initializations but before the first Update/FixedUpdate
- AddToPlayerLoop(NetworkEarlyUpdate, typeof(NetworkLoop), ref playerLoop, typeof(EarlyUpdate), AddMode.End);
- // add NetworkLateUpdate to the end of PreLateUpdate so it runs after
- // LateUpdate(). adding to the beginning of PostLateUpdate doesn't
- // actually work.
- AddToPlayerLoop(NetworkLateUpdate, typeof(NetworkLoop), ref playerLoop, typeof(PreLateUpdate), AddMode.End);
- // set the new loop
- PlayerLoop.SetPlayerLoop(playerLoop);
- }
- static void NetworkEarlyUpdate()
- {
- //Debug.Log("NetworkEarlyUpdate @ " + Time.time);
- NetworkServer.NetworkEarlyUpdate();
- NetworkClient.NetworkEarlyUpdate();
- }
- static void NetworkLateUpdate()
- {
- //Debug.Log("NetworkLateUpdate @ " + Time.time);
- NetworkServer.NetworkLateUpdate();
- NetworkClient.NetworkLateUpdate();
- }
- }
- }
|