NetworkBehaviour.cs 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using Mirror.RemoteCalls;
  5. using UnityEngine;
  6. namespace Mirror
  7. {
  8. public enum SyncMode { Observers, Owner }
  9. /// <summary>Base class for networked components.</summary>
  10. [AddComponentMenu("")]
  11. [RequireComponent(typeof(NetworkIdentity))]
  12. [HelpURL("https://mirror-networking.gitbook.io/docs/guides/networkbehaviour")]
  13. public abstract class NetworkBehaviour : MonoBehaviour
  14. {
  15. /// <summary>sync mode for OnSerialize</summary>
  16. // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
  17. [Tooltip("By default synced data is sent from the server to all Observers of the object.\nChange this to Owner to only have the server update the client that has ownership authority for this object")]
  18. [HideInInspector] public SyncMode syncMode = SyncMode.Observers;
  19. /// <summary>sync interval for OnSerialize (in seconds)</summary>
  20. // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
  21. // [0,2] should be enough. anything >2s is too laggy anyway.
  22. [Tooltip("Time in seconds until next change is synchronized to the client. '0' means send immediately if changed. '0.5' means only send changes every 500ms.\n(This is for state synchronization like SyncVars, SyncLists, OnSerialize. Not for Cmds, Rpcs, etc.)")]
  23. [Range(0, 2)]
  24. [HideInInspector] public float syncInterval = 0.1f;
  25. internal double lastSyncTime;
  26. /// <summary>True if this object is on the server and has been spawned.</summary>
  27. // This is different from NetworkServer.active, which is true if the
  28. // server itself is active rather than this object being active.
  29. public bool isServer => netIdentity.isServer;
  30. /// <summary>True if this object is on the client and has been spawned by the server.</summary>
  31. public bool isClient => netIdentity.isClient;
  32. /// <summary>True if this object is the the client's own local player.</summary>
  33. public bool isLocalPlayer => netIdentity.isLocalPlayer;
  34. /// <summary>True if this object is on the server-only, not host.</summary>
  35. public bool isServerOnly => netIdentity.isServerOnly;
  36. /// <summary>True if this object is on the client-only, not host.</summary>
  37. public bool isClientOnly => netIdentity.isClientOnly;
  38. /// <summary>This returns true if this object is the authoritative version of the object in the distributed network application.</summary>
  39. // keeping this ridiculous summary as a reminder of a time long gone...
  40. public bool hasAuthority => netIdentity.hasAuthority;
  41. /// <summary>The unique network Id of this object (unique at runtime).</summary>
  42. public uint netId => netIdentity.netId;
  43. /// <summary>Client's network connection to the server. This is only valid for player objects on the client.</summary>
  44. public NetworkConnection connectionToServer => netIdentity.connectionToServer;
  45. /// <summary>Server's network connection to the client. This is only valid for player objects on the server.</summary>
  46. public NetworkConnection connectionToClient => netIdentity.connectionToClient;
  47. // SyncLists, SyncSets, etc.
  48. protected readonly List<SyncObject> syncObjects = new List<SyncObject>();
  49. // NetworkBehaviourInspector needs to know if we have SyncObjects
  50. internal bool HasSyncObjects() => syncObjects.Count > 0;
  51. // NetworkIdentity based values set from NetworkIdentity.Awake(),
  52. // which is way more simple and way faster than trying to figure out
  53. // component index from in here by searching all NetworkComponents.
  54. /// <summary>Returns the NetworkIdentity of this object</summary>
  55. public NetworkIdentity netIdentity { get; internal set; }
  56. /// <summary>Returns the index of the component on this object</summary>
  57. public int ComponentIndex { get; internal set; }
  58. // to avoid fully serializing entities every time, we have two options:
  59. // * run a delta compression algorithm
  60. // -> for fixed size types this is as easy as varint(b-a) for all
  61. // -> for dynamically sized types like strings this is not easy.
  62. // algorithms need to detect inserts/deletions, i.e. Myers Diff.
  63. // those are very cpu intensive and barely fast enough for large
  64. // scale multiplayer games (in Unity)
  65. // * or we use dirty bits as meta data about which fields have changed
  66. // -> spares us from running delta algorithms
  67. // -> still supports dynamically sized types
  68. //
  69. // 64 bit mask, tracking up to 64 SyncVars.
  70. protected ulong syncVarDirtyBits { get; private set; }
  71. // 64 bit mask, tracking up to 64 sync collections (internal for tests).
  72. // internal for tests, field for faster access (instead of property)
  73. // TODO 64 SyncLists are too much. consider smaller mask later.
  74. internal ulong syncObjectDirtyBits;
  75. // Weaver replaces '[SyncVar] int health' with 'Networkhealth' property.
  76. // setter calls the hook if value changed.
  77. // if we then modify the [SyncVar] from inside the setter,
  78. // the setter would call the hook and we deadlock.
  79. // hook guard prevents that.
  80. ulong syncVarHookGuard;
  81. // USED BY WEAVER to set syncvars in host mode without deadlocking
  82. protected bool GetSyncVarHookGuard(ulong dirtyBit) =>
  83. (syncVarHookGuard & dirtyBit) != 0UL;
  84. // Deprecated 2021-09-16 (old weavers used it)
  85. [Obsolete("Renamed to GetSyncVarHookGuard (uppercase)")]
  86. protected bool getSyncVarHookGuard(ulong dirtyBit) => GetSyncVarHookGuard(dirtyBit);
  87. // USED BY WEAVER to set syncvars in host mode without deadlocking
  88. protected void SetSyncVarHookGuard(ulong dirtyBit, bool value)
  89. {
  90. // set the bit
  91. if (value)
  92. syncVarHookGuard |= dirtyBit;
  93. // clear the bit
  94. else
  95. syncVarHookGuard &= ~dirtyBit;
  96. }
  97. // Deprecated 2021-09-16 (old weavers used it)
  98. [Obsolete("Renamed to SetSyncVarHookGuard (uppercase)")]
  99. protected void setSyncVarHookGuard(ulong dirtyBit, bool value) => SetSyncVarHookGuard(dirtyBit, value);
  100. /// <summary>Set as dirty so that it's synced to clients again.</summary>
  101. // these are masks, not bit numbers, ie. 110011b not '2' for 2nd bit.
  102. public void SetSyncVarDirtyBit(ulong dirtyBit)
  103. {
  104. syncVarDirtyBits |= dirtyBit;
  105. }
  106. // Deprecated 2021-09-19
  107. [Obsolete("SetDirtyBit was renamed to SetSyncVarDirtyBit because that's what it does")]
  108. public void SetDirtyBit(ulong dirtyBit) => SetSyncVarDirtyBit(dirtyBit);
  109. // true if syncInterval elapsed and any SyncVar or SyncObject is dirty
  110. public bool IsDirty()
  111. {
  112. if (NetworkTime.localTime - lastSyncTime >= syncInterval)
  113. {
  114. // OR both bitmasks. != 0 if either was dirty.
  115. return (syncVarDirtyBits | syncObjectDirtyBits) != 0UL;
  116. }
  117. return false;
  118. }
  119. /// <summary>Clears all the dirty bits that were set by SetDirtyBits()</summary>
  120. // automatically invoked when an update is sent for this object, but can
  121. // be called manually as well.
  122. public void ClearAllDirtyBits()
  123. {
  124. lastSyncTime = NetworkTime.localTime;
  125. syncVarDirtyBits = 0L;
  126. syncObjectDirtyBits = 0L;
  127. // clear all unsynchronized changes in syncobjects
  128. // (Linq allocates, use for instead)
  129. for (int i = 0; i < syncObjects.Count; ++i)
  130. {
  131. syncObjects[i].ClearChanges();
  132. }
  133. }
  134. // this gets called in the constructor by the weaver
  135. // for every SyncObject in the component (e.g. SyncLists).
  136. // We collect all of them and we synchronize them with OnSerialize/OnDeserialize
  137. protected void InitSyncObject(SyncObject syncObject)
  138. {
  139. if (syncObject == null)
  140. {
  141. Debug.LogError("Uninitialized SyncObject. Manually call the constructor on your SyncList, SyncSet or SyncDictionary");
  142. return;
  143. }
  144. // add it, remember the index in list (if Count=0, index=0 etc.)
  145. int index = syncObjects.Count;
  146. syncObjects.Add(syncObject);
  147. // OnDirty needs to set nth bit in our dirty mask
  148. ulong nthBit = 1UL << index;
  149. syncObject.OnDirty = () => syncObjectDirtyBits |= nthBit;
  150. // only record changes while we have observers.
  151. // prevents ever growing .changes lists:
  152. // if a monster has no observers but we keep modifing a SyncObject,
  153. // then the changes would never be flushed and keep growing,
  154. // because OnSerialize isn't called without observers.
  155. syncObject.IsRecording = () => netIdentity.observers?.Count > 0;
  156. }
  157. protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId, bool requiresAuthority = true)
  158. {
  159. // this was in Weaver before
  160. // NOTE: we could remove this later to allow calling Cmds on Server
  161. // to avoid Wrapper functions. a lot of people requested this.
  162. if (!NetworkClient.active)
  163. {
  164. Debug.LogError($"Command Function {cmdName} called without an active client.");
  165. return;
  166. }
  167. // local players can always send commands, regardless of authority, other objects must have authority.
  168. if (!(!requiresAuthority || isLocalPlayer || hasAuthority))
  169. {
  170. Debug.LogWarning($"Trying to send command for object without authority. {invokeClass}.{cmdName}");
  171. return;
  172. }
  173. // previously we used NetworkClient.readyConnection.
  174. // now we check .ready separately and use .connection instead.
  175. if (!NetworkClient.ready)
  176. {
  177. Debug.LogError("Send command attempted while NetworkClient is not ready.");
  178. return;
  179. }
  180. // IMPORTANT: can't use .connectionToServer here because calling
  181. // a command on other objects is allowed if requireAuthority is
  182. // false. other objects don't have a .connectionToServer.
  183. // => so we always need to use NetworkClient.connection instead.
  184. // => see also: https://github.com/vis2k/Mirror/issues/2629
  185. if (NetworkClient.connection == null)
  186. {
  187. Debug.LogError("Send command attempted with no client running.");
  188. return;
  189. }
  190. // construct the message
  191. CommandMessage message = new CommandMessage
  192. {
  193. netId = netId,
  194. componentIndex = (byte)ComponentIndex,
  195. // type+func so Inventory.RpcUse != Equipment.RpcUse
  196. functionHash = RemoteCallHelper.GetMethodHash(invokeClass, cmdName),
  197. // segment to avoid reader allocations
  198. payload = writer.ToArraySegment()
  199. };
  200. // IMPORTANT: can't use .connectionToServer here because calling
  201. // a command on other objects is allowed if requireAuthority is
  202. // false. other objects don't have a .connectionToServer.
  203. // => so we always need to use NetworkClient.connection instead.
  204. // => see also: https://github.com/vis2k/Mirror/issues/2629
  205. NetworkClient.connection.Send(message, channelId);
  206. }
  207. protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter writer, int channelId, bool includeOwner)
  208. {
  209. // this was in Weaver before
  210. if (!NetworkServer.active)
  211. {
  212. Debug.LogError($"RPC Function {rpcName} called on Client.");
  213. return;
  214. }
  215. // This cannot use NetworkServer.active, as that is not specific to this object.
  216. if (!isServer)
  217. {
  218. Debug.LogWarning($"ClientRpc {rpcName} called on un-spawned object: {name}");
  219. return;
  220. }
  221. // construct the message
  222. RpcMessage message = new RpcMessage
  223. {
  224. netId = netId,
  225. componentIndex = (byte)ComponentIndex,
  226. // type+func so Inventory.RpcUse != Equipment.RpcUse
  227. functionHash = RemoteCallHelper.GetMethodHash(invokeClass, rpcName),
  228. // segment to avoid reader allocations
  229. payload = writer.ToArraySegment()
  230. };
  231. NetworkServer.SendToReadyObservers(netIdentity, message, includeOwner, channelId);
  232. }
  233. protected void SendTargetRPCInternal(NetworkConnection conn, Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
  234. {
  235. if (!NetworkServer.active)
  236. {
  237. Debug.LogError($"TargetRPC {rpcName} called when server not active");
  238. return;
  239. }
  240. if (!isServer)
  241. {
  242. Debug.LogWarning($"TargetRpc {rpcName} called on {name} but that object has not been spawned or has been unspawned");
  243. return;
  244. }
  245. // connection parameter is optional. assign if null.
  246. if (conn is null)
  247. {
  248. conn = connectionToClient;
  249. }
  250. // if still null
  251. if (conn is null)
  252. {
  253. Debug.LogError($"TargetRPC {rpcName} was given a null connection, make sure the object has an owner or you pass in the target connection");
  254. return;
  255. }
  256. if (!(conn is NetworkConnectionToClient))
  257. {
  258. Debug.LogError($"TargetRPC {rpcName} requires a NetworkConnectionToClient but was given {conn.GetType().Name}");
  259. return;
  260. }
  261. // construct the message
  262. RpcMessage message = new RpcMessage
  263. {
  264. netId = netId,
  265. componentIndex = (byte)ComponentIndex,
  266. // type+func so Inventory.RpcUse != Equipment.RpcUse
  267. functionHash = RemoteCallHelper.GetMethodHash(invokeClass, rpcName),
  268. // segment to avoid reader allocations
  269. payload = writer.ToArraySegment()
  270. };
  271. conn.Send(message, channelId);
  272. }
  273. // helper function for [SyncVar] GameObjects.
  274. // needs to be public so that tests & NetworkBehaviours from other
  275. // assemblies both find it
  276. [EditorBrowsable(EditorBrowsableState.Never)]
  277. public static bool SyncVarGameObjectEqual(GameObject newGameObject, uint netIdField)
  278. {
  279. uint newNetId = 0;
  280. if (newGameObject != null)
  281. {
  282. NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
  283. if (identity != null)
  284. {
  285. newNetId = identity.netId;
  286. if (newNetId == 0)
  287. {
  288. Debug.LogWarning($"SetSyncVarGameObject GameObject {newGameObject} has a zero netId. Maybe it is not spawned yet?");
  289. }
  290. }
  291. }
  292. return newNetId == netIdField;
  293. }
  294. // helper function for [SyncVar] GameObjects.
  295. // dirtyBit is a mask like 00010
  296. protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
  297. {
  298. if (GetSyncVarHookGuard(dirtyBit))
  299. return;
  300. uint newNetId = 0;
  301. if (newGameObject != null)
  302. {
  303. NetworkIdentity identity = newGameObject.GetComponent<NetworkIdentity>();
  304. if (identity != null)
  305. {
  306. newNetId = identity.netId;
  307. if (newNetId == 0)
  308. {
  309. Debug.LogWarning($"SetSyncVarGameObject GameObject {newGameObject} has a zero netId. Maybe it is not spawned yet?");
  310. }
  311. }
  312. }
  313. //Debug.Log($"SetSyncVar GameObject {GetType().Name} bit:{dirtyBit} netfieldId:{netIdField} -> {newNetId}");
  314. SetSyncVarDirtyBit(dirtyBit);
  315. // assign new one on the server, and in case we ever need it on client too
  316. gameObjectField = newGameObject;
  317. netIdField = newNetId;
  318. }
  319. // helper function for [SyncVar] GameObjects.
  320. // -> ref GameObject as second argument makes OnDeserialize processing easier
  321. protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
  322. {
  323. // server always uses the field
  324. if (isServer)
  325. {
  326. return gameObjectField;
  327. }
  328. // client always looks up based on netId because objects might get in and out of range
  329. // over and over again, which shouldn't null them forever
  330. if (NetworkClient.spawned.TryGetValue(netId, out NetworkIdentity identity) && identity != null)
  331. return gameObjectField = identity.gameObject;
  332. return null;
  333. }
  334. // helper function for [SyncVar] NetworkIdentities.
  335. // needs to be public so that tests & NetworkBehaviours from other
  336. // assemblies both find it
  337. [EditorBrowsable(EditorBrowsableState.Never)]
  338. public static bool SyncVarNetworkIdentityEqual(NetworkIdentity newIdentity, uint netIdField)
  339. {
  340. uint newNetId = 0;
  341. if (newIdentity != null)
  342. {
  343. newNetId = newIdentity.netId;
  344. if (newNetId == 0)
  345. {
  346. Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newIdentity} has a zero netId. Maybe it is not spawned yet?");
  347. }
  348. }
  349. // netId changed?
  350. return newNetId == netIdField;
  351. }
  352. // helper function for [SyncVar] NetworkIdentities.
  353. // dirtyBit is a mask like 00010
  354. protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
  355. {
  356. if (GetSyncVarHookGuard(dirtyBit))
  357. return;
  358. uint newNetId = 0;
  359. if (newIdentity != null)
  360. {
  361. newNetId = newIdentity.netId;
  362. if (newNetId == 0)
  363. {
  364. Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newIdentity} has a zero netId. Maybe it is not spawned yet?");
  365. }
  366. }
  367. //Debug.Log($"SetSyncVarNetworkIdentity NetworkIdentity {GetType().Name} bit:{dirtyBit} netIdField:{netIdField} -> {newNetId}");
  368. SetSyncVarDirtyBit(dirtyBit);
  369. netIdField = newNetId;
  370. // assign new one on the server, and in case we ever need it on client too
  371. identityField = newIdentity;
  372. }
  373. // helper function for [SyncVar] NetworkIdentities.
  374. // -> ref GameObject as second argument makes OnDeserialize processing easier
  375. protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
  376. {
  377. // server always uses the field
  378. if (isServer)
  379. {
  380. return identityField;
  381. }
  382. // client always looks up based on netId because objects might get in and out of range
  383. // over and over again, which shouldn't null them forever
  384. NetworkClient.spawned.TryGetValue(netId, out identityField);
  385. return identityField;
  386. }
  387. protected static bool SyncVarNetworkBehaviourEqual<T>(T newBehaviour, NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
  388. {
  389. uint newNetId = 0;
  390. int newComponentIndex = 0;
  391. if (newBehaviour != null)
  392. {
  393. newNetId = newBehaviour.netId;
  394. newComponentIndex = newBehaviour.ComponentIndex;
  395. if (newNetId == 0)
  396. {
  397. Debug.LogWarning($"SetSyncVarNetworkIdentity NetworkIdentity {newBehaviour} has a zero netId. Maybe it is not spawned yet?");
  398. }
  399. }
  400. // netId changed?
  401. return syncField.Equals(newNetId, newComponentIndex);
  402. }
  403. // helper function for [SyncVar] NetworkIdentities.
  404. // dirtyBit is a mask like 00010
  405. protected void SetSyncVarNetworkBehaviour<T>(T newBehaviour, ref T behaviourField, ulong dirtyBit, ref NetworkBehaviourSyncVar syncField) where T : NetworkBehaviour
  406. {
  407. if (GetSyncVarHookGuard(dirtyBit))
  408. return;
  409. uint newNetId = 0;
  410. int componentIndex = 0;
  411. if (newBehaviour != null)
  412. {
  413. newNetId = newBehaviour.netId;
  414. componentIndex = newBehaviour.ComponentIndex;
  415. if (newNetId == 0)
  416. {
  417. Debug.LogWarning($"{nameof(SetSyncVarNetworkBehaviour)} NetworkIdentity {newBehaviour} has a zero netId. Maybe it is not spawned yet?");
  418. }
  419. }
  420. syncField = new NetworkBehaviourSyncVar(newNetId, componentIndex);
  421. SetSyncVarDirtyBit(dirtyBit);
  422. // assign new one on the server, and in case we ever need it on client too
  423. behaviourField = newBehaviour;
  424. // Debug.Log($"SetSyncVarNetworkBehaviour NetworkIdentity {GetType().Name} bit [{dirtyBit}] netIdField:{oldField}->{syncField}");
  425. }
  426. // helper function for [SyncVar] NetworkIdentities.
  427. // -> ref GameObject as second argument makes OnDeserialize processing easier
  428. protected T GetSyncVarNetworkBehaviour<T>(NetworkBehaviourSyncVar syncNetBehaviour, ref T behaviourField) where T : NetworkBehaviour
  429. {
  430. // server always uses the field
  431. if (isServer)
  432. {
  433. return behaviourField;
  434. }
  435. // client always looks up based on netId because objects might get in and out of range
  436. // over and over again, which shouldn't null them forever
  437. if (!NetworkClient.spawned.TryGetValue(syncNetBehaviour.netId, out NetworkIdentity identity))
  438. {
  439. return null;
  440. }
  441. behaviourField = identity.NetworkBehaviours[syncNetBehaviour.componentIndex] as T;
  442. return behaviourField;
  443. }
  444. // backing field for sync NetworkBehaviour
  445. public struct NetworkBehaviourSyncVar : IEquatable<NetworkBehaviourSyncVar>
  446. {
  447. public uint netId;
  448. // limited to 255 behaviours per identity
  449. public byte componentIndex;
  450. public NetworkBehaviourSyncVar(uint netId, int componentIndex) : this()
  451. {
  452. this.netId = netId;
  453. this.componentIndex = (byte)componentIndex;
  454. }
  455. public bool Equals(NetworkBehaviourSyncVar other)
  456. {
  457. return other.netId == netId && other.componentIndex == componentIndex;
  458. }
  459. public bool Equals(uint netId, int componentIndex)
  460. {
  461. return this.netId == netId && this.componentIndex == componentIndex;
  462. }
  463. public override string ToString()
  464. {
  465. return $"[netId:{netId} compIndex:{componentIndex}]";
  466. }
  467. }
  468. protected static bool SyncVarEqual<T>(T value, ref T fieldValue)
  469. {
  470. // newly initialized or changed value?
  471. // value.Equals(fieldValue) allocates without 'where T : IEquatable'
  472. // seems like we use EqualityComparer to avoid allocations,
  473. // because not all SyncVars<T> are IEquatable
  474. return EqualityComparer<T>.Default.Equals(value, fieldValue);
  475. }
  476. // dirtyBit is a mask like 00010
  477. protected void SetSyncVar<T>(T value, ref T fieldValue, ulong dirtyBit)
  478. {
  479. //Debug.Log($"SetSyncVar {GetType().Name} bit:{dirtyBit} fieldValue:{value}");
  480. SetSyncVarDirtyBit(dirtyBit);
  481. fieldValue = value;
  482. }
  483. /// <summary>Override to do custom serialization (instead of SyncVars/SyncLists). Use OnDeserialize too.</summary>
  484. // if a class has syncvars, then OnSerialize/OnDeserialize are added
  485. // automatically.
  486. //
  487. // initialState is true for full spawns, false for delta syncs.
  488. // note: SyncVar hooks are only called when inital=false
  489. public virtual bool OnSerialize(NetworkWriter writer, bool initialState)
  490. {
  491. bool objectWritten = false;
  492. // if initialState: write all SyncVars.
  493. // otherwise write dirtyBits+dirty SyncVars
  494. if (initialState)
  495. {
  496. objectWritten = SerializeObjectsAll(writer);
  497. }
  498. else
  499. {
  500. objectWritten = SerializeObjectsDelta(writer);
  501. }
  502. bool syncVarWritten = SerializeSyncVars(writer, initialState);
  503. return objectWritten || syncVarWritten;
  504. }
  505. /// <summary>Override to do custom deserialization (instead of SyncVars/SyncLists). Use OnSerialize too.</summary>
  506. public virtual void OnDeserialize(NetworkReader reader, bool initialState)
  507. {
  508. if (initialState)
  509. {
  510. DeSerializeObjectsAll(reader);
  511. }
  512. else
  513. {
  514. DeSerializeObjectsDelta(reader);
  515. }
  516. DeserializeSyncVars(reader, initialState);
  517. }
  518. // USED BY WEAVER
  519. protected virtual bool SerializeSyncVars(NetworkWriter writer, bool initialState)
  520. {
  521. return false;
  522. // SyncVar are written here in subclass
  523. // if initialState
  524. // write all SyncVars
  525. // else
  526. // write syncVarDirtyBits
  527. // write dirty SyncVars
  528. }
  529. // USED BY WEAVER
  530. protected virtual void DeserializeSyncVars(NetworkReader reader, bool initialState)
  531. {
  532. // SyncVars are read here in subclass
  533. // if initialState
  534. // read all SyncVars
  535. // else
  536. // read syncVarDirtyBits
  537. // read dirty SyncVars
  538. }
  539. public bool SerializeObjectsAll(NetworkWriter writer)
  540. {
  541. bool dirty = false;
  542. for (int i = 0; i < syncObjects.Count; i++)
  543. {
  544. SyncObject syncObject = syncObjects[i];
  545. syncObject.OnSerializeAll(writer);
  546. dirty = true;
  547. }
  548. return dirty;
  549. }
  550. public bool SerializeObjectsDelta(NetworkWriter writer)
  551. {
  552. bool dirty = false;
  553. // write the mask
  554. writer.WriteULong(syncObjectDirtyBits);
  555. // serializable objects, such as synclists
  556. for (int i = 0; i < syncObjects.Count; i++)
  557. {
  558. // check dirty mask at nth bit
  559. SyncObject syncObject = syncObjects[i];
  560. if ((syncObjectDirtyBits & (1UL << i)) != 0)
  561. {
  562. syncObject.OnSerializeDelta(writer);
  563. dirty = true;
  564. }
  565. }
  566. return dirty;
  567. }
  568. internal void DeSerializeObjectsAll(NetworkReader reader)
  569. {
  570. for (int i = 0; i < syncObjects.Count; i++)
  571. {
  572. SyncObject syncObject = syncObjects[i];
  573. syncObject.OnDeserializeAll(reader);
  574. }
  575. }
  576. internal void DeSerializeObjectsDelta(NetworkReader reader)
  577. {
  578. ulong dirty = reader.ReadULong();
  579. for (int i = 0; i < syncObjects.Count; i++)
  580. {
  581. // check dirty mask at nth bit
  582. SyncObject syncObject = syncObjects[i];
  583. if ((dirty & (1UL << i)) != 0)
  584. {
  585. syncObject.OnDeserializeDelta(reader);
  586. }
  587. }
  588. }
  589. internal void ResetSyncObjects()
  590. {
  591. foreach (SyncObject syncObject in syncObjects)
  592. {
  593. syncObject.Reset();
  594. }
  595. }
  596. /// <summary>Like Start(), but only called on server and host.</summary>
  597. public virtual void OnStartServer() {}
  598. /// <summary>Stop event, only called on server and host.</summary>
  599. public virtual void OnStopServer() {}
  600. /// <summary>Like Start(), but only called on client and host.</summary>
  601. public virtual void OnStartClient() {}
  602. /// <summary>Stop event, only called on client and host.</summary>
  603. public virtual void OnStopClient() {}
  604. /// <summary>Like Start(), but only called on client and host for the local player object.</summary>
  605. public virtual void OnStartLocalPlayer() {}
  606. /// <summary>Like Start(), but only called for objects the client has authority over.</summary>
  607. public virtual void OnStartAuthority() {}
  608. /// <summary>Stop event, only called for objects the client has authority over.</summary>
  609. public virtual void OnStopAuthority() {}
  610. }
  611. }