Mirror Networking
NetworkLoop.cs
1// our ideal update looks like this:
2// transport.process_incoming()
3// update_world()
4// transport.process_outgoing()
5//
6// this way we avoid unnecessary latency for low-ish server tick rates.
7// for example, if we were to use this tick:
8// transport.process_incoming/outgoing()
9// update_world()
10//
11// then anything sent in update_world wouldn't be actually sent out by the
12// transport until the next frame. if server runs at 60Hz, then this can add
13// 16ms latency for every single packet.
14//
15// => instead we process incoming, update world, process_outgoing in the same
16// frame. it's more clear (no race conditions) and lower latency.
17// => we need to add custom Update functions to the Unity engine:
18// NetworkEarlyUpdate before Update()/FixedUpdate()
19// NetworkLateUpdate after LateUpdate()
20// this way the user can update the world in Update/FixedUpdate/LateUpdate
21// and networking still runs before/after those functions no matter what!
22// => see also: https://docs.unity3d.com/Manual/ExecutionOrder.html
23// => update order:
24// * we add to the end of EarlyUpdate so it runs after any Unity initializations
25// * we add to the end of PreLateUpdate so it runs after LateUpdate(). adding
26// to the beginning of PostLateUpdate doesn't actually work.
27using System;
28using UnityEngine;
29
30// PlayerLoop and LowLevel were in the Experimental namespace until 2019.3
31// https://docs.unity3d.com/2019.2/Documentation/ScriptReference/Experimental.LowLevel.PlayerLoop.html
32// https://docs.unity3d.com/2019.3/Documentation/ScriptReference/LowLevel.PlayerLoop.html
33#if UNITY_2019_3_OR_NEWER
34using UnityEngine.LowLevel;
35using UnityEngine.PlayerLoop;
36#else
37using UnityEngine.Experimental.LowLevel;
38using UnityEngine.Experimental.PlayerLoop;
39#endif
40
41namespace Mirror
42{
43 public static class NetworkLoop
44 {
45 // helper enum to add loop to begin/end of subSystemList
46 internal enum AddMode { Beginning, End }
47
48 // callbacks in case someone needs to use early/lateupdate too.
49 public static Action OnEarlyUpdate;
50 public static Action OnLateUpdate;
51
52 // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload
53 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
54 static void ResetStatics()
55 {
56 OnEarlyUpdate = null;
57 OnLateUpdate = null;
58 }
59
60 // helper function to find an update function's index in a player loop
61 // type. this is used for testing to guarantee our functions are added
62 // at the beginning/end properly.
63 internal static int FindPlayerLoopEntryIndex(PlayerLoopSystem.UpdateFunction function, PlayerLoopSystem playerLoop, Type playerLoopSystemType)
64 {
65 // did we find the type? e.g. EarlyUpdate/PreLateUpdate/etc.
66 if (playerLoop.type == playerLoopSystemType)
67 return Array.FindIndex(playerLoop.subSystemList, (elem => elem.updateDelegate == function));
68
69 // recursively keep looking
70 if (playerLoop.subSystemList != null)
71 {
72 for(int i = 0; i < playerLoop.subSystemList.Length; ++i)
73 {
74 int index = FindPlayerLoopEntryIndex(function, playerLoop.subSystemList[i], playerLoopSystemType);
75 if (index != -1) return index;
76 }
77 }
78 return -1;
79 }
80
81 // MODIFIED AddSystemToPlayerLoopList from Unity.Entities.ScriptBehaviourUpdateOrder (ECS)
82 //
83 // => adds an update function to the Unity internal update type.
84 // => Unity has different update loops:
85 // https://medium.com/@thebeardphantom/unity-2018-and-playerloop-5c46a12a677
86 // EarlyUpdate
87 // FixedUpdate
88 // PreUpdate
89 // Update
90 // PreLateUpdate
91 // PostLateUpdate
92 //
93 // function: the custom update function to add
94 // IMPORTANT: according to a comment in Unity.Entities.ScriptBehaviourUpdateOrder,
95 // the UpdateFunction can not be virtual because
96 // Mono 4.6 has problems invoking virtual methods
97 // as delegates from native!
98 // ownerType: the .type to fill in so it's obvious who the new function
99 // belongs to. seems to be mostly for debugging. pass any.
100 // addMode: prepend or append to update list
101 internal static bool AddToPlayerLoop(PlayerLoopSystem.UpdateFunction function, Type ownerType, ref PlayerLoopSystem playerLoop, Type playerLoopSystemType, AddMode addMode)
102 {
103 // did we find the type? e.g. EarlyUpdate/PreLateUpdate/etc.
104 if (playerLoop.type == playerLoopSystemType)
105 {
106 // debugging
107 //Debug.Log($"Found playerLoop of type {playerLoop.type} with {playerLoop.subSystemList.Length} Functions:");
108 //foreach (PlayerLoopSystem sys in playerLoop.subSystemList)
109 // Debug.Log($" ->{sys.type}");
110
111 // resize & expand subSystemList to fit one more entry
112 int oldListLength = (playerLoop.subSystemList != null) ? playerLoop.subSystemList.Length : 0;
113 Array.Resize(ref playerLoop.subSystemList, oldListLength + 1);
114
115 // IMPORTANT: always insert a FRESH PlayerLoopSystem!
116 // We CAN NOT resize and then OVERWRITE an entry's type/loop.
117 // => PlayerLoopSystem has native IntPtr loop members
118 // => forgetting to clear those would cause undefined behaviour!
119 // see also: https://github.com/vis2k/Mirror/pull/2652
120 PlayerLoopSystem system = new PlayerLoopSystem {
121 type = ownerType,
122 updateDelegate = function
123 };
124
125 // prepend our custom loop to the beginning
126 if (addMode == AddMode.Beginning)
127 {
128 // shift to the right, write into first array element
129 Array.Copy(playerLoop.subSystemList, 0, playerLoop.subSystemList, 1, playerLoop.subSystemList.Length - 1);
130 playerLoop.subSystemList[0] = system;
131
132 }
133 // append our custom loop to the end
134 else if (addMode == AddMode.End)
135 {
136 // simply write into last array element
137 playerLoop.subSystemList[oldListLength] = system;
138 }
139
140 // debugging
141 //Debug.Log($"New playerLoop of type {playerLoop.type} with {playerLoop.subSystemList.Length} Functions:");
142 //foreach (PlayerLoopSystem sys in playerLoop.subSystemList)
143 // Debug.Log($" ->{sys.type}");
144
145 return true;
146 }
147
148 // recursively keep looking
149 if (playerLoop.subSystemList != null)
150 {
151 for(int i = 0; i < playerLoop.subSystemList.Length; ++i)
152 {
153 if (AddToPlayerLoop(function, ownerType, ref playerLoop.subSystemList[i], playerLoopSystemType, addMode))
154 return true;
155 }
156 }
157 return false;
158 }
159
160 // hook into Unity runtime to actually add our custom functions
161 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
162 static void RuntimeInitializeOnLoad()
163 {
164 //Debug.Log("Mirror: adding Network[Early/Late]Update to Unity...");
165
166 // get loop
167 // 2019 has GetCURRENTPlayerLoop which is safe to use without
168 // breaking other custom system's custom loops.
169 // see also: https://github.com/vis2k/Mirror/pull/2627/files
170 PlayerLoopSystem playerLoop =
171#if UNITY_2019_3_OR_NEWER
172 PlayerLoop.GetCurrentPlayerLoop();
173#else
174 PlayerLoop.GetDefaultPlayerLoop();
175#endif
176
177 // add NetworkEarlyUpdate to the end of EarlyUpdate so it runs after
178 // any Unity initializations but before the first Update/FixedUpdate
179 AddToPlayerLoop(NetworkEarlyUpdate, typeof(NetworkLoop), ref playerLoop, typeof(EarlyUpdate), AddMode.End);
180
181 // add NetworkLateUpdate to the end of PreLateUpdate so it runs after
182 // LateUpdate(). adding to the beginning of PostLateUpdate doesn't
183 // actually work.
184 AddToPlayerLoop(NetworkLateUpdate, typeof(NetworkLoop), ref playerLoop, typeof(PreLateUpdate), AddMode.End);
185
186 // set the new loop
187 PlayerLoop.SetPlayerLoop(playerLoop);
188 }
189
190 static void NetworkEarlyUpdate()
191 {
192 //Debug.Log($"NetworkEarlyUpdate {Time.time}");
193 NetworkServer.NetworkEarlyUpdate();
194 NetworkClient.NetworkEarlyUpdate();
195 // invoke event after mirror has done it's early updating.
196 OnEarlyUpdate?.Invoke();
197 }
198
199 static void NetworkLateUpdate()
200 {
201 //Debug.Log($"NetworkLateUpdate {Time.time}");
202 // invoke event before mirror does its final late updating.
203 OnLateUpdate?.Invoke();
204 NetworkServer.NetworkLateUpdate();
205 NetworkClient.NetworkLateUpdate();
206 }
207 }
208}
NetworkClient with connection to server.
NetworkServer handles remote connections and has a local connection for a local client.