context: using System;
  using System.Collections.Generic;
  using System.ComponentModel;
  using System.Security.Cryptography;
  using UnityEngine;
  using UnityEngine.Serialization;
  #if UNITY_EDITOR
  using UnityEditor;
  #if UNITY_2018_3_OR_NEWER
  using UnityEditor.Experimental.SceneManagement;
  #endif
  #endif
  
  namespace Mirror
  {
      /// <summary>
      /// The NetworkIdentity identifies objects across the network, between server and clients. Its primary data is a NetworkInstanceId which is allocated by the server and then set on clients. This is used in network communications to be able to lookup game objects on different machines.
      /// </summary>
      /// <remarks>
      /// <para>The NetworkIdentity is used to synchronize information in the object with the network. Only the server should create instances of objects which have NetworkIdentity as otherwise they will not be properly connected to the system.</para>
      /// <para>For complex objects with a hierarchy of subcomponents, the NetworkIdentity must be on the root of the hierarchy. It is not supported to have multiple NetworkIdentity components on subcomponents of a hierarchy.</para>
      /// <para>NetworkBehaviour scripts require a NetworkIdentity on the game object to be able to function.</para>
      /// <para>The NetworkIdentity manages the dirty state of the NetworkBehaviours of the object. When it discovers that NetworkBehaviours are dirty, it causes an update packet to be created and sent to clients.</para>
      /// <para>The flow for serialization updates managed by the NetworkIdentity is:</para>
      /// <para>* Each NetworkBehaviour has a dirty mask. This mask is available inside OnSerialize as syncVarDirtyBits</para>
      /// <para>* Each SyncVar in a NetworkBehaviour script is assigned a bit in the dirty mask.</para>
      /// <para>* Changing the value of SyncVars causes the bit for that SyncVar to be set in the dirty mask</para>
      /// <para>* Alternatively, calling SetDirtyBit() writes directly to the dirty mask</para>
      /// <para>* NetworkIdentity objects are checked on the server as part of it&apos;s update loop</para>
      /// <para>* If any NetworkBehaviours on a NetworkIdentity are dirty, then an UpdateVars packet is created for that object</para>
      /// <para>* The UpdateVars packet is populated by calling OnSerialize on each NetworkBehaviour on the object</para>
      /// <para>* NetworkBehaviours that are NOT dirty write a zero to the packet for their dirty bits</para>
      /// <para>* NetworkBehaviours that are dirty write their dirty mask, then the values for the SyncVars that have changed</para>
      /// <para>* If OnSerialize returns true for a NetworkBehaviour, the dirty mask is reset for that NetworkBehaviour, so it will not send again until its value changes.</para>
      /// <para>* The UpdateVars packet is sent to ready clients that are observing the object</para>
      /// <para>On the client:</para>
      /// <para>* an UpdateVars packet is received for an object</para>
      /// <para>* The OnDeserialize function is called for each NetworkBehaviour script on the object</para>
      /// <para>* Each NetworkBehaviour script on the object reads a dirty mask.</para>
      /// <para>* If the dirty mask for a NetworkBehaviour is zero, the OnDeserialize functions returns without reading any more</para>
      /// <para>* If the dirty mask is non-zero value, then the OnDeserialize function reads the values for the SyncVars that correspond to the dirty bits that are set</para>
      /// <para>* If there are SyncVar hook functions, those are invoked with the value read from the stream.</para>
      /// </remarks>
      [ExecuteInEditMode]
      [DisallowMultipleComponent]
      [AddComponentMenu("Network/NetworkIdentity")]
      [HelpURL("https://mirror-networking.com/docs/Components/NetworkIdentity.html")]
      public sealed class NetworkIdentity : MonoBehaviour
      {
  	    public bool isDirty;
          // configuration
          NetworkBehaviour[] networkBehavioursCache;
  
          // member used to mark a identity for future reset
          // check MarkForReset for more information.
          bool reset;
  
          /// <summary>
          /// Returns true if running as a client and this object was spawned by a server.
          /// </summary>
          //
          // IMPORTANT: checking NetworkClient.active means that isClient is false in OnDestroy:
          //   public bool isClient => NetworkClient.active && netId != 0 && !serverOnly;
          // but we need it in OnDestroy, e.g. when saving skillbars on quit. this
          // works fine if we keep the UNET way of setting isClient manually.
          // => fixes https://github.com/vis2k/Mirror/issues/1475
          public bool isClient { get; internal set; }
  
          /// <summary>
          /// Returns true if NetworkServer.active and server is not stopped.
          /// </summary>
          //
          // IMPORTANT: checking NetworkServer.active means that isServer is false in OnDestroy:
          //   public bool isServer => NetworkServer.active && netId != 0;
          // but we need it in OnDestroy, e.g. when saving players on quit. this
          // works fine if we keep the UNET way of setting isServer manually.
          // => fixes https://github.com/vis2k/Mirror/issues/1484
          public bool isServer { get; internal set; }
  
          /// <summary>
          /// This returns true if this object is the one that represents the player on the local machine.
          /// <para>This is set when the server has spawned an object for this particular client.</para>
          /// </summary>
          public bool isLocalPlayer => ClientScene.localPlayer == this;
  
          /// <summary>
          /// This returns true if this object is the authoritative player object on the client.
          /// <para>This value is determined at runtime. For most objects, authority is held by the server.</para>
          /// <para>For objects that had their authority set by AssignClientAuthority on the server, this will be true on the client that owns the object. NOT on other clients.</para>
          /// </summary>
          public bool hasAuthority { get; internal set; }
  
          /// <summary>
          /// The set of network connections (players) that can see this object.
          /// <para>null until OnStartServer was called. this is necessary for SendTo* to work properly in server-only mode.</para>
          /// </summary>
          public Dictionary<int, NetworkConnection> observers;
  
          /// <summary>
          /// Unique identifier for this particular object instance, used for tracking objects between networked clients and the server.
          /// <para>This is a unique identifier for this particular GameObject instance. Use it to track GameObjects between networked clients and the server.</para>
          /// </summary>
          public uint netId { get; internal set; }
  
          /// <summary>
          /// A unique identifier for NetworkIdentity objects within a scene.
          /// <para>This is used for spawning scene objects on clients.</para>
          /// </summary>
          // persistent scene id <sceneHash/32,sceneId/32> (see AssignSceneID comments)
          [FormerlySerializedAs("m_SceneId"), HideInInspector]
          public ulong sceneId;
  
          /// <summary>
          /// Flag to make this object only exist when the game is running as a server (or host).
          /// </summary>
          [FormerlySerializedAs("m_ServerOnly")]
          public bool serverOnly;
  
          /// <summary>
          /// The NetworkConnection associated with this NetworkIdentity. This is only valid for player objects on a local client.
          /// </summary>
          public NetworkConnection connectionToServer { get; internal set; }
  
          NetworkConnectionToClient _connectionToClient;
  
          /// <summary>
          /// The NetworkConnection associated with this <see cref="NetworkIdentity">NetworkIdentity.</see> This is valid for player and other owned objects in the server.
          /// <para>Use it to return details such as the connection&apos;s identity, IP address and ready status.</para>
          /// </summary>
          public NetworkConnectionToClient connectionToClient
          {
              get => _connectionToClient;
  
              internal set
              {
                  if (_connectionToClient != null)
                      _connectionToClient.RemoveOwnedObject(this);
  
                  _connectionToClient = value;
                  _connectionToClient?.AddOwnedObject(this);
              }
          }
  
          /// <summary>
          /// All spawned NetworkIdentities by netId. Available on server and client.
          /// </summary>
          public static readonly Dictionary<uint, NetworkIdentity> spawned = new Dictionary<uint, NetworkIdentity>();
  
          public NetworkBehaviour[] NetworkBehaviours => networkBehavioursCache = networkBehavioursCache ?? GetComponents<NetworkBehaviour>();
  
          [SerializeField, HideInInspector] string m_AssetId;
  
          // the AssetId trick:
          // - ideally we would have a serialized 'Guid m_AssetId' but Unity can't
          //   serialize it because Guid's internal bytes are private
          // - UNET used 'NetworkHash128' originally, with byte0, ..., byte16
          //   which works, but it just unnecessary extra code
          // - using just the Guid string would work, but it's 32 chars long and
          //   would then be sent over the network as 64 instead of 16 bytes
          // -> the solution is to serialize the string internally here and then
          //    use the real 'Guid' type for everything else via .assetId
          /// <summary>
          /// Unique identifier used to find the source assets when server spawns the on clients.
          /// </summary>
          public Guid assetId
          {
              get
              {
  #if UNITY_EDITOR
                  // This is important because sometimes OnValidate does not run (like when adding view to prefab with no child links)
                  if (string.IsNullOrEmpty(m_AssetId))
                      SetupIDs();
  #endif
                  // convert string to Guid and use .Empty to avoid exception if
                  // we would use 'new Guid("")'
                  return string.IsNullOrEmpty(m_AssetId) ? Guid.Empty : new Guid(m_AssetId);
              }
              internal set
              {
                  string newAssetIdString = value.ToString("N");
                  if (string.IsNullOrEmpty(m_AssetId) || m_AssetId == newAssetIdString)
                  {
                      m_AssetId = newAssetIdString;
                  }
                  else Debug.LogWarning($"SetDynamicAssetId object {name} already has an assetId {m_AssetId}, new asset id {newAssetIdString}");
              }
          }
  
          // keep track of all sceneIds to detect scene duplicates
          static readonly Dictionary<ulong, NetworkIdentity> sceneIds = new Dictionary<ulong, NetworkIdentity>();
  
          /// <summary>
          /// Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id
          /// </summary>
          /// <param name="id"></param>
          /// <returns>NetworkIdentity from the sceneIds dictionary</returns>
          public static NetworkIdentity GetSceneIdentity(ulong id) => sceneIds[id];
  
          // used when adding players
          internal void SetClientOwner(NetworkConnection conn)
          {
              // do nothing if it already has an owner
              if (connectionToClient != null && conn != connectionToClient)
              {
                  Debug.LogError($"Object {this} netId={netId} already has an owner. Use RemoveClientAuthority() first", this);
                  return;
              }
  
              // otherwise set the owner connection
              connectionToClient = (NetworkConnectionToClient)conn;
          }
  
          static uint nextNetworkId = 1;
          internal static uint GetNextNetworkId() => nextNetworkId++;
  
          /// <summary>
          /// Resets nextNetworkId = 1
          /// </summary>
          public static void ResetNextNetworkId() => nextNetworkId = 1;
  
          /// <summary>
          /// The delegate type for the clientAuthorityCallback.
          /// </summary>
          /// <param name="conn">The network connection that is gaining or losing authority.</param>
          /// <param name="identity">The object whose client authority status is being changed.</param>
          /// <param name="authorityState">The new state of client authority of the object for the connection.</param>
          public delegate void ClientAuthorityCallback(NetworkConnection conn, NetworkIdentity identity, bool authorityState);
  
          /// <summary>
          /// A callback that can be populated to be notified when the client-authority state of objects changes.
          /// <para>Whenever an object is spawned using SpawnWithClientAuthority, or the client authority status of an object is changed with AssignClientAuthority or RemoveClientAuthority, then this callback will be invoked.</para>
          /// <para>This callback is only invoked on the server.</para>
          /// </summary>
          public static ClientAuthorityCallback clientAuthorityCallback;
  
          // this is used when a connection is destroyed, since the "observers" property is read-only
          internal void RemoveObserverInternal(NetworkConnection conn)
          {
              observers?.Remove(conn.connectionId);
          }
  
          void Awake()
          {
              // detect runtime sceneId duplicates, e.g. if a user tries to
              // Instantiate a sceneId object at runtime. if we don't detect it,
              // then the client won't know which of the two objects to use for a
              // SpawnSceneObject message, and it's likely going to be the wrong
              // object.
              //
              // This might happen if for example we have a Dungeon GameObject
              // which contains a Skeleton monster as child, and when a player
              // runs into the Dungeon we create a Dungeon Instance of that
              // Dungeon, which would duplicate a scene object.
              //
              // see also: https://github.com/vis2k/Mirror/issues/384
              if (Application.isPlaying && sceneId != 0)
              {
                  if (sceneIds.TryGetValue(sceneId, out NetworkIdentity existing) && existing != this)
                  {
                      Debug.LogError(name + "'s sceneId: " + sceneId.ToString("X") + " is already taken by: " + existing.name + ". Don't call Instantiate for NetworkIdentities that were in the scene since the beginning (aka scene objects). Otherwise the client won't know which object to use for a SpawnSceneObject message.");
                      Destroy(gameObject);
                  }
                  else
                  {
                      sceneIds[sceneId] = this;
                  }
              }
          }
  
          void OnValidate()
          {
  #if UNITY_EDITOR
              SetupIDs();
  #endif
          }
  
  #if UNITY_EDITOR
          void AssignAssetID(GameObject prefab) => AssignAssetID(AssetDatabase.GetAssetPath(prefab));
          void AssignAssetID(string path) => m_AssetId = AssetDatabase.AssetPathToGUID(path);
  
          bool ThisIsAPrefab() => PrefabUtility.IsPartOfPrefabAsset(gameObject);
  
          bool ThisIsASceneObjectWithPrefabParent(out GameObject prefab)
          {
              prefab = null;
  
              if (!PrefabUtility.IsPartOfPrefabInstance(gameObject))
              {
                  return false;
              }
              prefab = PrefabUtility.GetCorrespondingObjectFromSource(gameObject);
  
              if (prefab == null)
              {
                  Debug.LogError("Failed to find prefab parent for scene object [name:" + gameObject.name + "]");
                  return false;
              }
              return true;
          }
  
          static uint GetRandomUInt()
          {
              // use Crypto RNG to avoid having time based duplicates
              using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
              {
                  byte[] bytes = new byte[4];
                  rng.GetBytes(bytes);
                  return BitConverter.ToUInt32(bytes, 0);
              }
          }
  
          // persistent sceneId assignment
          // (because scene objects have no persistent unique ID in Unity)
          //
          // original UNET used OnPostProcessScene to assign an index based on
          // FindObjectOfType<NetworkIdentity> order.
          // -> this didn't work because FindObjectOfType order isn't deterministic.
          // -> one workaround is to sort them by sibling paths, but it can still
          //    get out of sync when we open scene2 in editor and we have
          //    DontDestroyOnLoad objects that messed with the sibling index.
          //
          // we absolutely need a persistent id. challenges:
          // * it needs to be 0 for prefabs
          //   => we set it to 0 in SetupIDs() if prefab!
          // * it needs to be only assigned in edit time, not at runtime because
          //   only the objects that were in the scene since beginning should have
          //   a scene id.
          //   => Application.isPlaying check solves that
          // * it needs to detect duplicated sceneIds after duplicating scene
          //   objects
          //   => sceneIds dict takes care of that
          // * duplicating the whole scene file shouldn't result in duplicate
          //   scene objects
          //   => buildIndex is shifted into sceneId for that.
          //   => if we have no scenes in build index then it doesn't matter
          //      because by definition a build can't switch to other scenes
          //   => if we do have scenes in build index then it will be != -1
          //   note: the duplicated scene still needs to be opened once for it to
          //          be set properly
          // * scene objects need the correct scene index byte even if the scene's
          //   build index was changed or a duplicated scene wasn't opened yet.
          //   => OnPostProcessScene is the only function that gets called for
          //      each scene before runtime, so this is where we set the scene
          //      byte.
          // * disabled scenes in build settings should result in same scene index
          //   in editor and in build
          //   => .gameObject.scene.buildIndex filters out disabled scenes by
          //      default
          // * generated sceneIds absolutely need to set scene dirty and force the
          //   user to resave.
          //   => Undo.RecordObject does that perfectly.
          // * sceneIds should never be generated temporarily for unopened scenes
          //   when building, otherwise editor and build get out of sync
          //   => BuildPipeline.isBuildingPlayer check solves that
          public void AssignSceneID()
          {
              // we only ever assign sceneIds at edit time, never at runtime.
              // by definition, only the original scene objects should get one.
              // -> if we assign at runtime then server and client would generate
              //    different random numbers!
              if (Application.isPlaying)
                  return;
  
              // no valid sceneId yet, or duplicate?
              bool duplicate = sceneIds.TryGetValue(sceneId, out NetworkIdentity existing) && existing != null && existing != this;
              if (sceneId == 0 || duplicate)
              {
                  // clear in any case, because it might have been a duplicate
                  sceneId = 0;
  
                  // if a scene was never opened and we are building it, then a
                  // sceneId would be assigned to build but not saved in editor,
                  // resulting in them getting out of sync.
                  // => don't ever assign temporary ids. they always need to be
                  //    permanent
                  // => throw an exception to cancel the build and let the user
                  //    know how to fix it!
                  if (BuildPipeline.isBuildingPlayer)
                      throw new Exception("Scene " + gameObject.scene.path + " needs to be opened and resaved before building, because the scene object " + name + " has no valid sceneId yet.");
  
                  // if we generate the sceneId then we MUST be sure to set dirty
                  // in order to save the scene object properly. otherwise it
                  // would be regenerated every time we reopen the scene, and
                  // upgrading would be very difficult.
                  // -> Undo.RecordObject is the new EditorUtility.SetDirty!
                  // -> we need to call it before changing.
                  Undo.RecordObject(this, "Generated SceneId");
  
                  // generate random sceneId part (0x00000000FFFFFFFF)
                  uint randomId = GetRandomUInt();
  
                  // only assign if not a duplicate of an existing scene id
                  // (small chance, but possible)
                  duplicate = sceneIds.TryGetValue(randomId, out existing) && existing != null && existing != this;
                  if (!duplicate)
                  {
                      sceneId = randomId;
                      //Debug.Log(name + " in scene=" + gameObject.scene.name + " sceneId assigned to: " + m_SceneId.ToString("X"));
                  }
              }
  
              // add to sceneIds dict no matter what
              // -> even if we didn't generate anything new, because we still need
              //    existing sceneIds in there to check duplicates
              sceneIds[sceneId] = this;
          }
  
          // copy scene path hash into sceneId for scene objects.
          // this is the only way for scene file duplication to not contain
          // duplicate sceneIds as it seems.
          // -> sceneId before: 0x00000000AABBCCDD
          // -> then we clear the left 4 bytes, so that our 'OR' uses 0x00000000
          // -> then we OR the hash into the 0x00000000 part
          // -> buildIndex is not enough, because Editor and Build have different
          //    build indices if there are disabled scenes in build settings, and
          //    if no scene is in build settings then Editor and Build have
          //    different indices too (Editor=0, Build=-1)
          // => ONLY USE THIS FROM POSTPROCESSSCENE!
          [EditorBrowsable(EditorBrowsableState.Never)]
          public void SetSceneIdSceneHashPartInternal()
          {
              // get deterministic scene hash
              uint pathHash = (uint)gameObject.scene.path.GetStableHashCode();
  
              // shift hash from 0x000000FFFFFFFF to 0xFFFFFFFF00000000
              ulong shiftedHash = (ulong)pathHash << 32;
  
              // OR into scene id
              sceneId = (sceneId & 0xFFFFFFFF) | shiftedHash;
  
              // log it. this is incredibly useful to debug sceneId issues.
              if (LogFilter.Debug) Debug.Log(name + " in scene=" + gameObject.scene.name + " scene index hash(" + pathHash.ToString("X") + ") copied into sceneId: " + sceneId.ToString("X"));
          }
  
          void SetupIDs()
          {
              if (ThisIsAPrefab())
              {
                  // force 0 for prefabs
                  sceneId = 0;
                  AssignAssetID(gameObject);
              }
              // are we currently in prefab editing mode? aka prefab stage
              // => check prefabstage BEFORE SceneObjectWithPrefabParent
              //    (fixes https://github.com/vis2k/Mirror/issues/976)
              // => if we don't check GetCurrentPrefabStage and only check
              //    GetPrefabStage(gameObject), then the 'else' case where we
              //    assign a sceneId and clear the assetId would still be
              //    triggered for prefabs. in other words: if we are in prefab
              //    stage, do not bother with anything else ever!
              else if (PrefabStageUtility.GetCurrentPrefabStage() != null)
              {
                  // when modifying a prefab in prefab stage, Unity calls
                  // OnValidate for that prefab and for all scene objects based on
                  // that prefab.
                  //
                  // is this GameObject the prefab that we modify, and not just a
                  // scene object based on the prefab?
                  //   * GetCurrentPrefabStage = 'are we editing ANY prefab?'
                  //   * GetPrefabStage(go) = 'are we editing THIS prefab?'
                  if (PrefabStageUtility.GetPrefabStage(gameObject) != null)
                  {
                      // force 0 for prefabs
                      sceneId = 0;
                      //Debug.Log(name + " @ scene: " + gameObject.scene.name + " sceneid reset to 0 because CurrentPrefabStage=" + PrefabStageUtility.GetCurrentPrefabStage() + " PrefabStage=" + PrefabStageUtility.GetPrefabStage(gameObject));
                      // NOTE: might make sense to use GetPrefabStage for asset
                      //       path, but let's not touch it while it works.
                      string path = PrefabStageUtility.GetCurrentPrefabStage().prefabAssetPath;
                      AssignAssetID(path);
                  }
              }
              else if (ThisIsASceneObjectWithPrefabParent(out GameObject prefab))
              {
                  AssignSceneID();
                  AssignAssetID(prefab);
              }
              else
              {
                  AssignSceneID();
                  m_AssetId = "";
              }
          }
  #endif
  
          // Unity will Destroy all networked objects on Scene Change, so we have to handle that here silently.
          // That means we cannot have any warning or logging in this method.
          void OnDestroy()
          {
              // remove from sceneIds
              // -> remove with (0xFFFFFFFFFFFFFFFF) and without (0x00000000FFFFFFFF)
              //    sceneHash to be 100% safe.
              sceneIds.Remove(sceneId);
              sceneIds.Remove(sceneId & 0x00000000FFFFFFFF);
  
              // Only call NetworkServer.Destroy on server and only if reset is false
              // reset will be false from incorrect use of Destroy instead of NetworkServer.Destroy
              // reset will be true if NetworkServer.Destroy was correctly invoked to begin with
              // Users are supposed to call NetworkServer.Destroy instead of just regular Destroy for networked objects.
              // This is a safeguard in case users accidentally call regular Destroy instead.
              // We cover their mistake by calling NetworkServer.Destroy for them.
              // If, however, they call NetworkServer.Destroy correctly, which leads to NetworkIdentity.MarkForReset,
              // then we don't need to call it again, so the check for reset is needed to prevent the doubling.
              if (isServer && !reset)
              {
                  // Do not add logging to this (see above)
                  NetworkServer.Destroy(gameObject);
              }
          }
  
          internal void OnStartServer()
          {
              // do nothing if already spawned
              if (isServer)
                  return;
  
              // set isServer flag
              isServer = true;
  
              // If the instance/net ID is invalid here then this is an object instantiated from a prefab and the server should assign a valid ID
              // NOTE: this might not be necessary because the above m_IsServer
              //       check already checks netId. BUT this case here checks only
              //       netId, so it would still check cases where isServer=false
              //       but netId!=0.
              if (netId != 0)
              {
                  // This object has already been spawned, this method might be called again
                  // if we try to respawn all objects.  This can happen when we add a scene
                  // in that case there is nothing else to do.
                  return;
              }
  
              netId = GetNextNetworkId();
              observers = new Dictionary<int, NetworkConnection>();
  
              if (LogFilter.Debug) Debug.Log("OnStartServer " + this + " NetId:" + netId + " SceneId:" + sceneId);
  
              // add to spawned (note: the original EnableIsServer isn't needed
              // because we already set m_isServer=true above)
              spawned[netId] = this;
  
              // in host mode we set isClient true before calling OnStartServer,
              // otherwise isClient is false in OnStartServer.
              if (NetworkClient.active)
              {
                  isClient = true;
              }
  
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnStartServer should be caught, so that one
                  // component's exception doesn't stop all other components from
                  // being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      comp.OnStartServer();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnStartServer:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          bool clientStarted;
          internal void OnStartClient()
          {
              if (clientStarted)
                  return;
              clientStarted = true;
  
              isClient = true;
  
              if (LogFilter.Debug) Debug.Log("OnStartClient " + gameObject + " netId:" + netId);
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnStartClient should be caught, so that one
                  // component's exception doesn't stop all other components from
                  // being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      // user implemented startup
                      comp.OnStartClient();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnStartClient:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          static NetworkIdentity previousLocalPlayer = null;
          internal void OnStartLocalPlayer()
          {
              if (previousLocalPlayer == this)
                  return;
              previousLocalPlayer = this;
  
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnStartLocalPlayer should be caught, so that
                  // one component's exception doesn't stop all other components
                  // from being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      comp.OnStartLocalPlayer();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnStartLocalPlayer:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          bool hadAuthority;
          internal void NotifyAuthority()
          {
              if (!hadAuthority && hasAuthority)
                  OnStartAuthority();
              if (hadAuthority && !hasAuthority)
                  OnStopAuthority();
              hadAuthority = hasAuthority;
          }
  
          internal void OnStartAuthority()
          {
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnStartAuthority should be caught, so that one
                  // component's exception doesn't stop all other components from
                  // being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      comp.OnStartAuthority();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnStartAuthority:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          internal void OnStopAuthority()
          {
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnStopAuthority should be caught, so that one
                  // component's exception doesn't stop all other components from
                  // being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      comp.OnStopAuthority();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnStopAuthority:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          internal void OnSetHostVisibility(bool visible)
          {
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  try
                  {
                      comp.OnSetHostVisibility(visible);
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnSetLocalVisibility:" + e.Message + " " + e.StackTrace);
                  }
              }
          }
  
          internal bool OnCheckObserver(NetworkConnection conn)
          {
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  try
                  {
                      if (!comp.OnCheckObserver(conn))
                          return false;
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnCheckObserver:" + e.Message + " " + e.StackTrace);
                  }
              }
              return true;
          }
  
          internal void OnNetworkDestroy()
          {
              foreach (NetworkBehaviour comp in NetworkBehaviours)
              {
                  // an exception in OnNetworkDestroy should be caught, so that
                  // one component's exception doesn't stop all other components
                  // from being initialized
                  // => this is what Unity does for Start() etc. too.
                  //    one exception doesn't stop all the other Start() calls!
                  try
                  {
                      comp.OnNetworkDestroy();
                  }
                  catch (Exception e)
                  {
                      Debug.LogError("Exception in OnNetworkDestroy:" + e.Message + " " + e.StackTrace);
                  }
                  isServer = false;
              }
          }
  
          // vis2k: readstring bug prevention: https://issuetracker.unity3d.com/issues/unet-networkwriter-dot-write-causing-readstring-slash-readbytes-out-of-range-errors-in-clients
          // -> OnSerialize writes length,componentData,length,componentData,...
          // -> OnDeserialize carefully extracts each data, then deserializes each component with separate readers
          //    -> it will be impossible to read too many or too few bytes in OnDeserialize
          //    -> we can properly track down errors
          bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, bool initialState)
          {
              // write placeholder length bytes
              // (jumping back later is WAY faster than allocating a temporary
              //  writer for the payload, then writing payload.size, payload)
              int headerPosition = writer.Position;
              writer.WriteInt32(0);
              int contentPosition = writer.Position;
  
              // write payload
              bool result = false;
              try
              {
                  result = comp.OnSerialize(writer, initialState);
              }
              catch (Exception e)
              {
                  // show a detailed error and let the user know what went wrong
                  Debug.LogError("OnSerialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + sceneId.ToString("X") + "\n\n" + e);
              }
              int endPosition = writer.Position;
  
              // fill in length now
              writer.Position = headerPosition;
              writer.WriteInt32(endPosition - contentPosition);
              writer.Position = endPosition;
  
              if (LogFilter.Debug) Debug.Log("OnSerializeSafely written for object=" + comp.name + " component=" + comp.GetType() + " sceneId=" + sceneId.ToString("X") + "header@" + headerPosition + " content@" + contentPosition + " end@" + endPosition + " contentSize=" + (endPosition - contentPosition));
  
              return result;
          }
  
          // serialize all components (or only dirty ones if not initial state)
          // -> check ownerWritten/observersWritten to know if anything was written
          internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter, out int ownerWritten, NetworkWriter observersWriter, out int observersWritten)
          {
              // clear 'written' variables
              ownerWritten = observersWritten = 0;
  
              if (NetworkBehaviours.Length > 64)
              {
                  Debug.LogError("Only 64 NetworkBehaviour components are allowed for NetworkIdentity: " + name + " because of the dirtyComponentMask");
                  return;
              }
              ulong dirtyComponentsMask = GetDirtyMask(initialState);
  
              if (dirtyComponentsMask == 0L)
                  return;
  
              // calculate syncMode mask at runtime. this allows users to change
              // component.syncMode while the game is running, which can be a huge
              // advantage over syncvar-based sync modes. e.g. if a player decides
              // to share or not share his inventory, or to go invisible, etc.
              //
              // (this also lets the TestSynchronizingObjects test pass because
              //  otherwise if we were to cache it in Awake, then we would call
              //  GetComponents<NetworkBehaviour> before all the test behaviours
              //  were added)
  
gt:             ulong syncModeObserversMask = GetSyncModeObserversMask();