diff --git a/Assets/Code/NetMessage.cs b/Assets/Code/NetMessage.cs
index 287ccce..435aad9 100644
--- a/Assets/Code/NetMessage.cs
+++ b/Assets/Code/NetMessage.cs
@@ -1,4 +1,4 @@
-using Telepathy;
+using Mirror;
namespace NetMessage
diff --git a/Assets/Packages.meta b/Assets/Packages.meta
new file mode 100644
index 0000000..b4e5532
--- /dev/null
+++ b/Assets/Packages.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d2723bee01450514382d1e32aa01f968
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror.meta b/Assets/Packages/Mirror.meta
new file mode 100644
index 0000000..a7a3dd0
--- /dev/null
+++ b/Assets/Packages/Mirror.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5cf8eb36be0834b3da408c694a41cb88
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components.meta b/Assets/Packages/Mirror/Components.meta
new file mode 100644
index 0000000..c2771d9
--- /dev/null
+++ b/Assets/Packages/Mirror/Components.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9bee879fbc8ef4b1a9a9f7088bfbf726
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/Mirror.Components.asmdef b/Assets/Packages/Mirror/Components/Mirror.Components.asmdef
new file mode 100644
index 0000000..a61c7db
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/Mirror.Components.asmdef
@@ -0,0 +1,14 @@
+{
+ "name": "Mirror.Components",
+ "references": [
+ "Mirror"
+ ],
+ "optionalUnityReferences": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": []
+}
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/Components/Mirror.Components.asmdef.meta b/Assets/Packages/Mirror/Components/Mirror.Components.asmdef.meta
new file mode 100644
index 0000000..263b6f0
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/Mirror.Components.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 72872094b21c16e48b631b2224833d49
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkAnimator.cs b/Assets/Packages/Mirror/Components/NetworkAnimator.cs
new file mode 100644
index 0000000..e09c30c
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkAnimator.cs
@@ -0,0 +1,427 @@
+using UnityEngine;
+using UnityEngine.Serialization;
+
+namespace Mirror
+{
+ ///
+ /// A component to synchronize Mecanim animation states for networked objects.
+ ///
+ ///
+ /// The animation of game objects can be networked by this component. There are two models of authority for networked movement:
+ /// If the object has authority on the client, then it should animated locally on the owning client. The animation state information will be sent from the owning client to the server, then broadcast to all of the other clients. This is common for player objects.
+ /// If the object has authority on the server, then it should be animated on the server and state information will be sent to all clients. This is common for objects not related to a specific client, such as an enemy unit.
+ /// The NetworkAnimator synchronizes the animation parameters that are checked in the inspector view. It does not automatically sychronize triggers. The function SetTrigger can by used by an object with authority to fire an animation trigger on other clients.
+ ///
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Network/NetworkAnimator")]
+ [RequireComponent(typeof(NetworkIdentity))]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkAnimator.html")]
+ public class NetworkAnimator : NetworkBehaviour
+ {
+ ///
+ /// The animator component to synchronize.
+ ///
+ [FormerlySerializedAs("m_Animator")]
+ public Animator animator;
+
+ // Note: not an object[] array because otherwise initialization is real annoying
+ int[] lastIntParameters;
+ float[] lastFloatParameters;
+ bool[] lastBoolParameters;
+ AnimatorControllerParameter[] parameters;
+
+ int[] animationHash; // multiple layers
+ int[] transitionHash;
+ float sendTimer;
+
+ bool sendMessagesAllowed
+ {
+ get
+ {
+ if (isServer)
+ {
+ if (!localPlayerAuthority)
+ return true;
+
+ // This is a special case where we have localPlayerAuthority set
+ // on a NetworkIdentity but we have not assigned the client who has
+ // authority over it, no animator data will be sent over the network by the server.
+ //
+ // So we check here for a clientAuthorityOwner and if it is null we will
+ // let the server send animation data until we receive an owner.
+ if (netIdentity != null && netIdentity.clientAuthorityOwner == null)
+ return true;
+ }
+
+ return hasAuthority;
+ }
+ }
+
+ void Awake()
+ {
+ // store the animator parameters in a variable - the "Animator.parameters" getter allocates
+ // a new parameter array every time it is accessed so we should avoid doing it in a loop
+ parameters = animator.parameters;
+ lastIntParameters = new int[parameters.Length];
+ lastFloatParameters = new float[parameters.Length];
+ lastBoolParameters = new bool[parameters.Length];
+
+ animationHash = new int[animator.layerCount];
+ transitionHash = new int[animator.layerCount];
+ }
+
+ void FixedUpdate()
+ {
+ if (!sendMessagesAllowed)
+ return;
+
+ CheckSendRate();
+
+ for(int i = 0; i < animator.layerCount; i++)
+ {
+ int stateHash;
+ float normalizedTime;
+ if (!CheckAnimStateChanged(out stateHash, out normalizedTime, i))
+ {
+ continue;
+ }
+
+ NetworkWriter writer = new NetworkWriter();
+ WriteParameters(writer);
+
+ SendAnimationMessage(stateHash, normalizedTime, i, writer.ToArray());
+ }
+ }
+
+ bool CheckAnimStateChanged(out int stateHash, out float normalizedTime, int layerId)
+ {
+ stateHash = 0;
+ normalizedTime = 0;
+
+ if (animator.IsInTransition(layerId))
+ {
+ AnimatorTransitionInfo tt = animator.GetAnimatorTransitionInfo(layerId);
+ if (tt.fullPathHash != transitionHash[layerId])
+ {
+ // first time in this transition
+ transitionHash[layerId] = tt.fullPathHash;
+ animationHash[layerId] = 0;
+ return true;
+ }
+ return false;
+ }
+
+ AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(layerId);
+ if (st.fullPathHash != animationHash[layerId])
+ {
+ // first time in this animation state
+ if (animationHash[layerId] != 0)
+ {
+ // came from another animation directly - from Play()
+ stateHash = st.fullPathHash;
+ normalizedTime = st.normalizedTime;
+ }
+ transitionHash[layerId] = 0;
+ animationHash[layerId] = st.fullPathHash;
+ return true;
+ }
+ return false;
+ }
+
+ void CheckSendRate()
+ {
+ if (sendMessagesAllowed && syncInterval != 0 && sendTimer < Time.time)
+ {
+ sendTimer = Time.time + syncInterval;
+
+ NetworkWriter writer = new NetworkWriter();
+ if (WriteParameters(writer))
+ {
+ SendAnimationParametersMessage(writer.ToArray());
+ }
+ }
+ }
+
+ void SendAnimationMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
+ {
+ if (isServer)
+ {
+ RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
+ }
+ else if (ClientScene.readyConnection != null)
+ {
+ CmdOnAnimationServerMessage(stateHash, normalizedTime, layerId, parameters);
+ }
+ }
+
+ void SendAnimationParametersMessage(byte[] parameters)
+ {
+ if (isServer)
+ {
+ RpcOnAnimationParametersClientMessage(parameters);
+ }
+ else if (ClientScene.readyConnection != null)
+ {
+ CmdOnAnimationParametersServerMessage(parameters);
+ }
+ }
+
+ void HandleAnimMsg(int stateHash, float normalizedTime, int layerId, NetworkReader reader)
+ {
+ if (hasAuthority)
+ return;
+
+ // usually transitions will be triggered by parameters, if not, play anims directly.
+ // NOTE: this plays "animations", not transitions, so any transitions will be skipped.
+ // NOTE: there is no API to play a transition(?)
+ if (stateHash != 0)
+ {
+ animator.Play(stateHash, layerId, normalizedTime);
+ }
+
+ ReadParameters(reader);
+ }
+
+ void HandleAnimParamsMsg(NetworkReader reader)
+ {
+ if (hasAuthority)
+ return;
+
+ ReadParameters(reader);
+ }
+
+ void HandleAnimTriggerMsg(int hash)
+ {
+ animator.SetTrigger(hash);
+ }
+
+ ulong NextDirtyBits()
+ {
+ ulong dirtyBits = 0;
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ AnimatorControllerParameter par = parameters[i];
+ bool changed = false;
+ if (par.type == AnimatorControllerParameterType.Int)
+ {
+ int newIntValue = animator.GetInteger(par.nameHash);
+ changed = newIntValue != lastIntParameters[i];
+ if (changed)
+ {
+ lastIntParameters[i] = newIntValue;
+ }
+ }
+ else if (par.type == AnimatorControllerParameterType.Float)
+ {
+ float newFloatValue = animator.GetFloat(par.nameHash);
+ changed = Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f;
+ if (changed)
+ {
+ lastFloatParameters[i] = newFloatValue;
+ }
+ }
+ else if (par.type == AnimatorControllerParameterType.Bool)
+ {
+ bool newBoolValue = animator.GetBool(par.nameHash);
+ changed = newBoolValue != lastBoolParameters[i];
+ if (changed)
+ {
+ lastBoolParameters[i] = newBoolValue;
+ }
+ }
+ if (changed)
+ {
+ dirtyBits |= 1ul << i;
+ }
+ }
+ return dirtyBits;
+ }
+
+ bool WriteParameters(NetworkWriter writer)
+ {
+ ulong dirtyBits = NextDirtyBits();
+ writer.WritePackedUInt64(dirtyBits);
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if ((dirtyBits & (1ul << i)) == 0)
+ continue;
+
+ AnimatorControllerParameter par = parameters[i];
+ if (par.type == AnimatorControllerParameterType.Int)
+ {
+ int newIntValue = animator.GetInteger(par.nameHash);
+ writer.WritePackedInt32(newIntValue);
+ }
+ else if (par.type == AnimatorControllerParameterType.Float)
+ {
+ float newFloatValue = animator.GetFloat(par.nameHash);
+ writer.WriteSingle(newFloatValue);
+ }
+ else if (par.type == AnimatorControllerParameterType.Bool)
+ {
+ bool newBoolValue = animator.GetBool(par.nameHash);
+ writer.WriteBoolean(newBoolValue);
+ }
+ }
+ return dirtyBits != 0;
+ }
+
+ void ReadParameters(NetworkReader reader)
+ {
+ ulong dirtyBits = reader.ReadPackedUInt64();
+ for (int i = 0; i < parameters.Length; i++)
+ {
+ if ((dirtyBits & (1ul << i)) == 0)
+ continue;
+
+ AnimatorControllerParameter par = parameters[i];
+ if (par.type == AnimatorControllerParameterType.Int)
+ {
+ int newIntValue = reader.ReadPackedInt32();
+ animator.SetInteger(par.nameHash, newIntValue);
+ }
+ else if (par.type == AnimatorControllerParameterType.Float)
+ {
+ float newFloatValue = reader.ReadSingle();
+ animator.SetFloat(par.nameHash, newFloatValue);
+ }
+ else if (par.type == AnimatorControllerParameterType.Bool)
+ {
+ bool newBoolValue = reader.ReadBoolean();
+ animator.SetBool(par.nameHash, newBoolValue);
+ }
+ }
+ }
+
+ ///
+ /// Custom Serialization
+ ///
+ ///
+ ///
+ ///
+ public override bool OnSerialize(NetworkWriter writer, bool forceAll)
+ {
+ if (forceAll)
+ {
+ for(int i = 0; i < animator.layerCount; i++)
+ {
+ if (animator.IsInTransition(i))
+ {
+ AnimatorStateInfo st = animator.GetNextAnimatorStateInfo(i);
+ writer.WriteInt32(st.fullPathHash);
+ writer.WriteSingle(st.normalizedTime);
+ }
+ else
+ {
+ AnimatorStateInfo st = animator.GetCurrentAnimatorStateInfo(i);
+ writer.WriteInt32(st.fullPathHash);
+ writer.WriteSingle(st.normalizedTime);
+ }
+ }
+ WriteParameters(writer);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Custom Deserialization
+ ///
+ ///
+ ///
+ public override void OnDeserialize(NetworkReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ for(int i = 0; i < animator.layerCount; i++)
+ {
+ int stateHash = reader.ReadInt32();
+ float normalizedTime = reader.ReadSingle();
+ animator.Play(stateHash, i, normalizedTime);
+ }
+
+ ReadParameters(reader);
+ }
+ }
+
+ ///
+ /// Causes an animation trigger to be invoked for a networked object.
+ /// If local authority is set, and this is called from the client, then the trigger will be invoked on the server and all clients. If not, then this is called on the server, and the trigger will be called on all clients.
+ ///
+ /// Name of trigger.
+ public void SetTrigger(string triggerName)
+ {
+ SetTrigger(Animator.StringToHash(triggerName));
+ }
+
+ ///
+ /// Causes an animation trigger to be invoked for a networked object.
+ ///
+ /// Hash id of trigger (from the Animator).
+ public void SetTrigger(int hash)
+ {
+ if (hasAuthority && localPlayerAuthority)
+ {
+ if (ClientScene.readyConnection != null)
+ {
+ CmdOnAnimationTriggerServerMessage(hash);
+ }
+ return;
+ }
+
+ if (isServer && !localPlayerAuthority)
+ {
+ RpcOnAnimationTriggerClientMessage(hash);
+ }
+ }
+
+ #region server message handlers
+ [Command]
+ void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
+ {
+ if (LogFilter.Debug) Debug.Log("OnAnimationMessage for netId=" + netId);
+
+ // handle and broadcast
+ HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
+ RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, parameters);
+ }
+
+ [Command]
+ void CmdOnAnimationParametersServerMessage(byte[] parameters)
+ {
+ // handle and broadcast
+ HandleAnimParamsMsg(new NetworkReader(parameters));
+ RpcOnAnimationParametersClientMessage(parameters);
+ }
+
+ [Command]
+ void CmdOnAnimationTriggerServerMessage(int hash)
+ {
+ // handle and broadcast
+ HandleAnimTriggerMsg(hash);
+ RpcOnAnimationTriggerClientMessage(hash);
+ }
+ #endregion
+
+ #region client message handlers
+ [ClientRpc]
+ void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, int layerId, byte[] parameters)
+ {
+ HandleAnimMsg(stateHash, normalizedTime, layerId, new NetworkReader(parameters));
+ }
+
+ [ClientRpc]
+ void RpcOnAnimationParametersClientMessage(byte[] parameters)
+ {
+ HandleAnimParamsMsg(new NetworkReader(parameters));
+ }
+
+ // server sends this to one client
+ [ClientRpc]
+ void RpcOnAnimationTriggerClientMessage(int hash)
+ {
+ HandleAnimTriggerMsg(hash);
+ }
+ #endregion
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkAnimator.cs.meta b/Assets/Packages/Mirror/Components/NetworkAnimator.cs.meta
new file mode 100644
index 0000000..5fb8576
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkAnimator.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 7f6f3bf89aa97405989c802ba270f815
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs b/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs
new file mode 100644
index 0000000..69039ac
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs
@@ -0,0 +1,659 @@
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.Serialization;
+
+namespace Mirror
+{
+ ///
+ /// This is a specialized NetworkManager that includes a networked lobby.
+ ///
+ ///
+ /// The lobby has slots that track the joined players, and a maximum player count that is enforced. It requires that the NetworkLobbyPlayer component be on the lobby player objects.
+ /// NetworkLobbyManager is derived from NetworkManager, and so it implements many of the virtual functions provided by the NetworkManager class. To avoid accidentally replacing functionality of the NetworkLobbyManager, there are new virtual functions on the NetworkLobbyManager that begin with "OnLobby". These should be used on classes derived from NetworkLobbyManager instead of the virtual functions on NetworkManager.
+ /// The OnLobby*() functions have empty implementations on the NetworkLobbyManager base class, so the base class functions do not have to be called.
+ ///
+ [AddComponentMenu("Network/NetworkLobbyManager")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyManager.html")]
+ public class NetworkLobbyManager : NetworkManager
+ {
+ public struct PendingPlayer
+ {
+ public NetworkConnection conn;
+ public GameObject lobbyPlayer;
+ }
+
+ [Header("Lobby Settings")]
+
+ [FormerlySerializedAs("m_ShowLobbyGUI")]
+ [SerializeField]
+ internal bool showLobbyGUI = true;
+
+ [FormerlySerializedAs("m_MinPlayers")]
+ [SerializeField]
+ int minPlayers = 1;
+
+ [FormerlySerializedAs("m_LobbyPlayerPrefab")]
+ [SerializeField]
+ NetworkLobbyPlayer lobbyPlayerPrefab;
+
+ ///
+ /// The scene to use for the lobby. This is similar to the offlineScene of the NetworkManager.
+ ///
+ [Scene]
+ public string LobbyScene;
+
+ ///
+ /// The scene to use for the playing the game from the lobby. This is similar to the onlineScene of the NetworkManager.
+ ///
+ [Scene]
+ public string GameplayScene;
+
+ ///
+ /// List of players that are in the Lobby
+ ///
+ [FormerlySerializedAs("m_PendingPlayers")]
+ public List pendingPlayers = new List();
+
+ ///
+ /// These slots track players that enter the lobby.
+ /// The slotId on players is global to the game - across all players.
+ ///
+ public List lobbySlots = new List();
+
+ ///
+ /// True when all players have submitted a Ready message
+ ///
+ public bool allPlayersReady;
+
+ public override void OnValidate()
+ {
+ // always >= 0
+ maxConnections = Mathf.Max(maxConnections, 0);
+
+ // always <= maxConnections
+ minPlayers = Mathf.Min(minPlayers, maxConnections);
+
+ // always >= 0
+ minPlayers = Mathf.Max(minPlayers, 0);
+
+ if (lobbyPlayerPrefab != null)
+ {
+ NetworkIdentity identity = lobbyPlayerPrefab.GetComponent();
+ if (identity == null)
+ {
+ lobbyPlayerPrefab = null;
+ Debug.LogError("LobbyPlayer prefab must have a NetworkIdentity component.");
+ }
+ }
+
+ base.OnValidate();
+ }
+
+ internal void ReadyStatusChanged()
+ {
+ int CurrentPlayers = 0;
+ int ReadyPlayers = 0;
+
+ foreach (NetworkLobbyPlayer item in lobbySlots)
+ {
+ if (item != null)
+ {
+ CurrentPlayers++;
+ if (item.readyToBegin)
+ ReadyPlayers++;
+ }
+ }
+
+ if (CurrentPlayers == ReadyPlayers)
+ CheckReadyToBegin();
+ else
+ allPlayersReady = false;
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnServerReady(NetworkConnection conn)
+ {
+ if (LogFilter.Debug) Debug.Log("NetworkLobbyManager OnServerReady");
+ base.OnServerReady(conn);
+
+ if (conn != null && conn.playerController != null)
+ {
+ GameObject lobbyPlayer = conn.playerController.gameObject;
+
+ // if null or not a lobby player, dont replace it
+ if (lobbyPlayer != null && lobbyPlayer.GetComponent() != null)
+ SceneLoadedForPlayer(conn, lobbyPlayer);
+ }
+ }
+
+ void SceneLoadedForPlayer(NetworkConnection conn, GameObject lobbyPlayer)
+ {
+ if (LogFilter.Debug) Debug.LogFormat("NetworkLobby SceneLoadedForPlayer scene: {0} {1}", SceneManager.GetActiveScene().name, conn);
+
+ if (SceneManager.GetActiveScene().name == LobbyScene)
+ {
+ // cant be ready in lobby, add to ready list
+ PendingPlayer pending;
+ pending.conn = conn;
+ pending.lobbyPlayer = lobbyPlayer;
+ pendingPlayers.Add(pending);
+ return;
+ }
+
+ GameObject gamePlayer = OnLobbyServerCreateGamePlayer(conn);
+ if (gamePlayer == null)
+ {
+ // get start position from base class
+ Transform startPos = GetStartPosition();
+ gamePlayer = startPos != null
+ ? Instantiate(playerPrefab, startPos.position, startPos.rotation)
+ : Instantiate(playerPrefab, Vector3.zero, Quaternion.identity);
+ gamePlayer.name = playerPrefab.name;
+ }
+
+ if (!OnLobbyServerSceneLoadedForPlayer(lobbyPlayer, gamePlayer))
+ return;
+
+ // replace lobby player with game player
+ NetworkServer.ReplacePlayerForConnection(conn, gamePlayer);
+ }
+
+ ///
+ /// CheckReadyToBegin checks all of the players in the lobby to see if their readyToBegin flag is set.
+ /// If all of the players are ready, then the server switches from the LobbyScene to the PlayScene - essentially starting the game. This is called automatically in response to NetworkLobbyPlayer.SendReadyToBeginMessage().
+ ///
+ public void CheckReadyToBegin()
+ {
+ if (SceneManager.GetActiveScene().name != LobbyScene) return;
+
+ if (minPlayers > 0 && NetworkServer.connections.Count(conn => conn.Value != null && conn.Value.playerController.gameObject.GetComponent().readyToBegin) < minPlayers)
+ {
+ allPlayersReady = false;
+ return;
+ }
+
+ pendingPlayers.Clear();
+ allPlayersReady = true;
+ OnLobbyServerPlayersReady();
+ }
+
+ void CallOnClientEnterLobby()
+ {
+ OnLobbyClientEnter();
+ foreach (NetworkLobbyPlayer player in lobbySlots)
+ if (player != null)
+ {
+ player.OnClientEnterLobby();
+ }
+ }
+
+ void CallOnClientExitLobby()
+ {
+ OnLobbyClientExit();
+ foreach (NetworkLobbyPlayer player in lobbySlots)
+ if (player != null)
+ {
+ player.OnClientExitLobby();
+ }
+ }
+
+ #region server handlers
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnServerConnect(NetworkConnection conn)
+ {
+ if (numPlayers >= maxConnections)
+ {
+ conn.Disconnect();
+ return;
+ }
+
+ // cannot join game in progress
+ if (SceneManager.GetActiveScene().name != LobbyScene)
+ {
+ conn.Disconnect();
+ return;
+ }
+
+ base.OnServerConnect(conn);
+ OnLobbyServerConnect(conn);
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnServerDisconnect(NetworkConnection conn)
+ {
+ if (conn.playerController != null)
+ {
+ NetworkLobbyPlayer player = conn.playerController.GetComponent();
+
+ if (player != null)
+ lobbySlots.Remove(player);
+ }
+
+ allPlayersReady = false;
+
+ foreach (NetworkLobbyPlayer player in lobbySlots)
+ {
+ if (player != null)
+ player.GetComponent().readyToBegin = false;
+ }
+
+ if (SceneManager.GetActiveScene().name == LobbyScene)
+ RecalculateLobbyPlayerIndices();
+
+ base.OnServerDisconnect(conn);
+ OnLobbyServerDisconnect(conn);
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ ///
+ public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage)
+ {
+ if (SceneManager.GetActiveScene().name != LobbyScene) return;
+
+ if (lobbySlots.Count == maxConnections) return;
+
+ allPlayersReady = false;
+
+ if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager.OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name);
+
+ GameObject newLobbyGameObject = OnLobbyServerCreateLobbyPlayer(conn);
+ if (newLobbyGameObject == null)
+ newLobbyGameObject = (GameObject)Instantiate(lobbyPlayerPrefab.gameObject, Vector3.zero, Quaternion.identity);
+
+ NetworkLobbyPlayer newLobbyPlayer = newLobbyGameObject.GetComponent();
+
+ lobbySlots.Add(newLobbyPlayer);
+
+ RecalculateLobbyPlayerIndices();
+
+ NetworkServer.AddPlayerForConnection(conn, newLobbyGameObject);
+ }
+
+ void RecalculateLobbyPlayerIndices()
+ {
+ if (lobbySlots.Count > 0)
+ {
+ for (int i = 0; i < lobbySlots.Count; i++)
+ {
+ lobbySlots[i].index = i;
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void ServerChangeScene(string sceneName)
+ {
+ if (sceneName == LobbyScene)
+ {
+ foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
+ {
+ if (lobbyPlayer == null) continue;
+
+ // find the game-player object for this connection, and destroy it
+ NetworkIdentity identity = lobbyPlayer.GetComponent();
+
+ NetworkIdentity playerController = identity.connectionToClient.playerController;
+ NetworkServer.Destroy(playerController.gameObject);
+
+ if (NetworkServer.active)
+ {
+ // re-add the lobby object
+ lobbyPlayer.GetComponent().readyToBegin = false;
+ NetworkServer.ReplacePlayerForConnection(identity.connectionToClient, lobbyPlayer.gameObject);
+ }
+ }
+ }
+ else
+ {
+ if (dontDestroyOnLoad)
+ {
+ foreach (NetworkLobbyPlayer lobbyPlayer in lobbySlots)
+ {
+ if (lobbyPlayer != null)
+ {
+ lobbyPlayer.transform.SetParent(null);
+ DontDestroyOnLoad(lobbyPlayer);
+ }
+ }
+ }
+ }
+
+ base.ServerChangeScene(sceneName);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void OnServerSceneChanged(string sceneName)
+ {
+ if (sceneName != LobbyScene)
+ {
+ // call SceneLoadedForPlayer on any players that become ready while we were loading the scene.
+ foreach (PendingPlayer pending in pendingPlayers)
+ SceneLoadedForPlayer(pending.conn, pending.lobbyPlayer);
+
+ pendingPlayers.Clear();
+ }
+
+ OnLobbyServerSceneChanged(sceneName);
+ }
+
+ ///
+ ///
+ ///
+ public override void OnStartServer()
+ {
+ if (string.IsNullOrEmpty(LobbyScene))
+ {
+ Debug.LogError("NetworkLobbyManager LobbyScene is empty. Set the LobbyScene in the inspector for the NetworkLobbyMangaer");
+ return;
+ }
+
+ if (string.IsNullOrEmpty(GameplayScene))
+ {
+ Debug.LogError("NetworkLobbyManager PlayScene is empty. Set the PlayScene in the inspector for the NetworkLobbyMangaer");
+ return;
+ }
+
+ OnLobbyStartServer();
+ }
+
+ ///
+ ///
+ ///
+ public override void OnStartHost()
+ {
+ OnLobbyStartHost();
+ }
+
+ ///
+ ///
+ ///
+ public override void OnStopServer()
+ {
+ lobbySlots.Clear();
+ base.OnStopServer();
+ }
+
+ ///
+ ///
+ ///
+ public override void OnStopHost()
+ {
+ OnLobbyStopHost();
+ }
+
+ #endregion
+
+ #region client handlers
+
+ ///
+ ///
+ ///
+ public override void OnStartClient()
+ {
+ if (lobbyPlayerPrefab == null || lobbyPlayerPrefab.gameObject == null)
+ Debug.LogError("NetworkLobbyManager no LobbyPlayer prefab is registered. Please add a LobbyPlayer prefab.");
+ else
+ ClientScene.RegisterPrefab(lobbyPlayerPrefab.gameObject);
+
+ if (playerPrefab == null)
+ Debug.LogError("NetworkLobbyManager no GamePlayer prefab is registered. Please add a GamePlayer prefab.");
+ else
+ ClientScene.RegisterPrefab(playerPrefab);
+
+ OnLobbyStartClient();
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnClientConnect(NetworkConnection conn)
+ {
+ OnLobbyClientConnect(conn);
+ CallOnClientEnterLobby();
+ base.OnClientConnect(conn);
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnClientDisconnect(NetworkConnection conn)
+ {
+ OnLobbyClientDisconnect(conn);
+ base.OnClientDisconnect(conn);
+ }
+
+ ///
+ ///
+ ///
+ public override void OnStopClient()
+ {
+ OnLobbyStopClient();
+ CallOnClientExitLobby();
+
+ if (!string.IsNullOrEmpty(offlineScene))
+ {
+ // Move the LobbyManager from the virtual DontDestroyOnLoad scene to the Game scene.
+ // This let's it be destroyed when client changes to the Offline scene.
+ SceneManager.MoveGameObjectToScene(gameObject, SceneManager.GetActiveScene());
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public override void OnClientChangeScene(string newSceneName)
+ {
+ if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene from {0} to {1}", SceneManager.GetActiveScene().name, newSceneName);
+
+ if (SceneManager.GetActiveScene().name == LobbyScene && newSceneName == GameplayScene && dontDestroyOnLoad && NetworkClient.isConnected)
+ {
+ if (NetworkClient.connection != null && NetworkClient.connection.playerController != null)
+ {
+ GameObject lobbyPlayer = NetworkClient.connection.playerController.gameObject;
+ if (lobbyPlayer != null)
+ {
+ lobbyPlayer.transform.SetParent(null);
+ DontDestroyOnLoad(lobbyPlayer);
+ }
+ else
+ Debug.LogWarningFormat("OnClientChangeScene: lobbyPlayer is null");
+ }
+ }
+ else
+ if (LogFilter.Debug) Debug.LogFormat("OnClientChangeScene {0} {1}", dontDestroyOnLoad, NetworkClient.isConnected);
+ }
+
+ ///
+ ///
+ ///
+ /// Connection of the client
+ public override void OnClientSceneChanged(NetworkConnection conn)
+ {
+ if (SceneManager.GetActiveScene().name == LobbyScene)
+ {
+ if (NetworkClient.isConnected)
+ CallOnClientEnterLobby();
+ }
+ else
+ CallOnClientExitLobby();
+
+ base.OnClientSceneChanged(conn);
+ OnLobbyClientSceneChanged(conn);
+ }
+
+ #endregion
+
+ #region lobby server virtuals
+
+ ///
+ /// This is called on the host when a host is started.
+ ///
+ public virtual void OnLobbyStartHost() { }
+
+ ///
+ /// This is called on the host when the host is stopped.
+ ///
+ public virtual void OnLobbyStopHost() { }
+
+ ///
+ /// This is called on the server when the server is started - including when a host is started.
+ ///
+ public virtual void OnLobbyStartServer() { }
+
+ ///
+ /// This is called on the server when a new client connects to the server.
+ ///
+ /// The new connection.
+ public virtual void OnLobbyServerConnect(NetworkConnection conn) { }
+
+ ///
+ /// This is called on the server when a client disconnects.
+ ///
+ /// The connection that disconnected.
+ public virtual void OnLobbyServerDisconnect(NetworkConnection conn) { }
+
+ ///
+ /// This is called on the server when a networked scene finishes loading.
+ ///
+ /// Name of the new scene.
+ public virtual void OnLobbyServerSceneChanged(string sceneName) { }
+
+ ///
+ /// This allows customization of the creation of the lobby-player object on the server.
+ /// By default the lobbyPlayerPrefab is used to create the lobby-player, but this function allows that behaviour to be customized.
+ ///
+ /// The connection the player object is for.
+ /// The new lobby-player object.
+ public virtual GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn)
+ {
+ return null;
+ }
+
+ ///
+ /// This allows customization of the creation of the GamePlayer object on the server.
+ /// By default the gamePlayerPrefab is used to create the game-player, but this function allows that behaviour to be customized. The object returned from the function will be used to replace the lobby-player on the connection.
+ ///
+ /// The connection the player object is for.
+ /// A new GamePlayer object.
+ public virtual GameObject OnLobbyServerCreateGamePlayer(NetworkConnection conn)
+ {
+ return null;
+ }
+
+ // for users to apply settings from their lobby player object to their in-game player object
+ ///
+ /// This is called on the server when it is told that a client has finished switching from the lobby scene to a game player scene.
+ /// When switching from the lobby, the lobby-player is replaced with a game-player object. This callback function gives an opportunity to apply state from the lobby-player to the game-player object.
+ ///
+ /// The lobby player object.
+ /// The game player object.
+ /// False to not allow this player to replace the lobby player.
+ public virtual bool OnLobbyServerSceneLoadedForPlayer(GameObject lobbyPlayer, GameObject gamePlayer)
+ {
+ return true;
+ }
+
+ ///
+ /// This is called on the server when all the players in the lobby are ready.
+ /// The default implementation of this function uses ServerChangeScene() to switch to the game player scene. By implementing this callback you can customize what happens when all the players in the lobby are ready, such as adding a countdown or a confirmation for a group leader.
+ ///
+ public virtual void OnLobbyServerPlayersReady()
+ {
+ // all players are readyToBegin, start the game
+ ServerChangeScene(GameplayScene);
+ }
+
+ #endregion
+
+ #region lobby client virtuals
+
+ ///
+ /// This is a hook to allow custom behaviour when the game client enters the lobby.
+ ///
+ public virtual void OnLobbyClientEnter() { }
+
+ ///
+ /// This is a hook to allow custom behaviour when the game client exits the lobby.
+ ///
+ public virtual void OnLobbyClientExit() { }
+
+ ///
+ /// This is called on the client when it connects to server.
+ ///
+ /// The connection that connected.
+ public virtual void OnLobbyClientConnect(NetworkConnection conn) { }
+
+ ///
+ /// This is called on the client when disconnected from a server.
+ ///
+ /// The connection that disconnected.
+ public virtual void OnLobbyClientDisconnect(NetworkConnection conn) { }
+
+ ///
+ /// This is called on the client when a client is started.
+ ///
+ /// The connection for the lobby.
+ public virtual void OnLobbyStartClient() { }
+
+ ///
+ /// This is called on the client when the client stops.
+ ///
+ public virtual void OnLobbyStopClient() { }
+
+ ///
+ /// This is called on the client when the client is finished loading a new networked scene.
+ ///
+ /// The connection that finished loading a new networked scene.
+ public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) { }
+
+ ///
+ /// Called on the client when adding a player to the lobby fails.
+ /// This could be because the lobby is full, or the connection is not allowed to have more players.
+ ///
+ public virtual void OnLobbyClientAddPlayerFailed() { }
+
+ #endregion
+
+ #region optional UI
+
+ ///
+ /// virtual so inheriting classes can roll their own
+ ///
+ public virtual void OnGUI()
+ {
+ if (!showLobbyGUI)
+ return;
+
+ if (SceneManager.GetActiveScene().name != LobbyScene)
+ return;
+
+ GUI.Box(new Rect(10f, 180f, 520f, 150f), "PLAYERS");
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs.meta b/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs.meta
new file mode 100644
index 0000000..35b6436
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkLobbyManager.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 615e6c6589cf9e54cad646b5a11e0529
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs b/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs
new file mode 100644
index 0000000..45231c1
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs
@@ -0,0 +1,155 @@
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Mirror
+{
+ ///
+ /// This component works in conjunction with the NetworkLobbyManager to make up the multiplayer lobby system.
+ /// The LobbyPrefab object of the NetworkLobbyManager must have this component on it. This component holds basic lobby player data required for the lobby to function. Game specific data for lobby players can be put in other components on the LobbyPrefab or in scripts derived from NetworkLobbyPlayer.
+ ///
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Network/NetworkLobbyPlayer")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkLobbyPlayer.html")]
+ public class NetworkLobbyPlayer : NetworkBehaviour
+ {
+ ///
+ /// This flag controls whether the default UI is shown for the lobby player.
+ /// As this UI is rendered using the old GUI system, it is only recommended for testing purposes.
+ ///
+ public bool showLobbyGUI = true;
+
+ ///
+ /// This is a flag that control whether this player is ready for the game to begin.
+ /// When all players are ready to begin, the game will start. This should not be set directly, the SendReadyToBeginMessage function should be called on the client to set it on the server.
+ ///
+ [SyncVar(hook = nameof(ReadyStateChanged))]
+ public bool readyToBegin;
+
+ ///
+ /// Current index of the player, e.g. Player1, Player2, etc.
+ ///
+ [SyncVar]
+ public int index;
+
+ #region Unity Callbacks
+
+ ///
+ /// Do not use Start - Override OnStartrHost / OnStartClient instead!
+ ///
+ public void Start()
+ {
+ if (NetworkManager.singleton as NetworkLobbyManager)
+ OnClientEnterLobby();
+ else
+ Debug.LogError("LobbyPlayer could not find a NetworkLobbyManager. The LobbyPlayer requires a NetworkLobbyManager object to function. Make sure that there is one in the scene.");
+ }
+
+ #endregion
+
+ #region Commands
+
+ [Command]
+ public void CmdChangeReadyState(bool readyState)
+ {
+ readyToBegin = readyState;
+ NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
+ if (lobby != null)
+ {
+ lobby.ReadyStatusChanged();
+ }
+ }
+
+ #endregion
+
+ #region SyncVar Hooks
+
+ void ReadyStateChanged(bool newReadyState)
+ {
+ OnClientReady(readyToBegin);
+ }
+
+ #endregion
+
+ #region Lobby Client Virtuals
+
+ ///
+ /// This is a hook that is invoked on all player objects when entering the lobby.
+ /// Note: isLocalPlayer is not guaranteed to be set until OnStartLocalPlayer is called.
+ ///
+ public virtual void OnClientEnterLobby() { }
+
+ ///
+ /// This is a hook that is invoked on all player objects when exiting the lobby.
+ ///
+ public virtual void OnClientExitLobby() { }
+
+ ///
+ /// This is a hook that is invoked on clients when a LobbyPlayer switches between ready or not ready.
+ /// This function is called when the a client player calls SendReadyToBeginMessage() or SendNotReadyToBeginMessage().
+ ///
+ /// Whether the player is ready or not.
+ public virtual void OnClientReady(bool readyState) { }
+
+ #endregion
+
+ #region Optional UI
+
+ ///
+ /// Render a UI for the lobby. Override to provide your on UI
+ ///
+ public virtual void OnGUI()
+ {
+ if (!showLobbyGUI)
+ return;
+
+ NetworkLobbyManager lobby = NetworkManager.singleton as NetworkLobbyManager;
+ if (lobby)
+ {
+ if (!lobby.showLobbyGUI)
+ return;
+
+ if (SceneManager.GetActiveScene().name != lobby.LobbyScene)
+ return;
+
+ GUILayout.BeginArea(new Rect(20f + (index * 100), 200f, 90f, 130f));
+
+ GUILayout.Label($"Player [{index + 1}]");
+
+ if (readyToBegin)
+ GUILayout.Label("Ready");
+ else
+ GUILayout.Label("Not Ready");
+
+ if (((isServer && index > 0) || isServerOnly) && GUILayout.Button("REMOVE"))
+ {
+ // This button only shows on the Host for all players other than the Host
+ // Host and Players can't remove themselves (stop the client instead)
+ // Host can kick a Player this way.
+ GetComponent().connectionToClient.Disconnect();
+ }
+
+ GUILayout.EndArea();
+
+ if (NetworkClient.active && isLocalPlayer)
+ {
+ GUILayout.BeginArea(new Rect(20f, 300f, 120f, 20f));
+
+ if (readyToBegin)
+ {
+ if (GUILayout.Button("Cancel"))
+ CmdChangeReadyState(false);
+ }
+ else
+ {
+ if (GUILayout.Button("Ready"))
+ CmdChangeReadyState(true);
+ }
+
+ GUILayout.EndArea();
+ }
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs.meta b/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs.meta
new file mode 100644
index 0000000..3062e0e
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkLobbyPlayer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 79874ac94d5b1314788ecf0e86bd23fd
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs b/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs
new file mode 100644
index 0000000..97c4bbf
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs
@@ -0,0 +1,171 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace Mirror
+{
+ ///
+ /// Component that controls visibility of networked objects for players.
+ /// Any object with this component on it will not be visible to players more than a (configurable) distance away.
+ ///
+ [AddComponentMenu("Network/NetworkProximityChecker")]
+ [RequireComponent(typeof(NetworkIdentity))]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkProximityChecker.html")]
+ public class NetworkProximityChecker : NetworkBehaviour
+ {
+ ///
+ /// Enumeration of methods to use to check proximity.
+ ///
+ public enum CheckMethod
+ {
+ Physics3D,
+ Physics2D
+ }
+
+ ///
+ /// The maximim range that objects will be visible at.
+ ///
+ [Tooltip("The maximum range that objects will be visible at.")]
+ public int visRange = 10;
+
+ ///
+ /// How often (in seconds) that this object should update the list of observers that can see it.
+ ///
+ [Tooltip("How often (in seconds) that this object should update the list of observers that can see it.")]
+ public float visUpdateInterval = 1;
+
+ ///
+ /// Which method to use for checking proximity of players.
+ /// Physics3D uses 3D physics to determine proximity.
+ /// Physics2D uses 2D physics to determine proximity.
+ ///
+ [Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\nPhysics2D uses 2D physics to determine proximity.")]
+ public CheckMethod checkMethod = CheckMethod.Physics3D;
+
+ ///
+ /// Flag to force this object to be hidden for players.
+ /// If this object is a player object, it will not be hidden for that player.
+ ///
+ [Tooltip("Enable to force this object to be hidden from players.")]
+ public bool forceHidden;
+
+ // Layers are used anyway, might as well expose them to the user.
+ ///
+ /// Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.
+ /// ~0 means 'Everything'.
+ ///
+ [Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")]
+ public LayerMask castLayers = ~0;
+
+ float lastUpdateTime;
+
+ // OverlapSphereNonAlloc array to avoid allocations.
+ // -> static so we don't create one per component
+ // -> this is worth it because proximity checking happens for just about
+ // every entity on the server!
+ // -> should be big enough to work in just about all cases
+ static Collider[] hitsBuffer3D = new Collider[10000];
+ static Collider2D[] hitsBuffer2D = new Collider2D[10000];
+
+ void Update()
+ {
+ if (!NetworkServer.active)
+ return;
+
+ if (Time.time - lastUpdateTime > visUpdateInterval)
+ {
+ netIdentity.RebuildObservers(false);
+ lastUpdateTime = Time.time;
+ }
+ }
+
+ ///
+ /// Called when a new player enters
+ ///
+ ///
+ ///
+ public override bool OnCheckObserver(NetworkConnection newObserver)
+ {
+ if (forceHidden)
+ return false;
+
+ return Vector3.Distance(newObserver.playerController.transform.position, transform.position) < visRange;
+ }
+
+ ///
+ /// Called when a new player enters, and when scene changes occur
+ ///
+ /// List of players to be updated. Modify this set with all the players that can see this object
+ /// True if this is the first time the method is called for this object
+ /// True if this component calculated the list of observers
+ public override bool OnRebuildObservers(HashSet observers, bool initial)
+ {
+ // if force hidden then return without adding any observers.
+ if (forceHidden)
+ // always return true when overwriting OnRebuildObservers so that
+ // Mirror knows not to use the built in rebuild method.
+ return true;
+
+ // find players within range
+ switch (checkMethod)
+ {
+ case CheckMethod.Physics3D:
+ {
+ // cast without allocating GC for maximum performance
+ int hitCount = Physics.OverlapSphereNonAlloc(transform.position, visRange, hitsBuffer3D, castLayers);
+ if (hitCount == hitsBuffer3D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapSphere test for " + name + " has filled the whole buffer(" + hitsBuffer3D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
+
+ for (int i = 0; i < hitCount; i++)
+ {
+ Collider hit = hitsBuffer3D[i];
+ // collider might be on pelvis, often the NetworkIdentity is in a parent
+ // (looks in the object itself and then parents)
+ NetworkIdentity identity = hit.GetComponentInParent();
+ // (if an object has a connectionToClient, it is a player)
+ if (identity != null && identity.connectionToClient != null)
+ {
+ observers.Add(identity.connectionToClient);
+ }
+ }
+ break;
+ }
+
+ case CheckMethod.Physics2D:
+ {
+ // cast without allocating GC for maximum performance
+ int hitCount = Physics2D.OverlapCircleNonAlloc(transform.position, visRange, hitsBuffer2D, castLayers);
+ if (hitCount == hitsBuffer2D.Length) Debug.LogWarning("NetworkProximityChecker's OverlapCircle test for " + name + " has filled the whole buffer(" + hitsBuffer2D.Length + "). Some results might have been omitted. Consider increasing buffer size.");
+
+ for (int i = 0; i < hitCount; i++)
+ {
+ Collider2D hit = hitsBuffer2D[i];
+ // collider might be on pelvis, often the NetworkIdentity is in a parent
+ // (looks in the object itself and then parents)
+ NetworkIdentity identity = hit.GetComponentInParent();
+ // (if an object has a connectionToClient, it is a player)
+ if (identity != null && identity.connectionToClient != null)
+ {
+ observers.Add(identity.connectionToClient);
+ }
+ }
+ break;
+ }
+ }
+
+ // always return true when overwriting OnRebuildObservers so that
+ // Mirror knows not to use the built in rebuild method.
+ return true;
+ }
+
+ ///
+ /// Called when hiding and showing objects on the host
+ ///
+ ///
+ public override void OnSetLocalVisibility(bool visible)
+ {
+ foreach (Renderer rend in GetComponentsInChildren())
+ {
+ rend.enabled = visible;
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs.meta b/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs.meta
new file mode 100644
index 0000000..79e50e8
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkProximityChecker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1731d8de2d0c84333b08ebe1e79f4118
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkTransform.cs b/Assets/Packages/Mirror/Components/NetworkTransform.cs
new file mode 100644
index 0000000..d55e26d
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransform.cs
@@ -0,0 +1,12 @@
+using UnityEngine;
+
+namespace Mirror
+{
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Network/NetworkTransform")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransform.html")]
+ public class NetworkTransform : NetworkTransformBase
+ {
+ protected override Transform targetComponent => transform;
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkTransform.cs.meta b/Assets/Packages/Mirror/Components/NetworkTransform.cs.meta
new file mode 100644
index 0000000..d1af9ec
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransform.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2f74aedd71d9a4f55b3ce499326d45fb
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkTransformBase.cs b/Assets/Packages/Mirror/Components/NetworkTransformBase.cs
new file mode 100644
index 0000000..4ed5ad9
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransformBase.cs
@@ -0,0 +1,447 @@
+// vis2k:
+// base class for NetworkTransform and NetworkTransformChild.
+// New method is simple and stupid. No more 1500 lines of code.
+//
+// Server sends current data.
+// Client saves it and interpolates last and latest data points.
+// Update handles transform movement / rotation
+// FixedUpdate handles rigidbody movement / rotation
+//
+// Notes:
+// * Built-in Teleport detection in case of lags / teleport / obstacles
+// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp
+// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code.
+// * Initial delay might happen if server sends packet immediately after moving
+// just 1cm, hence we move 1cm and then wait 100ms for next packet
+// * Only way for smooth movement is to use a fixed movement speed during
+// interpolation. interpolation over time is never that good.
+//
+using UnityEngine;
+
+namespace Mirror
+{
+ public abstract class NetworkTransformBase : NetworkBehaviour
+ {
+ // rotation compression. not public so that other scripts can't modify
+ // it at runtime. alternatively we could send 1 extra byte for the mode
+ // each time so clients know how to decompress, but the whole point was
+ // to save bandwidth in the first place.
+ // -> can still be modified in the Inspector while the game is running,
+ // but would cause errors immediately and be pretty obvious.
+ [Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")]
+ [SerializeField] Compression compressRotation = Compression.Much;
+ public enum Compression { None, Much, Lots, NoRotation }; // easily understandable and funny
+
+ // server
+ Vector3 lastPosition;
+ Quaternion lastRotation;
+ private Vector3 lastScale;
+
+ // client
+ public class DataPoint
+ {
+ public float timeStamp;
+ // use local position/rotation for VR support
+ public Vector3 localPosition;
+ public Quaternion localRotation;
+ public Vector3 localScale;
+ public float movementSpeed;
+ }
+ // interpolation start and goal
+ DataPoint start;
+ DataPoint goal;
+
+ // local authority send time
+ float lastClientSendTime;
+
+ // target transform to sync. can be on a child.
+ protected abstract Transform targetComponent { get; }
+
+ // serialization is needed by OnSerialize and by manual sending from authority
+ static void SerializeIntoWriter(NetworkWriter writer, Vector3 position, Quaternion rotation, Compression compressRotation, Vector3 scale)
+ {
+ // serialize position
+ writer.WriteVector3(position);
+
+ // serialize rotation
+ // writing quaternion = 16 byte
+ // writing euler angles = 12 byte
+ // -> quaternion->euler->quaternion always works.
+ // -> gimbal lock only occurs when adding.
+ Vector3 euler = rotation.eulerAngles;
+ if (compressRotation == Compression.None)
+ {
+ // write 3 floats = 12 byte
+ writer.WriteSingle(euler.x);
+ writer.WriteSingle(euler.y);
+ writer.WriteSingle(euler.z);
+ }
+ else if (compressRotation == Compression.Much)
+ {
+ // write 3 byte. scaling [0,360] to [0,255]
+ writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue));
+ writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue));
+ writer.WriteByte(FloatBytePacker.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue));
+ }
+ else if (compressRotation == Compression.Lots)
+ {
+ // write 2 byte, 5 bits for each float
+ writer.WriteUInt16(FloatBytePacker.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360));
+ }
+
+ // serialize scale
+ writer.WriteVector3(scale);
+ }
+
+ public override bool OnSerialize(NetworkWriter writer, bool initialState)
+ {
+ // use local position/rotation/scale for VR support
+ SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
+ return true;
+ }
+
+ // try to estimate movement speed for a data point based on how far it
+ // moved since the previous one
+ // => if this is the first time ever then we use our best guess:
+ // -> delta based on transform.localPosition
+ // -> elapsed based on send interval hoping that it roughly matches
+ static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval)
+ {
+ Vector3 delta = to.localPosition - (from != null ? from.localPosition : transform.localPosition);
+ float elapsed = from != null ? to.timeStamp - from.timeStamp : sendInterval;
+ return elapsed > 0 ? delta.magnitude / elapsed : 0; // avoid NaN
+ }
+
+ // serialization is needed by OnSerialize and by manual sending from authority
+ void DeserializeFromReader(NetworkReader reader)
+ {
+ // put it into a data point immediately
+ DataPoint temp = new DataPoint
+ {
+ // deserialize position
+ localPosition = reader.ReadVector3()
+ };
+
+ // deserialize rotation
+ if (compressRotation == Compression.None)
+ {
+ // read 3 floats = 16 byte
+ float x = reader.ReadSingle();
+ float y = reader.ReadSingle();
+ float z = reader.ReadSingle();
+ temp.localRotation = Quaternion.Euler(x, y, z);
+ }
+ else if (compressRotation == Compression.Much)
+ {
+ // read 3 byte. scaling [0,255] to [0,360]
+ float x = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
+ float y = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
+ float z = FloatBytePacker.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360);
+ temp.localRotation = Quaternion.Euler(x, y, z);
+ }
+ else if (compressRotation == Compression.Lots)
+ {
+ // read 2 byte, 5 bits per float
+ Vector3 xyz = FloatBytePacker.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360);
+ temp.localRotation = Quaternion.Euler(xyz.x, xyz.y, xyz.z);
+ }
+
+ temp.localScale = reader.ReadVector3();
+
+ temp.timeStamp = Time.time;
+
+ // movement speed: based on how far it moved since last time
+ // has to be calculated before 'start' is overwritten
+ temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetComponent.transform, syncInterval);
+
+ // reassign start wisely
+ // -> first ever data point? then make something up for previous one
+ // so that we can start interpolation without waiting for next.
+ if (start == null)
+ {
+ start = new DataPoint
+ {
+ timeStamp = Time.time - syncInterval,
+ // local position/rotation for VR support
+ localPosition = targetComponent.transform.localPosition,
+ localRotation = targetComponent.transform.localRotation,
+ localScale = targetComponent.transform.localScale,
+ movementSpeed = temp.movementSpeed
+ };
+ }
+ // -> second or nth data point? then update previous, but:
+ // we start at where ever we are right now, so that it's
+ // perfectly smooth and we don't jump anywhere
+ //
+ // example if we are at 'x':
+ //
+ // A--x->B
+ //
+ // and then receive a new point C:
+ //
+ // A--x--B
+ // |
+ // |
+ // C
+ //
+ // then we don't want to just jump to B and start interpolation:
+ //
+ // x
+ // |
+ // |
+ // C
+ //
+ // we stay at 'x' and interpolate from there to C:
+ //
+ // x..B
+ // \ .
+ // \.
+ // C
+ //
+ else
+ {
+ float oldDistance = Vector3.Distance(start.localPosition, goal.localPosition);
+ float newDistance = Vector3.Distance(goal.localPosition, temp.localPosition);
+
+ start = goal;
+
+ // teleport / lag / obstacle detection: only continue at current
+ // position if we aren't too far away
+ //
+ // // local position/rotation for VR support
+ if (Vector3.Distance(targetComponent.transform.localPosition, start.localPosition) < oldDistance + newDistance)
+ {
+ start.localPosition = targetComponent.transform.localPosition;
+ start.localRotation = targetComponent.transform.localRotation;
+ start.localScale = targetComponent.transform.localScale;
+ }
+ }
+
+ // set new destination in any case. new data is best data.
+ goal = temp;
+ }
+
+ public override void OnDeserialize(NetworkReader reader, bool initialState)
+ {
+ // deserialize
+ DeserializeFromReader(reader);
+ }
+
+ // local authority client sends sync message to server for broadcasting
+ [Command]
+ void CmdClientToServerSync(byte[] payload)
+ {
+ // deserialize payload
+ NetworkReader reader = new NetworkReader(payload);
+ DeserializeFromReader(reader);
+
+ // server-only mode does no interpolation to save computations,
+ // but let's set the position directly
+ if (isServer && !isClient)
+ ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
+
+ // set dirty so that OnSerialize broadcasts it
+ SetDirtyBit(1UL);
+ }
+
+ // where are we in the timeline between start and goal? [0,1]
+ static float CurrentInterpolationFactor(DataPoint start, DataPoint goal)
+ {
+ if (start != null)
+ {
+ float difference = goal.timeStamp - start.timeStamp;
+
+ // the moment we get 'goal', 'start' is supposed to
+ // start, so elapsed time is based on:
+ float elapsed = Time.time - goal.timeStamp;
+ return difference > 0 ? elapsed / difference : 0; // avoid NaN
+ }
+ return 0;
+ }
+
+ static Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition)
+ {
+ if (start != null)
+ {
+ // Option 1: simply interpolate based on time. but stutter
+ // will happen, it's not that smooth. especially noticeable if
+ // the camera automatically follows the player
+ // float t = CurrentInterpolationFactor();
+ // return Vector3.Lerp(start.position, goal.position, t);
+
+ // Option 2: always += speed
+ // -> speed is 0 if we just started after idle, so always use max
+ // for best results
+ float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed);
+ return Vector3.MoveTowards(currentPosition, goal.localPosition, speed * Time.deltaTime);
+ }
+ return currentPosition;
+ }
+
+ static Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation)
+ {
+ if (start != null)
+ {
+ float t = CurrentInterpolationFactor(start, goal);
+ return Quaternion.Slerp(start.localRotation, goal.localRotation, t);
+ }
+ return defaultRotation;
+ }
+
+ static Vector3 InterpolateScale(DataPoint start, DataPoint goal, Vector3 currentScale)
+ {
+ if (start != null)
+ {
+ float t = CurrentInterpolationFactor(start, goal);
+ return Vector3.Lerp(start.localScale, goal.localScale, t);
+ }
+ return currentScale;
+ }
+
+ // teleport / lag / stuck detection
+ // -> checking distance is not enough since there could be just a tiny
+ // fence between us and the goal
+ // -> checking time always works, this way we just teleport if we still
+ // didn't reach the goal after too much time has elapsed
+ bool NeedsTeleport()
+ {
+ // calculate time between the two data points
+ float startTime = start != null ? start.timeStamp : Time.time - syncInterval;
+ float goalTime = goal != null ? goal.timeStamp : Time.time;
+ float difference = goalTime - startTime;
+ float timeSinceGoalReceived = Time.time - goalTime;
+ return timeSinceGoalReceived > difference * 5;
+ }
+
+ // moved since last time we checked it?
+ bool HasEitherMovedRotatedScaled()
+ {
+ // moved or rotated or scaled?
+ // local position/rotation/scale for VR support
+ bool moved = lastPosition != targetComponent.transform.localPosition;
+ bool rotated = lastRotation != targetComponent.transform.localRotation;
+ bool scaled = lastScale != targetComponent.transform.localScale;
+
+ // save last for next frame to compare
+ // (only if change was detected. otherwise slow moving objects might
+ // never sync because of C#'s float comparison tolerance. see also:
+ // https://github.com/vis2k/Mirror/pull/428)
+ bool change = moved || rotated || scaled;
+ if (change)
+ {
+ // local position/rotation for VR support
+ lastPosition = targetComponent.transform.localPosition;
+ lastRotation = targetComponent.transform.localRotation;
+ lastScale = targetComponent.transform.localScale;
+ }
+ return change;
+ }
+
+ // set position carefully depending on the target component
+ void ApplyPositionRotationScale(Vector3 position, Quaternion rotation, Vector3 scale)
+ {
+ // local position/rotation for VR support
+ targetComponent.transform.localPosition = position;
+ if (Compression.NoRotation != compressRotation)
+ {
+ targetComponent.transform.localRotation = rotation;
+ }
+ targetComponent.transform.localScale = scale;
+ }
+
+ void Update()
+ {
+ // if server then always sync to others.
+ if (isServer)
+ {
+ // just use OnSerialize via SetDirtyBit only sync when position
+ // changed. set dirty bits 0 or 1
+ SetDirtyBit(HasEitherMovedRotatedScaled() ? 1UL : 0UL);
+ }
+
+ // no 'else if' since host mode would be both
+ if (isClient)
+ {
+ // send to server if we have local authority (and aren't the server)
+ // -> only if connectionToServer has been initialized yet too
+ if (!isServer && hasAuthority)
+ {
+ // check only each 'syncInterval'
+ if (Time.time - lastClientSendTime >= syncInterval)
+ {
+ if (HasEitherMovedRotatedScaled())
+ {
+ // serialize
+ // local position/rotation for VR support
+ NetworkWriter writer = new NetworkWriter();
+ SerializeIntoWriter(writer, targetComponent.transform.localPosition, targetComponent.transform.localRotation, compressRotation, targetComponent.transform.localScale);
+
+ // send to server
+ CmdClientToServerSync(writer.ToArray());
+ }
+ lastClientSendTime = Time.time;
+ }
+ }
+
+ // apply interpolation on client for all players
+ // unless this client has authority over the object. could be
+ // himself or another object that he was assigned authority over
+ if (!hasAuthority)
+ {
+ // received one yet? (initialized?)
+ if (goal != null)
+ {
+ // teleport or interpolate
+ if (NeedsTeleport())
+ {
+ // local position/rotation for VR support
+ ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale);
+ }
+ else
+ {
+ // local position/rotation for VR support
+ ApplyPositionRotationScale(InterpolatePosition(start, goal, targetComponent.transform.localPosition),
+ InterpolateRotation(start, goal, targetComponent.transform.localRotation),
+ InterpolateScale(start, goal, targetComponent.transform.localScale));
+ }
+ }
+ }
+ }
+ }
+
+ static void DrawDataPointGizmo(DataPoint data, Color color)
+ {
+ // use a little offset because transform.localPosition might be in
+ // the ground in many cases
+ Vector3 offset = Vector3.up * 0.01f;
+
+ // draw position
+ Gizmos.color = color;
+ Gizmos.DrawSphere(data.localPosition + offset, 0.5f);
+
+ // draw forward and up
+ Gizmos.color = Color.blue; // like unity move tool
+ Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.forward);
+
+ Gizmos.color = Color.green; // like unity move tool
+ Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.up);
+ }
+
+ static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color)
+ {
+ Gizmos.color = color;
+ Gizmos.DrawLine(data1.localPosition, data2.localPosition);
+ }
+
+ // draw the data points for easier debugging
+ void OnDrawGizmos()
+ {
+ // draw start and goal points
+ if (start != null) DrawDataPointGizmo(start, Color.gray);
+ if (goal != null) DrawDataPointGizmo(goal, Color.white);
+
+ // draw line between them
+ if (start != null && goal != null) DrawLineBetweenDataPoints(start, goal, Color.cyan);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkTransformBase.cs.meta b/Assets/Packages/Mirror/Components/NetworkTransformBase.cs.meta
new file mode 100644
index 0000000..2c3c3e1
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransformBase.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2e77294d8ccbc4e7cb8ca2bd0d3e99ea
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Components/NetworkTransformChild.cs b/Assets/Packages/Mirror/Components/NetworkTransformChild.cs
new file mode 100644
index 0000000..03a364f
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransformChild.cs
@@ -0,0 +1,16 @@
+using UnityEngine;
+
+namespace Mirror
+{
+ ///
+ /// A component to synchronize the position of child transforms of networked objects.
+ /// There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the recieved values.
+ ///
+ [AddComponentMenu("Network/NetworkTransformChild")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkTransformChild.html")]
+ public class NetworkTransformChild : NetworkTransformBase
+ {
+ public Transform target;
+ protected override Transform targetComponent => target;
+ }
+}
diff --git a/Assets/Packages/Mirror/Components/NetworkTransformChild.cs.meta b/Assets/Packages/Mirror/Components/NetworkTransformChild.cs.meta
new file mode 100644
index 0000000..9c068f2
--- /dev/null
+++ b/Assets/Packages/Mirror/Components/NetworkTransformChild.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 734b48bea0b204338958ee3d885e11f0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor.meta b/Assets/Packages/Mirror/Editor.meta
new file mode 100644
index 0000000..f679511
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 2539267b6934a4026a505690a1e1eda2
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef b/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef
new file mode 100644
index 0000000..d18558b
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef
@@ -0,0 +1,16 @@
+{
+ "name": "Mirror.Editor",
+ "references": [
+ "Mirror"
+ ],
+ "optionalUnityReferences": [],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": []
+}
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef.meta b/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef.meta
new file mode 100644
index 0000000..e2e6f2a
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Mirror.Editor.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1c7c33eb5480dd24c9e29a8250c1a775
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs b/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs
new file mode 100644
index 0000000..20db50b
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs
@@ -0,0 +1,5 @@
+// This file was removed in Mirror 3.4.9
+// The purpose of this file is to get the old file overwritten
+// when users update from the asset store to prevent a flood of errors
+// from having the old file still in the project as a straggler.
+// This file will be dropped from the Asset Store package in May 2019
diff --git a/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs.meta b/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs.meta
new file mode 100644
index 0000000..1b537bc
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkAnimatorEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9589e903d4e98490fb1157762a307fd7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs b/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs
new file mode 100644
index 0000000..44d55f5
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs
@@ -0,0 +1,197 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using UnityEditor;
+using UnityEngine;
+
+namespace Mirror
+{
+ [CustomEditor(typeof(NetworkBehaviour), true)]
+ [CanEditMultipleObjects]
+ public class NetworkBehaviourInspector : Editor
+ {
+ bool initialized;
+ protected List syncVarNames = new List();
+ bool syncsAnything;
+ bool[] showSyncLists;
+
+ readonly GUIContent syncVarIndicatorContent = new GUIContent("SyncVar", "This variable has been marked with the [SyncVar] attribute.");
+
+ internal virtual bool HideScriptField => false;
+
+ // does this type sync anything? otherwise we don't need to show syncInterval
+ bool SyncsAnything(Type scriptClass)
+ {
+ // has OnSerialize that is not in NetworkBehaviour?
+ // then it either has a syncvar or custom OnSerialize. either way
+ // this means we have something to sync.
+ MethodInfo method = scriptClass.GetMethod("OnSerialize");
+ if (method != null && method.DeclaringType != typeof(NetworkBehaviour))
+ {
+ return true;
+ }
+
+ // SyncObjects are serialized in NetworkBehaviour.OnSerialize, which
+ // is always there even if we don't use SyncObjects. so we need to
+ // search for SyncObjects manually.
+ // (look for 'Mirror.Sync'. not '.SyncObject' because we'd have to
+ // check base type for that again)
+ foreach (FieldInfo field in scriptClass.GetFields())
+ {
+ if (field.FieldType.BaseType != null &&
+ field.FieldType.BaseType.FullName != null &&
+ field.FieldType.BaseType.FullName.Contains("Mirror.Sync"))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ void Init(MonoScript script)
+ {
+ initialized = true;
+ Type scriptClass = script.GetClass();
+
+ // find public SyncVars to show (user doesn't want protected ones to be shown in inspector)
+ foreach (FieldInfo field in scriptClass.GetFields(BindingFlags.Public | BindingFlags.Instance))
+ {
+ Attribute[] fieldMarkers = (Attribute[])field.GetCustomAttributes(typeof(SyncVarAttribute), true);
+ if (fieldMarkers.Length > 0)
+ {
+ syncVarNames.Add(field.Name);
+ }
+ }
+
+ int numSyncLists = scriptClass.GetFields().Count(
+ field => field.FieldType.BaseType != null &&
+ field.FieldType.BaseType.Name.Contains("SyncList"));
+ if (numSyncLists > 0)
+ {
+ showSyncLists = new bool[numSyncLists];
+ }
+
+ syncsAnything = SyncsAnything(scriptClass);
+ }
+
+ public override void OnInspectorGUI()
+ {
+ if (!initialized)
+ {
+ serializedObject.Update();
+ SerializedProperty scriptProperty = serializedObject.FindProperty("m_Script");
+ if (scriptProperty == null)
+ return;
+
+ MonoScript targetScript = scriptProperty.objectReferenceValue as MonoScript;
+ Init(targetScript);
+ }
+
+ EditorGUI.BeginChangeCheck();
+ serializedObject.Update();
+
+ // Loop through properties and create one field (including children) for each top level property.
+ SerializedProperty property = serializedObject.GetIterator();
+ bool expanded = true;
+ while (property.NextVisible(expanded))
+ {
+ bool isSyncVar = syncVarNames.Contains(property.name);
+ if (property.propertyType == SerializedPropertyType.ObjectReference)
+ {
+ if (property.name == "m_Script")
+ {
+ if (HideScriptField)
+ {
+ continue;
+ }
+
+ EditorGUI.BeginDisabledGroup(true);
+ }
+
+ EditorGUILayout.PropertyField(property, true);
+
+ if (isSyncVar)
+ {
+ GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
+ }
+
+ if (property.name == "m_Script")
+ {
+ EditorGUI.EndDisabledGroup();
+ }
+ }
+ else
+ {
+ EditorGUILayout.BeginHorizontal();
+
+ EditorGUILayout.PropertyField(property, true);
+
+ if (isSyncVar)
+ {
+ GUILayout.Label(syncVarIndicatorContent, EditorStyles.miniLabel, GUILayout.Width(EditorStyles.miniLabel.CalcSize(syncVarIndicatorContent).x));
+ }
+
+ EditorGUILayout.EndHorizontal();
+ }
+ expanded = false;
+ }
+ serializedObject.ApplyModifiedProperties();
+ EditorGUI.EndChangeCheck();
+
+ // find SyncLists.. they are not properties.
+ int syncListIndex = 0;
+ foreach (FieldInfo field in serializedObject.targetObject.GetType().GetFields())
+ {
+ if (field.FieldType.BaseType != null && field.FieldType.BaseType.Name.Contains("SyncList"))
+ {
+ showSyncLists[syncListIndex] = EditorGUILayout.Foldout(showSyncLists[syncListIndex], "SyncList " + field.Name + " [" + field.FieldType.Name + "]");
+ if (showSyncLists[syncListIndex])
+ {
+ EditorGUI.indentLevel += 1;
+ if (field.GetValue(serializedObject.targetObject) is IEnumerable synclist)
+ {
+ int index = 0;
+ IEnumerator enu = synclist.GetEnumerator();
+ while (enu.MoveNext())
+ {
+ if (enu.Current != null)
+ {
+ EditorGUILayout.LabelField("Item:" + index, enu.Current.ToString());
+ }
+ index += 1;
+ }
+ }
+ EditorGUI.indentLevel -= 1;
+ }
+ syncListIndex += 1;
+ }
+ }
+
+ // does it sync anything? then show extra properties
+ // (no need to show it if the class only has Cmds/Rpcs and no sync)
+ if (syncsAnything)
+ {
+ NetworkBehaviour networkBehaviour = target as NetworkBehaviour;
+ if (networkBehaviour != null)
+ {
+ // syncMode
+ serializedObject.FindProperty("syncMode").enumValueIndex = (int)(SyncMode)
+ EditorGUILayout.EnumPopup("Network Sync Mode", networkBehaviour.syncMode);
+
+ // syncInterval
+ // [0,2] should be enough. anything >2s is too laggy anyway.
+ serializedObject.FindProperty("syncInterval").floatValue = EditorGUILayout.Slider(
+ new GUIContent("Network Sync Interval",
+ "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.)"),
+ networkBehaviour.syncInterval, 0, 2);
+
+ // apply
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+ }
+ }
+} //namespace
diff --git a/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs.meta b/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs.meta
new file mode 100644
index 0000000..78d9fa8
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkBehaviourInspector.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f02853db46b6346e4866594a96c3b0e7
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs b/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs
new file mode 100644
index 0000000..3a79340
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs
@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+
+namespace Mirror
+{
+ [CustomEditor(typeof(NetworkIdentity), true)]
+ [CanEditMultipleObjects]
+ public class NetworkIdentityEditor : Editor
+ {
+ SerializedProperty serverOnlyProperty;
+ SerializedProperty localPlayerAuthorityProperty;
+
+ readonly GUIContent serverOnlyLabel = new GUIContent("Server Only", "True if the object should only exist on the server.");
+ readonly GUIContent localPlayerAuthorityLabel = new GUIContent("Local Player Authority", "True if this object will be controlled by a player on a client.");
+ readonly GUIContent spawnLabel = new GUIContent("Spawn Object", "This causes an unspawned server object to be spawned on clients");
+
+ NetworkIdentity networkIdentity;
+ bool initialized;
+ bool showObservers;
+
+ void Init()
+ {
+ if (initialized)
+ {
+ return;
+ }
+ initialized = true;
+ networkIdentity = target as NetworkIdentity;
+
+ serverOnlyProperty = serializedObject.FindProperty("serverOnly");
+ localPlayerAuthorityProperty = serializedObject.FindProperty("localPlayerAuthority");
+ }
+
+ public override void OnInspectorGUI()
+ {
+ if (serverOnlyProperty == null)
+ {
+ initialized = false;
+ }
+
+ Init();
+
+ serializedObject.Update();
+
+ if (serverOnlyProperty.boolValue)
+ {
+ EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
+ EditorGUILayout.LabelField("Local Player Authority cannot be set for server-only objects");
+ }
+ else if (localPlayerAuthorityProperty.boolValue)
+ {
+ EditorGUILayout.LabelField("Server Only cannot be set for Local Player Authority objects");
+ EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
+ }
+ else
+ {
+ EditorGUILayout.PropertyField(serverOnlyProperty, serverOnlyLabel);
+ EditorGUILayout.PropertyField(localPlayerAuthorityProperty, localPlayerAuthorityLabel);
+ }
+
+ serializedObject.ApplyModifiedProperties();
+
+ if (!Application.isPlaying)
+ {
+ return;
+ }
+
+ // Runtime actions below here
+
+ EditorGUILayout.Separator();
+
+ if (networkIdentity.observers != null && networkIdentity.observers.Count > 0)
+ {
+ showObservers = EditorGUILayout.Foldout(showObservers, "Observers");
+ if (showObservers)
+ {
+ EditorGUI.indentLevel += 1;
+ foreach (KeyValuePair kvp in networkIdentity.observers)
+ {
+ if (kvp.Value.playerController != null)
+ EditorGUILayout.ObjectField("Connection " + kvp.Value.connectionId, kvp.Value.playerController.gameObject, typeof(GameObject), false);
+ else
+ EditorGUILayout.TextField("Connection " + kvp.Value.connectionId);
+ }
+ EditorGUI.indentLevel -= 1;
+ }
+ }
+
+ if (PrefabUtility.IsPartOfPrefabAsset(networkIdentity.gameObject))
+ return;
+
+ if (networkIdentity.gameObject.activeSelf && networkIdentity.netId == 0 && NetworkServer.active)
+ {
+ EditorGUILayout.BeginHorizontal();
+ EditorGUILayout.LabelField(spawnLabel);
+ if (GUILayout.Toggle(false, "Spawn", EditorStyles.miniButtonLeft))
+ {
+ NetworkServer.Spawn(networkIdentity.gameObject);
+ EditorUtility.SetDirty(target); // preview window STILL doens't update immediately..
+ }
+ EditorGUILayout.EndHorizontal();
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs.meta b/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs.meta
new file mode 100644
index 0000000..cb4a5cf
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkIdentityEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 1b6e3680cc14b4769bff378e5dbc3544
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs b/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs
new file mode 100644
index 0000000..9699f6d
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs
@@ -0,0 +1,280 @@
+using System.Collections.Generic;
+using UnityEditor;
+using UnityEngine;
+using UnityObject = UnityEngine.Object;
+
+namespace Mirror
+{
+ [CustomPreview(typeof(GameObject))]
+ class NetworkInformationPreview : ObjectPreview
+ {
+ class NetworkIdentityInfo
+ {
+ public GUIContent name;
+ public GUIContent value;
+ }
+
+ class NetworkBehaviourInfo
+ {
+ // This is here just so we can check if it's enabled/disabled
+ public NetworkBehaviour behaviour;
+ public GUIContent name;
+ }
+
+ class Styles
+ {
+ public GUIStyle labelStyle = new GUIStyle(EditorStyles.label);
+ public GUIStyle componentName = new GUIStyle(EditorStyles.boldLabel);
+ public GUIStyle disabledName = new GUIStyle(EditorStyles.miniLabel);
+
+ public Styles()
+ {
+ Color fontColor = new Color(0.7f, 0.7f, 0.7f);
+ labelStyle.padding.right += 20;
+ labelStyle.normal.textColor = fontColor;
+ labelStyle.active.textColor = fontColor;
+ labelStyle.focused.textColor = fontColor;
+ labelStyle.hover.textColor = fontColor;
+ labelStyle.onNormal.textColor = fontColor;
+ labelStyle.onActive.textColor = fontColor;
+ labelStyle.onFocused.textColor = fontColor;
+ labelStyle.onHover.textColor = fontColor;
+
+ componentName.normal.textColor = fontColor;
+ componentName.active.textColor = fontColor;
+ componentName.focused.textColor = fontColor;
+ componentName.hover.textColor = fontColor;
+ componentName.onNormal.textColor = fontColor;
+ componentName.onActive.textColor = fontColor;
+ componentName.onFocused.textColor = fontColor;
+ componentName.onHover.textColor = fontColor;
+
+ disabledName.normal.textColor = fontColor;
+ disabledName.active.textColor = fontColor;
+ disabledName.focused.textColor = fontColor;
+ disabledName.hover.textColor = fontColor;
+ disabledName.onNormal.textColor = fontColor;
+ disabledName.onActive.textColor = fontColor;
+ disabledName.onFocused.textColor = fontColor;
+ disabledName.onHover.textColor = fontColor;
+ }
+ }
+
+ List info;
+ List behavioursInfo;
+ NetworkIdentity identity;
+ GUIContent title;
+ Styles styles = new Styles();
+
+ public override void Initialize(UnityObject[] targets)
+ {
+ base.Initialize(targets);
+ GetNetworkInformation(target as GameObject);
+ }
+
+ public override GUIContent GetPreviewTitle()
+ {
+ if (title == null)
+ {
+ title = new GUIContent("Network Information");
+ }
+ return title;
+ }
+
+ public override bool HasPreviewGUI()
+ {
+ return info != null && info.Count > 0;
+ }
+
+ public override void OnPreviewGUI(Rect r, GUIStyle background)
+ {
+ if (Event.current.type != EventType.Repaint)
+ return;
+
+ if (info == null || info.Count == 0)
+ return;
+
+ if (styles == null)
+ styles = new Styles();
+
+ // Get required label size for the names of the information values we're going to show
+ // There are two columns, one with label for the name of the info and the next for the value
+ Vector2 maxNameLabelSize = new Vector2(140, 16);
+ Vector2 maxValueLabelSize = GetMaxNameLabelSize();
+
+ //Apply padding
+ RectOffset previewPadding = new RectOffset(-5, -5, -5, -5);
+ Rect paddedr = previewPadding.Add(r);
+
+ //Centering
+ float initialX = paddedr.x + 10;
+ float initialY = paddedr.y + 10;
+
+ Rect labelRect = new Rect(initialX, initialY, maxNameLabelSize.x, maxNameLabelSize.y);
+ Rect idLabelRect = new Rect(maxNameLabelSize.x, initialY, maxValueLabelSize.x, maxValueLabelSize.y);
+
+ foreach (NetworkIdentityInfo info in info)
+ {
+ GUI.Label(labelRect, info.name, styles.labelStyle);
+ GUI.Label(idLabelRect, info.value, styles.componentName);
+ labelRect.y += labelRect.height;
+ labelRect.x = initialX;
+ idLabelRect.y += idLabelRect.height;
+ }
+
+ // Show behaviours list in a different way than the name/value pairs above
+ float lastY = labelRect.y;
+ if (behavioursInfo != null && behavioursInfo.Count > 0)
+ {
+ Vector2 maxBehaviourLabelSize = GetMaxBehaviourLabelSize();
+ Rect behaviourRect = new Rect(initialX, labelRect.y + 10, maxBehaviourLabelSize.x, maxBehaviourLabelSize.y);
+
+ GUI.Label(behaviourRect, new GUIContent("Network Behaviours"), styles.labelStyle);
+ behaviourRect.x += 20; // indent names
+ behaviourRect.y += behaviourRect.height;
+
+ foreach (NetworkBehaviourInfo info in behavioursInfo)
+ {
+ if (info.behaviour == null)
+ {
+ // could be the case in the editor after existing play mode.
+ continue;
+ }
+
+ GUI.Label(behaviourRect, info.name, info.behaviour.enabled ? styles.componentName : styles.disabledName);
+ behaviourRect.y += behaviourRect.height;
+ lastY = behaviourRect.y;
+ }
+
+ if (identity.observers != null && identity.observers.Count > 0)
+ {
+ Rect observerRect = new Rect(initialX, lastY + 10, 200, 20);
+
+ GUI.Label(observerRect, new GUIContent("Network observers"), styles.labelStyle);
+ observerRect.x += 20; // indent names
+ observerRect.y += observerRect.height;
+
+ foreach (KeyValuePair kvp in identity.observers)
+ {
+ GUI.Label(observerRect, kvp.Value.address + ":" + kvp.Value.connectionId, styles.componentName);
+ observerRect.y += observerRect.height;
+ lastY = observerRect.y;
+ }
+ }
+
+ if (identity.clientAuthorityOwner != null)
+ {
+ Rect ownerRect = new Rect(initialX, lastY + 10, 400, 20);
+ GUI.Label(ownerRect, new GUIContent("Client Authority: " + identity.clientAuthorityOwner), styles.labelStyle);
+ }
+ }
+ }
+
+ // Get the maximum size used by the value of information items
+ Vector2 GetMaxNameLabelSize()
+ {
+ Vector2 maxLabelSize = Vector2.zero;
+ foreach (NetworkIdentityInfo info in info)
+ {
+ Vector2 labelSize = styles.labelStyle.CalcSize(info.value);
+ if (maxLabelSize.x < labelSize.x)
+ {
+ maxLabelSize.x = labelSize.x;
+ }
+ if (maxLabelSize.y < labelSize.y)
+ {
+ maxLabelSize.y = labelSize.y;
+ }
+ }
+ return maxLabelSize;
+ }
+
+ Vector2 GetMaxBehaviourLabelSize()
+ {
+ Vector2 maxLabelSize = Vector2.zero;
+ foreach (NetworkBehaviourInfo behaviour in behavioursInfo)
+ {
+ Vector2 labelSize = styles.labelStyle.CalcSize(behaviour.name);
+ if (maxLabelSize.x < labelSize.x)
+ {
+ maxLabelSize.x = labelSize.x;
+ }
+ if (maxLabelSize.y < labelSize.y)
+ {
+ maxLabelSize.y = labelSize.y;
+ }
+ }
+ return maxLabelSize;
+ }
+
+ void GetNetworkInformation(GameObject gameObject)
+ {
+ identity = gameObject.GetComponent();
+ if (identity != null)
+ {
+ info = new List
+ {
+ GetAssetId(),
+ GetString("Scene ID", identity.sceneId.ToString("X"))
+ };
+
+ if (!Application.isPlaying)
+ {
+ return;
+ }
+
+ info.Add(GetString("Network ID", identity.netId.ToString()));
+
+ info.Add(GetBoolean("Is Client", identity.isClient));
+ info.Add(GetBoolean("Is Server", identity.isServer));
+ info.Add(GetBoolean("Has Authority", identity.hasAuthority));
+ info.Add(GetBoolean("Is Local Player", identity.isLocalPlayer));
+
+ NetworkBehaviour[] behaviours = gameObject.GetComponents();
+ if (behaviours.Length > 0)
+ {
+ behavioursInfo = new List();
+ foreach (NetworkBehaviour behaviour in behaviours)
+ {
+ NetworkBehaviourInfo info = new NetworkBehaviourInfo
+ {
+ name = new GUIContent(behaviour.GetType().FullName),
+ behaviour = behaviour
+ };
+ behavioursInfo.Add(info);
+ }
+ }
+ }
+ }
+
+ NetworkIdentityInfo GetAssetId()
+ {
+ string assetId = identity.assetId.ToString();
+ if (string.IsNullOrEmpty(assetId))
+ {
+ assetId = "";
+ }
+ return GetString("Asset ID", assetId);
+ }
+
+ static NetworkIdentityInfo GetString(string name, string value)
+ {
+ NetworkIdentityInfo info = new NetworkIdentityInfo
+ {
+ name = new GUIContent(name),
+ value = new GUIContent(value)
+ };
+ return info;
+ }
+
+ static NetworkIdentityInfo GetBoolean(string name, bool value)
+ {
+ NetworkIdentityInfo info = new NetworkIdentityInfo
+ {
+ name = new GUIContent(name),
+ value = new GUIContent((value ? "Yes" : "No"))
+ };
+ return info;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs.meta b/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs.meta
new file mode 100644
index 0000000..9bf2de4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkInformationPreview.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 51a99294efe134232932c34606737356
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs b/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs
new file mode 100644
index 0000000..0552cfa
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs
@@ -0,0 +1,112 @@
+using UnityEditor;
+using UnityEditorInternal;
+using UnityEngine;
+
+namespace Mirror
+{
+ [CustomEditor(typeof(NetworkManager), true)]
+ [CanEditMultipleObjects]
+ public class NetworkManagerEditor : Editor
+ {
+ SerializedProperty spawnListProperty;
+
+ ReorderableList spawnList;
+
+ protected NetworkManager networkManager;
+
+ protected void Init()
+ {
+ if (spawnList == null)
+ {
+
+ networkManager = target as NetworkManager;
+
+ spawnListProperty = serializedObject.FindProperty("spawnPrefabs");
+
+ spawnList = new ReorderableList(serializedObject, spawnListProperty)
+ {
+ drawHeaderCallback = DrawHeader,
+ drawElementCallback = DrawChild,
+ onReorderCallback = Changed,
+ onRemoveCallback = RemoveButton,
+ onChangedCallback = Changed,
+ onAddCallback = AddButton,
+ elementHeight = 16 // this uses a 16x16 icon. other sizes make it stretch.
+ };
+ }
+ }
+
+ public override void OnInspectorGUI()
+ {
+ Init();
+ DrawDefaultInspector();
+ EditorGUI.BeginChangeCheck();
+ spawnList.DoLayoutList();
+ if (EditorGUI.EndChangeCheck())
+ {
+ serializedObject.ApplyModifiedProperties();
+ }
+ }
+
+ static void DrawHeader(Rect headerRect)
+ {
+ GUI.Label(headerRect, "Registered Spawnable Prefabs:");
+ }
+
+ internal void DrawChild(Rect r, int index, bool isActive, bool isFocused)
+ {
+ SerializedProperty prefab = spawnListProperty.GetArrayElementAtIndex(index);
+ GameObject go = (GameObject)prefab.objectReferenceValue;
+
+ GUIContent label;
+ if (go == null)
+ {
+ label = new GUIContent("Empty", "Drag a prefab with a NetworkIdentity here");
+ }
+ else
+ {
+ NetworkIdentity identity = go.GetComponent();
+ label = new GUIContent(go.name, identity != null ? "AssetId: [" + identity.assetId + "]" : "No Network Identity");
+ }
+
+ GameObject newGameObject = (GameObject)EditorGUI.ObjectField(r, label, go, typeof(GameObject), false);
+
+ if (newGameObject != go)
+ {
+ if (newGameObject != null && !newGameObject.GetComponent())
+ {
+ Debug.LogError("Prefab " + newGameObject + " cannot be added as spawnable as it doesn't have a NetworkIdentity.");
+ return;
+ }
+ prefab.objectReferenceValue = newGameObject;
+ }
+ }
+
+ internal void Changed(ReorderableList list)
+ {
+ EditorUtility.SetDirty(target);
+ }
+
+ internal void AddButton(ReorderableList list)
+ {
+ spawnListProperty.arraySize += 1;
+ list.index = spawnListProperty.arraySize - 1;
+
+ SerializedProperty obj = spawnListProperty.GetArrayElementAtIndex(spawnListProperty.arraySize - 1);
+ obj.objectReferenceValue = null;
+
+ spawnList.index = spawnList.count - 1;
+
+ Changed(list);
+ }
+
+ internal void RemoveButton(ReorderableList list)
+ {
+ spawnListProperty.DeleteArrayElementAtIndex(spawnList.index);
+ if (list.index >= spawnListProperty.arraySize)
+ {
+ list.index = spawnListProperty.arraySize - 1;
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs.meta b/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs.meta
new file mode 100644
index 0000000..7fe8dbc
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkManagerEditor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 519712eb07f7a44039df57664811c2c5
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs b/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs
new file mode 100644
index 0000000..866a50b
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs
@@ -0,0 +1,90 @@
+using System.Collections.Generic;
+using System.Linq;
+using UnityEditor;
+using UnityEditor.Callbacks;
+using UnityEngine;
+
+namespace Mirror
+{
+ public class NetworkScenePostProcess : MonoBehaviour
+ {
+ [PostProcessScene]
+ public static void OnPostProcessScene()
+ {
+ // find all NetworkIdentities in all scenes
+ // => can't limit it to GetActiveScene() because that wouldn't work
+ // for additive scene loads (the additively loaded scene is never
+ // the active scene)
+ // => ignore DontDestroyOnLoad scene! this avoids weird situations
+ // like in NetworkZones when we destroy the local player and
+ // load another scene afterwards, yet the local player is still
+ // in the FindObjectsOfType result with scene=DontDestroyOnLoad
+ // for some reason
+ // => OfTypeAll so disabled objects are included too
+ // => Unity 2019 returns prefabs here too, so filter them out.
+ IEnumerable identities = Resources.FindObjectsOfTypeAll()
+ .Where(identity => identity.gameObject.hideFlags != HideFlags.NotEditable &&
+ identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
+ identity.gameObject.scene.name != "DontDestroyOnLoad" &&
+ !PrefabUtility.IsPartOfPrefabAsset(identity.gameObject));
+
+ foreach (NetworkIdentity identity in identities)
+ {
+ // if we had a [ConflictComponent] attribute that would be better than this check.
+ // also there is no context about which scene this is in.
+ if (identity.GetComponent() != null)
+ {
+ Debug.LogError("NetworkManager has a NetworkIdentity component. This will cause the NetworkManager object to be disabled, so it is not recommended.");
+ }
+
+ // not spawned before?
+ // OnPostProcessScene is called after additive scene loads too,
+ // and we don't want to set main scene's objects inactive again
+ if (!identity.isClient && !identity.isServer)
+ {
+ // valid scene object?
+ // otherwise it might be an unopened scene that still has null
+ // sceneIds. builds are interrupted if they contain 0 sceneIds,
+ // but it's still possible that we call LoadScene in Editor
+ // for a previously unopened scene.
+ // (and only do SetActive if this was actually a scene object)
+ if (identity.sceneId != 0)
+ {
+ // set scene hash
+ identity.SetSceneIdSceneHashPartInternal();
+
+ // disable it
+ // note: NetworkIdentity.OnDisable adds itself to the
+ // spawnableObjects dictionary (only if sceneId != 0)
+ identity.gameObject.SetActive(false);
+
+ // safety check for prefabs with more than one NetworkIdentity
+ #if UNITY_2018_2_OR_NEWER
+ GameObject prefabGO = PrefabUtility.GetCorrespondingObjectFromSource(identity.gameObject) as GameObject;
+ #else
+ GameObject prefabGO = PrefabUtility.GetPrefabParent(identity.gameObject) as GameObject;
+ #endif
+ if (prefabGO)
+ {
+ #if UNITY_2018_3_OR_NEWER
+ GameObject prefabRootGO = prefabGO.transform.root.gameObject;
+ #else
+ GameObject prefabRootGO = PrefabUtility.FindPrefabRoot(prefabGO);
+ #endif
+ if (prefabRootGO)
+ {
+ if (prefabRootGO.GetComponentsInChildren().Length > 1)
+ {
+ Debug.LogWarningFormat("Prefab '{0}' has several NetworkIdentity components attached to itself or its children, this is not supported.", prefabRootGO.name);
+ }
+ }
+ }
+ }
+ // throwing an exception would only show it for one object
+ // because this function would return afterwards.
+ else Debug.LogError("Scene " + identity.gameObject.scene.path + " needs to be opened and resaved, because the scene object " + identity.name + " has no valid sceneId yet.");
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs.meta b/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs.meta
new file mode 100644
index 0000000..b567cc9
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/NetworkScenePostProcess.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3ec1c414d821444a9e77f18a2c130ea
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs b/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs
new file mode 100644
index 0000000..dd55178
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using UnityEditor;
+
+namespace Mirror
+{
+ static class PreprocessorDefine
+ {
+ ///
+ /// Add define symbols as soon as Unity gets done compiling.
+ ///
+ [InitializeOnLoadMethod]
+ static void AddDefineSymbols()
+ {
+ HashSet defines = new HashSet(PlayerSettings.GetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup).Split(';'))
+ {
+ "MIRROR",
+ "MIRROR_1726_OR_NEWER",
+ "MIRROR_3_0_OR_NEWER",
+ "MIRROR_3_12_OR_NEWER"
+ };
+ PlayerSettings.SetScriptingDefineSymbolsForGroup(EditorUserBuildSettings.selectedBuildTargetGroup, string.Join(";", defines));
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs.meta b/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs.meta
new file mode 100644
index 0000000..30806d0
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/PreprocessorDefine.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f1d66fe74ec6f42dd974cba37d25d453
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/SceneDrawer.cs b/Assets/Packages/Mirror/Editor/SceneDrawer.cs
new file mode 100644
index 0000000..b6c04f4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/SceneDrawer.cs
@@ -0,0 +1,56 @@
+using UnityEditor;
+using UnityEngine;
+
+namespace Mirror
+{
+
+ [CustomPropertyDrawer(typeof(SceneAttribute))]
+ public class SceneDrawer : PropertyDrawer
+ {
+
+ public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
+ {
+
+ if (property.propertyType == SerializedPropertyType.String)
+ {
+ SceneAsset sceneObject = GetSceneObject(property.stringValue);
+ SceneAsset scene = (SceneAsset)EditorGUI.ObjectField(position, label, sceneObject, typeof(SceneAsset), true);
+ if (scene == null)
+ {
+ property.stringValue = "";
+ }
+ else if (scene.name != property.stringValue)
+ {
+ SceneAsset sceneObj = GetSceneObject(scene.name);
+ if (sceneObj == null)
+ {
+ Debug.LogWarning("The scene " + scene.name + " cannot be used. To use this scene add it to the build settings for the project");
+ }
+ else
+ {
+ property.stringValue = scene.name;
+ }
+ }
+ }
+ else
+ EditorGUI.LabelField(position, label.text, "Use [Scene] with strings.");
+ }
+ protected SceneAsset GetSceneObject(string sceneObjectName)
+ {
+ if (string.IsNullOrEmpty(sceneObjectName))
+ {
+ return null;
+ }
+
+ foreach (EditorBuildSettingsScene editorScene in EditorBuildSettings.scenes)
+ {
+ if (editorScene.path.IndexOf(sceneObjectName) != -1)
+ {
+ return AssetDatabase.LoadAssetAtPath(editorScene.path, typeof(SceneAsset)) as SceneAsset;
+ }
+ }
+ Debug.LogWarning("Scene [" + sceneObjectName + "] cannot be used. Add this scene to the 'Scenes in the Build' in build settings.");
+ return null;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/SceneDrawer.cs.meta b/Assets/Packages/Mirror/Editor/SceneDrawer.cs.meta
new file mode 100644
index 0000000..6a996dc
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/SceneDrawer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b24704a46211b4ea294aba8f58715cea
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver.meta b/Assets/Packages/Mirror/Editor/Weaver.meta
new file mode 100644
index 0000000..121fbf4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d9f8e6274119b4ce29e498cfb8aca8a4
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs b/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs
new file mode 100644
index 0000000..501d378
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs
@@ -0,0 +1,141 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using UnityEditor;
+using UnityEditor.Compilation;
+using UnityEngine;
+using UnityAssembly = UnityEditor.Compilation.Assembly;
+
+namespace Mirror.Weaver
+{
+ public static class CompilationFinishedHook
+ {
+ const string MirrorRuntimeAssemblyName = "Mirror";
+ const string MirrorWeaverAssemblyName = "Mirror.Weaver";
+
+ public static Action OnWeaverMessage; // delegate for subscription to Weaver debug messages
+ public static Action OnWeaverWarning; // delegate for subscription to Weaver warning messages
+ public static Action OnWeaverError; // delete for subscription to Weaver error messages
+
+ public static bool WeaverEnabled { get; set; } // controls whether we weave any assemblies when CompilationPipeline delegates are invoked
+ public static bool UnityLogEnabled = true; // controls weather Weaver errors are reported direct to the Unity console (tests enable this)
+ public static bool WeaveFailed { get; private set; } // holds the result status of our latest Weave operation
+
+ // debug message handler that also calls OnMessageMethod delegate
+ static void HandleMessage(string msg)
+ {
+ if (UnityLogEnabled) Debug.Log(msg);
+ if (OnWeaverMessage != null) OnWeaverMessage.Invoke(msg);
+ }
+
+ // warning message handler that also calls OnWarningMethod delegate
+ static void HandleWarning(string msg)
+ {
+ if (UnityLogEnabled) Debug.LogWarning(msg);
+ if (OnWeaverWarning != null) OnWeaverWarning.Invoke(msg);
+ }
+
+ // error message handler that also calls OnErrorMethod delegate
+ static void HandleError(string msg)
+ {
+ if (UnityLogEnabled) Debug.LogError(msg);
+ if (OnWeaverError != null) OnWeaverError.Invoke(msg);
+ }
+
+ [InitializeOnLoadMethod]
+ static void OnInitializeOnLoad()
+ {
+ CompilationPipeline.assemblyCompilationFinished += OnCompilationFinished;
+ }
+
+ static string FindMirrorRuntime()
+ {
+ foreach (UnityAssembly assembly in CompilationPipeline.GetAssemblies())
+ {
+ if (assembly.name == MirrorRuntimeAssemblyName)
+ {
+ return assembly.outputPath;
+ }
+ }
+ return "";
+ }
+
+ static bool CompilerMessagesContainError(CompilerMessage[] messages)
+ {
+ return messages.Any(msg => msg.type == CompilerMessageType.Error);
+ }
+
+ static void OnCompilationFinished(string assemblyPath, CompilerMessage[] messages)
+ {
+ // Do nothing if there were compile errors on the target
+ if (CompilerMessagesContainError(messages))
+ {
+ Debug.Log("Weaver: stop because compile errors on target");
+ return;
+ }
+
+ // Should not run on the editor only assemblies
+ if (assemblyPath.Contains("-Editor") || assemblyPath.Contains(".Editor"))
+ {
+ return;
+ }
+
+ // don't weave mirror files
+ string assemblyName = Path.GetFileNameWithoutExtension(assemblyPath);
+ if (assemblyName == MirrorRuntimeAssemblyName || assemblyName == MirrorWeaverAssemblyName)
+ {
+ return;
+ }
+
+ // find Mirror.dll
+ string mirrorRuntimeDll = FindMirrorRuntime();
+ if (string.IsNullOrEmpty(mirrorRuntimeDll))
+ {
+ Debug.LogError("Failed to find Mirror runtime assembly");
+ return;
+ }
+ if (!File.Exists(mirrorRuntimeDll))
+ {
+ // this is normal, it happens with any assembly that is built before mirror
+ // such as unity packages or your own assemblies
+ // those don't need to be weaved
+ // if any assembly depends on mirror, then it will be built after
+ return;
+ }
+
+ // find UnityEngine.CoreModule.dll
+ string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath();
+ if (string.IsNullOrEmpty(unityEngineCoreModuleDLL))
+ {
+ Debug.LogError("Failed to find UnityEngine assembly");
+ return;
+ }
+
+ // build directory list for later asm/symbol resolving using CompilationPipeline refs
+ HashSet dependencyPaths = new HashSet();
+ dependencyPaths.Add(Path.GetDirectoryName(assemblyPath));
+ foreach (UnityAssembly unityAsm in CompilationPipeline.GetAssemblies())
+ {
+ if (unityAsm.outputPath != assemblyPath) continue;
+
+ foreach (string unityAsmRef in unityAsm.compiledAssemblyReferences)
+ {
+ dependencyPaths.Add(Path.GetDirectoryName(unityAsmRef));
+ }
+ }
+
+ // passing null in the outputDirectory param will do an in-place update of the assembly
+ if (Program.Process(unityEngineCoreModuleDLL, mirrorRuntimeDll, null, new[] { assemblyPath }, dependencyPaths.ToArray(), HandleWarning, HandleError))
+ {
+ WeaveFailed = false;
+ //Debug.Log("Weaving succeeded for: " + assemblyPath);
+ }
+ else
+ {
+ WeaveFailed = true;
+ if (UnityLogEnabled) Debug.LogError("Weaving failed for: " + assemblyPath);
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta
new file mode 100644
index 0000000..ed537ab
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: de2aeb2e8068f421a9a1febe408f7051
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs b/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs
new file mode 100644
index 0000000..f2fad51
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs
@@ -0,0 +1,151 @@
+using System;
+using Mono.CecilX;
+
+namespace Mirror.Weaver
+{
+ public static class Extensions
+ {
+ public static bool IsDerivedFrom(this TypeDefinition td, TypeReference baseClass)
+ {
+ if (!td.IsClass)
+ return false;
+
+ // are ANY parent classes of baseClass?
+ TypeReference parent = td.BaseType;
+ while (parent != null)
+ {
+ string parentName = parent.FullName;
+
+ // strip generic parameters
+ int index = parentName.IndexOf('<');
+ if (index != -1)
+ {
+ parentName = parentName.Substring(0, index);
+ }
+
+ if (parentName == baseClass.FullName)
+ {
+ return true;
+ }
+ try
+ {
+ parent = parent.Resolve().BaseType;
+ }
+ catch (AssemblyResolutionException)
+ {
+ // this can happen for plugins.
+ //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
+ break;
+ }
+ }
+ return false;
+ }
+
+ public static TypeReference GetEnumUnderlyingType(this TypeDefinition td)
+ {
+ foreach (FieldDefinition field in td.Fields)
+ {
+ if (!field.IsStatic)
+ return field.FieldType;
+ }
+ throw new ArgumentException($"Invalid enum {td.FullName}");
+ }
+
+ public static bool ImplementsInterface(this TypeDefinition td, TypeReference baseInterface)
+ {
+ TypeDefinition typedef = td;
+ while (typedef != null)
+ {
+ foreach (InterfaceImplementation iface in typedef.Interfaces)
+ {
+ if (iface.InterfaceType.FullName == baseInterface.FullName)
+ return true;
+ }
+
+ try
+ {
+ TypeReference parent = typedef.BaseType;
+ typedef = parent?.Resolve();
+ }
+ catch (AssemblyResolutionException)
+ {
+ // this can happen for pluins.
+ //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ public static bool IsArrayType(this TypeReference tr)
+ {
+ if ((tr.IsArray && ((ArrayType)tr).ElementType.IsArray) || // jagged array
+ (tr.IsArray && ((ArrayType)tr).Rank > 1)) // multidimensional array
+ return false;
+ return true;
+ }
+
+ public static bool CanBeResolved(this TypeReference parent)
+ {
+ while (parent != null)
+ {
+ if (parent.Scope.Name == "Windows")
+ {
+ return false;
+ }
+
+ if (parent.Scope.Name == "mscorlib")
+ {
+ TypeDefinition resolved = parent.Resolve();
+ return resolved != null;
+ }
+
+ try
+ {
+ parent = parent.Resolve().BaseType;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+
+ // Given a method of a generic class such as ArraySegment.get_Count,
+ // and a generic instance such as ArraySegment
+ // Creates a reference to the specialized method ArraySegment.get_Count;
+ // Note that calling ArraySegment.get_Count directly gives an invalid IL error
+ public static MethodReference MakeHostInstanceGeneric(this MethodReference self, GenericInstanceType instanceType)
+ {
+
+ MethodReference reference = new MethodReference(self.Name, self.ReturnType, instanceType)
+ {
+ CallingConvention = self.CallingConvention,
+ HasThis = self.HasThis,
+ ExplicitThis = self.ExplicitThis
+ };
+
+ foreach (ParameterDefinition parameter in self.Parameters)
+ reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
+
+ foreach (GenericParameter generic_parameter in self.GenericParameters)
+ reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
+
+ return Weaver.CurrentAssembly.MainModule.ImportReference(reference);
+ }
+
+ public static CustomAttribute GetCustomAttribute(this MethodDefinition method, string attributeName)
+ {
+ foreach (CustomAttribute ca in method.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == attributeName)
+ return ca;
+ }
+ return null;
+ }
+
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs.meta
new file mode 100644
index 0000000..78660f9
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Extensions.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 562a5cf0254cc45738e9aa549a7100b2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs b/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs
new file mode 100644
index 0000000..a776954
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+using Mono.CecilX.Mdb;
+using Mono.CecilX.Pdb;
+
+namespace Mirror.Weaver
+{
+ class Helpers
+ {
+ // This code is taken from SerializationWeaver
+
+ class AddSearchDirectoryHelper
+ {
+ delegate void AddSearchDirectoryDelegate(string directory);
+ readonly AddSearchDirectoryDelegate _addSearchDirectory;
+
+ public AddSearchDirectoryHelper(IAssemblyResolver assemblyResolver)
+ {
+ // reflection is used because IAssemblyResolver doesn't implement AddSearchDirectory but both DefaultAssemblyResolver and NuGetAssemblyResolver do
+ MethodInfo addSearchDirectory = assemblyResolver.GetType().GetMethod("AddSearchDirectory", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string) }, null);
+ if (addSearchDirectory == null)
+ throw new Exception("Assembly resolver doesn't implement AddSearchDirectory method.");
+ _addSearchDirectory = (AddSearchDirectoryDelegate)Delegate.CreateDelegate(typeof(AddSearchDirectoryDelegate), assemblyResolver, addSearchDirectory);
+ }
+
+ public void AddSearchDirectory(string directory)
+ {
+ _addSearchDirectory(directory);
+ }
+ }
+
+ public static string UnityEngineDLLDirectoryName()
+ {
+ string directoryName = Path.GetDirectoryName(Assembly.GetExecutingAssembly().CodeBase);
+ return directoryName?.Replace(@"file:\", "");
+ }
+
+ public static ISymbolReaderProvider GetSymbolReaderProvider(string inputFile)
+ {
+ string nakedFileName = inputFile.Substring(0, inputFile.Length - 4);
+ if (File.Exists(nakedFileName + ".pdb"))
+ {
+ Console.WriteLine("Symbols will be read from " + nakedFileName + ".pdb");
+ return new PdbReaderProvider();
+ }
+ if (File.Exists(nakedFileName + ".dll.mdb"))
+ {
+ Console.WriteLine("Symbols will be read from " + nakedFileName + ".dll.mdb");
+ return new MdbReaderProvider();
+ }
+ Console.WriteLine("No symbols for " + inputFile);
+ return null;
+ }
+
+ public static string DestinationFileFor(string outputDir, string assemblyPath)
+ {
+ string fileName = Path.GetFileName(assemblyPath);
+ Debug.Assert(fileName != null, "fileName != null");
+
+ return Path.Combine(outputDir, fileName);
+ }
+
+ public static string PrettyPrintType(TypeReference type)
+ {
+ // generic instances, such as List
+ if (type.IsGenericInstance)
+ {
+ GenericInstanceType giType = (GenericInstanceType)type;
+ return giType.Name.Substring(0, giType.Name.Length - 2) + "<" + string.Join(", ", giType.GenericArguments.Select(PrettyPrintType).ToArray()) + ">";
+ }
+
+ // generic types, such as List
+ if (type.HasGenericParameters)
+ {
+ return type.Name.Substring(0, type.Name.Length - 2) + "<" + string.Join(", ", type.GenericParameters.Select(x => x.Name).ToArray()) + ">";
+ }
+
+ // non-generic type such as Int
+ return type.Name;
+ }
+
+ public static ReaderParameters ReaderParameters(string assemblyPath, IEnumerable extraPaths, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath)
+ {
+ ReaderParameters parameters = new ReaderParameters {ReadWrite = true};
+ if (assemblyResolver == null)
+ assemblyResolver = new DefaultAssemblyResolver();
+ AddSearchDirectoryHelper helper = new AddSearchDirectoryHelper(assemblyResolver);
+ helper.AddSearchDirectory(Path.GetDirectoryName(assemblyPath));
+ helper.AddSearchDirectory(UnityEngineDLLDirectoryName());
+ helper.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath));
+ helper.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath));
+ if (extraPaths != null)
+ {
+ foreach (string path in extraPaths)
+ helper.AddSearchDirectory(path);
+ }
+ parameters.AssemblyResolver = assemblyResolver;
+ parameters.SymbolReaderProvider = GetSymbolReaderProvider(assemblyPath);
+ return parameters;
+ }
+
+ public static WriterParameters GetWriterParameters(ReaderParameters readParams)
+ {
+ WriterParameters writeParams = new WriterParameters();
+ if (readParams.SymbolReaderProvider is PdbReaderProvider)
+ {
+ //Log("Will export symbols of pdb format");
+ writeParams.SymbolWriterProvider = new PdbWriterProvider();
+ }
+ else if (readParams.SymbolReaderProvider is MdbReaderProvider)
+ {
+ //Log("Will export symbols of mdb format");
+ writeParams.SymbolWriterProvider = new MdbWriterProvider();
+ }
+ return writeParams;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs.meta
new file mode 100644
index 0000000..231f539
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Helpers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 6c4ed76daf48547c5abb7c58f8d20886
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef b/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
new file mode 100644
index 0000000..5122428
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef
@@ -0,0 +1,14 @@
+{
+ "name": "Mirror.Weaver",
+ "references": [],
+ "optionalUnityReferences": [],
+ "includePlatforms": [
+ "Editor"
+ ],
+ "excludePlatforms": [],
+ "allowUnsafeCode": true,
+ "overrideReferences": false,
+ "precompiledReferences": [],
+ "autoReferenced": true,
+ "defineConstraints": []
+}
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta b/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta
new file mode 100644
index 0000000..b65a0cd
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 1d0b9d21c3ff546a4aa32399dfd33474
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors.meta
new file mode 100644
index 0000000..eb719b4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e538d627280d2471b8c72fdea822ca49
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
new file mode 100644
index 0000000..2a1688f
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs
@@ -0,0 +1,153 @@
+// all the [Command] code from NetworkBehaviourProcessor in one place
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class CommandProcessor
+ {
+ const string CmdPrefix = "InvokeCmd";
+
+ /*
+ // generates code like:
+ public void CallCmdThrust(float thrusting, int spin)
+ {
+ if (isServer)
+ {
+ // we are ON the server, invoke directly
+ CmdThrust(thrusting, spin);
+ return;
+ }
+
+ NetworkWriter networkWriter = new NetworkWriter();
+ networkWriter.Write(thrusting);
+ networkWriter.WritePackedUInt32((uint)spin);
+ base.SendCommandInternal(cmdName, networkWriter, cmdName);
+ }
+ */
+ public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
+ {
+ MethodDefinition cmd = new MethodDefinition("Call" + md.Name,
+ MethodAttributes.Public | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ // add parameters
+ foreach (ParameterDefinition pd in md.Parameters)
+ {
+ cmd.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
+ }
+
+ ILProcessor cmdWorker = cmd.Body.GetILProcessor();
+
+ NetworkBehaviourProcessor.WriteSetupLocals(cmdWorker);
+
+ if (Weaver.GenerateLogErrors)
+ {
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, "Call Command function " + md.Name));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.logErrorReference));
+ }
+
+ // local client check
+ Instruction localClientLabel = cmdWorker.Create(OpCodes.Nop);
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getBehaviourIsServer));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Brfalse, localClientLabel));
+
+ // call the cmd function directly.
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
+ for (int i = 0; i < md.Parameters.Count; i++)
+ {
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg, i + 1));
+ }
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Call, md));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
+ cmdWorker.Append(localClientLabel);
+
+ // NetworkWriter writer = new NetworkWriter();
+ NetworkBehaviourProcessor.WriteCreateWriter(cmdWorker);
+
+ // write all the arguments that the user passed to the Cmd call
+ if (!NetworkBehaviourProcessor.WriteArguments(cmdWorker, md, false))
+ return null;
+
+ string cmdName = md.Name;
+ int index = cmdName.IndexOf(CmdPrefix);
+ if (index > -1)
+ {
+ cmdName = cmdName.Substring(CmdPrefix.Length);
+ }
+
+ // invoke internal send and return
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0)); // load 'base.' to call the SendCommand function with
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldtoken, td));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, cmdName));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldloc_0)); // writer
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.sendCommandInternal));
+
+ NetworkBehaviourProcessor.WriteRecycleWriter(cmdWorker);
+
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
+
+ return cmd;
+ }
+
+ /*
+ // generates code like:
+ protected static void InvokeCmdCmdThrust(NetworkBehaviour obj, NetworkReader reader)
+ {
+ if (!NetworkServer.active)
+ {
+ return;
+ }
+ ((ShipControl)obj).CmdThrust(reader.ReadSingle(), (int)reader.ReadPackedUInt32());
+ }
+ */
+ public static MethodDefinition ProcessCommandInvoke(TypeDefinition td, MethodDefinition md)
+ {
+ MethodDefinition cmd = new MethodDefinition(CmdPrefix + md.Name,
+ MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ ILProcessor cmdWorker = cmd.Body.GetILProcessor();
+ Instruction label = cmdWorker.Create(OpCodes.Nop);
+
+ NetworkBehaviourProcessor.WriteServerActiveCheck(cmdWorker, md.Name, label, "Command");
+
+ // setup for reader
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
+
+ if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, cmdWorker, false))
+ return null;
+
+ // invoke actual command function
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, md));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
+
+ NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
+
+ return cmd;
+ }
+
+ public static bool ProcessMethodsValidateCommand(MethodDefinition md, CustomAttribute ca)
+ {
+ if (!md.Name.StartsWith("Cmd"))
+ {
+ Weaver.Error($"{md} must start with Cmd. Consider renaming it to Cmd{md.Name}");
+ return false;
+ }
+
+ if (md.IsStatic)
+ {
+ Weaver.Error($"{md} cannot be static");
+ return false;
+ }
+
+ // validate
+ return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
+ NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs.meta
new file mode 100644
index 0000000..20c3e15
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/CommandProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 73f6c9cdbb9e54f65b3a0a35cc8e55c2
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs
new file mode 100644
index 0000000..131a6a5
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs
@@ -0,0 +1,135 @@
+// this class generates OnSerialize/OnDeserialize when inheriting from MessageBase
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ static class MessageClassProcessor
+ {
+ public static void Process(TypeDefinition td)
+ {
+ Weaver.DLog(td, "MessageClassProcessor Start");
+
+ GenerateSerialization(td);
+ if (Weaver.WeavingFailed)
+ {
+ return;
+ }
+
+ GenerateDeSerialization(td);
+ Weaver.DLog(td, "MessageClassProcessor Done");
+ }
+
+ static void GenerateSerialization(TypeDefinition td)
+ {
+ Weaver.DLog(td, " GenerateSerialization");
+ foreach (MethodDefinition m in td.Methods)
+ {
+ if (m.Name == "Serialize")
+ return;
+ }
+
+ if (td.Fields.Count == 0)
+ {
+ return;
+ }
+
+ // check for self-referencing types
+ foreach (FieldDefinition field in td.Fields)
+ {
+ if (field.FieldType.FullName == td.FullName)
+ {
+ Weaver.Error($"{td} has field ${field} that references itself");
+ return;
+ }
+ }
+
+ MethodDefinition serializeFunc = new MethodDefinition("Serialize",
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
+
+ foreach (FieldDefinition field in td.Fields)
+ {
+ if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
+ continue;
+
+ if (field.FieldType.Resolve().HasGenericParameters && !field.FieldType.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
+ {
+ Weaver.Error($"{field} cannot have generic type {field.FieldType}. Consider creating a class that derives the generic type");
+ return;
+ }
+
+ if (field.FieldType.Resolve().IsInterface)
+ {
+ Weaver.Error($"{field} has unsupported type. Use a concrete class instead of interface {field.FieldType}");
+ return;
+ }
+
+ MethodReference writeFunc = Writers.GetWriteFunc(field.FieldType);
+ if (writeFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldfld, field));
+ serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
+ }
+ else
+ {
+ Weaver.Error($"{field} has unsupported type");
+ return;
+ }
+ }
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+
+ td.Methods.Add(serializeFunc);
+ }
+
+ static void GenerateDeSerialization(TypeDefinition td)
+ {
+ Weaver.DLog(td, " GenerateDeserialization");
+ foreach (MethodDefinition m in td.Methods)
+ {
+ if (m.Name == "Deserialize")
+ return;
+ }
+
+ if (td.Fields.Count == 0)
+ {
+ return;
+ }
+
+ MethodDefinition serializeFunc = new MethodDefinition("Deserialize",
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+ ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
+
+ foreach (FieldDefinition field in td.Fields)
+ {
+ if (field.IsStatic || field.IsPrivate || field.IsSpecialName)
+ continue;
+
+ MethodReference readerFunc = Readers.GetReadFunc(field.FieldType);
+ if (readerFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
+ serWorker.Append(serWorker.Create(OpCodes.Stfld, field));
+ }
+ else
+ {
+ Weaver.Error($"{field} has unsupported type");
+ return;
+ }
+ }
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+
+ td.Methods.Add(serializeFunc);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs.meta
new file mode 100644
index 0000000..875cf9a
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3544c9f00f6e5443ea3c30873c5a06ef
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs
new file mode 100644
index 0000000..c5f880e
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs
@@ -0,0 +1,77 @@
+// this class only shows warnings in case we use SyncVars etc. for MonoBehaviour.
+using Mono.CecilX;
+
+namespace Mirror.Weaver
+{
+ static class MonoBehaviourProcessor
+ {
+ public static void Process(TypeDefinition td)
+ {
+ ProcessSyncVars(td);
+ ProcessMethods(td);
+ }
+
+ static void ProcessSyncVars(TypeDefinition td)
+ {
+ // find syncvars
+ foreach (FieldDefinition fd in td.Fields)
+ {
+ foreach (CustomAttribute ca in fd.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
+ {
+ Weaver.Error($"[SyncVar] {fd} must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
+ }
+ }
+
+ if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
+ {
+ Weaver.Error($"{fd} is a SyncObject and must be inside a NetworkBehaviour. {td} is not a NetworkBehaviour");
+ }
+ }
+ }
+
+ static void ProcessMethods(TypeDefinition td)
+ {
+ // find command and RPC functions
+ foreach (MethodDefinition md in td.Methods)
+ {
+ foreach (CustomAttribute ca in md.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
+ {
+ Weaver.Error($"[Command] {md} must be declared inside a NetworkBehaviour");
+ }
+
+ if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
+ {
+ Weaver.Error($"[ClienRpc] {md} must be declared inside a NetworkBehaviour");
+ }
+
+ if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
+ {
+ Weaver.Error($"[TargetRpc] {md} must be declared inside a NetworkBehaviour");
+ }
+
+ string attributeName = ca.Constructor.DeclaringType.ToString();
+
+ switch (attributeName)
+ {
+ case "Mirror.ServerAttribute":
+ Weaver.Error($"[Server] {md} must be declared inside a NetworkBehaviour");
+ break;
+ case "Mirror.ServerCallbackAttribute":
+ Weaver.Error($"[ServerCallback] {md} must be declared inside a NetworkBehaviour");
+ break;
+ case "Mirror.ClientAttribute":
+ Weaver.Error($"[Client] {md} must be declared inside a NetworkBehaviour");
+ break;
+ case "Mirror.ClientCallbackAttribute":
+ Weaver.Error($"[ClientCallback] {md} must be declared inside a NetworkBehaviour");
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs.meta
new file mode 100644
index 0000000..ef3f5f4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 35c16722912b64af894e4f6668f2e54c
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
new file mode 100644
index 0000000..0b12e03
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs
@@ -0,0 +1,858 @@
+// this class processes SyncVars, Cmds, Rpcs, etc. of NetworkBehaviours
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ class NetworkBehaviourProcessor
+ {
+ readonly List syncVars = new List();
+ readonly List syncObjects = new List();
+ readonly Dictionary syncVarNetIds = new Dictionary(); //
+ readonly List commands = new List();
+ readonly List clientRpcs = new List();
+ readonly List targetRpcs = new List();
+ readonly List eventRpcs = new List();
+ readonly List commandInvocationFuncs = new List();
+ readonly List clientRpcInvocationFuncs = new List();
+ readonly List targetRpcInvocationFuncs = new List();
+ readonly List eventRpcInvocationFuncs = new List();
+
+ readonly List commandCallFuncs = new List();
+ readonly List clientRpcCallFuncs = new List();
+ readonly List targetRpcCallFuncs = new List();
+
+ readonly TypeDefinition netBehaviourSubclass;
+
+ public NetworkBehaviourProcessor(TypeDefinition td)
+ {
+ Weaver.DLog(td, "NetworkBehaviourProcessor");
+ netBehaviourSubclass = td;
+ }
+
+ public void Process()
+ {
+ if (netBehaviourSubclass.HasGenericParameters)
+ {
+ Weaver.Error($"{netBehaviourSubclass} cannot have generic parameters");
+ return;
+ }
+ Weaver.DLog(netBehaviourSubclass, "Process Start");
+ MarkAsProcessed(netBehaviourSubclass);
+ SyncVarProcessor.ProcessSyncVars(netBehaviourSubclass, syncVars, syncObjects, syncVarNetIds);
+
+ ProcessMethods();
+
+ SyncEventProcessor.ProcessEvents(netBehaviourSubclass, eventRpcs, eventRpcInvocationFuncs);
+ if (Weaver.WeavingFailed)
+ {
+ return;
+ }
+ GenerateConstants();
+
+ GenerateSerialization();
+ if (Weaver.WeavingFailed)
+ {
+ return;
+ }
+
+ GenerateDeSerialization();
+ Weaver.DLog(netBehaviourSubclass, "Process Done");
+ }
+
+ /*
+ generates code like:
+ if (!NetworkClient.active)
+ Debug.LogError((object) "Command function CmdRespawn called on server.");
+
+ which is used in InvokeCmd, InvokeRpc, etc.
+ */
+ public static void WriteClientActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
+ {
+ // client active check
+ worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
+ worker.Append(worker.Create(OpCodes.Brtrue, label));
+
+ worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on server."));
+ worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
+ worker.Append(worker.Create(OpCodes.Ret));
+ worker.Append(label);
+ }
+ /*
+ generates code like:
+ if (!NetworkServer.active)
+ Debug.LogError((object) "Command CmdMsgWhisper called on client.");
+ */
+ public static void WriteServerActiveCheck(ILProcessor worker, string mdName, Instruction label, string errString)
+ {
+ // server active check
+ worker.Append(worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
+ worker.Append(worker.Create(OpCodes.Brtrue, label));
+
+ worker.Append(worker.Create(OpCodes.Ldstr, errString + " " + mdName + " called on client."));
+ worker.Append(worker.Create(OpCodes.Call, Weaver.logErrorReference));
+ worker.Append(worker.Create(OpCodes.Ret));
+ worker.Append(label);
+ }
+
+ public static void WriteSetupLocals(ILProcessor worker)
+ {
+ worker.Body.InitLocals = true;
+ worker.Body.Variables.Add(new VariableDefinition(Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ }
+
+ public static void WriteCreateWriter(ILProcessor worker)
+ {
+ // create writer
+ worker.Append(worker.Create(OpCodes.Call, Weaver.GetPooledWriterReference));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+ }
+
+ public static void WriteRecycleWriter(ILProcessor worker)
+ {
+ // NetworkWriterPool.Recycle(writer);
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Call, Weaver.RecycleWriterReference));
+ }
+
+ public static bool WriteArguments(ILProcessor worker, MethodDefinition md, bool skipFirst)
+ {
+ // write each argument
+ short argNum = 1;
+ foreach (ParameterDefinition pd in md.Parameters)
+ {
+ if (argNum == 1 && skipFirst)
+ {
+ argNum += 1;
+ continue;
+ }
+
+ MethodReference writeFunc = Writers.GetWriteFunc(pd.ParameterType);
+ if (writeFunc == null)
+ {
+ Weaver.Error($"{md} has invalid parameter {pd}" );
+ return false;
+ }
+ // use built-in writer func on writer object
+ worker.Append(worker.Create(OpCodes.Ldloc_0)); // writer object
+ worker.Append(worker.Create(OpCodes.Ldarg, argNum)); // argument
+ worker.Append(worker.Create(OpCodes.Call, writeFunc)); // call writer func on writer object
+ argNum += 1;
+ }
+ return true;
+ }
+
+ #region mark / check type as processed
+ public const string ProcessedFunctionName = "MirrorProcessed";
+
+ // by adding an empty MirrorProcessed() function
+ public static bool WasProcessed(TypeDefinition td)
+ {
+ return td.Methods.Any(method => method.Name == ProcessedFunctionName);
+ }
+
+ public static void MarkAsProcessed(TypeDefinition td)
+ {
+ if (!WasProcessed(td))
+ {
+ MethodDefinition versionMethod = new MethodDefinition(ProcessedFunctionName, MethodAttributes.Private, Weaver.voidType);
+ ILProcessor worker = versionMethod.Body.GetILProcessor();
+ worker.Append(worker.Create(OpCodes.Ret));
+ td.Methods.Add(versionMethod);
+ }
+ }
+ #endregion
+
+ void GenerateConstants()
+ {
+ if (commands.Count == 0 && clientRpcs.Count == 0 && targetRpcs.Count == 0 && eventRpcs.Count == 0 && syncObjects.Count == 0)
+ return;
+
+ Weaver.DLog(netBehaviourSubclass, " GenerateConstants ");
+
+ // find static constructor
+ MethodDefinition cctor = null;
+ bool cctorFound = false;
+ foreach (MethodDefinition md in netBehaviourSubclass.Methods)
+ {
+ if (md.Name == ".cctor")
+ {
+ cctor = md;
+ cctorFound = true;
+ }
+ }
+ if (cctor != null)
+ {
+ // remove the return opcode from end of function. will add our own later.
+ if (cctor.Body.Instructions.Count != 0)
+ {
+ Instruction ret = cctor.Body.Instructions[cctor.Body.Instructions.Count - 1];
+ if (ret.OpCode == OpCodes.Ret)
+ {
+ cctor.Body.Instructions.RemoveAt(cctor.Body.Instructions.Count - 1);
+ }
+ else
+ {
+ Weaver.Error($"{netBehaviourSubclass} has invalid class constructor");
+ return;
+ }
+ }
+ }
+ else
+ {
+ // make one!
+ cctor = new MethodDefinition(".cctor", MethodAttributes.Private |
+ MethodAttributes.HideBySig |
+ MethodAttributes.SpecialName |
+ MethodAttributes.RTSpecialName |
+ MethodAttributes.Static,
+ Weaver.voidType);
+ }
+
+ // find instance constructor
+ MethodDefinition ctor = null;
+
+ foreach (MethodDefinition md in netBehaviourSubclass.Methods)
+ {
+ if (md.Name == ".ctor")
+ {
+ ctor = md;
+
+ Instruction ret = ctor.Body.Instructions[ctor.Body.Instructions.Count - 1];
+ if (ret.OpCode == OpCodes.Ret)
+ {
+ ctor.Body.Instructions.RemoveAt(ctor.Body.Instructions.Count - 1);
+ }
+ else
+ {
+ Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
+ return;
+ }
+
+ break;
+ }
+ }
+
+ if (ctor == null)
+ {
+ Weaver.Error($"{netBehaviourSubclass} has invalid constructor");
+ return;
+ }
+
+ ILProcessor ctorWorker = ctor.Body.GetILProcessor();
+ ILProcessor cctorWorker = cctor.Body.GetILProcessor();
+
+ for (int i = 0; i < commands.Count; ++i)
+ {
+ GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerCommandDelegateReference, commandInvocationFuncs[i], commands[i].Name);
+ }
+
+ for (int i = 0; i < clientRpcs.Count; ++i)
+ {
+ GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, clientRpcInvocationFuncs[i], clientRpcs[i].Name);
+ }
+
+ for (int i = 0; i < targetRpcs.Count; ++i)
+ {
+ GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerRpcDelegateReference, targetRpcInvocationFuncs[i], targetRpcs[i].Name);
+ }
+
+ for (int i = 0; i < eventRpcs.Count; ++i)
+ {
+ GenerateRegisterCommandDelegate(cctorWorker, Weaver.registerEventDelegateReference, eventRpcInvocationFuncs[i], eventRpcs[i].Name);
+ }
+
+ foreach (FieldDefinition fd in syncObjects)
+ {
+ SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd);
+ }
+
+ cctorWorker.Append(cctorWorker.Create(OpCodes.Ret));
+ if (!cctorFound)
+ {
+ netBehaviourSubclass.Methods.Add(cctor);
+ }
+
+ // finish ctor
+ ctorWorker.Append(ctorWorker.Create(OpCodes.Ret));
+
+ // in case class had no cctor, it might have BeforeFieldInit, so injected cctor would be called too late
+ netBehaviourSubclass.Attributes &= ~TypeAttributes.BeforeFieldInit;
+ }
+
+ /*
+ // This generates code like:
+ NetworkBehaviour.RegisterCommandDelegate(base.GetType(), "CmdThrust", new NetworkBehaviour.CmdDelegate(ShipControl.InvokeCmdCmdThrust));
+ */
+ void GenerateRegisterCommandDelegate(ILProcessor awakeWorker, MethodReference registerMethod, MethodDefinition func, string cmdName)
+ {
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Ldtoken, netBehaviourSubclass));
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference));
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Ldstr, cmdName));
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Ldnull));
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Ldftn, func));
+
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Newobj, Weaver.CmdDelegateConstructor));
+ awakeWorker.Append(awakeWorker.Create(OpCodes.Call, registerMethod));
+ }
+
+ void GenerateSerialization()
+ {
+ Weaver.DLog(netBehaviourSubclass, " GenerateSerialization");
+
+ foreach (MethodDefinition m in netBehaviourSubclass.Methods)
+ {
+ if (m.Name == "OnSerialize")
+ return;
+ }
+
+ if (syncVars.Count == 0)
+ {
+ // no synvars, no need for custom OnSerialize
+ return;
+ }
+
+ MethodDefinition serialize = new MethodDefinition("OnSerialize",
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ Weaver.boolType);
+
+ serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ serialize.Parameters.Add(new ParameterDefinition("forceAll", ParameterAttributes.None, Weaver.boolType));
+ ILProcessor serWorker = serialize.Body.GetILProcessor();
+
+ serialize.Body.InitLocals = true;
+
+ // loc_0, this local variable is to determine if any variable was dirty
+ VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType);
+ serialize.Body.Variables.Add(dirtyLocal);
+
+ MethodReference baseSerialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnSerialize");
+ if (baseSerialize != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
+ serWorker.Append(serWorker.Create(OpCodes.Call, baseSerialize));
+ serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to result of base.OnSerialize()
+ }
+
+ // Generates: if (forceAll);
+ Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // forceAll
+ serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
+
+ foreach (FieldDefinition syncVar in syncVars)
+ {
+ // Generates a writer call for each sync variable
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this
+ serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
+ MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
+ if (writeFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
+ }
+ else
+ {
+ Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
+ return;
+ }
+ }
+
+ // always return true if forceAll
+
+ // Generates: return true
+ serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+
+ // Generates: end if (forceAll);
+ serWorker.Append(initialStateLabel);
+
+ // write dirty bits before the data fields
+ // Generates: writer.WritePackedUInt64 (base.get_syncVarDirtyBits ());
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
+ serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
+ serWorker.Append(serWorker.Create(OpCodes.Call, Writers.GetWriteFunc(Weaver.uint64Type)));
+
+ // generate a writer call for any dirty variable in this class
+
+ // start at number of syncvars in parent
+ int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName);
+ foreach (FieldDefinition syncVar in syncVars)
+ {
+ Instruction varLabel = serWorker.Create(OpCodes.Nop);
+
+ // Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL)
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
+ serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.NetworkBehaviourDirtyBitsReference));
+ serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit)); // 8 bytes = long
+ serWorker.Append(serWorker.Create(OpCodes.And));
+ serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
+
+ // Generates a call to the writer for that field
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // writer
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
+ serWorker.Append(serWorker.Create(OpCodes.Ldfld, syncVar));
+
+ MethodReference writeFunc = Writers.GetWriteFunc(syncVar.FieldType);
+ if (writeFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
+ }
+ else
+ {
+ Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
+ return;
+ }
+
+ // something was dirty
+ serWorker.Append(serWorker.Create(OpCodes.Ldc_I4_1));
+ serWorker.Append(serWorker.Create(OpCodes.Stloc_0)); // set dirtyLocal to true
+
+ serWorker.Append(varLabel);
+ dirtyBit += 1;
+ }
+
+ if (Weaver.GenerateLogErrors)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + netBehaviourSubclass.Name));
+ serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
+ }
+
+ // generate: return dirtyLocal
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+ netBehaviourSubclass.Methods.Add(serialize);
+ }
+
+ public static int GetChannelId(CustomAttribute ca)
+ {
+ foreach (CustomAttributeNamedArgument customField in ca.Fields)
+ {
+ if (customField.Name == "channel")
+ {
+ return (int)customField.Argument.Value;
+ }
+ }
+
+ return 0;
+ }
+
+ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker, MethodDefinition deserialize)
+ {
+ // check for Hook function
+ if (!SyncVarProcessor.CheckForHookFunction(netBehaviourSubclass, syncVar, out MethodDefinition foundMethod))
+ {
+ return;
+ }
+
+ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName ||
+ syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ {
+ // GameObject/NetworkIdentity SyncVar:
+ // OnSerialize sends writer.Write(go);
+ // OnDeserialize reads to __netId manually so we can use
+ // lookups in the getter (so it still works if objects
+ // move in and out of range repeatedly)
+ FieldDefinition netIdField = syncVarNetIds[syncVar];
+
+ VariableDefinition tmpValue = new VariableDefinition(Weaver.uint32Type);
+ deserialize.Body.Variables.Add(tmpValue);
+
+ // read id and store in a local variable
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint32Type)));
+ serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
+
+ if (foundMethod != null)
+ {
+ // call Hook(this.GetSyncVarGameObject/NetworkIdentity(reader.ReadPackedUInt32()))
+ // because we send/receive the netID, not the GameObject/NetworkIdentity
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // this.
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldflda, syncVar));
+ if (syncVar.FieldType.FullName == Weaver.gameObjectType.FullName)
+ serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarGameObjectReference));
+ else if (syncVar.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ serWorker.Append(serWorker.Create(OpCodes.Callvirt, Weaver.getSyncVarNetworkIdentityReference));
+ serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
+ }
+ // set the netid field
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
+ serWorker.Append(serWorker.Create(OpCodes.Stfld, netIdField));
+ }
+ else
+ {
+ MethodReference readFunc = Readers.GetReadFunc(syncVar.FieldType);
+ if (readFunc == null)
+ {
+ Weaver.Error($"{syncVar} has unsupported type. Use a supported Mirror type instead");
+ return;
+ }
+ VariableDefinition tmpValue = new VariableDefinition(syncVar.FieldType);
+ deserialize.Body.Variables.Add(tmpValue);
+
+ // read value and put it in a local variable
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Call, readFunc));
+ serWorker.Append(serWorker.Create(OpCodes.Stloc, tmpValue));
+
+ if (foundMethod != null)
+ {
+ // call hook
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
+ serWorker.Append(serWorker.Create(OpCodes.Call, foundMethod));
+ }
+ // set the property
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc, tmpValue));
+ serWorker.Append(serWorker.Create(OpCodes.Stfld, syncVar));
+ }
+
+ }
+
+ void GenerateDeSerialization()
+ {
+ Weaver.DLog(netBehaviourSubclass, " GenerateDeSerialization");
+
+ foreach (MethodDefinition m in netBehaviourSubclass.Methods)
+ {
+ if (m.Name == "OnDeserialize")
+ return;
+ }
+
+ if (syncVars.Count == 0)
+ {
+ // no synvars, no need for custom OnDeserialize
+ return;
+ }
+
+ MethodDefinition serialize = new MethodDefinition("OnDeserialize",
+ MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+ serialize.Parameters.Add(new ParameterDefinition("initialState", ParameterAttributes.None, Weaver.boolType));
+ ILProcessor serWorker = serialize.Body.GetILProcessor();
+ // setup local for dirty bits
+ serialize.Body.InitLocals = true;
+ VariableDefinition dirtyBitsLocal = new VariableDefinition(Weaver.int64Type);
+ serialize.Body.Variables.Add(dirtyBitsLocal);
+
+ MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(netBehaviourSubclass.BaseType, Weaver.CurrentAssembly, "OnDeserialize");
+ if (baseDeserialize != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1)); // reader
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_2)); // initialState
+ serWorker.Append(serWorker.Create(OpCodes.Call, baseDeserialize));
+ }
+
+ // Generates: if (initialState);
+ Instruction initialStateLabel = serWorker.Create(OpCodes.Nop);
+
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
+ serWorker.Append(serWorker.Create(OpCodes.Brfalse, initialStateLabel));
+
+ foreach (FieldDefinition syncVar in syncVars)
+ {
+ DeserializeField(syncVar, serWorker, serialize);
+ }
+
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+
+ // Generates: end if (initialState);
+ serWorker.Append(initialStateLabel);
+
+
+ // get dirty bits
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Call, Readers.GetReadFunc(Weaver.uint64Type)));
+ serWorker.Append(serWorker.Create(OpCodes.Stloc_0));
+
+ // conditionally read each syncvar
+ int dirtyBit = Weaver.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); // start at number of syncvars in parent
+ foreach (FieldDefinition syncVar in syncVars)
+ {
+ Instruction varLabel = serWorker.Create(OpCodes.Nop);
+
+ // check if dirty bit is set
+ serWorker.Append(serWorker.Create(OpCodes.Ldloc_0));
+ serWorker.Append(serWorker.Create(OpCodes.Ldc_I8, 1L << dirtyBit));
+ serWorker.Append(serWorker.Create(OpCodes.And));
+ serWorker.Append(serWorker.Create(OpCodes.Brfalse, varLabel));
+
+ DeserializeField(syncVar, serWorker, serialize);
+
+ serWorker.Append(varLabel);
+ dirtyBit += 1;
+ }
+
+ if (Weaver.GenerateLogErrors)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + netBehaviourSubclass.Name));
+ serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference));
+ }
+
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+ netBehaviourSubclass.Methods.Add(serialize);
+ }
+
+ public static bool ProcessNetworkReaderParameters(MethodDefinition md, ILProcessor worker, bool skipFirst)
+ {
+ int count = 0;
+
+ // read cmd args from NetworkReader
+ foreach (ParameterDefinition arg in md.Parameters)
+ {
+ if (count++ == 0 && skipFirst)
+ {
+ continue;
+ }
+ MethodReference readFunc = Readers.GetReadFunc(arg.ParameterType); //?
+
+ if (readFunc != null)
+ {
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Call, readFunc));
+
+ // conversion.. is this needed?
+ if (arg.ParameterType.FullName == Weaver.singleType.FullName)
+ {
+ worker.Append(worker.Create(OpCodes.Conv_R4));
+ }
+ else if (arg.ParameterType.FullName == Weaver.doubleType.FullName)
+ {
+ worker.Append(worker.Create(OpCodes.Conv_R8));
+ }
+ }
+ else
+ {
+ Weaver.Error($"{md} has invalid parameter {arg}. Unsupported type {arg.ParameterType}, use a supported Mirror type instead");
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public static void AddInvokeParameters(ICollection collection)
+ {
+ collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, Weaver.NetworkBehaviourType2));
+ collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+ }
+
+ public static bool ProcessMethodsValidateFunction(MethodReference md)
+ {
+ if (md.ReturnType.FullName == Weaver.IEnumeratorType.FullName)
+ {
+ Weaver.Error($"{md} cannot be a coroutine");
+ return false;
+ }
+ if (md.ReturnType.FullName != Weaver.voidType.FullName)
+ {
+ Weaver.Error($"{md} cannot return a value. Make it void instead");
+ return false;
+ }
+ if (md.HasGenericParameters)
+ {
+ Weaver.Error($"{md} cannot have generic parameters");
+ return false;
+ }
+ return true;
+ }
+
+ public static bool ProcessMethodsValidateParameters(MethodReference md, CustomAttribute ca)
+ {
+ for (int i = 0; i < md.Parameters.Count; ++i)
+ {
+ ParameterDefinition p = md.Parameters[i];
+ if (p.IsOut)
+ {
+ Weaver.Error($"{md} cannot have out parameters");
+ return false;
+ }
+ if (p.IsOptional)
+ {
+ Weaver.Error($"{md} cannot have optional parameters");
+ return false;
+ }
+ if (p.ParameterType.Resolve().IsAbstract)
+ {
+ Weaver.Error($"{md} has invalid parameter {p}. Use concrete type instead of abstract type {p.ParameterType}");
+ return false;
+ }
+ if (p.ParameterType.IsByReference)
+ {
+ Weaver.Error($"{md} has invalid parameter {p}. Use supported type instead of reference type {p.ParameterType}");
+ return false;
+ }
+ // TargetRPC is an exception to this rule and can have a NetworkConnection as first parameter
+ if (p.ParameterType.FullName == Weaver.NetworkConnectionType.FullName &&
+ !(ca.AttributeType.FullName == Weaver.TargetRpcType.FullName && i == 0))
+ {
+ Weaver.Error($"{md} has invalid parameer {p}. Cannot pass NeworkConnections");
+ return false;
+ }
+ if (p.ParameterType.Resolve().IsDerivedFrom(Weaver.ComponentType))
+ {
+ if (p.ParameterType.FullName != Weaver.NetworkIdentityType.FullName)
+ {
+ Weaver.Error($"{md} has invalid parameter {p}. Cannot pass components in remote method calls");
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ void ProcessMethods()
+ {
+ HashSet names = new HashSet();
+
+ // find command and RPC functions
+ foreach (MethodDefinition md in netBehaviourSubclass.Methods)
+ {
+ foreach (CustomAttribute ca in md.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.CommandType.FullName)
+ {
+ ProcessCommand(names, md, ca);
+ break;
+ }
+
+ if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName)
+ {
+ ProcessTargetRpc(names, md, ca);
+ break;
+ }
+
+ if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName)
+ {
+ ProcessClientRpc(names, md, ca);
+ break;
+ }
+ }
+ }
+
+ // cmds
+ foreach (MethodDefinition md in commandInvocationFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+ foreach (MethodDefinition md in commandCallFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+
+ // rpcs
+ foreach (MethodDefinition md in clientRpcInvocationFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+ foreach (MethodDefinition md in targetRpcInvocationFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+ foreach (MethodDefinition md in clientRpcCallFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+ foreach (MethodDefinition md in targetRpcCallFuncs)
+ {
+ netBehaviourSubclass.Methods.Add(md);
+ }
+ }
+
+ void ProcessClientRpc(HashSet names, MethodDefinition md, CustomAttribute ca)
+ {
+ if (!RpcProcessor.ProcessMethodsValidateRpc(md, ca))
+ {
+ return;
+ }
+
+ if (names.Contains(md.Name))
+ {
+ Weaver.Error("Duplicate ClientRpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
+ return;
+ }
+ names.Add(md.Name);
+ clientRpcs.Add(md);
+
+ MethodDefinition rpcFunc = RpcProcessor.ProcessRpcInvoke(netBehaviourSubclass, md);
+ if (rpcFunc != null)
+ {
+ clientRpcInvocationFuncs.Add(rpcFunc);
+ }
+
+ MethodDefinition rpcCallFunc = RpcProcessor.ProcessRpcCall(netBehaviourSubclass, md, ca);
+ if (rpcCallFunc != null)
+ {
+ clientRpcCallFuncs.Add(rpcCallFunc);
+ Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
+ }
+ }
+
+ void ProcessTargetRpc(HashSet names, MethodDefinition md, CustomAttribute ca)
+ {
+ if (!TargetRpcProcessor.ProcessMethodsValidateTargetRpc(md, ca))
+ return;
+
+ if (names.Contains(md.Name))
+ {
+ Weaver.Error("Duplicate Target Rpc name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
+ return;
+ }
+ names.Add(md.Name);
+ targetRpcs.Add(md);
+
+ MethodDefinition rpcFunc = TargetRpcProcessor.ProcessTargetRpcInvoke(netBehaviourSubclass, md);
+ if (rpcFunc != null)
+ {
+ targetRpcInvocationFuncs.Add(rpcFunc);
+ }
+
+ MethodDefinition rpcCallFunc = TargetRpcProcessor.ProcessTargetRpcCall(netBehaviourSubclass, md, ca);
+ if (rpcCallFunc != null)
+ {
+ targetRpcCallFuncs.Add(rpcCallFunc);
+ Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc;
+ }
+ }
+
+ void ProcessCommand(HashSet names, MethodDefinition md, CustomAttribute ca)
+ {
+ if (!CommandProcessor.ProcessMethodsValidateCommand(md, ca))
+ return;
+
+ if (names.Contains(md.Name))
+ {
+ Weaver.Error("Duplicate Command name [" + netBehaviourSubclass.FullName + ":" + md.Name + "]");
+ return;
+ }
+
+ names.Add(md.Name);
+ commands.Add(md);
+
+ MethodDefinition cmdFunc = CommandProcessor.ProcessCommandInvoke(netBehaviourSubclass, md);
+ if (cmdFunc != null)
+ {
+ commandInvocationFuncs.Add(cmdFunc);
+ }
+
+ MethodDefinition cmdCallFunc = CommandProcessor.ProcessCommandCall(netBehaviourSubclass, md, ca);
+ if (cmdCallFunc != null)
+ {
+ commandCallFuncs.Add(cmdCallFunc);
+ Weaver.WeaveLists.replaceMethods[md.FullName] = cmdCallFunc;
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs.meta
new file mode 100644
index 0000000..67c27dc
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 8118d606be3214e5d99943ec39530dd8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
new file mode 100644
index 0000000..b45e724
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs
@@ -0,0 +1,356 @@
+using System;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class PropertySiteProcessor
+ {
+ public static void ProcessSitesModule(ModuleDefinition moduleDef)
+ {
+ DateTime startTime = DateTime.Now;
+
+ //Search through the types
+ foreach (TypeDefinition td in moduleDef.Types)
+ {
+ if (td.IsClass)
+ {
+ ProcessSiteClass(td);
+ }
+ }
+ if (Weaver.WeaveLists.generateContainerClass != null)
+ {
+ moduleDef.Types.Add(Weaver.WeaveLists.generateContainerClass);
+ Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.WeaveLists.generateContainerClass);
+
+ foreach (MethodDefinition f in Weaver.WeaveLists.generatedReadFunctions)
+ {
+ Weaver.CurrentAssembly.MainModule.ImportReference(f);
+ }
+
+ foreach (MethodDefinition f in Weaver.WeaveLists.generatedWriteFunctions)
+ {
+ Weaver.CurrentAssembly.MainModule.ImportReference(f);
+ }
+ }
+ Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime));
+ }
+
+ static void ProcessSiteClass(TypeDefinition td)
+ {
+ //Console.WriteLine(" ProcessSiteClass " + td);
+ foreach (MethodDefinition md in td.Methods)
+ {
+ ProcessSiteMethod(td, md);
+ }
+
+ foreach (TypeDefinition nested in td.NestedTypes)
+ {
+ ProcessSiteClass(nested);
+ }
+ }
+
+ static void ProcessSiteMethod(TypeDefinition td, MethodDefinition md)
+ {
+ // process all references to replaced members with properties
+ //Weaver.DLog(td, " ProcessSiteMethod " + md);
+
+ if (md.Name == ".cctor" ||
+ md.Name == NetworkBehaviourProcessor.ProcessedFunctionName ||
+ md.Name.StartsWith("CallCmd") ||
+ md.Name.StartsWith("InvokeCmd") ||
+ md.Name.StartsWith("InvokeRpc") ||
+ md.Name.StartsWith("InvokeSyn"))
+ return;
+
+ if (md.Body != null && md.Body.Instructions != null)
+ {
+ // TODO move this to NetworkBehaviourProcessor
+ foreach (CustomAttribute attr in md.CustomAttributes)
+ {
+ switch (attr.Constructor.DeclaringType.ToString())
+ {
+ case "Mirror.ServerAttribute":
+ InjectServerGuard(td, md, true);
+ break;
+ case "Mirror.ServerCallbackAttribute":
+ InjectServerGuard(td, md, false);
+ break;
+ case "Mirror.ClientAttribute":
+ InjectClientGuard(td, md, true);
+ break;
+ case "Mirror.ClientCallbackAttribute":
+ InjectClientGuard(td, md, false);
+ break;
+ }
+ }
+
+ for (int iCount= 0; iCount < md.Body.Instructions.Count;)
+ {
+ Instruction instr = md.Body.Instructions[iCount];
+ iCount += ProcessInstruction(md, instr, iCount);
+ }
+ }
+ }
+
+ static void InjectServerGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
+ {
+ if (!Weaver.IsNetworkBehaviour(td))
+ {
+ Weaver.Error($"[Server] {md} must be declared in a NetworkBehaviour");
+ return;
+ }
+ ILProcessor worker = md.Body.GetILProcessor();
+ Instruction top = md.Body.Instructions[0];
+
+ worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkServerGetActive));
+ worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
+ if (logWarning)
+ {
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Server] function '" + md.FullName + "' called on client"));
+ worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
+ }
+ InjectGuardParameters(md, worker, top);
+ InjectGuardReturnValue(md, worker, top);
+ worker.InsertBefore(top, worker.Create(OpCodes.Ret));
+ }
+
+ static void InjectClientGuard(TypeDefinition td, MethodDefinition md, bool logWarning)
+ {
+ if (!Weaver.IsNetworkBehaviour(td))
+ {
+ Weaver.Error($"[Client] {md} must be declared in a NetworkBehaviour");
+ return;
+ }
+ ILProcessor worker = md.Body.GetILProcessor();
+ Instruction top = md.Body.Instructions[0];
+
+ worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.NetworkClientGetActive));
+ worker.InsertBefore(top, worker.Create(OpCodes.Brtrue, top));
+ if (logWarning)
+ {
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldstr, "[Client] function '" + md.FullName + "' called on server"));
+ worker.InsertBefore(top, worker.Create(OpCodes.Call, Weaver.logWarningReference));
+ }
+
+ InjectGuardParameters(md, worker, top);
+ InjectGuardReturnValue(md, worker, top);
+ worker.InsertBefore(top, worker.Create(OpCodes.Ret));
+ }
+
+ // replaces syncvar write access with the NetworkXYZ.get property calls
+ static void ProcessInstructionSetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
+ {
+ // dont replace property call sites in constructors
+ if (md.Name == ".ctor")
+ return;
+
+ // does it set a field that we replaced?
+ if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
+ {
+ //replace with property
+ //DLog(td, " replacing " + md.Name + ":" + i);
+ i.OpCode = OpCodes.Call;
+ i.Operand = replacement;
+ //DLog(td, " replaced " + md.Name + ":" + i);
+ }
+ }
+
+ // replaces syncvar read access with the NetworkXYZ.get property calls
+ static void ProcessInstructionGetterField(MethodDefinition md, Instruction i, FieldDefinition opField)
+ {
+ // dont replace property call sites in constructors
+ if (md.Name == ".ctor")
+ return;
+
+ // does it set a field that we replaced?
+ if (Weaver.WeaveLists.replacementGetterProperties.TryGetValue(opField, out MethodDefinition replacement))
+ {
+ //replace with property
+ //DLog(td, " replacing " + md.Name + ":" + i);
+ i.OpCode = OpCodes.Call;
+ i.Operand = replacement;
+ //DLog(td, " replaced " + md.Name + ":" + i);
+ }
+ }
+
+ static int ProcessInstruction(MethodDefinition md, Instruction instr, int iCount)
+ {
+ if (instr.OpCode == OpCodes.Call || instr.OpCode == OpCodes.Callvirt)
+ {
+ if (instr.Operand is MethodReference opMethod)
+ {
+ ProcessInstructionMethod(md, instr, opMethod, iCount);
+ }
+ }
+
+ if (instr.OpCode == OpCodes.Stfld)
+ {
+ // this instruction sets the value of a field. cache the field reference.
+ if (instr.Operand is FieldDefinition opField)
+ {
+ ProcessInstructionSetterField(md, instr, opField);
+ }
+ }
+
+ if (instr.OpCode == OpCodes.Ldfld)
+ {
+ // this instruction gets the value of a field. cache the field reference.
+ if (instr.Operand is FieldDefinition opField)
+ {
+ ProcessInstructionGetterField(md, instr, opField);
+ }
+ }
+
+ if (instr.OpCode == OpCodes.Ldflda)
+ {
+ // loading a field by reference, watch out for initobj instruction
+ // see https://github.com/vis2k/Mirror/issues/696
+
+ if (instr.Operand is FieldDefinition opField)
+ {
+ return ProcessInstructionLoadAddress(md, instr, opField, iCount);
+ }
+ }
+
+ return 1;
+ }
+
+ static int ProcessInstructionLoadAddress(MethodDefinition md, Instruction instr, FieldDefinition opField, int iCount)
+ {
+ // dont replace property call sites in constructors
+ if (md.Name == ".ctor")
+ return 1;
+
+ // does it set a field that we replaced?
+ if (Weaver.WeaveLists.replacementSetterProperties.TryGetValue(opField, out MethodDefinition replacement))
+ {
+ // we have a replacement for this property
+ // is the next instruction a initobj?
+ Instruction nextInstr = md.Body.Instructions[iCount + 1];
+
+ if (nextInstr.OpCode == OpCodes.Initobj)
+ {
+ // we need to replace this code with:
+ // var tmp = new MyStruct();
+ // this.set_Networkxxxx(tmp);
+ ILProcessor worker = md.Body.GetILProcessor();
+ VariableDefinition tmpVariable = new VariableDefinition(opField.FieldType);
+ md.Body.Variables.Add(tmpVariable);
+
+ worker.InsertBefore(instr, worker.Create(OpCodes.Ldloca, tmpVariable));
+ worker.InsertBefore(instr, worker.Create(OpCodes.Initobj, opField.FieldType));
+ worker.InsertBefore(instr, worker.Create(OpCodes.Ldloc, tmpVariable));
+ worker.InsertBefore(instr, worker.Create(OpCodes.Call, replacement));
+
+ worker.Remove(instr);
+ worker.Remove(nextInstr);
+ return 4;
+
+ }
+
+ }
+
+ return 1;
+ }
+
+ static void ProcessInstructionMethod(MethodDefinition md, Instruction instr, MethodReference opMethodRef, int iCount)
+ {
+ //DLog(td, "ProcessInstructionMethod " + opMethod.Name);
+ if (opMethodRef.Name == "Invoke")
+ {
+ // Events use an "Invoke" method to call the delegate.
+ // this code replaces the "Invoke" instruction with the generated "Call***" instruction which send the event to the server.
+ // but the "Invoke" instruction is called on the event field - where the "call" instruction is not.
+ // so the earlier instruction that loads the event field is replaced with a Noop.
+
+ // go backwards until find a ldfld instruction that matches ANY event
+ bool found = false;
+ while (iCount > 0 && !found)
+ {
+ iCount -= 1;
+ Instruction inst = md.Body.Instructions[iCount];
+ if (inst.OpCode == OpCodes.Ldfld)
+ {
+ FieldReference opField = inst.Operand as FieldReference;
+
+ // find replaceEvent with matching name
+ // NOTE: original weaver compared .Name, not just the MethodDefinition,
+ // that's why we use dict.
+ if (Weaver.WeaveLists.replaceEvents.TryGetValue(opField.Name, out MethodDefinition replacement))
+ {
+ instr.Operand = replacement;
+ inst.OpCode = OpCodes.Nop;
+ found = true;
+ }
+ }
+ }
+ }
+ else
+ {
+ // should it be replaced?
+ // NOTE: original weaver compared .FullName, not just the MethodDefinition,
+ // that's why we use dict.
+ if (Weaver.WeaveLists.replaceMethods.TryGetValue(opMethodRef.FullName, out MethodDefinition replacement))
+ {
+ //DLog(td, " replacing " + md.Name + ":" + i);
+ instr.Operand = replacement;
+ //DLog(td, " replaced " + md.Name + ":" + i);
+ }
+ }
+ }
+
+
+ // this is required to early-out from a function with "ref" or "out" parameters
+ static void InjectGuardParameters(MethodDefinition md, ILProcessor worker, Instruction top)
+ {
+ int offset = md.Resolve().IsStatic ? 0 : 1;
+ for (int index = 0; index < md.Parameters.Count; index++)
+ {
+ ParameterDefinition param = md.Parameters[index];
+ if (param.IsOut)
+ {
+ TypeReference elementType = param.ParameterType.GetElementType();
+ if (elementType.IsPrimitive)
+ {
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
+ worker.InsertBefore(top, worker.Create(OpCodes.Stind_I4));
+ }
+ else
+ {
+ md.Body.Variables.Add(new VariableDefinition(elementType));
+ md.Body.InitLocals = true;
+
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldarg, index + offset));
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
+ worker.InsertBefore(top, worker.Create(OpCodes.Initobj, elementType));
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
+ worker.InsertBefore(top, worker.Create(OpCodes.Stobj, elementType));
+ }
+ }
+ }
+ }
+
+ // this is required to early-out from a function with a return value.
+ static void InjectGuardReturnValue(MethodDefinition md, ILProcessor worker, Instruction top)
+ {
+ if (md.ReturnType.FullName != Weaver.voidType.FullName)
+ {
+ if (md.ReturnType.IsPrimitive)
+ {
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldc_I4_0));
+ }
+ else
+ {
+ md.Body.Variables.Add(new VariableDefinition(md.ReturnType));
+ md.Body.InitLocals = true;
+
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldloca_S, (byte)(md.Body.Variables.Count - 1)));
+ worker.InsertBefore(top, worker.Create(OpCodes.Initobj, md.ReturnType));
+ worker.InsertBefore(top, worker.Create(OpCodes.Ldloc, md.Body.Variables.Count - 1));
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs.meta
new file mode 100644
index 0000000..e8c2500
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/PropertySiteProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d48f1ab125e9940a995603796bccc59e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
new file mode 100644
index 0000000..d87bf22
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs
@@ -0,0 +1,98 @@
+using System;
+using Mono.CecilX;
+using UnityEditor.Compilation;
+using System.Linq;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mirror.Weaver
+{
+ public static class ReaderWriterProcessor
+ {
+ // find all readers and writers and register them
+ public static void ProcessReadersAndWriters(AssemblyDefinition CurrentAssembly)
+ {
+ Readers.Init();
+ Writers.Init();
+
+ foreach (Assembly unityAsm in CompilationPipeline.GetAssemblies())
+ {
+ if (unityAsm.name != CurrentAssembly.Name.Name)
+ {
+ try
+ {
+ using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
+ using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(unityAsm.outputPath, new ReaderParameters { ReadWrite = false, ReadSymbols = false, AssemblyResolver = asmResolver }))
+ {
+ ProcessAssemblyClasses(CurrentAssembly, assembly);
+ }
+ }
+ catch(FileNotFoundException)
+ {
+ // During first import, this gets called before some assemblies
+ // are built, just skip them
+ }
+ }
+ }
+
+ ProcessAssemblyClasses(CurrentAssembly, CurrentAssembly);
+ }
+
+ static void ProcessAssemblyClasses(AssemblyDefinition CurrentAssembly, AssemblyDefinition assembly)
+ {
+ foreach (TypeDefinition klass in assembly.MainModule.Types)
+ {
+ // extension methods only live in static classes
+ // static classes are represented as sealed and abstract
+ if (klass.IsAbstract && klass.IsSealed)
+ {
+ LoadWriters(CurrentAssembly, klass);
+ LoadReaders(CurrentAssembly, klass);
+ }
+ }
+ }
+
+ static void LoadWriters(AssemblyDefinition currentAssembly, TypeDefinition klass)
+ {
+ // register all the writers in this class. Skip the ones with wrong signature
+ foreach (MethodDefinition method in klass.Methods)
+ {
+ if (method.Parameters.Count != 2)
+ continue;
+
+ if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkWriter")
+ continue;
+
+ if (method.ReturnType.FullName != "System.Void")
+ continue;
+
+ if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
+ continue;
+
+ TypeReference dataType = method.Parameters[1].ParameterType;
+ Writers.Register(dataType, currentAssembly.MainModule.ImportReference(method));
+ }
+ }
+
+ static void LoadReaders(AssemblyDefinition currentAssembly, TypeDefinition klass)
+ {
+ // register all the reader in this class. Skip the ones with wrong signature
+ foreach (MethodDefinition method in klass.Methods)
+ {
+ if (method.Parameters.Count != 1)
+ continue;
+
+ if (method.Parameters[0].ParameterType.FullName != "Mirror.NetworkReader")
+ continue;
+
+ if (method.ReturnType.FullName == "System.Void")
+ continue;
+
+ if (method.GetCustomAttribute("System.Runtime.CompilerServices.ExtensionAttribute") == null)
+ continue;
+
+ Readers.Register(method.ReturnType, currentAssembly.MainModule.ImportReference(method));
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs.meta
new file mode 100644
index 0000000..c14d6fa
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f3263602f0a374ecd8d08588b1fc2f76
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
new file mode 100644
index 0000000..947742e
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs
@@ -0,0 +1,110 @@
+// all the [Rpc] code from NetworkBehaviourProcessor in one place
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+namespace Mirror.Weaver
+{
+ public static class RpcProcessor
+ {
+ public const string RpcPrefix = "InvokeRpc";
+
+ public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinition md)
+ {
+ MethodDefinition rpc = new MethodDefinition(
+ RpcPrefix + md.Name,
+ MethodAttributes.Family | MethodAttributes.Static | MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ ILProcessor rpcWorker = rpc.Body.GetILProcessor();
+ Instruction label = rpcWorker.Create(OpCodes.Nop);
+
+ NetworkBehaviourProcessor.WriteClientActiveCheck(rpcWorker, md.Name, label, "RPC");
+
+ // setup for reader
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td));
+
+ if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, rpcWorker, false))
+ return null;
+
+ // invoke actual command function
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
+
+ NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
+
+ return rpc;
+ }
+
+ /* generates code like:
+ public void CallRpcTest (int param)
+ {
+ NetworkWriter writer = new NetworkWriter ();
+ writer.WritePackedUInt32((uint)param);
+ base.SendRPCInternal(typeof(class),"RpcTest", writer, 0);
+ }
+ */
+ public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
+ {
+ MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ // add paramters
+ foreach (ParameterDefinition pd in md.Parameters)
+ {
+ rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
+ }
+
+ ILProcessor rpcWorker = rpc.Body.GetILProcessor();
+
+ NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker);
+
+ NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker);
+
+ // write all the arguments that the user passed to the Rpc call
+ if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, false))
+ return null;
+
+ string rpcName = md.Name;
+ int index = rpcName.IndexOf(RpcPrefix);
+ if (index > -1)
+ {
+ rpcName = rpcName.Substring(RpcPrefix.Length);
+ }
+
+ // invoke SendInternal and return
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldtoken, td));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendRpcInternal));
+
+ NetworkBehaviourProcessor.WriteRecycleWriter(rpcWorker);
+
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
+
+ return rpc;
+ }
+
+ public static bool ProcessMethodsValidateRpc(MethodDefinition md, CustomAttribute ca)
+ {
+ if (!md.Name.StartsWith("Rpc"))
+ {
+ Weaver.Error($"{md} must start with Rpc. Consider renaming it to Rpc{md.Name}");
+ return false;
+ }
+
+ if (md.IsStatic)
+ {
+ Weaver.Error($"{md} must not be static");
+ return false;
+ }
+
+ // validate
+ return NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md) &&
+ NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
new file mode 100644
index 0000000..22375ba
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a3cb7051ff41947e59bba58bdd2b73fc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs
new file mode 100644
index 0000000..a095967
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs
@@ -0,0 +1,19 @@
+// this class generates OnSerialize/OnDeserialize for SyncLists
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ static class SyncDictionaryProcessor
+ {
+ ///
+ /// Generates serialization methods for synclists
+ ///
+ /// The synclist class
+ public static void Process(TypeDefinition td)
+ {
+ SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeKey", "DeserializeKey");
+ SyncObjectProcessor.GenerateSerialization(td, 1, "SerializeItem", "DeserializeItem");
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs.meta
new file mode 100644
index 0000000..0a7c2aa
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncDictionaryProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 29e4a45f69822462ab0b15adda962a29
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs
new file mode 100644
index 0000000..3a4e36f
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs
@@ -0,0 +1,152 @@
+// all the SyncEvent code from NetworkBehaviourProcessor in one place
+using System.Collections.Generic;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class SyncEventProcessor
+ {
+ public static MethodDefinition ProcessEventInvoke(TypeDefinition td, EventDefinition ed)
+ {
+ // find the field that matches the event
+ FieldDefinition eventField = null;
+ foreach (FieldDefinition fd in td.Fields)
+ {
+ if (fd.FullName == ed.FullName)
+ {
+ eventField = fd;
+ break;
+ }
+ }
+ if (eventField == null)
+ {
+ Weaver.Error($"{td} not found. Did you declare the event?");
+ return null;
+ }
+
+ MethodDefinition cmd = new MethodDefinition("InvokeSyncEvent" + ed.Name, MethodAttributes.Family |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ ILProcessor cmdWorker = cmd.Body.GetILProcessor();
+ Instruction label1 = cmdWorker.Create(OpCodes.Nop);
+ Instruction label2 = cmdWorker.Create(OpCodes.Nop);
+
+ NetworkBehaviourProcessor.WriteClientActiveCheck(cmdWorker, ed.Name, label1, "Event");
+
+ // null event check
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Brtrue, label2));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
+ cmdWorker.Append(label2);
+
+ // setup reader
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Castclass, td));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField));
+
+ // read the event arguments
+ MethodReference invoke = Resolvers.ResolveMethod(eventField.FieldType, Weaver.CurrentAssembly, "Invoke");
+ if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(invoke.Resolve(), cmdWorker, false))
+ return null;
+
+ // invoke actual event delegate function
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Callvirt, invoke));
+ cmdWorker.Append(cmdWorker.Create(OpCodes.Ret));
+
+ NetworkBehaviourProcessor.AddInvokeParameters(cmd.Parameters);
+
+ return cmd;
+ }
+
+ public static MethodDefinition ProcessEventCall(TypeDefinition td, EventDefinition ed, CustomAttribute ca)
+ {
+ MethodReference invoke = Resolvers.ResolveMethod(ed.EventType, Weaver.CurrentAssembly, "Invoke");
+ MethodDefinition evt = new MethodDefinition("Call" + ed.Name, MethodAttributes.Public |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+ // add paramters
+ foreach (ParameterDefinition pd in invoke.Parameters)
+ {
+ evt.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
+ }
+
+ ILProcessor evtWorker = evt.Body.GetILProcessor();
+ Instruction label = evtWorker.Create(OpCodes.Nop);
+
+ NetworkBehaviourProcessor.WriteSetupLocals(evtWorker);
+
+ NetworkBehaviourProcessor.WriteServerActiveCheck(evtWorker, ed.Name, label, "Event");
+
+ NetworkBehaviourProcessor.WriteCreateWriter(evtWorker);
+
+ // write all the arguments that the user passed to the syncevent
+ if (!NetworkBehaviourProcessor.WriteArguments(evtWorker, invoke.Resolve(), false))
+ return null;
+
+ // invoke interal send and return
+ evtWorker.Append(evtWorker.Create(OpCodes.Ldarg_0)); // this
+ evtWorker.Append(evtWorker.Create(OpCodes.Ldtoken, td));
+ evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
+ evtWorker.Append(evtWorker.Create(OpCodes.Ldstr, ed.Name));
+ evtWorker.Append(evtWorker.Create(OpCodes.Ldloc_0)); // writer
+ evtWorker.Append(evtWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
+ evtWorker.Append(evtWorker.Create(OpCodes.Call, Weaver.sendEventInternal));
+
+ NetworkBehaviourProcessor.WriteRecycleWriter(evtWorker);
+
+ evtWorker.Append(evtWorker.Create(OpCodes.Ret));
+
+ return evt;
+ }
+
+ public static void ProcessEvents(TypeDefinition td, List events, List eventInvocationFuncs)
+ {
+ // find events
+ foreach (EventDefinition ed in td.Events)
+ {
+ foreach (CustomAttribute ca in ed.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.SyncEventType.FullName)
+ {
+ if (!ed.Name.StartsWith("Event"))
+ {
+ Weaver.Error($"{ed} must start with Event. Consider renaming it to Event{ed.Name}");
+ return;
+ }
+
+ if (ed.EventType.Resolve().HasGenericParameters)
+ {
+ Weaver.Error($"{ed} must not have generic parameters. Consider creating a new class that inherits from {ed.EventType} instead");
+ return;
+ }
+
+ events.Add(ed);
+ MethodDefinition eventFunc = ProcessEventInvoke(td, ed);
+ if (eventFunc == null)
+ {
+ return;
+ }
+
+ td.Methods.Add(eventFunc);
+ eventInvocationFuncs.Add(eventFunc);
+
+ Weaver.DLog(td, "ProcessEvent " + ed);
+
+ MethodDefinition eventCallFunc = ProcessEventCall(td, ed, ca);
+ td.Methods.Add(eventCallFunc);
+
+ Weaver.WeaveLists.replaceEvents[ed.Name] = eventCallFunc; // original weaver compares .Name, not EventDefinition.
+
+ Weaver.DLog(td, " Event: " + ed.Name);
+ break;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs.meta
new file mode 100644
index 0000000..81b9576
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a5d8b25543a624384944b599e5a832a8
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs
new file mode 100644
index 0000000..20db50b
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs
@@ -0,0 +1,5 @@
+// This file was removed in Mirror 3.4.9
+// The purpose of this file is to get the old file overwritten
+// when users update from the asset store to prevent a flood of errors
+// from having the old file still in the project as a straggler.
+// This file will be dropped from the Asset Store package in May 2019
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs.meta
new file mode 100644
index 0000000..d3f5278
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 97068e5d8cc14490b85933feb119d827
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
new file mode 100644
index 0000000..0da1746
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs
@@ -0,0 +1,18 @@
+// this class generates OnSerialize/OnDeserialize for SyncLists
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ static class SyncListProcessor
+ {
+ ///
+ /// Generates serialization methods for synclists
+ ///
+ /// The synclist class
+ public static void Process(TypeDefinition td)
+ {
+ SyncObjectProcessor.GenerateSerialization(td, 0, "SerializeItem", "DeserializeItem");
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs.meta
new file mode 100644
index 0000000..b73b047
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 4f3445268e45d437fac325837aff3246
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs
new file mode 100644
index 0000000..20db50b
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs
@@ -0,0 +1,5 @@
+// This file was removed in Mirror 3.4.9
+// The purpose of this file is to get the old file overwritten
+// when users update from the asset store to prevent a flood of errors
+// from having the old file still in the project as a straggler.
+// This file will be dropped from the Asset Store package in May 2019
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta
new file mode 100644
index 0000000..8f234cd
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 28fb192f6a9bc1247b90aa4710f6d34f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs
new file mode 100644
index 0000000..14e27f6
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs
@@ -0,0 +1,89 @@
+// SyncObject code
+using System;
+using System.Linq;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class SyncObjectInitializer
+ {
+ public static void GenerateSyncObjectInitializer(ILProcessor methodWorker, FieldDefinition fd)
+ {
+ // call syncobject constructor
+ GenerateSyncObjectInstanceInitializer(methodWorker, fd);
+
+ // register syncobject in network behaviour
+ GenerateSyncObjectRegistration(methodWorker, fd);
+ }
+
+ // generates 'syncListInt = new SyncListInt()' if user didn't do that yet
+ static void GenerateSyncObjectInstanceInitializer(ILProcessor ctorWorker, FieldDefinition fd)
+ {
+ // check the ctor's instructions for an Stfld op-code for this specific sync list field.
+ foreach (Instruction ins in ctorWorker.Body.Instructions)
+ {
+ if (ins.OpCode.Code == Code.Stfld)
+ {
+ FieldDefinition field = (FieldDefinition)ins.Operand;
+ if (field.DeclaringType == fd.DeclaringType && field.Name == fd.Name)
+ {
+ // Already initialized by the user in the field definition, e.g:
+ // public SyncListInt Foo = new SyncListInt();
+ return;
+ }
+ }
+ }
+
+ // Not initialized by the user in the field definition, e.g:
+ // public SyncListInt Foo;
+ MethodReference objectConstructor;
+ try
+ {
+ objectConstructor = Weaver.CurrentAssembly.MainModule.ImportReference(fd.FieldType.Resolve().Methods.First(x => x.Name == ".ctor" && !x.HasParameters));
+ }
+ catch (Exception)
+ {
+ Weaver.Error($"{fd} does not have a default constructor");
+ return;
+ }
+
+ ctorWorker.Append(ctorWorker.Create(OpCodes.Ldarg_0));
+ ctorWorker.Append(ctorWorker.Create(OpCodes.Newobj, objectConstructor));
+ ctorWorker.Append(ctorWorker.Create(OpCodes.Stfld, fd));
+ }
+
+ public static bool ImplementsSyncObject(TypeReference typeRef)
+ {
+ try
+ {
+ // value types cant inherit from SyncObject
+ if (typeRef.IsValueType)
+ {
+ return false;
+ }
+
+ return typeRef.Resolve().ImplementsInterface(Weaver.SyncObjectType);
+ }
+ catch
+ {
+ // sometimes this will fail if we reference a weird library that can't be resolved, so we just swallow that exception and return false
+ }
+
+ return false;
+ }
+
+ /*
+ // generates code like:
+ this.InitSyncObject(m_sizes);
+ */
+ static void GenerateSyncObjectRegistration(ILProcessor methodWorker, FieldDefinition fd)
+ {
+ methodWorker.Append(methodWorker.Create(OpCodes.Ldarg_0));
+ methodWorker.Append(methodWorker.Create(OpCodes.Ldarg_0));
+ methodWorker.Append(methodWorker.Create(OpCodes.Ldfld, fd));
+
+ methodWorker.Append(methodWorker.Create(OpCodes.Call, Weaver.InitSyncObjectReference));
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs.meta
new file mode 100644
index 0000000..22f976e
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d02219b00b3674e59a2151f41e791688
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
new file mode 100644
index 0000000..9eb7494
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs
@@ -0,0 +1,123 @@
+using System;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class SyncObjectProcessor
+ {
+ ///
+ /// Generates the serialization and deserialization methods for a specified generic argument
+ ///
+ /// The type of the class that needs serialization methods
+ /// Which generic argument to serialize, 0 is the first one
+ /// The name of the serialize method
+ /// The name of the deserialize method
+ public static void GenerateSerialization(TypeDefinition td, int genericArgument, string serializeMethod, string deserializeMethod)
+ {
+ // find item type
+ GenericInstanceType gt = (GenericInstanceType)td.BaseType;
+ if (gt.GenericArguments.Count <= genericArgument)
+ {
+ Weaver.Error($"{td} should have {genericArgument} generic arguments");
+ return;
+ }
+ TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[genericArgument]);
+
+ Weaver.DLog(td, "SyncObjectProcessor Start item:" + itemType.FullName);
+
+ MethodReference writeItemFunc = GenerateSerialization(serializeMethod, td, itemType);
+ if (Weaver.WeavingFailed)
+ {
+ return;
+ }
+
+ MethodReference readItemFunc = GenerateDeserialization(deserializeMethod, td, itemType);
+
+ if (readItemFunc == null || writeItemFunc == null)
+ return;
+
+ Weaver.DLog(td, "SyncObjectProcessor Done");
+ }
+
+ // serialization of individual element
+ static MethodReference GenerateSerialization(string methodName, TypeDefinition td, TypeReference itemType)
+ {
+ Weaver.DLog(td, " GenerateSerialization");
+ foreach (MethodDefinition m in td.Methods)
+ {
+ if (m.Name == methodName)
+ return m;
+ }
+
+ MethodDefinition serializeFunc = new MethodDefinition(methodName, MethodAttributes.Public |
+ MethodAttributes.Virtual |
+ MethodAttributes.Public |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ serializeFunc.Parameters.Add(new ParameterDefinition("item", ParameterAttributes.None, itemType));
+ ILProcessor serWorker = serializeFunc.Body.GetILProcessor();
+
+ if (itemType.IsGenericInstance)
+ {
+ Weaver.Error($"{td} cannot have generic elements {itemType}");
+ return null;
+ }
+
+ MethodReference writeFunc = Writers.GetWriteFunc(itemType);
+ if (writeFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_2));
+ serWorker.Append(serWorker.Create(OpCodes.Call, writeFunc));
+ }
+ else
+ {
+ Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead");
+ return null;
+ }
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+
+ td.Methods.Add(serializeFunc);
+ return serializeFunc;
+ }
+
+ static MethodReference GenerateDeserialization(string methodName, TypeDefinition td, TypeReference itemType)
+ {
+ Weaver.DLog(td, " GenerateDeserialization");
+ foreach (MethodDefinition m in td.Methods)
+ {
+ if (m.Name == methodName)
+ return m;
+ }
+
+ MethodDefinition deserializeFunction = new MethodDefinition(methodName, MethodAttributes.Public |
+ MethodAttributes.Virtual |
+ MethodAttributes.Public |
+ MethodAttributes.HideBySig,
+ itemType);
+
+ deserializeFunction.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+
+ ILProcessor serWorker = deserializeFunction.Body.GetILProcessor();
+
+ MethodReference readerFunc = Readers.GetReadFunc(itemType);
+ if (readerFunc != null)
+ {
+ serWorker.Append(serWorker.Create(OpCodes.Ldarg_1));
+ serWorker.Append(serWorker.Create(OpCodes.Call, readerFunc));
+ serWorker.Append(serWorker.Create(OpCodes.Ret));
+ }
+ else
+ {
+ Weaver.Error($"{td} cannot have item of type {itemType}. Use a type supported by mirror instead");
+ return null;
+ }
+
+ td.Methods.Add(deserializeFunction);
+ return deserializeFunction;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs.meta
new file mode 100644
index 0000000..0efe434
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 78f71efc83cde4917b7d21efa90bcc9a
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
new file mode 100644
index 0000000..86be11c
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs
@@ -0,0 +1,335 @@
+// all the [SyncVar] code from NetworkBehaviourProcessor in one place
+using System.Collections.Generic;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class SyncVarProcessor
+ {
+ const int SyncVarLimit = 64; // ulong = 64 bytes
+
+ // returns false for error, not for no-hook-exists
+ public static bool CheckForHookFunction(TypeDefinition td, FieldDefinition syncVar, out MethodDefinition foundMethod)
+ {
+ foundMethod = null;
+ foreach (CustomAttribute ca in syncVar.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
+ {
+ foreach (CustomAttributeNamedArgument customField in ca.Fields)
+ {
+ if (customField.Name == "hook")
+ {
+ string hookFunctionName = customField.Argument.Value as string;
+
+ foreach (MethodDefinition m in td.Methods)
+ {
+ if (m.Name == hookFunctionName)
+ {
+ if (m.Parameters.Count == 1)
+ {
+ if (m.Parameters[0].ParameterType != syncVar.FieldType)
+ {
+ Weaver.Error($"{m} should have signature:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}");
+ return false;
+ }
+ foundMethod = m;
+ return true;
+ }
+ Weaver.Error($"{m} should have signature:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}");
+ return false;
+ }
+ }
+ Weaver.Error($"No hook implementation found for {syncVar}. Add this method to your class:\npublic void {hookFunctionName}({syncVar.FieldType} value) {{ }}" );
+ return false;
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ public static MethodDefinition ProcessSyncVarGet(FieldDefinition fd, string originalName, FieldDefinition netFieldId)
+ {
+ //Create the get method
+ MethodDefinition get = new MethodDefinition(
+ "get_Network" + originalName, MethodAttributes.Public |
+ MethodAttributes.SpecialName |
+ MethodAttributes.HideBySig,
+ fd.FieldType);
+
+ ILProcessor getWorker = get.Body.GetILProcessor();
+
+ // [SyncVar] GameObject?
+ if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
+ {
+ // return this.GetSyncVarGameObject(ref field, uint netId);
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
+ getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
+ getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
+ getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarGameObjectReference));
+ getWorker.Append(getWorker.Create(OpCodes.Ret));
+ }
+ // [SyncVar] NetworkIdentity?
+ else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ {
+ // return this.GetSyncVarNetworkIdentity(ref field, uint netId);
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0)); // this.
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
+ getWorker.Append(getWorker.Create(OpCodes.Ldfld, netFieldId));
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
+ getWorker.Append(getWorker.Create(OpCodes.Ldflda, fd));
+ getWorker.Append(getWorker.Create(OpCodes.Call, Weaver.getSyncVarNetworkIdentityReference));
+ getWorker.Append(getWorker.Create(OpCodes.Ret));
+ }
+ // [SyncVar] int, string, etc.
+ else
+ {
+ getWorker.Append(getWorker.Create(OpCodes.Ldarg_0));
+ getWorker.Append(getWorker.Create(OpCodes.Ldfld, fd));
+ getWorker.Append(getWorker.Create(OpCodes.Ret));
+ }
+
+ get.Body.Variables.Add(new VariableDefinition(fd.FieldType));
+ get.Body.InitLocals = true;
+ get.SemanticsAttributes = MethodSemanticsAttributes.Getter;
+
+ return get;
+ }
+
+ public static MethodDefinition ProcessSyncVarSet(TypeDefinition td, FieldDefinition fd, string originalName, long dirtyBit, FieldDefinition netFieldId)
+ {
+ //Create the set method
+ MethodDefinition set = new MethodDefinition("set_Network" + originalName, MethodAttributes.Public |
+ MethodAttributes.SpecialName |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ ILProcessor setWorker = set.Body.GetILProcessor();
+
+ CheckForHookFunction(td, fd, out MethodDefinition hookFunctionMethod);
+
+ if (hookFunctionMethod != null)
+ {
+ //if (NetworkServer.localClientActive && !getSyncVarHookGuard(dirtyBit))
+ Instruction label = setWorker.Create(OpCodes.Nop);
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.NetworkServerGetLocalClientActive));
+ setWorker.Append(setWorker.Create(OpCodes.Brfalse, label));
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.getSyncVarHookGuard));
+ setWorker.Append(setWorker.Create(OpCodes.Brtrue, label));
+
+ // setSyncVarHookGuard(dirtyBit, true);
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_1));
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
+
+ // call hook
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
+ setWorker.Append(setWorker.Create(OpCodes.Call, hookFunctionMethod));
+
+ // setSyncVarHookGuard(dirtyBit, false);
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit));
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I4_0));
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarHookGuard));
+
+ setWorker.Append(label);
+ }
+
+ // this
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+
+ // new value to set
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_1));
+
+ // reference to field to set
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldflda, fd));
+
+ // dirty bit
+ setWorker.Append(setWorker.Create(OpCodes.Ldc_I8, dirtyBit)); // 8 byte integer aka long
+
+
+ if (fd.FieldType.FullName == Weaver.gameObjectType.FullName)
+ {
+ // reference to netId Field to set
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
+
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarGameObjectReference));
+ }
+ else if (fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ {
+ // reference to netId Field to set
+ setWorker.Append(setWorker.Create(OpCodes.Ldarg_0));
+ setWorker.Append(setWorker.Create(OpCodes.Ldflda, netFieldId));
+
+ setWorker.Append(setWorker.Create(OpCodes.Call, Weaver.setSyncVarNetworkIdentityReference));
+ }
+ else
+ {
+ // make generic version of SetSyncVar with field type
+ GenericInstanceMethod gm = new GenericInstanceMethod(Weaver.setSyncVarReference);
+ gm.GenericArguments.Add(fd.FieldType);
+
+ // invoke SetSyncVar
+ setWorker.Append(setWorker.Create(OpCodes.Call, gm));
+ }
+
+ setWorker.Append(setWorker.Create(OpCodes.Ret));
+
+ set.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.In, fd.FieldType));
+ set.SemanticsAttributes = MethodSemanticsAttributes.Setter;
+
+ return set;
+ }
+
+ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary syncVarNetIds, long dirtyBit)
+ {
+ string originalName = fd.Name;
+ Weaver.DLog(td, "Sync Var " + fd.Name + " " + fd.FieldType + " " + Weaver.gameObjectType);
+
+ // GameObject/NetworkIdentity SyncVars have a new field for netId
+ FieldDefinition netIdField = null;
+ if (fd.FieldType.FullName == Weaver.gameObjectType.FullName ||
+ fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ {
+ netIdField = new FieldDefinition("___" + fd.Name + "NetId",
+ FieldAttributes.Private,
+ Weaver.uint32Type);
+
+ syncVarNetIds[fd] = netIdField;
+ }
+
+ MethodDefinition get = ProcessSyncVarGet(fd, originalName, netIdField);
+ MethodDefinition set = ProcessSyncVarSet(td, fd, originalName, dirtyBit, netIdField);
+
+ //NOTE: is property even needed? Could just use a setter function?
+ //create the property
+ PropertyDefinition propertyDefinition = new PropertyDefinition("Network" + originalName, PropertyAttributes.None, fd.FieldType)
+ {
+ GetMethod = get, SetMethod = set
+ };
+
+ //add the methods and property to the type.
+ td.Methods.Add(get);
+ td.Methods.Add(set);
+ td.Properties.Add(propertyDefinition);
+ Weaver.WeaveLists.replacementSetterProperties[fd] = set;
+
+ // replace getter field if GameObject/NetworkIdentity so it uses
+ // netId instead
+ // -> only for GameObjects, otherwise an int syncvar's getter would
+ // end up in recursion.
+ if (fd.FieldType.FullName == Weaver.gameObjectType.FullName ||
+ fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName)
+ {
+ Weaver.WeaveLists.replacementGetterProperties[fd] = get;
+ }
+ }
+
+ public static void ProcessSyncVars(TypeDefinition td, List syncVars, List syncObjects, Dictionary syncVarNetIds)
+ {
+ int numSyncVars = 0;
+
+ // the mapping of dirtybits to sync-vars is implicit in the order of the fields here. this order is recorded in m_replacementProperties.
+ // start assigning syncvars at the place the base class stopped, if any
+ int dirtyBitCounter = Weaver.GetSyncVarStart(td.BaseType.FullName);
+
+ syncVarNetIds.Clear();
+
+ // find syncvars
+ foreach (FieldDefinition fd in td.Fields)
+ {
+ foreach (CustomAttribute ca in fd.CustomAttributes)
+ {
+ if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName)
+ {
+ TypeDefinition resolvedField = fd.FieldType.Resolve();
+
+ if (resolvedField.IsDerivedFrom(Weaver.NetworkBehaviourType))
+ {
+ Weaver.Error($"{fd} has invalid type. SyncVars cannot be NetworkBehaviours");
+ return;
+ }
+
+ if (resolvedField.IsDerivedFrom(Weaver.ScriptableObjectType))
+ {
+ Weaver.Error($"{fd} has invalid type. SyncVars cannot be scriptable objects");
+ return;
+ }
+
+ if ((fd.Attributes & FieldAttributes.Static) != 0)
+ {
+ Weaver.Error($"{fd} cannot be static");
+ return;
+ }
+
+ if (resolvedField.HasGenericParameters)
+ {
+ Weaver.Error($"{fd} has invalid type. SyncVars cannot have generic parameters");
+ return;
+ }
+
+ if (resolvedField.IsInterface)
+ {
+ Weaver.Error($"{fd} has invalid type. Use a concrete type instead of interface {fd.FieldType}");
+ return;
+ }
+
+ if (fd.FieldType.IsArray)
+ {
+ Weaver.Error($"{fd} has invalid type. Use SyncLists instead of arrays");
+ return;
+ }
+
+ if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType))
+ {
+ Log.Warning($"{fd} has [SyncVar] attribute. SyncLists should not be marked with SyncVar");
+ break;
+ }
+
+ syncVars.Add(fd);
+
+ ProcessSyncVar(td, fd, syncVarNetIds, 1L << dirtyBitCounter);
+ dirtyBitCounter += 1;
+ numSyncVars += 1;
+
+ if (dirtyBitCounter == SyncVarLimit)
+ {
+ Weaver.Error($"{td} has too many SyncVars. Consider refactoring your class into multiple components");
+ return;
+ }
+ break;
+ }
+ }
+
+ if (fd.FieldType.Resolve().ImplementsInterface(Weaver.SyncObjectType))
+ {
+ if (fd.IsStatic)
+ {
+ Weaver.Error($"{fd} cannot be static");
+ return;
+ }
+
+ syncObjects.Add(fd);
+ }
+ }
+
+ // add all the new SyncVar __netId fields
+ foreach (FieldDefinition fd in syncVarNetIds.Values)
+ {
+ td.Fields.Add(fd);
+ }
+
+ Weaver.SetNumSyncVars(td.FullName, numSyncVars);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs.meta
new file mode 100644
index 0000000..982f768
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f52c39bddd95d42b88f9cd554dfd9198
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs b/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
new file mode 100644
index 0000000..e20b2c9
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs
@@ -0,0 +1,151 @@
+// all the [TargetRpc] code from NetworkBehaviourProcessor in one place
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ public static class TargetRpcProcessor
+ {
+ const string TargetRpcPrefix = "InvokeTargetRpc";
+
+ // helper functions to check if the method has a NetworkConnection parameter
+ public static bool HasNetworkConnectionParameter(MethodDefinition md)
+ {
+ return md.Parameters.Count > 0 &&
+ md.Parameters[0].ParameterType.FullName == Weaver.NetworkConnectionType.FullName;
+ }
+
+ public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodDefinition md)
+ {
+ MethodDefinition rpc = new MethodDefinition(RpcProcessor.RpcPrefix + md.Name, MethodAttributes.Family |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ ILProcessor rpcWorker = rpc.Body.GetILProcessor();
+ Instruction label = rpcWorker.Create(OpCodes.Nop);
+
+ NetworkBehaviourProcessor.WriteClientActiveCheck(rpcWorker, md.Name, label, "TargetRPC");
+
+ // setup for reader
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td));
+
+ // NetworkConnection parameter is optional
+ bool hasNetworkConnection = HasNetworkConnectionParameter(md);
+ if (hasNetworkConnection)
+ {
+ //ClientScene.readyconnection
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.ReadyConnectionReference));
+ }
+
+ // process reader parameters and skip first one if first one is NetworkConnection
+ if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(md, rpcWorker, hasNetworkConnection))
+ return null;
+
+ // invoke actual command function
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, md));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
+
+ NetworkBehaviourProcessor.AddInvokeParameters(rpc.Parameters);
+
+ return rpc;
+ }
+
+ /* generates code like:
+ public void CallTargetTest (NetworkConnection conn, int param)
+ {
+ NetworkWriter writer = new NetworkWriter ();
+ writer.WritePackedUInt32 ((uint)param);
+ base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val);
+ }
+
+ or if optional:
+ public void CallTargetTest (int param)
+ {
+ NetworkWriter writer = new NetworkWriter ();
+ writer.WritePackedUInt32 ((uint)param);
+ base.SendTargetRPCInternal (null, typeof(class), "TargetTest", val);
+ }
+ */
+ public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDefinition md, CustomAttribute ca)
+ {
+ MethodDefinition rpc = new MethodDefinition("Call" + md.Name, MethodAttributes.Public |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ // add parameters
+ foreach (ParameterDefinition pd in md.Parameters)
+ {
+ rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType));
+ }
+
+ ILProcessor rpcWorker = rpc.Body.GetILProcessor();
+
+ NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker);
+
+ NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker);
+
+ // NetworkConnection parameter is optional
+ bool hasNetworkConnection = HasNetworkConnectionParameter(md);
+
+ // write all the arguments that the user passed to the TargetRpc call
+ // (skip first one if first one is NetworkConnection)
+ if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, hasNetworkConnection))
+ return null;
+
+ string rpcName = md.Name;
+ int index = rpcName.IndexOf(TargetRpcPrefix);
+ if (index > -1)
+ {
+ rpcName = rpcName.Substring(TargetRpcPrefix.Length);
+ }
+
+ // invoke SendInternal and return
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this
+ if (HasNetworkConnectionParameter(md))
+ {
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1)); // connection
+ }
+ else
+ {
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldnull)); // null
+ }
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldtoken, td));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.getTypeFromHandleReference)); // invokerClass
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, rpcName));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldloc_0)); // writer
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ldc_I4, NetworkBehaviourProcessor.GetChannelId(ca)));
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Callvirt, Weaver.sendTargetRpcInternal));
+
+ NetworkBehaviourProcessor.WriteRecycleWriter(rpcWorker);
+
+ rpcWorker.Append(rpcWorker.Create(OpCodes.Ret));
+
+ return rpc;
+ }
+
+ public static bool ProcessMethodsValidateTargetRpc(MethodDefinition md, CustomAttribute ca)
+ {
+ if (!md.Name.StartsWith("Target"))
+ {
+ Weaver.Error($"{md} must start with Target. Consider renaming it to Target{md.Name}");
+ return false;
+ }
+
+ if (md.IsStatic)
+ {
+ Weaver.Error($"{md} must not be static");
+ return false;
+ }
+
+ if (!NetworkBehaviourProcessor.ProcessMethodsValidateFunction(md))
+ {
+ return false;
+ }
+
+ // validate
+ return NetworkBehaviourProcessor.ProcessMethodsValidateParameters(md, ca);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs.meta
new file mode 100644
index 0000000..0ff7cc5
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: fb3ce6c6f3f2942ae88178b86f5a8282
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Program.cs b/Assets/Packages/Mirror/Editor/Weaver/Program.cs
new file mode 100644
index 0000000..62163d2
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Program.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+
+namespace Mirror.Weaver
+{
+ public static class Log
+ {
+ public static Action WarningMethod;
+ public static Action ErrorMethod;
+
+ public static void Warning(string msg)
+ {
+ WarningMethod("Mirror.Weaver warning: " + msg);
+ }
+
+ public static void Error(string msg)
+ {
+ ErrorMethod("Mirror.Weaver error: " + msg);
+ }
+ }
+
+ public static class Program
+ {
+ public static bool Process(string unityEngine, string netDLL, string outputDirectory, string[] assemblies, string[] extraAssemblyPaths, Action printWarning, Action printError)
+ {
+ CheckDLLPath(unityEngine);
+ CheckDLLPath(netDLL);
+ CheckOutputDirectory(outputDirectory);
+ CheckAssemblies(assemblies);
+ Log.WarningMethod = printWarning;
+ Log.ErrorMethod = printError;
+ return Weaver.WeaveAssemblies(assemblies, extraAssemblyPaths, outputDirectory, unityEngine, netDLL);
+ }
+
+ static void CheckDLLPath(string path)
+ {
+ if (!File.Exists(path))
+ throw new Exception("dll could not be located at " + path + "!");
+ }
+
+ static void CheckAssemblies(IEnumerable assemblyPaths)
+ {
+ foreach (string assemblyPath in assemblyPaths)
+ CheckAssemblyPath(assemblyPath);
+ }
+
+ static void CheckAssemblyPath(string assemblyPath)
+ {
+ if (!File.Exists(assemblyPath))
+ throw new Exception("Assembly " + assemblyPath + " does not exist!");
+ }
+
+ static void CheckOutputDirectory(string outputDir)
+ {
+ if (outputDir != null && !Directory.Exists(outputDir))
+ {
+ Directory.CreateDirectory(outputDir);
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Program.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Program.cs.meta
new file mode 100644
index 0000000..3f62978
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Program.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2a21c60c40a4c4d679c2b71a7c40882e
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Readers.cs b/Assets/Packages/Mirror/Editor/Weaver/Readers.cs
new file mode 100644
index 0000000..a50798d
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Readers.cs
@@ -0,0 +1,371 @@
+using System.Collections.Generic;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+using Mono.CecilX.Rocks;
+
+
+namespace Mirror.Weaver
+{
+ public static class Readers
+ {
+ const int MaxRecursionCount = 128;
+ static Dictionary readFuncs;
+
+ public static void Init()
+ {
+ readFuncs = new Dictionary();
+ }
+
+ internal static void Register(TypeReference dataType, MethodReference methodReference)
+ {
+ readFuncs[dataType.FullName] = methodReference;
+ }
+
+ public static MethodReference GetReadFunc(TypeReference variable, int recursionCount = 0)
+ {
+ if (readFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
+ {
+ return foundFunc;
+ }
+
+ TypeDefinition td = variable.Resolve();
+ if (td == null)
+ {
+ Weaver.Error($"{variable} is not a supported type");
+ return null;
+ }
+
+ if (variable.IsByReference)
+ {
+ // error??
+ Weaver.Error($"{variable} is not a supported reference type");
+ return null;
+ }
+
+ MethodDefinition newReaderFunc;
+
+ if (variable.IsArray)
+ {
+ newReaderFunc = GenerateArrayReadFunc(variable, recursionCount);
+ }
+ else if (td.IsEnum)
+ {
+ return GetReadFunc(td.GetEnumUnderlyingType(), recursionCount);
+ }
+ else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
+ {
+ newReaderFunc = GenerateArraySegmentReadFunc(variable, recursionCount);
+ }
+ else
+ {
+ newReaderFunc = GenerateStructReadFunction(variable, recursionCount);
+ }
+
+ if (newReaderFunc == null)
+ {
+ Weaver.Error($"{variable} is not a supported type");
+ return null;
+ }
+ RegisterReadFunc(variable.FullName, newReaderFunc);
+ return newReaderFunc;
+ }
+
+ static void RegisterReadFunc(string name, MethodDefinition newReaderFunc)
+ {
+ readFuncs[name] = newReaderFunc;
+ Weaver.WeaveLists.generatedReadFunctions.Add(newReaderFunc);
+
+ Weaver.ConfirmGeneratedCodeClass();
+ Weaver.WeaveLists.generateContainerClass.Methods.Add(newReaderFunc);
+ }
+
+ static MethodDefinition GenerateArrayReadFunc(TypeReference variable, int recursionCount)
+ {
+ if (!variable.IsArrayType())
+ {
+ Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported");
+ return null;
+ }
+
+ TypeReference elementType = variable.GetElementType();
+ MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1);
+ if (elementReadFunc == null)
+ {
+ return null;
+ }
+
+ string functionName = "_ReadArray" + variable.GetElementType().Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+
+ // create new reader for this type
+ MethodDefinition readerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ variable);
+
+ readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+
+ readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ readerFunc.Body.Variables.Add(new VariableDefinition(variable));
+ readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ readerFunc.Body.InitLocals = true;
+
+ ILProcessor worker = readerFunc.Body.GetILProcessor();
+
+ // int length = reader.ReadPackedInt32();
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Call, GetReadFunc(Weaver.int32Type)));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+
+ // if (length < 0) {
+ // return null
+ // }
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_0));
+ Instruction labelEmptyArray = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Bge, labelEmptyArray));
+ // return null
+ worker.Append(worker.Create(OpCodes.Ldnull));
+ worker.Append(worker.Create(OpCodes.Ret));
+ worker.Append(labelEmptyArray);
+
+
+ // T value = new T[length];
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Newarr, variable.GetElementType()));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+
+
+ // for (int i=0; i< length ; i++) {
+ worker.Append(worker.Create(OpCodes.Ldc_I4_0));
+ worker.Append(worker.Create(OpCodes.Stloc_2));
+ Instruction labelHead = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Br, labelHead));
+
+ // loop body
+ Instruction labelBody = worker.Create(OpCodes.Nop);
+ worker.Append(labelBody);
+ // value[i] = reader.ReadT();
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType()));
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Call, elementReadFunc));
+ worker.Append(worker.Create(OpCodes.Stobj, variable.GetElementType()));
+
+
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_1));
+ worker.Append(worker.Create(OpCodes.Add));
+ worker.Append(worker.Create(OpCodes.Stloc_2));
+
+ // loop while check
+ worker.Append(labelHead);
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Blt, labelBody));
+
+ // return value;
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ret));
+ return readerFunc;
+ }
+
+ static MethodDefinition GenerateArraySegmentReadFunc(TypeReference variable, int recursionCount)
+ {
+ GenericInstanceType genericInstance = (GenericInstanceType)variable;
+ TypeReference elementType = genericInstance.GenericArguments[0];
+
+ MethodReference elementReadFunc = GetReadFunc(elementType, recursionCount + 1);
+ if (elementReadFunc == null)
+ {
+ return null;
+ }
+
+ string functionName = "_ReadArraySegment_" + variable.GetElementType().Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+
+ // create new reader for this type
+ MethodDefinition readerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ variable);
+
+ readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+
+ // int lengh
+ readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ // T[] array
+ readerFunc.Body.Variables.Add(new VariableDefinition(elementType.MakeArrayType()));
+ // int i;
+ readerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ readerFunc.Body.InitLocals = true;
+
+ ILProcessor worker = readerFunc.Body.GetILProcessor();
+
+ // int length = reader.ReadPackedInt32();
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Call, GetReadFunc(Weaver.int32Type)));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+
+ // T[] array = new int[length]
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Newarr, elementType));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+
+
+ // loop through array and deserialize each element
+ // generates code like this
+ // for (int i=0; i< length ; i++)
+ // {
+ // value[i] = reader.ReadXXX();
+ // }
+ worker.Append(worker.Create(OpCodes.Ldc_I4_0));
+ worker.Append(worker.Create(OpCodes.Stloc_2));
+ Instruction labelHead = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Br, labelHead));
+
+ // loop body
+ Instruction labelBody = worker.Create(OpCodes.Nop);
+ worker.Append(labelBody);
+ {
+ // value[i] = reader.ReadT();
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldelema, elementType));
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Call, elementReadFunc));
+ worker.Append(worker.Create(OpCodes.Stobj, elementType));
+ }
+
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_1));
+ worker.Append(worker.Create(OpCodes.Add));
+ worker.Append(worker.Create(OpCodes.Stloc_2));
+
+ // loop while check
+ worker.Append(labelHead);
+ worker.Append(worker.Create(OpCodes.Ldloc_2));
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Blt, labelBody));
+
+ // return new ArraySegment(array);
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Newobj, Weaver.ArraySegmentConstructorReference.MakeHostInstanceGeneric(genericInstance)));
+ worker.Append(worker.Create(OpCodes.Ret));
+ return readerFunc;
+ }
+
+ static MethodDefinition GenerateStructReadFunction(TypeReference variable, int recursionCount)
+ {
+ if (recursionCount > MaxRecursionCount)
+ {
+ Weaver.Error($"{variable} can't be deserialized because it references itself");
+ return null;
+ }
+
+ if (!Weaver.IsValidTypeToGenerate(variable.Resolve()))
+ {
+ return null;
+ }
+
+ string functionName = "_Read" + variable.Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+
+ // create new reader for this type
+ MethodDefinition readerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ variable);
+
+ // create local for return value
+ readerFunc.Body.Variables.Add(new VariableDefinition(variable));
+ readerFunc.Body.InitLocals = true;
+
+ readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType)));
+
+ ILProcessor worker = readerFunc.Body.GetILProcessor();
+
+ if (variable.IsValueType)
+ {
+ // structs are created with Initobj
+ worker.Append(worker.Create(OpCodes.Ldloca, 0));
+ worker.Append(worker.Create(OpCodes.Initobj, variable));
+ }
+ else
+ {
+ // classes are created with their constructor
+
+ MethodDefinition ctor = Resolvers.ResolveDefaultPublicCtor(variable);
+ if (ctor == null)
+ {
+ Weaver.Error($"{variable} can't be deserialized bcause i has no default constructor");
+ return null;
+ }
+
+ worker.Append(worker.Create(OpCodes.Newobj, ctor));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+ }
+
+ uint fields = 0;
+ foreach (FieldDefinition field in variable.Resolve().Fields)
+ {
+ if (field.IsStatic || field.IsPrivate)
+ continue;
+
+ // mismatched ldloca/ldloc for struct/class combinations is invalid IL, which causes crash at runtime
+ OpCode opcode = variable.IsValueType ? OpCodes.Ldloca : OpCodes.Ldloc;
+ worker.Append(worker.Create(opcode, 0));
+
+ MethodReference readFunc = GetReadFunc(field.FieldType, recursionCount + 1);
+ if (readFunc != null)
+ {
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Call, readFunc));
+ }
+ else
+ {
+ Weaver.Error($"{field} has an unsupported type");
+ return null;
+ }
+
+ worker.Append(worker.Create(OpCodes.Stfld, field));
+ fields++;
+ }
+ if (fields == 0)
+ {
+ Log.Warning($"{variable} has no public or non-static fields to deserialize");
+ }
+
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Ret));
+ return readerFunc;
+ }
+
+ }
+
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Readers.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Readers.cs.meta
new file mode 100644
index 0000000..838ff59
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Readers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: be40277098a024539bf63d0205cae824
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs b/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs
new file mode 100644
index 0000000..434aed4
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs
@@ -0,0 +1,141 @@
+// all the resolve functions for the weaver
+// NOTE: these functions should be made extensions, but right now they still
+// make heavy use of Weaver.fail and we'd have to check each one's return
+// value for null otherwise.
+// (original FieldType.Resolve returns null if not found too, so
+// exceptions would be a bit inconsistent here)
+using Mono.CecilX;
+
+namespace Mirror.Weaver
+{
+ public static class Resolvers
+ {
+ public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition scriptDef, string name)
+ {
+ //Console.WriteLine("ResolveMethod " + t.ToString () + " " + name);
+ if (tr == null)
+ {
+ Weaver.Error("Type missing for " + name);
+ return null;
+ }
+ foreach (MethodDefinition methodRef in tr.Resolve().Methods)
+ {
+ if (methodRef.Name == name)
+ {
+ return scriptDef.MainModule.ImportReference(methodRef);
+ }
+ }
+ Weaver.Error($"{tr}.{name}() not found");
+ return null;
+ }
+
+ // TODO reuse ResolveMethod in here after Weaver.fail was removed
+ public static MethodReference ResolveMethodInParents(TypeReference tr, AssemblyDefinition scriptDef, string name)
+ {
+ if (tr == null)
+ {
+ Weaver.Error("Type missing for " + name);
+ return null;
+ }
+ foreach (MethodDefinition methodRef in tr.Resolve().Methods)
+ {
+ if (methodRef.Name == name)
+ {
+ return scriptDef.MainModule.ImportReference(methodRef);
+ }
+ }
+ // Could not find the method in this class, try the parent
+ return ResolveMethodInParents(tr.Resolve().BaseType, scriptDef, name);
+ }
+
+ // System.Byte[] arguments need a version with a string
+ public static MethodReference ResolveMethodWithArg(TypeReference tr, AssemblyDefinition scriptDef, string name, string argTypeFullName)
+ {
+ foreach (MethodDefinition methodRef in tr.Resolve().Methods)
+ {
+ if (methodRef.Name == name)
+ {
+ if (methodRef.Parameters.Count == 1)
+ {
+ if (methodRef.Parameters[0].ParameterType.FullName == argTypeFullName)
+ {
+ return scriptDef.MainModule.ImportReference(methodRef);
+ }
+ }
+ }
+ }
+ Weaver.Error($"{tr}.{name}({argTypeFullName}) not found");
+ return null;
+ }
+
+ // reuse ResolveMethodWithArg string version
+ public static MethodReference ResolveMethodWithArg(TypeReference tr, AssemblyDefinition scriptDef, string name, TypeReference argType)
+ {
+ return ResolveMethodWithArg(tr, scriptDef, name, argType.FullName);
+ }
+
+ public static MethodDefinition ResolveDefaultPublicCtor(TypeReference variable)
+ {
+ foreach (MethodDefinition methodRef in variable.Resolve().Methods)
+ {
+ if (methodRef.Name == ".ctor" &&
+ methodRef.Resolve().IsPublic &&
+ methodRef.Parameters.Count == 0)
+ {
+ return methodRef;
+ }
+ }
+ return null;
+ }
+
+ public static GenericInstanceMethod ResolveMethodGeneric(TypeReference t, AssemblyDefinition scriptDef, string name, TypeReference genericType)
+ {
+ foreach (MethodDefinition methodRef in t.Resolve().Methods)
+ {
+ if (methodRef.Name == name)
+ {
+ if (methodRef.Parameters.Count == 0)
+ {
+ if (methodRef.GenericParameters.Count == 1)
+ {
+ MethodReference tmp = scriptDef.MainModule.ImportReference(methodRef);
+ GenericInstanceMethod gm = new GenericInstanceMethod(tmp);
+ gm.GenericArguments.Add(genericType);
+ if (gm.GenericArguments[0].FullName == genericType.FullName)
+ {
+ return gm;
+ }
+ }
+ }
+ }
+ }
+
+ Weaver.Error($"{t}.{name}<{genericType}>() not found");
+ return null;
+ }
+
+ public static FieldReference ResolveField(TypeReference tr, AssemblyDefinition scriptDef, string name)
+ {
+ foreach (FieldDefinition fd in tr.Resolve().Fields)
+ {
+ if (fd.Name == name)
+ {
+ return scriptDef.MainModule.ImportReference(fd);
+ }
+ }
+ return null;
+ }
+
+ public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefinition scriptDef, string name)
+ {
+ foreach (PropertyDefinition pd in tr.Resolve().Properties)
+ {
+ if (pd.Name == name)
+ {
+ return scriptDef.MainModule.ImportReference(pd.GetMethod);
+ }
+ }
+ return null;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs.meta
new file mode 100644
index 0000000..f4f6602
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Resolvers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3039a59c76aec43c797ad66930430367
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs b/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs
new file mode 100644
index 0000000..cf905ad
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs
@@ -0,0 +1,598 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+ // This data is flushed each time - if we are run multiple times in the same process/domain
+ class WeaverLists
+ {
+ // setter functions that replace [SyncVar] member variable references. dict
+ public Dictionary replacementSetterProperties = new Dictionary();
+ // getter functions that replace [SyncVar] member variable references. dict
+ public Dictionary replacementGetterProperties = new Dictionary();
+
+ // [Command]/[ClientRpc] functions that should be replaced. dict
+ public Dictionary replaceMethods = new Dictionary();
+
+ // [SyncEvent] invoke functions that should be replaced. dict
+ public Dictionary replaceEvents = new Dictionary();
+
+ public List generatedReadFunctions = new List();
+ public List generatedWriteFunctions = new List();
+
+ public TypeDefinition generateContainerClass;
+
+ // amount of SyncVars per class. dict
+ public Dictionary numSyncVars = new Dictionary();
+ }
+
+ class Weaver
+ {
+ public static WeaverLists WeaveLists { get; private set; }
+ public static AssemblyDefinition CurrentAssembly { get; private set; }
+ public static ModuleDefinition CorLibModule { get; private set; }
+ public static AssemblyDefinition UnityAssembly { get; private set; }
+ public static AssemblyDefinition NetAssembly { get; private set; }
+ public static bool WeavingFailed { get; private set; }
+ public static bool GenerateLogErrors { get; set; }
+
+ // private properties
+ static readonly bool DebugLogEnabled = true;
+
+ // Network types
+ public static TypeReference NetworkBehaviourType;
+ public static TypeReference NetworkBehaviourType2;
+ public static TypeReference MonoBehaviourType;
+ public static TypeReference ScriptableObjectType;
+ public static TypeReference NetworkConnectionType;
+
+ public static TypeReference MessageBaseType;
+ public static TypeReference SyncListType;
+ public static TypeReference SyncSetType;
+ public static TypeReference SyncDictionaryType;
+
+ public static MethodReference NetworkBehaviourDirtyBitsReference;
+ public static MethodReference GetPooledWriterReference;
+ public static MethodReference RecycleWriterReference;
+ public static TypeReference NetworkClientType;
+ public static TypeReference NetworkServerType;
+
+ public static TypeReference NetworkReaderType;
+
+ public static TypeReference NetworkWriterType;
+
+ public static TypeReference NetworkIdentityType;
+ public static TypeReference IEnumeratorType;
+
+ public static TypeReference ClientSceneType;
+ public static MethodReference ReadyConnectionReference;
+
+ public static TypeReference ComponentType;
+
+ public static TypeReference CmdDelegateReference;
+ public static MethodReference CmdDelegateConstructor;
+
+ public static MethodReference NetworkServerGetActive;
+ public static MethodReference NetworkServerGetLocalClientActive;
+ public static MethodReference NetworkClientGetActive;
+ public static MethodReference getBehaviourIsServer;
+
+ // custom attribute types
+ public static TypeReference SyncVarType;
+ public static TypeReference CommandType;
+ public static TypeReference ClientRpcType;
+ public static TypeReference TargetRpcType;
+ public static TypeReference SyncEventType;
+ public static TypeReference SyncObjectType;
+ public static MethodReference InitSyncObjectReference;
+
+ // array segment
+ public static TypeReference ArraySegmentType;
+ public static MethodReference ArraySegmentConstructorReference;
+ public static MethodReference ArraySegmentArrayReference;
+ public static MethodReference ArraySegmentOffsetReference;
+ public static MethodReference ArraySegmentCountReference;
+
+ // system types
+ public static TypeReference voidType;
+ public static TypeReference singleType;
+ public static TypeReference doubleType;
+ public static TypeReference boolType;
+ public static TypeReference int64Type;
+ public static TypeReference uint64Type;
+ public static TypeReference int32Type;
+ public static TypeReference uint32Type;
+ public static TypeReference objectType;
+ public static TypeReference typeType;
+ public static TypeReference gameObjectType;
+ public static TypeReference transformType;
+
+ public static MethodReference setSyncVarReference;
+ public static MethodReference setSyncVarHookGuard;
+ public static MethodReference getSyncVarHookGuard;
+ public static MethodReference setSyncVarGameObjectReference;
+ public static MethodReference getSyncVarGameObjectReference;
+ public static MethodReference setSyncVarNetworkIdentityReference;
+ public static MethodReference getSyncVarNetworkIdentityReference;
+ public static MethodReference registerCommandDelegateReference;
+ public static MethodReference registerRpcDelegateReference;
+ public static MethodReference registerEventDelegateReference;
+ public static MethodReference getTypeReference;
+ public static MethodReference getTypeFromHandleReference;
+ public static MethodReference logErrorReference;
+ public static MethodReference logWarningReference;
+ public static MethodReference sendCommandInternal;
+ public static MethodReference sendRpcInternal;
+ public static MethodReference sendTargetRpcInternal;
+ public static MethodReference sendEventInternal;
+
+ public static void DLog(TypeDefinition td, string fmt, params object[] args)
+ {
+ if (!DebugLogEnabled)
+ return;
+
+ Console.WriteLine("[" + td.Name + "] " + string.Format(fmt, args));
+ }
+
+ // display weaver error
+ // and mark process as failed
+ public static void Error(string message)
+ {
+ Log.Error(message);
+ WeavingFailed = true;
+ }
+
+ public static int GetSyncVarStart(string className)
+ {
+ return WeaveLists.numSyncVars.ContainsKey(className)
+ ? WeaveLists.numSyncVars[className]
+ : 0;
+ }
+
+ public static void SetNumSyncVars(string className, int num)
+ {
+ WeaveLists.numSyncVars[className] = num;
+ }
+
+ internal static void ConfirmGeneratedCodeClass()
+ {
+ if (WeaveLists.generateContainerClass == null)
+ {
+ WeaveLists.generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode",
+ TypeAttributes.BeforeFieldInit | TypeAttributes.Class | TypeAttributes.AnsiClass | TypeAttributes.Public | TypeAttributes.AutoClass,
+ objectType);
+
+ const MethodAttributes methodAttributes = MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.SpecialName | MethodAttributes.RTSpecialName;
+ MethodDefinition method = new MethodDefinition(".ctor", methodAttributes, voidType);
+ method.Body.Instructions.Add(Instruction.Create(OpCodes.Ldarg_0));
+ method.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Resolvers.ResolveMethod(objectType, CurrentAssembly, ".ctor")));
+ method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret));
+
+ WeaveLists.generateContainerClass.Methods.Add(method);
+ }
+ }
+
+ static bool ProcessNetworkBehaviourType(TypeDefinition td)
+ {
+ if (!NetworkBehaviourProcessor.WasProcessed(td))
+ {
+ DLog(td, "Found NetworkBehaviour " + td.FullName);
+
+ NetworkBehaviourProcessor proc = new NetworkBehaviourProcessor(td);
+ proc.Process();
+ return true;
+ }
+ return false;
+ }
+
+ static void SetupUnityTypes()
+ {
+ gameObjectType = UnityAssembly.MainModule.GetType("UnityEngine.GameObject");
+ transformType = UnityAssembly.MainModule.GetType("UnityEngine.Transform");
+
+ NetworkClientType = NetAssembly.MainModule.GetType("Mirror.NetworkClient");
+ NetworkServerType = NetAssembly.MainModule.GetType("Mirror.NetworkServer");
+
+ SyncVarType = NetAssembly.MainModule.GetType("Mirror.SyncVarAttribute");
+ CommandType = NetAssembly.MainModule.GetType("Mirror.CommandAttribute");
+ ClientRpcType = NetAssembly.MainModule.GetType("Mirror.ClientRpcAttribute");
+ TargetRpcType = NetAssembly.MainModule.GetType("Mirror.TargetRpcAttribute");
+ SyncEventType = NetAssembly.MainModule.GetType("Mirror.SyncEventAttribute");
+ SyncObjectType = NetAssembly.MainModule.GetType("Mirror.SyncObject");
+ }
+
+ static void SetupCorLib()
+ {
+ AssemblyNameReference name = AssemblyNameReference.Parse("mscorlib");
+ ReaderParameters parameters = new ReaderParameters
+ {
+ AssemblyResolver = CurrentAssembly.MainModule.AssemblyResolver
+ };
+ CorLibModule = CurrentAssembly.MainModule.AssemblyResolver.Resolve(name, parameters).MainModule;
+ }
+
+ static TypeReference ImportCorLibType(string fullName)
+ {
+ TypeDefinition type = CorLibModule.GetType(fullName) ?? CorLibModule.ExportedTypes.First(t => t.FullName == fullName).Resolve();
+ if (type != null)
+ {
+ return CurrentAssembly.MainModule.ImportReference(type);
+ }
+ Error("Failed to import mscorlib type: " + fullName + " because Resolve failed. (Might happen when trying to Resolve in NetStandard dll, see also: https://github.com/vis2k/Mirror/issues/791)");
+ return null;
+ }
+
+ static void SetupTargetTypes()
+ {
+ // system types
+ SetupCorLib();
+ voidType = ImportCorLibType("System.Void");
+ singleType = ImportCorLibType("System.Single");
+ doubleType = ImportCorLibType("System.Double");
+ boolType = ImportCorLibType("System.Boolean");
+ int64Type = ImportCorLibType("System.Int64");
+ uint64Type = ImportCorLibType("System.UInt64");
+ int32Type = ImportCorLibType("System.Int32");
+ uint32Type = ImportCorLibType("System.UInt32");
+ objectType = ImportCorLibType("System.Object");
+ typeType = ImportCorLibType("System.Type");
+ IEnumeratorType = ImportCorLibType("System.Collections.IEnumerator");
+
+ ArraySegmentType = ImportCorLibType("System.ArraySegment`1");
+ ArraySegmentArrayReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Array");
+ ArraySegmentCountReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Count");
+ ArraySegmentOffsetReference = Resolvers.ResolveProperty(ArraySegmentType, CurrentAssembly, "Offset");
+ ArraySegmentConstructorReference = Resolvers.ResolveMethod(ArraySegmentType, CurrentAssembly, ".ctor");
+
+
+ NetworkReaderType = NetAssembly.MainModule.GetType("Mirror.NetworkReader");
+ NetworkWriterType = NetAssembly.MainModule.GetType("Mirror.NetworkWriter");
+
+ NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_active");
+ NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_localClientActive");
+ NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, CurrentAssembly, "get_active");
+
+ CmdDelegateReference = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour/CmdDelegate");
+ CmdDelegateConstructor = Resolvers.ResolveMethod(CmdDelegateReference, CurrentAssembly, ".ctor");
+ CurrentAssembly.MainModule.ImportReference(gameObjectType);
+ CurrentAssembly.MainModule.ImportReference(transformType);
+
+ TypeReference networkIdentityTmp = NetAssembly.MainModule.GetType("Mirror.NetworkIdentity");
+ NetworkIdentityType = CurrentAssembly.MainModule.ImportReference(networkIdentityTmp);
+
+ NetworkBehaviourType = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour");
+ NetworkBehaviourType2 = CurrentAssembly.MainModule.ImportReference(NetworkBehaviourType);
+ NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection");
+
+ MonoBehaviourType = UnityAssembly.MainModule.GetType("UnityEngine.MonoBehaviour");
+ ScriptableObjectType = UnityAssembly.MainModule.GetType("UnityEngine.ScriptableObject");
+
+ NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection");
+ NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType);
+
+ MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase");
+ SyncListType = NetAssembly.MainModule.GetType("Mirror.SyncList`1");
+ SyncSetType = NetAssembly.MainModule.GetType("Mirror.SyncSet`1");
+ SyncDictionaryType = NetAssembly.MainModule.GetType("Mirror.SyncDictionary`2");
+
+ NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits");
+ TypeDefinition NetworkWriterPoolType = NetAssembly.MainModule.GetType("Mirror.NetworkWriterPool");
+ GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, CurrentAssembly, "GetWriter");
+ RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, CurrentAssembly, "Recycle");
+
+ ComponentType = UnityAssembly.MainModule.GetType("UnityEngine.Component");
+ ClientSceneType = NetAssembly.MainModule.GetType("Mirror.ClientScene");
+ ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, CurrentAssembly, "get_readyConnection");
+
+ getBehaviourIsServer = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "get_isServer");
+ setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SetSyncVar");
+ setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "setSyncVarHookGuard");
+ getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "getSyncVarHookGuard");
+
+ setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SetSyncVarGameObject");
+ getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "GetSyncVarGameObject");
+ setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SetSyncVarNetworkIdentity");
+ getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "GetSyncVarNetworkIdentity");
+ registerCommandDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "RegisterCommandDelegate");
+ registerRpcDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "RegisterRpcDelegate");
+ registerEventDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "RegisterEventDelegate");
+ getTypeReference = Resolvers.ResolveMethod(objectType, CurrentAssembly, "GetType");
+ getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, CurrentAssembly, "GetTypeFromHandle");
+ logErrorReference = Resolvers.ResolveMethod(UnityAssembly.MainModule.GetType("UnityEngine.Debug"), CurrentAssembly, "LogError");
+ logWarningReference = Resolvers.ResolveMethod(UnityAssembly.MainModule.GetType("UnityEngine.Debug"), CurrentAssembly, "LogWarning");
+ sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendCommandInternal");
+ sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendRPCInternal");
+ sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendTargetRPCInternal");
+ sendEventInternal = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SendEventInternal");
+
+ SyncObjectType = CurrentAssembly.MainModule.ImportReference(SyncObjectType);
+ InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "InitSyncObject");
+ }
+
+ public static bool IsNetworkBehaviour(TypeDefinition td)
+ {
+ return td.IsDerivedFrom(NetworkBehaviourType);
+ }
+
+ public static bool IsValidTypeToGenerate(TypeDefinition variable)
+ {
+ // a valid type is a simple class or struct. so we generate only code for types we dont know, and if they are not inside
+ // this assembly it must mean that we are trying to serialize a variable outside our scope. and this will fail.
+ // no need to report an error here, the caller will report a better error
+ string assembly = CurrentAssembly.MainModule.Name;
+ return variable.Module.Name == assembly;
+ }
+
+ static void CheckMonoBehaviour(TypeDefinition td)
+ {
+ if (td.IsDerivedFrom(MonoBehaviourType))
+ {
+ MonoBehaviourProcessor.Process(td);
+ }
+ }
+
+ static bool CheckNetworkBehaviour(TypeDefinition td)
+ {
+ if (!td.IsClass)
+ return false;
+
+ if (!IsNetworkBehaviour(td))
+ {
+ CheckMonoBehaviour(td);
+ return false;
+ }
+
+ // process this and base classes from parent to child order
+
+ List behaviourClasses = new List();
+
+ TypeDefinition parent = td;
+ while (parent != null)
+ {
+ if (parent.FullName == NetworkBehaviourType.FullName)
+ {
+ break;
+ }
+ try
+ {
+ behaviourClasses.Insert(0, parent);
+ parent = parent.BaseType.Resolve();
+ }
+ catch (AssemblyResolutionException)
+ {
+ // this can happen for plugins.
+ //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
+ break;
+ }
+ }
+
+ bool didWork = false;
+ foreach (TypeDefinition behaviour in behaviourClasses)
+ {
+ didWork |= ProcessNetworkBehaviourType(behaviour);
+ }
+ return didWork;
+ }
+
+ static bool CheckMessageBase(TypeDefinition td)
+ {
+ if (!td.IsClass)
+ return false;
+
+ bool didWork = false;
+
+ // are ANY parent classes MessageBase
+ TypeReference parent = td.BaseType;
+ while (parent != null)
+ {
+ if (parent.FullName == MessageBaseType.FullName)
+ {
+ MessageClassProcessor.Process(td);
+ didWork = true;
+ break;
+ }
+ try
+ {
+ parent = parent.Resolve().BaseType;
+ }
+ catch (AssemblyResolutionException)
+ {
+ // this can happen for plugins.
+ //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
+ break;
+ }
+ }
+
+ // check for embedded types
+ foreach (TypeDefinition embedded in td.NestedTypes)
+ {
+ didWork |= CheckMessageBase(embedded);
+ }
+
+ return didWork;
+ }
+
+ static bool CheckSyncList(TypeDefinition td)
+ {
+ if (!td.IsClass)
+ return false;
+
+ bool didWork = false;
+
+ // are ANY parent classes SyncListStruct
+ TypeReference parent = td.BaseType;
+ while (parent != null)
+ {
+ if (parent.FullName.StartsWith(SyncListType.FullName, StringComparison.Ordinal))
+ {
+ SyncListProcessor.Process(td);
+ didWork = true;
+ break;
+ }
+ if (parent.FullName.StartsWith(SyncSetType.FullName, StringComparison.Ordinal))
+ {
+ SyncListProcessor.Process(td);
+ didWork = true;
+ break;
+ }
+ if (parent.FullName.StartsWith(SyncDictionaryType.FullName, StringComparison.Ordinal))
+ {
+ SyncDictionaryProcessor.Process(td);
+ didWork = true;
+ break;
+ }
+ try
+ {
+ parent = parent.Resolve().BaseType;
+ }
+ catch (AssemblyResolutionException)
+ {
+ // this can happen for pluins.
+ //Console.WriteLine("AssemblyResolutionException: "+ ex.ToString());
+ break;
+ }
+ }
+
+ // check for embedded types
+ foreach (TypeDefinition embedded in td.NestedTypes)
+ {
+ didWork |= CheckSyncList(embedded);
+ }
+
+ return didWork;
+ }
+
+ static bool Weave(string assName, IEnumerable dependencies, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir)
+ {
+ using (DefaultAssemblyResolver asmResolver = new DefaultAssemblyResolver())
+ using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = asmResolver }))
+ {
+ asmResolver.AddSearchDirectory(Path.GetDirectoryName(assName));
+ asmResolver.AddSearchDirectory(Helpers.UnityEngineDLLDirectoryName());
+ asmResolver.AddSearchDirectory(Path.GetDirectoryName(unityEngineDLLPath));
+ asmResolver.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath));
+ if (dependencies != null)
+ {
+ foreach (string path in dependencies)
+ {
+ asmResolver.AddSearchDirectory(path);
+ }
+ }
+
+ SetupTargetTypes();
+ System.Diagnostics.Stopwatch rwstopwatch = System.Diagnostics.Stopwatch.StartNew();
+ ReaderWriterProcessor.ProcessReadersAndWriters(CurrentAssembly);
+ rwstopwatch.Stop();
+ Console.WriteLine("Find all reader and writers took " + rwstopwatch.ElapsedMilliseconds + " milliseconds");
+
+ ModuleDefinition moduleDefinition = CurrentAssembly.MainModule;
+ Console.WriteLine("Script Module: {0}", moduleDefinition.Name);
+
+ // Process each NetworkBehaviour
+ bool didWork = false;
+
+ // We need to do 2 passes, because SyncListStructs might be referenced from other modules, so we must make sure we generate them first.
+ for (int pass = 0; pass < 2; pass++)
+ {
+ System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew();
+ foreach (TypeDefinition td in moduleDefinition.Types)
+ {
+ if (td.IsClass && td.BaseType.CanBeResolved())
+ {
+ try
+ {
+ if (pass == 0)
+ {
+ didWork |= CheckSyncList(td);
+ }
+ else
+ {
+ didWork |= CheckNetworkBehaviour(td);
+ didWork |= CheckMessageBase(td);
+ }
+ }
+ catch (Exception ex)
+ {
+ Error(ex.ToString());
+ throw ex;
+ }
+ }
+
+ if (WeavingFailed)
+ {
+ return false;
+ }
+ }
+ watch.Stop();
+ Console.WriteLine("Pass: " + pass + " took " + watch.ElapsedMilliseconds + " milliseconds");
+ }
+
+ if (didWork)
+ {
+ // this must be done for ALL code, not just NetworkBehaviours
+ try
+ {
+ PropertySiteProcessor.ProcessSitesModule(CurrentAssembly.MainModule);
+ }
+ catch (Exception e)
+ {
+ Log.Error("ProcessPropertySites exception: " + e);
+ return false;
+ }
+
+ if (WeavingFailed)
+ {
+ //Log.Error("Failed phase II.");
+ return false;
+ }
+
+ // write to outputDir if specified, otherwise perform in-place write
+ WriterParameters writeParams = new WriterParameters { WriteSymbols = true };
+ if (outputDir != null)
+ {
+ CurrentAssembly.Write(Helpers.DestinationFileFor(outputDir, assName), writeParams);
+ }
+ else
+ {
+ CurrentAssembly.Write(writeParams);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ public static bool WeaveAssemblies(IEnumerable assemblies, IEnumerable dependencies, string outputDir, string unityEngineDLLPath, string mirrorNetDLLPath)
+ {
+ WeavingFailed = false;
+ WeaveLists = new WeaverLists();
+
+ using (UnityAssembly = AssemblyDefinition.ReadAssembly(unityEngineDLLPath))
+ using (NetAssembly = AssemblyDefinition.ReadAssembly(mirrorNetDLLPath))
+ {
+ SetupUnityTypes();
+
+ try
+ {
+ foreach (string ass in assemblies)
+ {
+ if (!Weave(ass, dependencies, unityEngineDLLPath, mirrorNetDLLPath, outputDir))
+ {
+ return false;
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ Log.Error("Exception :" + e);
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs.meta
new file mode 100644
index 0000000..0ea2dfe
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Weaver.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: de160f52931054064852f2afd7e7a86f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Writers.cs b/Assets/Packages/Mirror/Editor/Weaver/Writers.cs
new file mode 100644
index 0000000..040960d
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Writers.cs
@@ -0,0 +1,350 @@
+using System.Collections.Generic;
+using Mono.CecilX;
+using Mono.CecilX.Cil;
+
+namespace Mirror.Weaver
+{
+
+ public static class Writers
+ {
+ const int MaxRecursionCount = 128;
+
+ static Dictionary writeFuncs;
+
+ public static void Init()
+ {
+ writeFuncs = new Dictionary();
+ }
+
+ public static void Register(TypeReference dataType, MethodReference methodReference)
+ {
+ writeFuncs[dataType.FullName] = methodReference;
+ }
+
+ public static MethodReference GetWriteFunc(TypeReference variable, int recursionCount = 0)
+ {
+ if (writeFuncs.TryGetValue(variable.FullName, out MethodReference foundFunc))
+ {
+ return foundFunc;
+ }
+
+ if (variable.IsByReference)
+ {
+ // error??
+ Weaver.Error($"{variable} has unsupported type. Use one of Mirror supported types instead");
+ return null;
+ }
+
+ MethodDefinition newWriterFunc;
+
+ if (variable.IsArray)
+ {
+ newWriterFunc = GenerateArrayWriteFunc(variable, recursionCount);
+ }
+ else if (variable.Resolve().IsEnum)
+ {
+ return GetWriteFunc(variable.Resolve().GetEnumUnderlyingType(), recursionCount);
+ }
+ else if (variable.FullName.StartsWith("System.ArraySegment`1", System.StringComparison.Ordinal))
+ {
+ newWriterFunc = GenerateArraySegmentWriteFunc(variable, recursionCount);
+ }
+ else
+ {
+ newWriterFunc = GenerateStructWriterFunction(variable, recursionCount);
+ }
+
+ if (newWriterFunc == null)
+ {
+ return null;
+ }
+
+ RegisterWriteFunc(variable.FullName, newWriterFunc);
+ return newWriterFunc;
+ }
+
+ static void RegisterWriteFunc(string name, MethodDefinition newWriterFunc)
+ {
+ writeFuncs[name] = newWriterFunc;
+ Weaver.WeaveLists.generatedWriteFunctions.Add(newWriterFunc);
+
+ Weaver.ConfirmGeneratedCodeClass();
+ Weaver.WeaveLists.generateContainerClass.Methods.Add(newWriterFunc);
+ }
+
+ static MethodDefinition GenerateStructWriterFunction(TypeReference variable, int recursionCount)
+ {
+ if (recursionCount > MaxRecursionCount)
+ {
+ Weaver.Error($"{variable} can't be serialized because it references itself");
+ return null;
+ }
+
+ if (!Weaver.IsValidTypeToGenerate(variable.Resolve()))
+ {
+ return null;
+ }
+
+ string functionName = "_Write" + variable.Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+ // create new writer for this type
+ MethodDefinition writerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
+
+ ILProcessor worker = writerFunc.Body.GetILProcessor();
+
+ uint fields = 0;
+ foreach (FieldDefinition field in variable.Resolve().Fields)
+ {
+ if (field.IsStatic || field.IsPrivate)
+ continue;
+
+ if (field.FieldType.Resolve().HasGenericParameters)
+ {
+ Weaver.Error($"{field} has unsupported type. Create a derived class instead of using generics");
+ return null;
+ }
+
+ if (field.FieldType.Resolve().IsInterface)
+ {
+ Weaver.Error($"{field} has unsupported type. Use a concrete class instead of an interface");
+ return null;
+ }
+
+ MethodReference writeFunc = GetWriteFunc(field.FieldType, recursionCount + 1);
+ if (writeFunc != null)
+ {
+ fields++;
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Ldfld, field));
+ worker.Append(worker.Create(OpCodes.Call, writeFunc));
+ }
+ else
+ {
+ Weaver.Error($"{field} has unsupported type. Use a type supported by Mirror instead");
+ return null;
+ }
+ }
+ if (fields == 0)
+ {
+ Log.Warning($" {variable} has no no public or non-static fields to serialize");
+ }
+ worker.Append(worker.Create(OpCodes.Ret));
+ return writerFunc;
+ }
+
+ static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, int recursionCount)
+ {
+
+ if (!variable.IsArrayType())
+ {
+ Weaver.Error($"{variable} is an unsupported type. Jagged and multidimensional arrays are not supported");
+ return null;
+ }
+
+ TypeReference elementType = variable.GetElementType();
+ MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
+ if (elementWriteFunc == null)
+ {
+ return null;
+ }
+
+ string functionName = "_WriteArray" + variable.GetElementType().Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+
+ // create new writer for this type
+ MethodDefinition writerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(variable)));
+
+ writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ writerFunc.Body.InitLocals = true;
+
+ ILProcessor worker = writerFunc.Body.GetILProcessor();
+
+ // if (value == null)
+ // {
+ // writer.WritePackedInt32(-1);
+ // return;
+ // }
+ Instruction labelNull = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Brtrue, labelNull));
+
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_M1));
+ worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
+ worker.Append(worker.Create(OpCodes.Ret));
+
+ // int length = value.Length;
+ worker.Append(labelNull);
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Ldlen));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+
+ // writer.WritePackedInt32(length);
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
+
+ // for (int i=0; i< value.length; i++) {
+ worker.Append(worker.Create(OpCodes.Ldc_I4_0));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+ Instruction labelHead = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Br, labelHead));
+
+ // loop body
+ Instruction labelBody = worker.Create(OpCodes.Nop);
+ worker.Append(labelBody);
+ // writer.Write(value[i]);
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldelema, variable.GetElementType()));
+ worker.Append(worker.Create(OpCodes.Ldobj, variable.GetElementType()));
+ worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
+
+
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_1));
+ worker.Append(worker.Create(OpCodes.Add));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+
+
+ // end for loop
+ worker.Append(labelHead);
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldarg_1));
+ worker.Append(worker.Create(OpCodes.Ldlen));
+ worker.Append(worker.Create(OpCodes.Conv_I4));
+ worker.Append(worker.Create(OpCodes.Blt, labelBody));
+
+ // return
+ worker.Append(worker.Create(OpCodes.Ret));
+ return writerFunc;
+ }
+
+ static MethodDefinition GenerateArraySegmentWriteFunc(TypeReference variable, int recursionCount)
+ {
+ GenericInstanceType genericInstance = (GenericInstanceType)variable;
+ TypeReference elementType = genericInstance.GenericArguments[0];
+ MethodReference elementWriteFunc = GetWriteFunc(elementType, recursionCount + 1);
+
+ if (elementWriteFunc == null)
+ {
+ return null;
+ }
+
+ string functionName = "_WriteArraySegment_" + elementType.Name + "_";
+ if (variable.DeclaringType != null)
+ {
+ functionName += variable.DeclaringType.Name;
+ }
+ else
+ {
+ functionName += "None";
+ }
+
+ // create new writer for this type
+ MethodDefinition writerFunc = new MethodDefinition(functionName,
+ MethodAttributes.Public |
+ MethodAttributes.Static |
+ MethodAttributes.HideBySig,
+ Weaver.voidType);
+
+ writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType)));
+ writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, variable));
+
+ writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ writerFunc.Body.Variables.Add(new VariableDefinition(Weaver.int32Type));
+ writerFunc.Body.InitLocals = true;
+
+ ILProcessor worker = writerFunc.Body.GetILProcessor();
+
+ MethodReference countref = Weaver.ArraySegmentCountReference.MakeHostInstanceGeneric(genericInstance);
+
+ // int length = value.Count;
+ worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
+ worker.Append(worker.Create(OpCodes.Call, countref));
+ worker.Append(worker.Create(OpCodes.Stloc_0));
+
+
+ // writer.WritePackedInt32(length);
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Call, GetWriteFunc(Weaver.int32Type)));
+
+ // Loop through the ArraySegment and call the writer for each element.
+ // generates this:
+ // for (int i=0; i< length; i++)
+ // {
+ // writer.Write(value.Array[i + value.Offset]);
+ // }
+ worker.Append(worker.Create(OpCodes.Ldc_I4_0));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+ Instruction labelHead = worker.Create(OpCodes.Nop);
+ worker.Append(worker.Create(OpCodes.Br, labelHead));
+
+ // loop body
+ Instruction labelBody = worker.Create(OpCodes.Nop);
+ worker.Append(labelBody);
+ {
+ // writer.Write(value.Array[i + value.Offset]);
+ worker.Append(worker.Create(OpCodes.Ldarg_0));
+ worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
+ worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentArrayReference.MakeHostInstanceGeneric(genericInstance)));
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldarga_S, (byte)1));
+ worker.Append(worker.Create(OpCodes.Call, Weaver.ArraySegmentOffsetReference.MakeHostInstanceGeneric(genericInstance)));
+ worker.Append(worker.Create(OpCodes.Add));
+ worker.Append(worker.Create(OpCodes.Ldelema, elementType));
+ worker.Append(worker.Create(OpCodes.Ldobj, elementType));
+ worker.Append(worker.Create(OpCodes.Call, elementWriteFunc));
+ }
+
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldc_I4_1));
+ worker.Append(worker.Create(OpCodes.Add));
+ worker.Append(worker.Create(OpCodes.Stloc_1));
+
+
+ // end for loop
+ worker.Append(labelHead);
+ worker.Append(worker.Create(OpCodes.Ldloc_1));
+ worker.Append(worker.Create(OpCodes.Ldloc_0));
+ worker.Append(worker.Create(OpCodes.Blt, labelBody));
+
+ // return
+ worker.Append(worker.Create(OpCodes.Ret));
+ return writerFunc;
+ }
+
+ }
+}
diff --git a/Assets/Packages/Mirror/Editor/Weaver/Writers.cs.meta b/Assets/Packages/Mirror/Editor/Weaver/Writers.cs.meta
new file mode 100644
index 0000000..3769f7f
--- /dev/null
+++ b/Assets/Packages/Mirror/Editor/Weaver/Writers.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a90060ad76ea044aba613080dd922709
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/License.txt b/Assets/Packages/Mirror/License.txt
new file mode 100644
index 0000000..2925e86
--- /dev/null
+++ b/Assets/Packages/Mirror/License.txt
@@ -0,0 +1,3 @@
+The Mirror DLLs in the Plugins folder are MIT licensed:
+
+https://github.com/vis2k/Mirror
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/License.txt.meta b/Assets/Packages/Mirror/License.txt.meta
new file mode 100644
index 0000000..6129dfe
--- /dev/null
+++ b/Assets/Packages/Mirror/License.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: dbf30d11d3879431f87403d009e47bf7
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Plugins.meta b/Assets/Packages/Mirror/Plugins.meta
new file mode 100644
index 0000000..9504239
--- /dev/null
+++ b/Assets/Packages/Mirror/Plugins.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 05eb4061e2eb94061b9a08c918fff99b
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil.meta b/Assets/Packages/Mirror/Plugins/Mono.Cecil.meta
new file mode 100644
index 0000000..a104e2e
--- /dev/null
+++ b/Assets/Packages/Mirror/Plugins/Mono.Cecil.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ce126b4e1a7d13b4c865cd92929f13c3
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll
new file mode 100644
index 0000000..f6815d3
Binary files /dev/null and b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll differ
diff --git a/Assets/Telepathy.dll.meta b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll.meta
similarity index 90%
rename from Assets/Telepathy.dll.meta
rename to Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll.meta
index d0a95b4..f75f642 100644
--- a/Assets/Telepathy.dll.meta
+++ b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Mdb.dll.meta
@@ -1,5 +1,5 @@
fileFormatVersion: 2
-guid: a8ec6c9ba48cb7348879483314d7ce07
+guid: a078fc7c0dc14d047a28dea9c93fd259
PluginImporter:
externalObjects: {}
serializedVersion: 2
@@ -9,7 +9,6 @@ PluginImporter:
isPreloaded: 0
isOverridable: 0
isExplicitlyReferenced: 0
- validateReferences: 1
platformData:
- first:
Any:
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll
new file mode 100644
index 0000000..3b58436
Binary files /dev/null and b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll differ
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll.meta b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll.meta
new file mode 100644
index 0000000..773758f
--- /dev/null
+++ b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Pdb.dll.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 534d998d93b238041bddcd864f7f1088
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll
new file mode 100644
index 0000000..7d820f0
Binary files /dev/null and b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll differ
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll.meta b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll.meta
new file mode 100644
index 0000000..a222084
--- /dev/null
+++ b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.Rocks.dll.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 7526641fb3ae25144aa0a96aad853745
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll
new file mode 100644
index 0000000..c042418
Binary files /dev/null and b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll differ
diff --git a/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll.meta b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll.meta
new file mode 100644
index 0000000..73262f2
--- /dev/null
+++ b/Assets/Packages/Mirror/Plugins/Mono.Cecil/Mono.CecilX.dll.meta
@@ -0,0 +1,32 @@
+fileFormatVersion: 2
+guid: 307911e5ad044dd42b1649eb8637aaf3
+PluginImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ iconMap: {}
+ executionOrder: {}
+ defineConstraints: []
+ isPreloaded: 0
+ isOverridable: 0
+ isExplicitlyReferenced: 0
+ platformData:
+ - first:
+ Any:
+ second:
+ enabled: 1
+ settings: {}
+ - first:
+ Editor: Editor
+ second:
+ enabled: 0
+ settings:
+ DefaultValueInitialized: true
+ - first:
+ Windows Store Apps: WindowsStoreApps
+ second:
+ enabled: 0
+ settings:
+ CPU: AnyCPU
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Readme.txt b/Assets/Packages/Mirror/Readme.txt
new file mode 100644
index 0000000..857cbaf
--- /dev/null
+++ b/Assets/Packages/Mirror/Readme.txt
@@ -0,0 +1,15 @@
+Mirror is a MMO Scale Networking library for Unity, used in uMMORPG, uSurvival
+and several MMO projects in development.
+
+*** IMPORTANT -- You must restart Unity after importing Mirror for the Components Menu to update! ***
+
+Requirements:
+ Unity 2018.3.6+
+ Runtime .Net 4.x (Project Settings > Player > Other Settings)
+
+Documentation:
+ https://vis2k.github.io/Mirror/
+
+Support:
+ Discord: https://discordapp.com/invite/N9QVxbM
+ Bug Reports: https://github.com/vis2k/Mirror/issues
diff --git a/Assets/Packages/Mirror/Readme.txt.meta b/Assets/Packages/Mirror/Readme.txt.meta
new file mode 100644
index 0000000..d52ccce
--- /dev/null
+++ b/Assets/Packages/Mirror/Readme.txt.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: f6d84e019c68446f28415a923b460a03
+TextScriptImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime.meta b/Assets/Packages/Mirror/Runtime.meta
new file mode 100644
index 0000000..85ee3eb
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime.meta
@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9f4328ccc5f724e45afe2215d275b5d5
+folderAsset: yes
+DefaultImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs b/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs
new file mode 100644
index 0000000..f0c4858
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Mirror.Tests")]
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs.meta b/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs.meta
new file mode 100644
index 0000000..cf3201c
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/AssemblyInfo.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e28d5f410e25b42e6a76a2ffc10e4675
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/ClientScene.cs b/Assets/Packages/Mirror/Runtime/ClientScene.cs
new file mode 100644
index 0000000..fa5d099
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/ClientScene.cs
@@ -0,0 +1,760 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using UnityEngine;
+using Guid = System.Guid;
+using Object = UnityEngine.Object;
+
+namespace Mirror
+{
+ ///
+ /// A client manager which contains static client information and functions.
+ /// This manager contains references to tracked static local objects such as spawner registrations. It also has the default message handlers used by clients when they registered none themselves. The manager handles adding/removing player objects to the game after a client connection has been set as ready.
+ /// The ClientScene is a singleton, and it has static convenience methods such as ClientScene.Ready().
+ /// The ClientScene is used by the NetworkManager, but it can be used by itself.
+ /// As the ClientScene manages player objects on the client, it is where clients request to add players. The NetworkManager does this via the ClientScene automatically when auto-add-players is set, but it can be done through code using the function ClientScene.AddPlayer(). This sends an AddPlayer message to the server and will cause a player object to be created for this client.
+ /// Like NetworkServer, the ClientScene understands the concept of the local client. The function ClientScene.ConnectLocalServer() is used to become a host by starting a local client (when a server is already running).
+ ///
+ public static class ClientScene
+ {
+ static bool isSpawnFinished;
+
+ ///
+ /// NetworkIdentity of the localPlayer
+ ///
+ public static NetworkIdentity localPlayer { get; private set; }
+
+ ///
+ /// Returns true when a client's connection has been set to ready.
+ /// A client that is ready recieves state updates from the server, while a client that is not ready does not. This useful when the state of the game is not normal, such as a scene change or end-of-game.
+ /// This is read-only. To change the ready state of a client, use ClientScene.Ready(). The server is able to set the ready state of clients using NetworkServer.SetClientReady(), NetworkServer.SetClientNotReady() and NetworkServer.SetAllClientsNotReady().
+ /// This is done when changing scenes so that clients don't receive state update messages during scene loading.
+ ///
+ public static bool ready { get; internal set; }
+
+ ///
+ /// The NetworkConnection object that is currently "ready". This is the connection to the server where objects are spawned from.
+ /// This connection can be used to send messages to the server. There can only be one ready connection at a time. There can be multiple NetworkClient instances in existence, each with their own NetworkConnections, but there is only one ClientScene instance and corresponding ready connection.
+ ///
+ public static NetworkConnection readyConnection { get; private set; }
+
+ ///
+ /// This is a dictionary of the prefabs that are registered on the client with ClientScene.RegisterPrefab().
+ /// The key to the dictionary is the prefab asset Id.
+ ///
+ public static Dictionary prefabs = new Dictionary();
+
+ ///
+ /// This is dictionary of the disabled NetworkIdentity objects in the scene that could be spawned by messages from the server.
+ /// The key to the dictionary is the NetworkIdentity sceneId.
+ ///
+ public static Dictionary spawnableObjects;
+
+ // spawn handlers
+ static readonly Dictionary spawnHandlers = new Dictionary();
+ static readonly Dictionary unspawnHandlers = new Dictionary();
+
+ // this is never called, and if we do call it in NetworkClient.Shutdown
+ // then the client's player object won't be removed after disconnecting!
+ internal static void Shutdown()
+ {
+ ClearSpawners();
+ spawnableObjects = null;
+ readyConnection = null;
+ ready = false;
+ isSpawnFinished = false;
+ DestroyAllClientObjects();
+ }
+
+ // this is called from message handler for Owner message
+ internal static void InternalAddPlayer(NetworkIdentity identity)
+ {
+ if (LogFilter.Debug) Debug.LogWarning("ClientScene.InternalAddPlayer");
+
+ // NOTE: It can be "normal" when changing scenes for the player to be destroyed and recreated.
+ // But, the player structures are not cleaned up, we'll just replace the old player
+ localPlayer = identity;
+ if (readyConnection != null)
+ {
+ readyConnection.playerController = identity;
+ }
+ else
+ {
+ Debug.LogWarning("No ready connection found for setting player controller during InternalAddPlayer");
+ }
+ }
+
+ ///
+ /// This adds a player GameObject for this client.
+ /// This causes an AddPlayer message to be sent to the server, and NetworkManager.OnServerAddPlayer is called.
+ ///
+ /// True if player was added.
+ public static bool AddPlayer() => AddPlayer(null);
+
+ ///
+ /// This adds a player GameObject for this client. This causes an AddPlayer message to be sent to the server, and NetworkManager.OnServerAddPlayer is called. If an extra message was passed to AddPlayer, then OnServerAddPlayer will be called with a NetworkReader that contains the contents of the message.
+ ///
+ /// The connection to become ready for this client.
+ /// True if player was added.
+ public static bool AddPlayer(NetworkConnection readyConn) => AddPlayer(readyConn, null);
+
+ ///
+ /// This adds a player GameObject for this client. This causes an AddPlayer message to be sent to the server, and NetworkManager.OnServerAddPlayer is called. If an extra message was passed to AddPlayer, then OnServerAddPlayer will be called with a NetworkReader that contains the contents of the message.
+ /// extraMessage can contain character selection, etc.
+ ///
+ /// The connection to become ready for this client.
+ /// An extra message object that can be passed to the server for this player.
+ /// True if player was added.
+ public static bool AddPlayer(NetworkConnection readyConn, byte[] extraData)
+ {
+ // ensure valid ready connection
+ if (readyConn != null)
+ {
+ ready = true;
+ readyConnection = readyConn;
+ }
+
+ if (!ready)
+ {
+ Debug.LogError("Must call AddPlayer() with a connection the first time to become ready.");
+ return false;
+ }
+
+ if (readyConnection.playerController != null)
+ {
+ Debug.LogError("ClientScene.AddPlayer: a PlayerController was already added. Did you call AddPlayer twice?");
+ return false;
+ }
+
+ if (LogFilter.Debug) Debug.Log("ClientScene.AddPlayer() called with connection [" + readyConnection + "]");
+
+ AddPlayerMessage message = new AddPlayerMessage()
+ {
+ value = extraData
+ };
+ readyConnection.Send(message);
+ return true;
+ }
+
+ ///
+ /// Removes the player from the game.
+ ///
+ /// True if succcessful
+ public static bool RemovePlayer()
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.RemovePlayer() called with connection [" + readyConnection + "]");
+
+ if (readyConnection.playerController != null)
+ {
+ readyConnection.Send(new RemovePlayerMessage());
+
+ Object.Destroy(readyConnection.playerController.gameObject);
+
+ readyConnection.playerController = null;
+ localPlayer = null;
+
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Signal that the client connection is ready to enter the game.
+ /// This could be for example when a client enters an ongoing game and has finished loading the current scene. The server should respond to the SYSTEM_READY event with an appropriate handler which instantiates the players object for example.
+ ///
+ /// The client connection which is ready.
+ /// True if succcessful
+ public static bool Ready(NetworkConnection conn)
+ {
+ if (ready)
+ {
+ Debug.LogError("A connection has already been set as ready. There can only be one.");
+ return false;
+ }
+
+ if (LogFilter.Debug) Debug.Log("ClientScene.Ready() called with connection [" + conn + "]");
+
+ if (conn != null)
+ {
+ conn.Send(new ReadyMessage());
+ ready = true;
+ readyConnection = conn;
+ readyConnection.isReady = true;
+ return true;
+ }
+ Debug.LogError("Ready() called with invalid connection object: conn=null");
+ return false;
+ }
+
+ internal static void HandleClientDisconnect(NetworkConnection conn)
+ {
+ if (readyConnection == conn && ready)
+ {
+ ready = false;
+ readyConnection = null;
+ }
+ }
+
+ static bool ConsiderForSpawning(NetworkIdentity identity)
+ {
+ // not spawned yet, not hidden, etc.?
+ return !identity.gameObject.activeSelf &&
+ identity.gameObject.hideFlags != HideFlags.NotEditable &&
+ identity.gameObject.hideFlags != HideFlags.HideAndDontSave &&
+ identity.sceneId != 0;
+ }
+
+ ///
+ /// Call this after loading/unloading a scene in the client after connection to register the spawnable objects
+ ///
+ public static void PrepareToSpawnSceneObjects()
+ {
+ // add all unspawned NetworkIdentities to spawnable objects
+ spawnableObjects = Resources.FindObjectsOfTypeAll()
+ .Where(ConsiderForSpawning)
+ .ToDictionary(identity => identity.sceneId, identity => identity);
+ }
+
+ static NetworkIdentity SpawnSceneObject(ulong sceneId)
+ {
+ if (spawnableObjects.TryGetValue(sceneId, out NetworkIdentity identity))
+ {
+ spawnableObjects.Remove(sceneId);
+ return identity;
+ }
+ Debug.LogWarning("Could not find scene object with sceneid:" + sceneId.ToString("X"));
+ return null;
+ }
+
+ // spawn handlers and prefabs
+ static bool GetPrefab(Guid assetId, out GameObject prefab)
+ {
+ prefab = null;
+ return assetId != Guid.Empty &&
+ prefabs.TryGetValue(assetId, out prefab) && prefab != null;
+ }
+
+ ///
+ /// Registers a prefab with the spawning system.
+ /// When a NetworkIdentity object is spawned on a server with NetworkServer.SpawnObject(), and the prefab that the object was created from was registered with RegisterPrefab(), the client will use that prefab to instantiate a corresponding client object with the same netId.
+ /// The NetworkManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.
+ /// The set of current spawnable object is available in the ClientScene static member variable ClientScene.prefabs, which is a dictionary of NetworkAssetIds and prefab references.
+ ///
+ /// A Prefab that will be spawned.
+ /// An assetId to be assigned to this prefab. This allows a dynamically created game object to be registered for an already known asset Id.
+ public static void RegisterPrefab(GameObject prefab, Guid newAssetId)
+ {
+ NetworkIdentity identity = prefab.GetComponent();
+ if (identity)
+ {
+ identity.assetId = newAssetId;
+
+ if (LogFilter.Debug) Debug.Log("Registering prefab '" + prefab.name + "' as asset:" + identity.assetId);
+ prefabs[identity.assetId] = prefab;
+ }
+ else
+ {
+ Debug.LogError("Could not register '" + prefab.name + "' since it contains no NetworkIdentity component");
+ }
+ }
+
+ ///
+ /// Registers a prefab with the spawning system.
+ /// When a NetworkIdentity object is spawned on a server with NetworkServer.SpawnObject(), and the prefab that the object was created from was registered with RegisterPrefab(), the client will use that prefab to instantiate a corresponding client object with the same netId.
+ /// The NetworkManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.
+ /// The set of current spawnable object is available in the ClientScene static member variable ClientScene.prefabs, which is a dictionary of NetworkAssetIds and prefab references.
+ ///
+ /// A Prefab that will be spawned.
+ public static void RegisterPrefab(GameObject prefab)
+ {
+ NetworkIdentity identity = prefab.GetComponent();
+ if (identity)
+ {
+ if (LogFilter.Debug) Debug.Log("Registering prefab '" + prefab.name + "' as asset:" + identity.assetId);
+ prefabs[identity.assetId] = prefab;
+
+ NetworkIdentity[] identities = prefab.GetComponentsInChildren();
+ if (identities.Length > 1)
+ {
+ Debug.LogWarning("The prefab '" + prefab.name +
+ "' has multiple NetworkIdentity components. There can only be one NetworkIdentity on a prefab, and it must be on the root object.");
+ }
+ }
+ else
+ {
+ Debug.LogError("Could not register '" + prefab.name + "' since it contains no NetworkIdentity component");
+ }
+ }
+
+ ///
+ /// Registers a prefab with the spawning system.
+ /// When a NetworkIdentity object is spawned on a server with NetworkServer.SpawnObject(), and the prefab that the object was created from was registered with RegisterPrefab(), the client will use that prefab to instantiate a corresponding client object with the same netId.
+ /// The NetworkManager has a list of spawnable prefabs, it uses this function to register those prefabs with the ClientScene.
+ /// The set of current spawnable object is available in the ClientScene static member variable ClientScene.prefabs, which is a dictionary of NetworkAssetIds and prefab references.
+ ///
+ /// A Prefab that will be spawned.
+ /// A method to use as a custom spawnhandler on clients.
+ /// A method to use as a custom un-spawnhandler on clients.
+ public static void RegisterPrefab(GameObject prefab, SpawnDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
+ {
+ NetworkIdentity identity = prefab.GetComponent();
+ if (identity == null)
+ {
+ Debug.LogError("Could not register '" + prefab.name + "' since it contains no NetworkIdentity component");
+ return;
+ }
+
+ if (spawnHandler == null || unspawnHandler == null)
+ {
+ Debug.LogError("RegisterPrefab custom spawn function null for " + identity.assetId);
+ return;
+ }
+
+ if (identity.assetId == Guid.Empty)
+ {
+ Debug.LogError("RegisterPrefab game object " + prefab.name + " has no prefab. Use RegisterSpawnHandler() instead?");
+ return;
+ }
+
+ if (LogFilter.Debug) Debug.Log("Registering custom prefab '" + prefab.name + "' as asset:" + identity.assetId + " " + spawnHandler.GetMethodName() + "/" + unspawnHandler.GetMethodName());
+
+ spawnHandlers[identity.assetId] = spawnHandler;
+ unspawnHandlers[identity.assetId] = unspawnHandler;
+ }
+
+ ///
+ /// Removes a registered spawn prefab that was setup with ClientScene.RegisterPrefab.
+ ///
+ /// The prefab to be removed from registration.
+ public static void UnregisterPrefab(GameObject prefab)
+ {
+ NetworkIdentity identity = prefab.GetComponent();
+ if (identity == null)
+ {
+ Debug.LogError("Could not unregister '" + prefab.name + "' since it contains no NetworkIdentity component");
+ return;
+ }
+ spawnHandlers.Remove(identity.assetId);
+ unspawnHandlers.Remove(identity.assetId);
+ }
+
+ ///
+ /// This is an advanced spawning function that registers a custom assetId with the UNET spawning system.
+ /// This can be used to register custom spawning methods for an assetId - instead of the usual method of registering spawning methods for a prefab. This should be used when no prefab exists for the spawned objects - such as when they are constructed dynamically at runtime from configuration data.
+ ///
+ /// Custom assetId string.
+ /// A method to use as a custom spawnhandler on clients.
+ /// A method to use as a custom un-spawnhandler on clients.
+ public static void RegisterSpawnHandler(Guid assetId, SpawnDelegate spawnHandler, UnSpawnDelegate unspawnHandler)
+ {
+ if (spawnHandler == null || unspawnHandler == null)
+ {
+ Debug.LogError("RegisterSpawnHandler custom spawn function null for " + assetId);
+ return;
+ }
+
+ if (LogFilter.Debug) Debug.Log("RegisterSpawnHandler asset '" + assetId + "' " + spawnHandler.GetMethodName() + "/" + unspawnHandler.GetMethodName());
+
+ spawnHandlers[assetId] = spawnHandler;
+ unspawnHandlers[assetId] = unspawnHandler;
+ }
+
+ ///
+ /// Removes a registered spawn handler function that was registered with ClientScene.RegisterHandler().
+ ///
+ /// The assetId for the handler to be removed for.
+ public static void UnregisterSpawnHandler(Guid assetId)
+ {
+ spawnHandlers.Remove(assetId);
+ unspawnHandlers.Remove(assetId);
+ }
+
+ ///
+ /// This clears the registered spawn prefabs and spawn handler functions for this client.
+ ///
+ public static void ClearSpawners()
+ {
+ prefabs.Clear();
+ spawnHandlers.Clear();
+ unspawnHandlers.Clear();
+ }
+
+ static bool InvokeUnSpawnHandler(Guid assetId, GameObject obj)
+ {
+ if (unspawnHandlers.TryGetValue(assetId, out UnSpawnDelegate handler) && handler != null)
+ {
+ handler(obj);
+ return true;
+ }
+ return false;
+ }
+
+ ///
+ /// Destroys all networked objects on the client.
+ /// This can be used to clean up when a network connection is closed.
+ ///
+ public static void DestroyAllClientObjects()
+ {
+ foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values)
+ {
+ if (identity != null && identity.gameObject != null)
+ {
+ if (!InvokeUnSpawnHandler(identity.assetId, identity.gameObject))
+ {
+ if (identity.sceneId == 0)
+ {
+ Object.Destroy(identity.gameObject);
+ }
+ else
+ {
+ identity.MarkForReset();
+ identity.gameObject.SetActive(false);
+ }
+ }
+ }
+ }
+ NetworkIdentity.spawned.Clear();
+ }
+
+ ///
+ /// Obsolete: Use instead.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkIdentity.spawned[netId] instead.")]
+ public static GameObject FindLocalObject(uint netId)
+ {
+ if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity))
+ {
+ return identity.gameObject;
+ }
+ return null;
+ }
+
+ static void ApplySpawnPayload(NetworkIdentity identity, Vector3 position, Quaternion rotation, Vector3 scale, ArraySegment payload, uint netId)
+ {
+ if (!identity.gameObject.activeSelf)
+ {
+ identity.gameObject.SetActive(true);
+ }
+
+ // apply local values for VR support
+ identity.transform.localPosition = position;
+ identity.transform.localRotation = rotation;
+ identity.transform.localScale = scale;
+
+ // deserialize components if any payload
+ // (Count is 0 if there were no components)
+ if (payload.Count > 0)
+ {
+ NetworkReader payloadReader = new NetworkReader(payload);
+ identity.OnUpdateVars(payloadReader, true);
+ }
+
+ identity.netId = netId;
+ NetworkIdentity.spawned[netId] = identity;
+
+ // objects spawned as part of initial state are started on a second pass
+ if (isSpawnFinished)
+ {
+ identity.OnStartClient();
+ CheckForOwner(identity);
+ }
+ }
+
+ internal static void OnSpawnPrefab(NetworkConnection _, SpawnPrefabMessage msg)
+ {
+ if (msg.assetId == Guid.Empty)
+ {
+ Debug.LogError("OnObjSpawn netId: " + msg.netId + " has invalid asset Id");
+ return;
+ }
+ if (LogFilter.Debug) Debug.Log("Client spawn handler instantiating [netId:" + msg.netId + " asset ID:" + msg.assetId + " pos:" + msg.position + "]");
+
+ // owner?
+ if (msg.owner)
+ {
+ OnSpawnMessageForOwner(msg.netId);
+ }
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ // this object already exists (was in the scene), just apply the update to existing object
+ localObject.Reset();
+ ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId);
+ return;
+ }
+
+ if (GetPrefab(msg.assetId, out GameObject prefab))
+ {
+ GameObject obj = Object.Instantiate(prefab, msg.position, msg.rotation);
+ if (LogFilter.Debug)
+ {
+ Debug.Log("Client spawn handler instantiating [netId:" + msg.netId + " asset ID:" + msg.assetId + " pos:" + msg.position + " rotation: " + msg.rotation + "]");
+ }
+
+ localObject = obj.GetComponent();
+ if (localObject == null)
+ {
+ Debug.LogError("Client object spawned for " + msg.assetId + " does not have a NetworkIdentity");
+ return;
+ }
+ localObject.Reset();
+ localObject.pendingOwner = msg.owner;
+ ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId);
+ }
+ // lookup registered factory for type:
+ else if (spawnHandlers.TryGetValue(msg.assetId, out SpawnDelegate handler))
+ {
+ GameObject obj = handler(msg.position, msg.assetId);
+ if (obj == null)
+ {
+ Debug.LogWarning("Client spawn handler for " + msg.assetId + " returned null");
+ return;
+ }
+ localObject = obj.GetComponent();
+ if (localObject == null)
+ {
+ Debug.LogError("Client object spawned for " + msg.assetId + " does not have a network identity");
+ return;
+ }
+ localObject.Reset();
+ localObject.pendingOwner = msg.owner;
+ localObject.assetId = msg.assetId;
+ ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId);
+ }
+ else
+ {
+ Debug.LogError("Failed to spawn server object, did you forget to add it to the NetworkManager? assetId=" + msg.assetId + " netId=" + msg.netId);
+ }
+ }
+
+ internal static void OnSpawnSceneObject(NetworkConnection _, SpawnSceneObjectMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("Client spawn scene handler instantiating [netId:" + msg.netId + " sceneId:" + msg.sceneId + " pos:" + msg.position);
+
+ // owner?
+ if (msg.owner)
+ {
+ OnSpawnMessageForOwner(msg.netId);
+ }
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ // this object already exists (was in the scene)
+ localObject.Reset();
+ ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId);
+ return;
+ }
+
+ NetworkIdentity spawnedId = SpawnSceneObject(msg.sceneId);
+ if (spawnedId == null)
+ {
+ Debug.LogError("Spawn scene object not found for " + msg.sceneId.ToString("X") + " SpawnableObjects.Count=" + spawnableObjects.Count);
+
+ // dump the whole spawnable objects dict for easier debugging
+ if (LogFilter.Debug)
+ {
+ foreach (KeyValuePair kvp in spawnableObjects)
+ Debug.Log("Spawnable: SceneId=" + kvp.Key + " name=" + kvp.Value.name);
+ }
+
+ return;
+ }
+
+ if (LogFilter.Debug) Debug.Log("Client spawn for [netId:" + msg.netId + "] [sceneId:" + msg.sceneId + "] obj:" + spawnedId.gameObject.name);
+ spawnedId.Reset();
+ spawnedId.pendingOwner = msg.owner;
+ ApplySpawnPayload(spawnedId, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId);
+ }
+
+ internal static void OnObjectSpawnStarted(NetworkConnection _, ObjectSpawnStartedMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("SpawnStarted");
+
+ PrepareToSpawnSceneObjects();
+ isSpawnFinished = false;
+ }
+
+ internal static void OnObjectSpawnFinished(NetworkConnection _, ObjectSpawnFinishedMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("SpawnFinished");
+
+ // paul: Initialize the objects in the same order as they were initialized
+ // in the server. This is important if spawned objects
+ // use data from scene objects
+ foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values.OrderBy(uv => uv.netId))
+ {
+ if (!identity.isClient)
+ {
+ identity.OnStartClient();
+ CheckForOwner(identity);
+ }
+ }
+ isSpawnFinished = true;
+ }
+
+ internal static void OnObjectHide(NetworkConnection _, ObjectHideMessage msg)
+ {
+ DestroyObject(msg.netId);
+ }
+
+ internal static void OnObjectDestroy(NetworkConnection _, ObjectDestroyMessage msg)
+ {
+ DestroyObject(msg.netId);
+ }
+
+ static void DestroyObject(uint netId)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnObjDestroy netId:" + netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ localObject.OnNetworkDestroy();
+
+ if (!InvokeUnSpawnHandler(localObject.assetId, localObject.gameObject))
+ {
+ // default handling
+ if (localObject.sceneId == 0)
+ {
+ Object.Destroy(localObject.gameObject);
+ }
+ else
+ {
+ // scene object.. disable it in scene instead of destroying
+ localObject.gameObject.SetActive(false);
+ spawnableObjects[localObject.sceneId] = localObject;
+ }
+ }
+ NetworkIdentity.spawned.Remove(netId);
+ localObject.MarkForReset();
+ }
+ else
+ {
+ if (LogFilter.Debug) Debug.LogWarning("Did not find target for destroy message for " + netId);
+ }
+ }
+
+ internal static void OnLocalClientObjectDestroy(NetworkConnection _, ObjectDestroyMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnLocalObjectObjDestroy netId:" + msg.netId);
+
+ NetworkIdentity.spawned.Remove(msg.netId);
+ }
+
+ internal static void OnLocalClientObjectHide(NetworkConnection _, ObjectHideMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene::OnLocalObjectObjHide netId:" + msg.netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ localObject.OnSetLocalVisibility(false);
+ }
+ }
+
+ internal static void OnLocalClientSpawnPrefab(NetworkConnection _, SpawnPrefabMessage msg)
+ {
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ localObject.OnSetLocalVisibility(true);
+ }
+ }
+
+ internal static void OnLocalClientSpawnSceneObject(NetworkConnection _, SpawnSceneObjectMessage msg)
+ {
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ localObject.OnSetLocalVisibility(true);
+ }
+ }
+
+ internal static void OnUpdateVarsMessage(NetworkConnection _, UpdateVarsMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnUpdateVarsMessage " + msg.netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ localObject.OnUpdateVars(new NetworkReader(msg.payload), false);
+ }
+ else
+ {
+ Debug.LogWarning("Did not find target for sync message for " + msg.netId + " . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message.");
+ }
+ }
+
+ internal static void OnRPCMessage(NetworkConnection _, RpcMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnRPCMessage hash:" + msg.functionHash + " netId:" + msg.netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity))
+ {
+ identity.HandleRPC(msg.componentIndex, msg.functionHash, new NetworkReader(msg.payload));
+ }
+ }
+
+ internal static void OnSyncEventMessage(NetworkConnection _, SyncEventMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnSyncEventMessage " + msg.netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity))
+ {
+ identity.HandleSyncEvent(msg.componentIndex, msg.functionHash, new NetworkReader(msg.payload));
+ }
+ else
+ {
+ Debug.LogWarning("Did not find target for SyncEvent message for " + msg.netId);
+ }
+ }
+
+ internal static void OnClientAuthority(NetworkConnection _, ClientAuthorityMessage msg)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnClientAuthority for netId: " + msg.netId);
+
+ if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity))
+ {
+ identity.HandleClientAuthority(msg.authority);
+ }
+ }
+
+ // called for the one object in the spawn message which is the owner!
+ internal static void OnSpawnMessageForOwner(uint netId)
+ {
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnOwnerMessage - connectionId=" + readyConnection.connectionId + " netId: " + netId);
+
+ // is there already an owner that is a different object??
+ if (readyConnection.playerController != null)
+ {
+ readyConnection.playerController.SetNotLocalPlayer();
+ }
+
+ if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity localObject) && localObject != null)
+ {
+ // this object already exists
+ localObject.connectionToServer = readyConnection;
+ localObject.SetLocalPlayer();
+ InternalAddPlayer(localObject);
+ }
+ }
+
+ static void CheckForOwner(NetworkIdentity identity)
+ {
+ if (identity.pendingOwner)
+ {
+ // found owner, turn into a local player
+
+ // Set isLocalPlayer to true on this NetworkIdentity and trigger OnStartLocalPlayer in all scripts on the same GO
+ identity.connectionToServer = readyConnection;
+ identity.SetLocalPlayer();
+
+ if (LogFilter.Debug) Debug.Log("ClientScene.OnOwnerMessage - player=" + identity.name);
+ if (readyConnection.connectionId < 0)
+ {
+ Debug.LogError("Owner message received on a local client.");
+ return;
+ }
+ InternalAddPlayer(identity);
+
+ identity.pendingOwner = false;
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/ClientScene.cs.meta b/Assets/Packages/Mirror/Runtime/ClientScene.cs.meta
new file mode 100644
index 0000000..c4f3a09
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/ClientScene.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 96fc7967f813e4960b9119d7c2118494
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/CustomAttributes.cs b/Assets/Packages/Mirror/Runtime/CustomAttributes.cs
new file mode 100644
index 0000000..40bdd7d
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/CustomAttributes.cs
@@ -0,0 +1,58 @@
+using System;
+using System.ComponentModel;
+using UnityEngine;
+
+namespace Mirror
+{
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkBehaviour.syncInterval field instead. Can be modified in the Inspector too.")]
+ [AttributeUsage(AttributeTargets.Class)]
+ public class NetworkSettingsAttribute : Attribute
+ {
+ public float sendInterval = 0.1f;
+ }
+
+ [AttributeUsage(AttributeTargets.Field)]
+ public class SyncVarAttribute : Attribute
+ {
+ public string hook;
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class CommandAttribute : Attribute
+ {
+ public int channel = Channels.DefaultReliable; // this is zero
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ClientRpcAttribute : Attribute
+ {
+ public int channel = Channels.DefaultReliable; // this is zero
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class TargetRpcAttribute : Attribute
+ {
+ public int channel = Channels.DefaultReliable; // this is zero
+ }
+
+ [AttributeUsage(AttributeTargets.Event)]
+ public class SyncEventAttribute : Attribute
+ {
+ public int channel = Channels.DefaultReliable; // this is zero
+ }
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ServerAttribute : Attribute {}
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ServerCallbackAttribute : Attribute {}
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ClientAttribute : Attribute {}
+
+ [AttributeUsage(AttributeTargets.Method)]
+ public class ClientCallbackAttribute : Attribute {}
+
+ // For Scene property Drawer
+ public class SceneAttribute : PropertyAttribute {}
+}
diff --git a/Assets/Packages/Mirror/Runtime/CustomAttributes.cs.meta b/Assets/Packages/Mirror/Runtime/CustomAttributes.cs.meta
new file mode 100644
index 0000000..22a1db2
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/CustomAttributes.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c04c722ee2ffd49c8a56ab33667b10b0
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs b/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs
new file mode 100644
index 0000000..fe57e17
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace Mirror
+{
+ internal static class DotNetCompatibility
+ {
+ internal static string GetMethodName(this Delegate func)
+ {
+#if NETFX_CORE
+ return func.GetMethodInfo().Name;
+#else
+ return func.Method.Name;
+#endif
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs.meta b/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs.meta
new file mode 100644
index 0000000..8742197
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/DotNetCompatibility.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: b307f850ccbbe450295acf24d70e5c28
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs b/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs
new file mode 100644
index 0000000..f6ec793
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs
@@ -0,0 +1,38 @@
+namespace Mirror
+{
+ // implementation of N-day EMA
+ // it calculates an exponential moving average roughy equivalent to the last n observations
+ // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average
+ public class ExponentialMovingAverage
+ {
+ readonly float alpha;
+ bool initialized;
+
+ public ExponentialMovingAverage(int n)
+ {
+ // standard N-day EMA alpha calculation
+ alpha = 2.0f / (n + 1);
+ }
+
+ public void Add(double newValue)
+ {
+ // simple algorithm for EMA described here:
+ // https://en.wikipedia.org/wiki/Moving_average#Exponentially_weighted_moving_variance_and_standard_deviation
+ if (initialized)
+ {
+ double delta = newValue - Value;
+ Value += alpha * delta;
+ Var = (1 - alpha) * (Var + alpha * delta * delta);
+ }
+ else
+ {
+ Value = newValue;
+ initialized = true;
+ }
+ }
+
+ public double Value { get; private set; }
+
+ public double Var { get; private set; }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs.meta b/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs.meta
new file mode 100644
index 0000000..5ce3055
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/ExponentialMovingAverage.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 05e858cbaa54b4ce4a48c8c7f50c1914
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs b/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs
new file mode 100644
index 0000000..2293d21
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs
@@ -0,0 +1,60 @@
+using UnityEngine;
+
+namespace Mirror
+{
+ public static class FloatBytePacker
+ {
+ // ScaleFloatToByte( -1f, -1f, 1f, byte.MinValue, byte.MaxValue) => 0
+ // ScaleFloatToByte( 0f, -1f, 1f, byte.MinValue, byte.MaxValue) => 127
+ // ScaleFloatToByte(0.5f, -1f, 1f, byte.MinValue, byte.MaxValue) => 191
+ // ScaleFloatToByte( 1f, -1f, 1f, byte.MinValue, byte.MaxValue) => 255
+ public static byte ScaleFloatToByte(float value, float minValue, float maxValue, byte minTarget, byte maxTarget)
+ {
+ // note: C# byte - byte => int, hence so many casts
+ int targetRange = maxTarget - minTarget; // max byte - min byte only fits into something bigger
+ float valueRange = maxValue - minValue;
+ float valueRelative = value - minValue;
+ return (byte)(minTarget + (byte)(valueRelative/valueRange * targetRange));
+ }
+
+ // ScaleByteToFloat( 0, byte.MinValue, byte.MaxValue, -1, 1) => -1
+ // ScaleByteToFloat(127, byte.MinValue, byte.MaxValue, -1, 1) => -0.003921569
+ // ScaleByteToFloat(191, byte.MinValue, byte.MaxValue, -1, 1) => 0.4980392
+ // ScaleByteToFloat(255, byte.MinValue, byte.MaxValue, -1, 1) => 1
+ public static float ScaleByteToFloat(byte value, byte minValue, byte maxValue, float minTarget, float maxTarget)
+ {
+ // note: C# byte - byte => int, hence so many casts
+ float targetRange = maxTarget - minTarget;
+ byte valueRange = (byte)(maxValue - minValue);
+ byte valueRelative = (byte)(value - minValue);
+ return minTarget + (valueRelative / (float)valueRange * targetRange);
+ }
+
+ // eulerAngles have 3 floats, putting them into 2 bytes of [x,y],[z,0]
+ // would be a waste. instead we compress into 5 bits each => 15 bits.
+ // so a ushort.
+ public static ushort PackThreeFloatsIntoUShort(float u, float v, float w, float minValue, float maxValue)
+ {
+ // 5 bits max value = 1+2+4+8+16 = 31 = 0x1F
+ byte lower = ScaleFloatToByte(u, minValue, maxValue, 0x00, 0x1F);
+ byte middle = ScaleFloatToByte(v, minValue, maxValue, 0x00, 0x1F);
+ byte upper = ScaleFloatToByte(w, minValue, maxValue, 0x00, 0x1F);
+ ushort combined = (ushort)(upper << 10 | middle << 5 | lower);
+ return combined;
+ }
+
+ // see PackThreeFloatsIntoUShort for explanation
+ public static Vector3 UnpackUShortIntoThreeFloats(ushort combined, float minTarget, float maxTarget)
+ {
+ byte lower = (byte)(combined & 0x1F);
+ byte middle = (byte)((combined >> 5) & 0x1F);
+ byte upper = (byte)(combined >> 10); // nothing on the left, no & needed
+
+ // note: we have to use 4 bits per float, so between 0x00 and 0x0F
+ float u = ScaleByteToFloat(lower, 0x00, 0x1F, minTarget, maxTarget);
+ float v = ScaleByteToFloat(middle, 0x00, 0x1F, minTarget, maxTarget);
+ float w = ScaleByteToFloat(upper, 0x00, 0x1F, minTarget, maxTarget);
+ return new Vector3(u, v, w);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs.meta b/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs.meta
new file mode 100644
index 0000000..92145fe
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/FloatBytePacker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: afd3cca6a786d4208b1d0f7f2b168901
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/LocalClient.cs b/Assets/Packages/Mirror/Runtime/LocalClient.cs
new file mode 100644
index 0000000..20db50b
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LocalClient.cs
@@ -0,0 +1,5 @@
+// This file was removed in Mirror 3.4.9
+// The purpose of this file is to get the old file overwritten
+// when users update from the asset store to prevent a flood of errors
+// from having the old file still in the project as a straggler.
+// This file will be dropped from the Asset Store package in May 2019
diff --git a/Assets/Packages/Mirror/Runtime/LocalClient.cs.meta b/Assets/Packages/Mirror/Runtime/LocalClient.cs.meta
new file mode 100644
index 0000000..6c073e6
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LocalClient.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 5c4d04450e91c438385de7300abef1b6
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/LocalConnections.cs b/Assets/Packages/Mirror/Runtime/LocalConnections.cs
new file mode 100644
index 0000000..33962d4
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LocalConnections.cs
@@ -0,0 +1,47 @@
+using System;
+using UnityEngine;
+
+namespace Mirror
+{
+ // a server's connection TO a LocalClient.
+ // sending messages on this connection causes the client's handler function to be invoked directly
+ class ULocalConnectionToClient : NetworkConnection
+ {
+ public ULocalConnectionToClient() : base ("localClient")
+ {
+ // local player always has connectionId == 0
+ connectionId = 0;
+ }
+
+ internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
+ {
+ NetworkClient.localClientPacketQueue.Enqueue(bytes);
+ return true;
+ }
+ }
+
+ // a localClient's connection TO a server.
+ // send messages on this connection causes the server's handler function to be invoked directly.
+ internal class ULocalConnectionToServer : NetworkConnection
+ {
+ public ULocalConnectionToServer() : base("localServer")
+ {
+ // local player always has connectionId == 0
+ connectionId = 0;
+ }
+
+ internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
+ {
+ if (bytes.Length == 0)
+ {
+ Debug.LogError("LocalConnection.SendBytes cannot send zero bytes");
+ return false;
+ }
+
+ // handle the server's message directly
+ // TODO any way to do this without NetworkServer.localConnection?
+ NetworkServer.localConnection.TransportReceive(new ArraySegment(bytes));
+ return true;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/LocalConnections.cs.meta b/Assets/Packages/Mirror/Runtime/LocalConnections.cs.meta
new file mode 100644
index 0000000..2a332c4
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LocalConnections.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: a88758df7db2043d6a9d926e0b6d4191
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/LogFilter.cs b/Assets/Packages/Mirror/Runtime/LogFilter.cs
new file mode 100644
index 0000000..3b225f7
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LogFilter.cs
@@ -0,0 +1,7 @@
+namespace Mirror
+{
+ public static class LogFilter
+ {
+ public static bool Debug = false;
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/LogFilter.cs.meta b/Assets/Packages/Mirror/Runtime/LogFilter.cs.meta
new file mode 100644
index 0000000..41cab50
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/LogFilter.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: f6928b080072948f7b2909b4025fcc79
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/MessagePacker.cs b/Assets/Packages/Mirror/Runtime/MessagePacker.cs
new file mode 100644
index 0000000..e134465
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/MessagePacker.cs
@@ -0,0 +1,136 @@
+using System;
+using System.ComponentModel;
+using UnityEngine;
+
+namespace Mirror
+{
+ // message packing all in one place, instead of constructing headers in all
+ // kinds of different places
+ //
+ // MsgType (1-n bytes)
+ // Content (ContentSize bytes)
+ //
+ // -> we use varint for headers because most messages will result in 1 byte
+ // type/size headers then instead of always
+ // using 2 bytes for shorts.
+ // -> this reduces bandwidth by 10% if average message size is 20 bytes
+ // (probably even shorter)
+ public static class MessagePacker
+ {
+ public static int GetId() where T : IMessageBase
+ {
+ // paul: 16 bits is enough to avoid collisions
+ // - keeps the message size small because it gets varinted
+ // - in case of collisions, Mirror will display an error
+ return typeof(T).FullName.GetStableHashCode() & 0xFFFF;
+ }
+
+ // pack message before sending
+ // -> pass writer instead of byte[] so we can reuse it
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use Pack instead")]
+ public static byte[] PackMessage(int msgType, MessageBase msg)
+ {
+ NetworkWriter writer = NetworkWriterPool.GetWriter();
+ try
+ {
+ // write message type
+ writer.WriteInt16((short)msgType);
+
+ // serialize message into writer
+ msg.Serialize(writer);
+
+ // return byte[]
+ return writer.ToArray();
+ }
+ finally
+ {
+ NetworkWriterPool.Recycle(writer);
+ }
+ }
+
+ // pack message before sending
+ public static byte[] Pack(T message) where T : IMessageBase
+ {
+ NetworkWriter writer = NetworkWriterPool.GetWriter();
+ try
+ {
+ // write message type
+ int msgType = GetId();
+ writer.WriteUInt16((ushort)msgType);
+
+ // serialize message into writer
+ message.Serialize(writer);
+
+ // return byte[]
+ return writer.ToArray();
+ }
+ finally
+ {
+ NetworkWriterPool.Recycle(writer);
+ }
+ }
+
+ // unpack a message we received
+ public static T Unpack(byte[] data) where T : IMessageBase, new()
+ {
+ NetworkReader reader = new NetworkReader(data);
+
+ int msgType = GetId();
+
+ int id = reader.ReadUInt16();
+ if (id != msgType)
+ throw new FormatException("Invalid message, could not unpack " + typeof(T).FullName);
+
+ T message = new T();
+ message.Deserialize(reader);
+ return message;
+ }
+
+ // unpack message after receiving
+ // -> pass NetworkReader so it's less strange if we create it in here
+ // and pass it upwards.
+ // -> NetworkReader will point at content afterwards!
+ public static bool UnpackMessage(NetworkReader messageReader, out int msgType)
+ {
+ // read message type (varint)
+ try
+ {
+ msgType = messageReader.ReadUInt16();
+ return true;
+ }
+ catch (System.IO.EndOfStreamException)
+ {
+ msgType = 0;
+ return false;
+ }
+ }
+
+ internal static NetworkMessageDelegate MessageHandler(Action handler) where T : IMessageBase, new() => networkMessage =>
+ {
+ // protect against DOS attacks if attackers try to send invalid
+ // data packets to crash the server/client. there are a thousand
+ // ways to cause an exception in data handling:
+ // - invalid headers
+ // - invalid message ids
+ // - invalid data causing exceptions
+ // - negative ReadBytesAndSize prefixes
+ // - invalid utf8 strings
+ // - etc.
+ //
+ // let's catch them all and then disconnect that connection to avoid
+ // further attacks.
+ T message = default;
+ try
+ {
+ message = networkMessage.ReadMessage();
+ }
+ catch (Exception exception)
+ {
+ Debug.LogError("Closed connection: " + networkMessage.conn.connectionId + ". This can happen if the other side accidentally (or an attacker intentionally) sent invalid data. Reason: " + exception);
+ networkMessage.conn.Disconnect();
+ return;
+ }
+ handler(networkMessage.conn, message);
+ };
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/MessagePacker.cs.meta b/Assets/Packages/Mirror/Runtime/MessagePacker.cs.meta
new file mode 100644
index 0000000..7ca61aa
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/MessagePacker.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 2db134099f0df4d96a84ae7a0cd9b4bc
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/Messages.cs b/Assets/Packages/Mirror/Runtime/Messages.cs
new file mode 100644
index 0000000..99c31ad
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/Messages.cs
@@ -0,0 +1,492 @@
+using System;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+
+namespace Mirror
+{
+ public interface IMessageBase
+ {
+ void Deserialize(NetworkReader reader);
+
+ void Serialize(NetworkWriter writer);
+ }
+
+ public abstract class MessageBase : IMessageBase
+ {
+ // De-serialize the contents of the reader into this message
+ public virtual void Deserialize(NetworkReader reader) {}
+
+ // Serialize the contents of this message into the writer
+ public virtual void Serialize(NetworkWriter writer) {}
+ }
+
+ #region General Typed Messages
+ public class StringMessage : MessageBase
+ {
+ public string value;
+
+ public StringMessage() {}
+
+ public StringMessage(string v)
+ {
+ value = v;
+ }
+
+ public override void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadString();
+ }
+
+ public override void Serialize(NetworkWriter writer)
+ {
+ writer.WriteString(value);
+ }
+ }
+
+ public class ByteMessage : MessageBase
+ {
+ public byte value;
+
+ public ByteMessage() {}
+
+ public ByteMessage(byte v)
+ {
+ value = v;
+ }
+
+ public override void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadByte();
+ }
+
+ public override void Serialize(NetworkWriter writer)
+ {
+ writer.WriteByte(value);
+ }
+ }
+
+ public class BytesMessage : MessageBase
+ {
+ public byte[] value;
+
+ public BytesMessage() {}
+
+ public BytesMessage(byte[] v)
+ {
+ value = v;
+ }
+
+ public override void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadBytesAndSize();
+ }
+
+ public override void Serialize(NetworkWriter writer)
+ {
+ writer.WriteBytesAndSize(value);
+ }
+ }
+
+ public class IntegerMessage : MessageBase
+ {
+ public int value;
+
+ public IntegerMessage() {}
+
+ public IntegerMessage(int v)
+ {
+ value = v;
+ }
+
+ public override void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadPackedInt32();
+ }
+
+ public override void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedInt32(value);
+ }
+ }
+
+ public class DoubleMessage : MessageBase
+ {
+ public double value;
+
+ public DoubleMessage() {}
+
+ public DoubleMessage(double v)
+ {
+ value = v;
+ }
+
+ public override void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadDouble();
+ }
+
+ public override void Serialize(NetworkWriter writer)
+ {
+ writer.WriteDouble(value);
+ }
+ }
+
+ public class EmptyMessage : MessageBase
+ {
+ public override void Deserialize(NetworkReader reader) {}
+
+ public override void Serialize(NetworkWriter writer) {}
+ }
+ #endregion
+
+ #region Public System Messages
+ public class ErrorMessage : ByteMessage {}
+
+ public struct ReadyMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ public struct NotReadyMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ public struct AddPlayerMessage : IMessageBase
+ {
+ public byte[] value;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ value = reader.ReadBytesAndSize();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WriteBytesAndSize(value);
+ }
+ }
+
+ public struct RemovePlayerMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ public struct DisconnectMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ public struct ConnectMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ public struct SceneMessage : IMessageBase
+ {
+ public string sceneName;
+ public LoadSceneMode sceneMode; // Single = 0, Additive = 1
+ public LocalPhysicsMode physicsMode; // None = 0, Physics3D = 1, Physics2D = 2
+
+ public void Deserialize(NetworkReader reader)
+ {
+ sceneName = reader.ReadString();
+ sceneMode = (LoadSceneMode)reader.ReadByte();
+ physicsMode = (LocalPhysicsMode)reader.ReadByte();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WriteString(sceneName);
+ writer.WriteByte((byte)sceneMode);
+ writer.WriteByte((byte)physicsMode);
+ }
+ }
+ #endregion
+
+ #region System Messages requried for code gen path
+ struct CommandMessage : IMessageBase
+ {
+ public uint netId;
+ public int componentIndex;
+ public int functionHash;
+ // the parameters for the Cmd function
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ componentIndex = (int)reader.ReadPackedUInt32();
+ functionHash = reader.ReadInt32(); // hash is always 4 full bytes, WritePackedInt would send 1 extra byte here
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WritePackedUInt32((uint)componentIndex);
+ writer.WriteInt32(functionHash);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+
+ struct RpcMessage : IMessageBase
+ {
+ public uint netId;
+ public int componentIndex;
+ public int functionHash;
+ // the parameters for the Cmd function
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ componentIndex = (int)reader.ReadPackedUInt32();
+ functionHash = reader.ReadInt32(); // hash is always 4 full bytes, WritePackedInt would send 1 extra byte here
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WritePackedUInt32((uint)componentIndex);
+ writer.WriteInt32(functionHash);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+
+ struct SyncEventMessage : IMessageBase
+ {
+ public uint netId;
+ public int componentIndex;
+ public int functionHash;
+ // the parameters for the Cmd function
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ componentIndex = (int)reader.ReadPackedUInt32();
+ functionHash = reader.ReadInt32(); // hash is always 4 full bytes, WritePackedInt would send 1 extra byte here
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WritePackedUInt32((uint)componentIndex);
+ writer.WriteInt32(functionHash);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+ #endregion
+
+ #region Internal System Messages
+ struct SpawnPrefabMessage : IMessageBase
+ {
+ public uint netId;
+ public bool owner;
+ public Guid assetId;
+ public Vector3 position;
+ public Quaternion rotation;
+ public Vector3 scale;
+ // the serialized component data
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ owner = reader.ReadBoolean();
+ assetId = reader.ReadGuid();
+ position = reader.ReadVector3();
+ rotation = reader.ReadQuaternion();
+ scale = reader.ReadVector3();
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WriteBoolean(owner);
+ writer.WriteGuid(assetId);
+ writer.WriteVector3(position);
+ writer.WriteQuaternion(rotation);
+ writer.WriteVector3(scale);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+
+ struct SpawnSceneObjectMessage : IMessageBase
+ {
+ public uint netId;
+ public bool owner;
+ public ulong sceneId;
+ public Vector3 position;
+ public Quaternion rotation;
+ public Vector3 scale;
+ // the serialized component data
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ owner = reader.ReadBoolean();
+ sceneId = reader.ReadUInt64();
+ position = reader.ReadVector3();
+ rotation = reader.ReadQuaternion();
+ scale = reader.ReadVector3();
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WriteBoolean(owner);
+ writer.WriteUInt64(sceneId);
+ writer.WriteVector3(position);
+ writer.WriteQuaternion(rotation);
+ writer.WriteVector3(scale);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+
+ struct ObjectSpawnStartedMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ struct ObjectSpawnFinishedMessage : IMessageBase
+ {
+ public void Deserialize(NetworkReader reader) { }
+
+ public void Serialize(NetworkWriter writer) { }
+ }
+
+ struct ObjectDestroyMessage : IMessageBase
+ {
+ public uint netId;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ }
+ }
+
+ struct ObjectHideMessage : IMessageBase
+ {
+ public uint netId;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ }
+ }
+
+ struct ClientAuthorityMessage : IMessageBase
+ {
+ public uint netId;
+ public bool authority;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ authority = reader.ReadBoolean();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WriteBoolean(authority);
+ }
+ }
+
+ struct UpdateVarsMessage : IMessageBase
+ {
+ public uint netId;
+ // the serialized component data
+ // -> ArraySegment to avoid unnecessary allocations
+ public ArraySegment payload;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ netId = reader.ReadPackedUInt32();
+ payload = reader.ReadBytesAndSizeSegment();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WritePackedUInt32(netId);
+ writer.WriteBytesAndSizeSegment(payload);
+ }
+ }
+
+ // A client sends this message to the server
+ // to calculate RTT and synchronize time
+ struct NetworkPingMessage : IMessageBase
+ {
+ public double clientTime;
+
+ public NetworkPingMessage(double value)
+ {
+ clientTime = value;
+ }
+
+ public void Deserialize(NetworkReader reader)
+ {
+ clientTime = reader.ReadDouble();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WriteDouble(clientTime);
+ }
+ }
+
+ // The server responds with this message
+ // The client can use this to calculate RTT and sync time
+ struct NetworkPongMessage : IMessageBase
+ {
+ public double clientTime;
+ public double serverTime;
+
+ public void Deserialize(NetworkReader reader)
+ {
+ clientTime = reader.ReadDouble();
+ serverTime = reader.ReadDouble();
+ }
+
+ public void Serialize(NetworkWriter writer)
+ {
+ writer.WriteDouble(clientTime);
+ writer.WriteDouble(serverTime);
+ }
+ }
+ #endregion
+}
diff --git a/Assets/Packages/Mirror/Runtime/Messages.cs.meta b/Assets/Packages/Mirror/Runtime/Messages.cs.meta
new file mode 100644
index 0000000..9afe21b
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/Messages.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 938f6f28a6c5b48a0bbd7782342d763b
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/Mirror.asmdef b/Assets/Packages/Mirror/Runtime/Mirror.asmdef
new file mode 100644
index 0000000..4f3dbbd
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/Mirror.asmdef
@@ -0,0 +1,8 @@
+{
+ "name": "Mirror",
+ "references": [],
+ "optionalUnityReferences": [],
+ "includePlatforms": [],
+ "excludePlatforms": [],
+ "allowUnsafeCode": false
+}
\ No newline at end of file
diff --git a/Assets/Packages/Mirror/Runtime/Mirror.asmdef.meta b/Assets/Packages/Mirror/Runtime/Mirror.asmdef.meta
new file mode 100644
index 0000000..202009b
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/Mirror.asmdef.meta
@@ -0,0 +1,7 @@
+fileFormatVersion: 2
+guid: 30817c1a0e6d646d99c048fc403f5979
+AssemblyDefinitionImporter:
+ externalObjects: {}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs
new file mode 100644
index 0000000..aee1e10
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs
@@ -0,0 +1,775 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using UnityEngine;
+
+namespace Mirror
+{
+ ///
+ /// Sync to everyone, or only to owner.
+ ///
+ public enum SyncMode { Observers, Owner }
+
+ ///
+ /// Base class which should be inherited by scripts which contain networking functionality.
+ ///
+ ///
+ /// This is a MonoBehaviour class so scripts which need to use the networking feature should inherit this class instead of MonoBehaviour. It allows you to invoke networked actions, receive various callbacks, and automatically synchronize state from server-to-client.
+ /// The NetworkBehaviour component requires a NetworkIdentity on the game object. There can be multiple NetworkBehaviours on a single game object. For an object with sub-components in a hierarchy, the NetworkIdentity must be on the root object, and NetworkBehaviour scripts must also be on the root object.
+ /// Some of the built-in components of the networking system are derived from NetworkBehaviour, including NetworkTransport, NetworkAnimator and NetworkProximityChecker.
+ ///
+ [RequireComponent(typeof(NetworkIdentity))]
+ [AddComponentMenu("")]
+ public class NetworkBehaviour : MonoBehaviour
+ {
+ float lastSyncTime;
+
+ // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
+ ///
+ /// sync mode for OnSerialize
+ ///
+ [HideInInspector] public SyncMode syncMode = SyncMode.Observers;
+
+ // hidden because NetworkBehaviourInspector shows it only if has OnSerialize.
+ ///
+ /// sync interval for OnSerialize (in seconds)
+ ///
+ [HideInInspector] public float syncInterval = 0.1f;
+
+ ///
+ /// This value is set on the NetworkIdentity and is accessible here for convenient access for scripts.
+ ///
+ public bool localPlayerAuthority => netIdentity.localPlayerAuthority;
+
+ ///
+ /// Returns true if this object is active on an active server.
+ /// This is only true if the object has been spawned. This is different from NetworkServer.active, which is true if the server itself is active rather than this object being active.
+ ///
+ public bool isServer => netIdentity.isServer;
+
+ ///
+ /// Returns true if running as a client and this object was spawned by a server.
+ ///
+ public bool isClient => netIdentity.isClient;
+
+ ///
+ /// This returns true if this object is the one that represents the player on the local machine.
+ /// In multiplayer games, there are multiple instances of the Player object. The client needs to know which one is for "themselves" so that only that player processes input and potentially has a camera attached. The IsLocalPlayer function will return true only for the player instance that belongs to the player on the local machine, so it can be used to filter out input for non-local players.
+ ///
+ public bool isLocalPlayer => netIdentity.isLocalPlayer;
+
+ ///
+ /// True if this object only exists on the server
+ ///
+ public bool isServerOnly => isServer && !isClient;
+
+ ///
+ /// True if this object exists on a client that is not also acting as a server
+ ///
+ public bool isClientOnly => isClient && !isServer;
+
+ ///
+ /// This returns true if this object is the authoritative version of the object in the distributed network application.
+ /// The localPlayerAuthority value on the NetworkIdentity determines how authority is determined. For most objects, authority is held by the server / host. For objects with localPlayerAuthority set, authority is held by the client of that player.
+ ///
+ public bool hasAuthority => netIdentity.hasAuthority;
+
+ ///
+ /// The unique network Id of this object.
+ /// This is assigned at runtime by the network server and will be unique for all objects for that network session.
+ ///
+ public uint netId => netIdentity.netId;
+
+ ///
+ /// The NetworkConnection associated with this NetworkIdentity. This is only valid for player objects on the server.
+ ///
+ public NetworkConnection connectionToServer => netIdentity.connectionToServer;
+
+ ///
+ /// The NetworkConnection associated with this NetworkIdentity. This is only valid for player objects on the server.
+ ///
+ public NetworkConnection connectionToClient => netIdentity.connectionToClient;
+
+ protected ulong syncVarDirtyBits { get; private set; }
+ private ulong syncVarHookGuard;
+
+ protected bool getSyncVarHookGuard(ulong dirtyBit)
+ {
+ return (syncVarHookGuard & dirtyBit) != 0UL;
+ }
+ protected void setSyncVarHookGuard(ulong dirtyBit, bool value)
+ {
+ if (value)
+ syncVarHookGuard |= dirtyBit;
+ else
+ syncVarHookGuard &= ~dirtyBit;
+ }
+
+ ///
+ /// Obsolete: Use instead.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use syncObjects instead.")]
+ protected List m_SyncObjects => syncObjects;
+
+ ///
+ /// objects that can synchronize themselves, such as synclists
+ ///
+ protected readonly List syncObjects = new List();
+
+ ///
+ /// NetworkIdentity component caching for easier access
+ ///
+ NetworkIdentity netIdentityCache;
+
+ ///
+ /// Returns the NetworkIdentity of this object
+ ///
+ public NetworkIdentity netIdentity
+ {
+ get
+ {
+ if (netIdentityCache == null)
+ {
+ netIdentityCache = GetComponent();
+ }
+ if (netIdentityCache == null)
+ {
+ Debug.LogError("There is no NetworkIdentity on " + name + ". Please add one.");
+ }
+ return netIdentityCache;
+ }
+ }
+
+ ///
+ /// Returns the index of the component on this object
+ ///
+ public int ComponentIndex
+ {
+ get
+ {
+ // note: FindIndex causes allocations, we search manually instead
+ for (int i = 0; i < netIdentity.NetworkBehaviours.Length; i++)
+ {
+ NetworkBehaviour component = netIdentity.NetworkBehaviours[i];
+ if (component == this)
+ return i;
+ }
+
+ // this should never happen
+ Debug.LogError("Could not find component in GameObject. You should not add/remove components in networked objects dynamically", this);
+
+ return -1;
+ }
+ }
+
+ // this gets called in the constructor by the weaver
+ // for every SyncObject in the component (e.g. SyncLists).
+ // We collect all of them and we synchronize them with OnSerialize/OnDeserialize
+ protected void InitSyncObject(SyncObject syncObject)
+ {
+ syncObjects.Add(syncObject);
+ }
+
+ #region Commands
+
+ private static int GetMethodHash(Type invokeClass, string methodName)
+ {
+ // (invokeClass + ":" + cmdName).GetStableHashCode() would cause allocations.
+ // so hash1 + hash2 is better.
+ unchecked
+ {
+ int hash = invokeClass.FullName.GetStableHashCode();
+ return hash * 503 + methodName.GetStableHashCode();
+ }
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWriter writer, int channelId)
+ {
+ // this was in Weaver before
+ // NOTE: we could remove this later to allow calling Cmds on Server
+ // to avoid Wrapper functions. a lot of people requested this.
+ if (!NetworkClient.active)
+ {
+ Debug.LogError("Command Function " + cmdName + " called on server without an active client.");
+ return;
+ }
+ // local players can always send commands, regardless of authority, other objects must have authority.
+ if (!(isLocalPlayer || hasAuthority))
+ {
+ Debug.LogWarning("Trying to send command for object without authority.");
+ return;
+ }
+
+ if (ClientScene.readyConnection == null)
+ {
+ Debug.LogError("Send command attempted with no client running [client=" + connectionToServer + "].");
+ return;
+ }
+
+ // construct the message
+ CommandMessage message = new CommandMessage
+ {
+ netId = netId,
+ componentIndex = ComponentIndex,
+ functionHash = GetMethodHash(invokeClass, cmdName), // type+func so Inventory.RpcUse != Equipment.RpcUse
+ payload = writer.ToArraySegment() // segment to avoid reader allocations
+ };
+
+ ClientScene.readyConnection.Send(message, channelId);
+ }
+
+ ///
+ /// Manually invoke a Command.
+ ///
+ /// Hash of the Command name.
+ /// Parameters to pass to the command.
+ /// Returns true if successful.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual bool InvokeCommand(int cmdHash, NetworkReader reader)
+ {
+ return InvokeHandlerDelegate(cmdHash, MirrorInvokeType.Command, reader);
+ }
+ #endregion
+
+ #region Client RPCs
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
+ {
+ // this was in Weaver before
+ if (!NetworkServer.active)
+ {
+ Debug.LogError("RPC Function " + rpcName + " called on Client.");
+ return;
+ }
+ // This cannot use NetworkServer.active, as that is not specific to this object.
+ if (!isServer)
+ {
+ Debug.LogWarning("ClientRpc " + rpcName + " called on un-spawned object: " + name);
+ return;
+ }
+
+ // construct the message
+ RpcMessage message = new RpcMessage
+ {
+ netId = netId,
+ componentIndex = ComponentIndex,
+ functionHash = GetMethodHash(invokeClass, rpcName), // type+func so Inventory.RpcUse != Equipment.RpcUse
+ payload = writer.ToArraySegment() // segment to avoid reader allocations
+ };
+
+ NetworkServer.SendToReady(netIdentity, message, channelId);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SendTargetRPCInternal(NetworkConnection conn, Type invokeClass, string rpcName, NetworkWriter writer, int channelId)
+ {
+ // this was in Weaver before
+ if (!NetworkServer.active)
+ {
+ Debug.LogError("TargetRPC Function " + rpcName + " called on client.");
+ return;
+ }
+ // connection parameter is optional. assign if null.
+ if (conn == null)
+ {
+ conn = connectionToClient;
+ }
+ // this was in Weaver before
+ if (conn is ULocalConnectionToServer)
+ {
+ Debug.LogError("TargetRPC Function " + rpcName + " called on connection to server");
+ return;
+ }
+ // This cannot use NetworkServer.active, as that is not specific to this object.
+ if (!isServer)
+ {
+ Debug.LogWarning("TargetRpc " + rpcName + " called on un-spawned object: " + name);
+ return;
+ }
+
+ // construct the message
+ RpcMessage message = new RpcMessage
+ {
+ netId = netId,
+ componentIndex = ComponentIndex,
+ functionHash = GetMethodHash(invokeClass, rpcName), // type+func so Inventory.RpcUse != Equipment.RpcUse
+ payload = writer.ToArraySegment() // segment to avoid reader allocations
+ };
+
+ conn.Send(message, channelId);
+ }
+
+ ///
+ /// Manually invoke an RPC function.
+ ///
+ /// Hash of the RPC name.
+ /// Parameters to pass to the RPC function.
+ /// Returns true if successful.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual bool InvokeRPC(int rpcHash, NetworkReader reader)
+ {
+ return InvokeHandlerDelegate(rpcHash, MirrorInvokeType.ClientRpc, reader);
+ }
+ #endregion
+
+ #region Sync Events
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SendEventInternal(Type invokeClass, string eventName, NetworkWriter writer, int channelId)
+ {
+ if (!NetworkServer.active)
+ {
+ Debug.LogWarning("SendEvent no server?");
+ return;
+ }
+
+ // construct the message
+ SyncEventMessage message = new SyncEventMessage
+ {
+ netId = netId,
+ componentIndex = ComponentIndex,
+ functionHash = GetMethodHash(invokeClass, eventName), // type+func so Inventory.RpcUse != Equipment.RpcUse
+ payload = writer.ToArraySegment() // segment to avoid reader allocations
+ };
+
+ NetworkServer.SendToReady(netIdentity, message, channelId);
+ }
+
+ ///
+ /// Manually invoke a SyncEvent.
+ ///
+ /// Hash of the SyncEvent name.
+ /// Parameters to pass to the SyncEvent.
+ /// Returns true if successful.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual bool InvokeSyncEvent(int eventHash, NetworkReader reader)
+ {
+ return InvokeHandlerDelegate(eventHash, MirrorInvokeType.SyncEvent, reader);
+ }
+ #endregion
+
+ #region Code Gen Path Helpers
+ ///
+ /// Delegate for Command functions.
+ ///
+ ///
+ ///
+ public delegate void CmdDelegate(NetworkBehaviour obj, NetworkReader reader);
+
+ protected class Invoker
+ {
+ public MirrorInvokeType invokeType;
+ public Type invokeClass;
+ public CmdDelegate invokeFunction;
+ }
+
+ static readonly Dictionary cmdHandlerDelegates = new Dictionary();
+
+ // helper function register a Command/Rpc/SyncEvent delegate
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func)
+ {
+ int cmdHash = GetMethodHash(invokeClass, cmdName); // type+func so Inventory.RpcUse != Equipment.RpcUse
+
+ if (cmdHandlerDelegates.ContainsKey(cmdHash))
+ {
+ // something already registered this hash
+ Invoker oldInvoker = cmdHandlerDelegates[cmdHash];
+ if (oldInvoker.invokeClass == invokeClass && oldInvoker.invokeType == invokerType && oldInvoker.invokeFunction == func)
+ {
+ // it's all right, it was the same function
+ return;
+ }
+
+ Debug.LogError($"Function {oldInvoker.invokeClass}.{oldInvoker.invokeFunction.GetMethodName()} and {invokeClass}.{oldInvoker.invokeFunction.GetMethodName()} have the same hash. Please rename one of them");
+ }
+ Invoker invoker = new Invoker
+ {
+ invokeType = invokerType,
+ invokeClass = invokeClass,
+ invokeFunction = func
+ };
+ cmdHandlerDelegates[cmdHash] = invoker;
+ if (LogFilter.Debug) Debug.Log("RegisterDelegate hash:" + cmdHash + " invokerType: " + invokerType + " method:" + func.GetMethodName());
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected static void RegisterCommandDelegate(Type invokeClass, string cmdName, CmdDelegate func)
+ {
+ RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected static void RegisterRpcDelegate(Type invokeClass, string rpcName, CmdDelegate func)
+ {
+ RegisterDelegate(invokeClass, rpcName, MirrorInvokeType.ClientRpc, func);
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected static void RegisterEventDelegate(Type invokeClass, string eventName, CmdDelegate func)
+ {
+ RegisterDelegate(invokeClass, eventName, MirrorInvokeType.SyncEvent, func);
+ }
+
+ static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker)
+ {
+ if (cmdHandlerDelegates.TryGetValue(cmdHash, out invoker) &&
+ invoker != null &&
+ invoker.invokeType == invokeType)
+ {
+ return true;
+ }
+
+ // debug message if not found, or null, or mismatched type
+ // (no need to throw an error, an attacker might just be trying to
+ // call an cmd with an rpc's hash)
+ if (LogFilter.Debug) Debug.Log("GetInvokerForHash hash:" + cmdHash + " not found");
+ return false;
+ }
+
+ // InvokeCmd/Rpc/SyncEventDelegate can all use the same function here
+ internal bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader)
+ {
+ if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) &&
+ invoker.invokeClass.IsInstanceOfType(this))
+ {
+ invoker.invokeFunction(this, reader);
+ return true;
+ }
+ return false;
+ }
+ #endregion
+
+ #region Helpers
+ // helper function for [SyncVar] GameObjects.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField)
+ {
+ if (getSyncVarHookGuard(dirtyBit))
+ return;
+
+ uint newNetId = 0;
+ if (newGameObject != null)
+ {
+ NetworkIdentity identity = newGameObject.GetComponent();
+ if (identity != null)
+ {
+ newNetId = identity.netId;
+ if (newNetId == 0)
+ {
+ Debug.LogWarning("SetSyncVarGameObject GameObject " + newGameObject + " has a zero netId. Maybe it is not spawned yet?");
+ }
+ }
+ }
+
+ // netId changed?
+ if (newNetId != netIdField)
+ {
+ if (LogFilter.Debug) Debug.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + netIdField + "->" + newNetId);
+ SetDirtyBit(dirtyBit);
+ gameObjectField = newGameObject; // assign new one on the server, and in case we ever need it on client too
+ netIdField = newNetId;
+ }
+ }
+
+ // helper function for [SyncVar] GameObjects.
+ // -> ref GameObject as second argument makes OnDeserialize processing easier
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected GameObject GetSyncVarGameObject(uint netId, ref GameObject gameObjectField)
+ {
+ // server always uses the field
+ if (isServer)
+ {
+ return gameObjectField;
+ }
+
+ // client always looks up based on netId because objects might get in and out of range
+ // over and over again, which shouldn't null them forever
+ if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity) && identity != null)
+ return identity.gameObject;
+ return null;
+ }
+
+ // helper function for [SyncVar] NetworkIdentities.
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref NetworkIdentity identityField, ulong dirtyBit, ref uint netIdField)
+ {
+ if (getSyncVarHookGuard(dirtyBit))
+ return;
+
+ uint newNetId = 0;
+ if (newIdentity != null)
+ {
+ newNetId = newIdentity.netId;
+ if (newNetId == 0)
+ {
+ Debug.LogWarning("SetSyncVarNetworkIdentity NetworkIdentity " + newIdentity + " has a zero netId. Maybe it is not spawned yet?");
+ }
+ }
+
+ // netId changed?
+ if (newNetId != netIdField)
+ {
+ if (LogFilter.Debug) Debug.Log("SetSyncVarNetworkIdentity NetworkIdentity " + GetType().Name + " bit [" + dirtyBit + "] netIdField:" + netIdField + "->" + newNetId);
+ SetDirtyBit(dirtyBit);
+ netIdField = newNetId;
+ identityField = newIdentity; // assign new one on the server, and in case we ever need it on client too
+ }
+ }
+
+ // helper function for [SyncVar] NetworkIdentities.
+ // -> ref GameObject as second argument makes OnDeserialize processing easier
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected NetworkIdentity GetSyncVarNetworkIdentity(uint netId, ref NetworkIdentity identityField)
+ {
+ // server always uses the field
+ if (isServer)
+ {
+ return identityField;
+ }
+
+ // client always looks up based on netId because objects might get in and out of range
+ // over and over again, which shouldn't null them forever
+ NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity);
+ return identity;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ protected void SetSyncVar(T value, ref T fieldValue, ulong dirtyBit)
+ {
+ // newly initialized or changed value?
+ if (!EqualityComparer.Default.Equals(value, fieldValue))
+ {
+ if (LogFilter.Debug) Debug.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value);
+ SetDirtyBit(dirtyBit);
+ fieldValue = value;
+ }
+ }
+ #endregion
+
+ ///
+ /// Used to set the behaviour as dirty, so that a network update will be sent for the object.
+ /// these are masks, not bit numbers, ie. 0x004 not 2
+ ///
+ /// Bit mask to set.
+ public void SetDirtyBit(ulong dirtyBit)
+ {
+ syncVarDirtyBits |= dirtyBit;
+ }
+
+ ///
+ /// This clears all the dirty bits that were set on this script by SetDirtyBits();
+ /// This is automatically invoked when an update is sent for this object, but can be called manually as well.
+ ///
+ public void ClearAllDirtyBits()
+ {
+ lastSyncTime = Time.time;
+ syncVarDirtyBits = 0L;
+
+ // flush all unsynchronized changes in syncobjects
+ // note: don't use List.ForEach here, this is a hot path
+ // List.ForEach: 432b/frame
+ // for: 231b/frame
+ for (int i = 0; i < syncObjects.Count; ++i)
+ {
+ syncObjects[i].Flush();
+ }
+ }
+
+ bool AnySyncObjectDirty()
+ {
+ // note: don't use Linq here. 1200 networked objects:
+ // Linq: 187KB GC/frame;, 2.66ms time
+ // for: 8KB GC/frame; 1.28ms time
+ for (int i = 0; i < syncObjects.Count; ++i)
+ {
+ if (syncObjects[i].IsDirty)
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ internal bool IsDirty()
+ {
+ if (Time.time - lastSyncTime >= syncInterval)
+ {
+ return syncVarDirtyBits != 0L || AnySyncObjectDirty();
+ }
+ return false;
+ }
+
+ ///
+ /// Virtual function to override to send custom serialization data. The corresponding function to send serialization data is OnDeserialize().
+ ///
+ ///
+ /// The initialState flag is useful to differentiate between the first time an object is serialized and when incremental updates can be sent. The first time an object is sent to a client, it must include a full state snapshot, but subsequent updates can save on bandwidth by including only incremental changes. Note that SyncVar hook functions are not called when initialState is true, only for incremental updates.
+ /// If a class has SyncVars, then an implementation of this function and OnDeserialize() are added automatically to the class. So a class that has SyncVars cannot also have custom serialization functions.
+ /// The OnSerialize function should return true to indicate that an update should be sent. If it returns true, then the dirty bits for that script are set to zero, if it returns false then the dirty bits are not changed. This allows multiple changes to a script to be accumulated over time and sent when the system is ready, instead of every frame.
+ ///
+ /// Writer to use to write to the stream.
+ /// If this is being called to send initial state.
+ /// True if data was written.
+ public virtual bool OnSerialize(NetworkWriter writer, bool initialState)
+ {
+ if (initialState)
+ {
+ return SerializeObjectsAll(writer);
+ }
+ return SerializeObjectsDelta(writer);
+ }
+
+ ///
+ /// Virtual function to override to receive custom serialization data. The corresponding function to send serialization data is OnSerialize().
+ ///
+ /// Reader to read from the stream.
+ /// True if being sent initial state.
+ public virtual void OnDeserialize(NetworkReader reader, bool initialState)
+ {
+ if (initialState)
+ {
+ DeSerializeObjectsAll(reader);
+ }
+ else
+ {
+ DeSerializeObjectsDelta(reader);
+ }
+ }
+
+ ulong DirtyObjectBits()
+ {
+ ulong dirtyObjects = 0;
+ for (int i = 0; i < syncObjects.Count; i++)
+ {
+ SyncObject syncObject = syncObjects[i];
+ if (syncObject.IsDirty)
+ {
+ dirtyObjects |= 1UL << i;
+ }
+ }
+ return dirtyObjects;
+ }
+
+ public bool SerializeObjectsAll(NetworkWriter writer)
+ {
+ bool dirty = false;
+ for (int i = 0; i < syncObjects.Count; i++)
+ {
+ SyncObject syncObject = syncObjects[i];
+ syncObject.OnSerializeAll(writer);
+ dirty = true;
+ }
+ return dirty;
+ }
+
+ public bool SerializeObjectsDelta(NetworkWriter writer)
+ {
+ bool dirty = false;
+ // write the mask
+ writer.WritePackedUInt64(DirtyObjectBits());
+ // serializable objects, such as synclists
+ for (int i = 0; i < syncObjects.Count; i++)
+ {
+ SyncObject syncObject = syncObjects[i];
+ if (syncObject.IsDirty)
+ {
+ syncObject.OnSerializeDelta(writer);
+ dirty = true;
+ }
+ }
+ return dirty;
+ }
+
+ void DeSerializeObjectsAll(NetworkReader reader)
+ {
+ for (int i = 0; i < syncObjects.Count; i++)
+ {
+ SyncObject syncObject = syncObjects[i];
+ syncObject.OnDeserializeAll(reader);
+ }
+ }
+
+ void DeSerializeObjectsDelta(NetworkReader reader)
+ {
+ ulong dirty = reader.ReadPackedUInt64();
+ for (int i = 0; i < syncObjects.Count; i++)
+ {
+ SyncObject syncObject = syncObjects[i];
+ if ((dirty & (1UL << i)) != 0)
+ {
+ syncObject.OnDeserializeDelta(reader);
+ }
+ }
+ }
+
+ ///
+ /// This is invoked on clients when the server has caused this object to be destroyed.
+ /// This can be used as a hook to invoke effects or do client specific cleanup.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public virtual void OnNetworkDestroy() {}
+
+ ///
+ /// This is invoked for NetworkBehaviour objects when they become active on the server.
+ /// This could be triggered by NetworkServer.Listen() for objects in the scene, or by NetworkServer.Spawn() for objects that are dynamically created.
+ /// This will be called for objects on a "host" as well as for object on a dedicated server.
+ ///
+ public virtual void OnStartServer() {}
+
+ ///
+ /// Called on every NetworkBehaviour when it is activated on a client.
+ /// Objects on the host have this function called, as there is a local client on the host. The values of SyncVars on object are guaranteed to be initialized correctly with the latest state from the server when this function is called on the client.
+ ///
+ public virtual void OnStartClient() {}
+
+ ///
+ /// Called when the local player object has been set up.
+ /// This happens after OnStartClient(), as it is triggered by an ownership message from the server. This is an appropriate place to activate components or functionality that should only be active for the local player, such as cameras and input.
+ ///
+ public virtual void OnStartLocalPlayer() {}
+
+ ///
+ /// This is invoked on behaviours that have authority, based on context and 'NetworkIdentity.localPlayerAuthority.'
+ /// This is called after OnStartServer and OnStartClient.
+ /// When is called on the server, this will be called on the client that owns the object. When an object is spawned with NetworkServer.SpawnWithClientAuthority, this will be called on the client that owns the object.
+ ///
+ public virtual void OnStartAuthority() {}
+
+ ///
+ /// This is invoked on behaviours when authority is removed.
+ /// When NetworkIdentity.RemoveClientAuthority is called on the server, this will be called on the client that owns the object.
+ ///
+ public virtual void OnStopAuthority() {}
+
+ ///
+ /// Callback used by the visibility system to (re)construct the set of observers that can see this object.
+ /// Implementations of this callback should add network connections of players that can see this object to the observers set.
+ ///
+ /// The new set of observers for this object.
+ /// True if the set of observers is being built for the first time.
+ /// true when overwriting so that Mirror knows that we wanted to rebuild observers ourselves. otherwise it uses built in rebuild.
+ public virtual bool OnRebuildObservers(HashSet observers, bool initialize)
+ {
+ return false;
+ }
+
+ ///
+ /// Callback used by the visibility system for objects on a host.
+ /// Objects on a host (with a local client) cannot be disabled or destroyed when they are not visibile to the local client. So this function is called to allow custom code to hide these objects. A typical implementation will disable renderer components on the object. This is only called on local clients on a host.
+ ///
+ /// New visibility state.
+ public virtual void OnSetLocalVisibility(bool vis) {}
+
+ ///
+ /// Callback used by the visibility system to determine if an observer (player) can see this object.
+ /// If this function returns true, the network connection will be added as an observer.
+ ///
+ /// Network connection of a player.
+ /// True if the player can see this object.
+ public virtual bool OnCheckObserver(NetworkConnection conn)
+ {
+ return true;
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs.meta b/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs.meta
new file mode 100644
index 0000000..84e619d
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkBehaviour.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 655ee8cba98594f70880da5cc4dc442d
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/NetworkClient.cs b/Assets/Packages/Mirror/Runtime/NetworkClient.cs
new file mode 100644
index 0000000..07473b3
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkClient.cs
@@ -0,0 +1,464 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using UnityEngine;
+
+namespace Mirror
+{
+ public enum ConnectState
+ {
+ None,
+ Connecting,
+ Connected,
+ Disconnected
+ }
+
+ // TODO make fully static after removing obsoleted singleton!
+ ///
+ /// This is a network client class used by the networking system. It contains a NetworkConnection that is used to connect to a network server.
+ /// The NetworkClient handle connection state, messages handlers, and connection configuration. There can be many NetworkClient instances in a process at a time, but only one that is connected to a game server (NetworkServer ) that uses spawned objects.
+ /// NetworkClient has an internal update function where it handles events from the transport layer. This includes asynchronous connect events, disconnect events and incoming data from a server.
+ /// The NetworkManager has a NetworkClient instance that it uses for games that it starts, but the NetworkClient may be used by itself.
+ ///
+ public class NetworkClient
+ {
+ ///
+ /// Obsolete: Use directly.
+ /// Singleton isn't needed anymore, all functions are static now. For example: NetworkClient.Send(message) instead of NetworkClient.singleton.Send(message).
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkClient directly. Singleton isn't needed anymore, all functions are static now. For example: NetworkClient.Send(message) instead of NetworkClient.singleton.Send(message).")]
+ public static NetworkClient singleton = new NetworkClient();
+
+ ///
+ /// A list of all the active network clients in the current process.
+ /// This is NOT a list of all clients that are connected to the remote server, it is client instances on the local game.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkClient directly instead. There is always exactly one client.")]
+ public static List allClients => new List { singleton };
+
+ ///
+ /// The registered network message handlers.
+ ///
+ public static readonly Dictionary handlers = new Dictionary();
+
+ ///
+ /// The NetworkConnection object this client is using.
+ ///
+ public static NetworkConnection connection { get; internal set; }
+
+ internal static ConnectState connectState = ConnectState.None;
+
+ ///
+ /// The IP address of the server that this client is connected to.
+ /// This will be empty if the client has not connected yet.
+ ///
+ public static string serverIp => connection.address;
+
+ ///
+ /// active is true while a client is connecting/connected
+ /// (= while the network is active)
+ ///
+ public static bool active => connectState == ConnectState.Connecting || connectState == ConnectState.Connected;
+
+ ///
+ /// This gives the current connection status of the client.
+ ///
+ public static bool isConnected => connectState == ConnectState.Connected;
+
+ ///
+ /// NetworkClient can connect to local server in host mode too
+ ///
+ public static bool isLocalClient => connection is ULocalConnectionToServer;
+
+ // local client in host mode might call Cmds/Rpcs during Update, but we
+ // want to apply them in LateUpdate like all other Transport messages
+ // to avoid race conditions. keep packets in Queue until LateUpdate.
+ internal static Queue localClientPacketQueue = new Queue();
+
+ ///
+ /// Connect client to a NetworkServer instance.
+ ///
+ ///
+ public static void Connect(string address)
+ {
+ if (LogFilter.Debug) Debug.Log("Client Connect: " + address);
+
+ RegisterSystemHandlers(false);
+ Transport.activeTransport.enabled = true;
+ InitializeTransportHandlers();
+
+ connectState = ConnectState.Connecting;
+ Transport.activeTransport.ClientConnect(address);
+
+ // setup all the handlers
+ connection = new NetworkConnection(address, 0);
+ connection.SetHandlers(handlers);
+ }
+
+ ///
+ /// connect host mode
+ ///
+ internal static void ConnectLocalServer()
+ {
+ if (LogFilter.Debug) Debug.Log("Client Connect Local Server");
+
+ RegisterSystemHandlers(true);
+
+ connectState = ConnectState.Connected;
+
+ // create local connection to server
+ connection = new ULocalConnectionToServer();
+ connection.SetHandlers(handlers);
+
+ // create server connection to local client
+ ULocalConnectionToClient connectionToClient = new ULocalConnectionToClient();
+ NetworkServer.SetLocalConnection(connectionToClient);
+
+ localClientPacketQueue.Enqueue(MessagePacker.Pack(new ConnectMessage()));
+ }
+
+ ///
+ /// Called by the server to set the LocalClient's LocalPlayer object during NetworkServer.AddPlayer()
+ ///
+ ///
+ internal static void AddLocalPlayer(NetworkIdentity localPlayer)
+ {
+ if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + connection.connectionId);
+ connection.isReady = true;
+ connection.playerController = localPlayer;
+ if (localPlayer != null)
+ {
+ localPlayer.isClient = true;
+ NetworkIdentity.spawned[localPlayer.netId] = localPlayer;
+ localPlayer.connectionToServer = connection;
+ }
+ // there is no SystemOwnerMessage for local client. add to ClientScene here instead
+ ClientScene.InternalAddPlayer(localPlayer);
+ }
+
+ static void InitializeTransportHandlers()
+ {
+ Transport.activeTransport.OnClientConnected.AddListener(OnConnected);
+ Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived);
+ Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected);
+ Transport.activeTransport.OnClientError.AddListener(OnError);
+ }
+
+ static void OnError(Exception exception)
+ {
+ Debug.LogException(exception);
+ }
+
+ static void OnDisconnected()
+ {
+ connectState = ConnectState.Disconnected;
+
+ ClientScene.HandleClientDisconnect(connection);
+
+ connection?.InvokeHandler(new DisconnectMessage());
+ }
+
+ internal static void OnDataReceived(ArraySegment data)
+ {
+ if (connection != null)
+ {
+ connection.TransportReceive(data);
+ }
+ else Debug.LogError("Skipped Data message handling because connection is null.");
+ }
+
+ static void OnConnected()
+ {
+ if (connection != null)
+ {
+ // reset network time stats
+ NetworkTime.Reset();
+
+ // the handler may want to send messages to the client
+ // thus we should set the connected state before calling the handler
+ connectState = ConnectState.Connected;
+ NetworkTime.UpdateClient();
+ connection.InvokeHandler(new ConnectMessage());
+ }
+ else Debug.LogError("Skipped Connect message handling because connection is null.");
+ }
+
+ ///
+ /// Disconnect from server.
+ /// The disconnect message will be invoked.
+ ///
+ public static void Disconnect()
+ {
+ connectState = ConnectState.Disconnected;
+ ClientScene.HandleClientDisconnect(connection);
+
+ // local or remote connection?
+ if (isLocalClient)
+ {
+ if (isConnected)
+ {
+ localClientPacketQueue.Enqueue(MessagePacker.Pack(new DisconnectMessage()));
+ }
+ NetworkServer.RemoveLocalConnection();
+ }
+ else
+ {
+ if (connection != null)
+ {
+ connection.Disconnect();
+ connection.Dispose();
+ connection = null;
+ RemoveTransportHandlers();
+ }
+ }
+ }
+
+ static void RemoveTransportHandlers()
+ {
+ // so that we don't register them more than once
+ Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected);
+ Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived);
+ Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected);
+ Transport.activeTransport.OnClientError.RemoveListener(OnError);
+ }
+
+ ///
+ /// Obsolete: Use instead with no message id instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use SendMessage instead with no message id instead")]
+ public static bool Send(short msgType, MessageBase msg)
+ {
+ if (connection != null)
+ {
+ if (connectState != ConnectState.Connected)
+ {
+ Debug.LogError("NetworkClient Send when not connected to a server");
+ return false;
+ }
+ return connection.Send(msgType, msg);
+ }
+ Debug.LogError("NetworkClient Send with no connection");
+ return false;
+ }
+
+ ///
+ /// This sends a network message with a message Id to the server. This message is sent on channel zero, which by default is the reliable channel.
+ /// The message must be an instance of a class derived from MessageBase.
+ /// The message id passed to Send() is used to identify the handler function to invoke on the server when the message is received.
+ ///
+ /// The message type to unregister.
+ ///
+ ///
+ /// True if message was sent.
+ public static bool Send(T message, int channelId = Channels.DefaultReliable) where T : IMessageBase
+ {
+ if (connection != null)
+ {
+ if (connectState != ConnectState.Connected)
+ {
+ Debug.LogError("NetworkClient Send when not connected to a server");
+ return false;
+ }
+ return connection.Send(message, channelId);
+ }
+ Debug.LogError("NetworkClient Send with no connection");
+ return false;
+ }
+
+ internal static void Update()
+ {
+ // local or remote connection?
+ if (isLocalClient)
+ {
+ // process internal messages so they are applied at the correct time
+ while (localClientPacketQueue.Count > 0)
+ {
+ byte[] packet = localClientPacketQueue.Dequeue();
+ OnDataReceived(new ArraySegment(packet));
+ }
+ }
+ else
+ {
+ // only update things while connected
+ if (active && connectState == ConnectState.Connected)
+ {
+ NetworkTime.UpdateClient();
+ }
+ }
+ }
+
+ /* TODO use or remove
+ void GenerateConnectError(byte error)
+ {
+ Debug.LogError("Mirror Client Error Connect Error: " + error);
+ GenerateError(error);
+ }
+
+ void GenerateDataError(byte error)
+ {
+ NetworkError dataError = (NetworkError)error;
+ Debug.LogError("Mirror Client Data Error: " + dataError);
+ GenerateError(error);
+ }
+
+ void GenerateDisconnectError(byte error)
+ {
+ NetworkError disconnectError = (NetworkError)error;
+ Debug.LogError("Mirror Client Disconnect Error: " + disconnectError);
+ GenerateError(error);
+ }
+
+ void GenerateError(byte error)
+ {
+ int msgId = MessageBase.GetId();
+ if (handlers.TryGetValue(msgId, out NetworkMessageDelegate msgDelegate))
+ {
+ ErrorMessage msg = new ErrorMessage
+ {
+ value = error
+ };
+
+ // write the message to a local buffer
+ NetworkWriter writer = new NetworkWriter();
+ msg.Serialize(writer);
+
+ NetworkMessage netMsg = new NetworkMessage
+ {
+ msgType = msgId,
+ reader = new NetworkReader(writer.ToArray()),
+ conn = connection
+ };
+ msgDelegate(netMsg);
+ }
+ }
+ */
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkTime.rtt instead")]
+ public static float GetRTT()
+ {
+ return (float)NetworkTime.rtt;
+ }
+
+ internal static void RegisterSystemHandlers(bool localClient)
+ {
+ // local client / regular client react to some messages differently.
+ // but we still need to add handlers for all of them to avoid
+ // 'message id not found' errors.
+ if (localClient)
+ {
+ RegisterHandler(ClientScene.OnLocalClientObjectDestroy);
+ RegisterHandler(ClientScene.OnLocalClientObjectHide);
+ RegisterHandler((conn, msg) => { });
+ RegisterHandler(ClientScene.OnLocalClientSpawnPrefab);
+ RegisterHandler(ClientScene.OnLocalClientSpawnSceneObject);
+ RegisterHandler((conn, msg) => { }); // host mode doesn't need spawning
+ RegisterHandler((conn, msg) => { }); // host mode doesn't need spawning
+ RegisterHandler((conn, msg) => { });
+ }
+ else
+ {
+ RegisterHandler(ClientScene.OnObjectDestroy);
+ RegisterHandler(ClientScene.OnObjectHide);
+ RegisterHandler(NetworkTime.OnClientPong);
+ RegisterHandler(ClientScene.OnSpawnPrefab);
+ RegisterHandler(ClientScene.OnSpawnSceneObject);
+ RegisterHandler(ClientScene.OnObjectSpawnStarted);
+ RegisterHandler(ClientScene.OnObjectSpawnFinished);
+ RegisterHandler(ClientScene.OnUpdateVarsMessage);
+ }
+ RegisterHandler(ClientScene.OnClientAuthority);
+ RegisterHandler(ClientScene.OnRPCMessage);
+ RegisterHandler(ClientScene.OnSyncEventMessage);
+ }
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use RegisterHandler instead")]
+ public static void RegisterHandler(int msgType, NetworkMessageDelegate handler)
+ {
+ if (handlers.ContainsKey(msgType))
+ {
+ if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + handler + " - " + msgType);
+ }
+ handlers[msgType] = handler;
+ }
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use RegisterHandler instead")]
+ public static void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler)
+ {
+ RegisterHandler((int)msgType, handler);
+ }
+
+ ///
+ /// Register a handler for a particular message type.
+ /// There are several system message types which you can add handlers for. You can also add your own message types.
+ ///
+ /// The message type to unregister.
+ ///
+ public static void RegisterHandler(Action handler) where T : IMessageBase, new()
+ {
+ int msgType = MessagePacker.GetId();
+ if (handlers.ContainsKey(msgType))
+ {
+ if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + handler + " - " + msgType);
+ }
+ handlers[msgType] = MessagePacker.MessageHandler(handler);
+ }
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use UnregisterHandler instead")]
+ public static void UnregisterHandler(int msgType)
+ {
+ handlers.Remove(msgType);
+ }
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use UnregisterHandler instead")]
+ public static void UnregisterHandler(MsgType msgType)
+ {
+ UnregisterHandler((int)msgType);
+ }
+
+ ///
+ /// Unregisters a network message handler.
+ ///
+ /// The message type to unregister.
+ public static void UnregisterHandler() where T : IMessageBase
+ {
+ // use int to minimize collisions
+ int msgType = MessagePacker.GetId();
+ handlers.Remove(msgType);
+ }
+
+ ///
+ /// Shut down a client.
+ /// This should be done when a client is no longer going to be used.
+ ///
+ public static void Shutdown()
+ {
+ if (LogFilter.Debug) Debug.Log("Shutting down client.");
+ ClientScene.Shutdown();
+ connectState = ConnectState.None;
+ }
+
+ ///
+ /// Obsolete: Call instead. There is only one client.
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Call NetworkClient.Shutdown() instead. There is only one client.")]
+ public static void ShutdownAll()
+ {
+ Shutdown();
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/NetworkClient.cs.meta b/Assets/Packages/Mirror/Runtime/NetworkClient.cs.meta
new file mode 100644
index 0000000..b43b514
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkClient.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: abe6be14204d94224a3e7cd99dd2ea73
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/NetworkConnection.cs b/Assets/Packages/Mirror/Runtime/NetworkConnection.cs
new file mode 100644
index 0000000..613147b
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkConnection.cs
@@ -0,0 +1,375 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using UnityEngine;
+
+namespace Mirror
+{
+ ///
+ /// A High level network connection. This is used for connections from client-to-server and for connection from server-to-client.
+ ///
+ ///
+ /// A NetworkConnection corresponds to a specific connection for a host in the transport layer. It has a connectionId that is assigned by the transport layer and passed to the Initialize function.
+ /// A NetworkClient has one NetworkConnection. A NetworkServerSimple manages multiple NetworkConnections. The NetworkServer has multiple "remote" connections and a "local" connection for the local client.
+ /// The NetworkConnection class provides message sending and handling facilities. For sending data over a network, there are methods to send message objects, byte arrays, and NetworkWriter objects. To handle data arriving from the network, handler functions can be registered for message Ids, byte arrays can be processed by HandleBytes(), and NetworkReader object can be processed by HandleReader().
+ /// NetworkConnection objects also act as observers for networked objects. When a connection is an observer of a networked object with a NetworkIdentity, then the object will be visible to corresponding client for the connection, and incremental state changes will be sent to the client.
+ /// NetworkConnection objects can "own" networked game objects. Owned objects will be destroyed on the server by default when the connection is destroyed. A connection owns the player objects created by its client, and other objects with client-authority assigned to the corresponding client.
+ /// There are many virtual functions on NetworkConnection that allow its behaviour to be customized. NetworkClient and NetworkServer can both be made to instantiate custom classes derived from NetworkConnection by setting their networkConnectionClass member variable.
+ ///
+ public class NetworkConnection : IDisposable
+ {
+ public readonly HashSet visList = new HashSet();
+
+ Dictionary messageHandlers;
+
+ ///
+ /// Unique identifier for this connection that is assigned by the transport layer.
+ ///
+ ///
+ /// On a server, this Id is unique for every connection on the server. On a client this Id is local to the client, it is not the same as the Id on the server for this connection.
+ /// Transport layers connections begin at one. So on a client with a single connection to a server, the connectionId of that connection will be one. In NetworkServer, the connectionId of the local connection is zero.
+ /// Clients do not know their connectionId on the server, and do not know the connectionId of other clients on the server.
+ ///
+ public int connectionId = -1;
+
+ ///
+ /// Flag that tells if the connection has been marked as "ready" by a client calling ClientScene.Ready().
+ /// This property is read-only. It is set by the system on the client when ClientScene.Ready() is called, and set by the system on the server when a ready message is received from a client.
+ /// A client that is ready is sent spawned objects by the server and updates to the state of spawned objects. A client that is not ready is not sent spawned objects.
+ ///
+ public bool isReady;
+
+ ///
+ /// The IP address / URL / FQDN associated with the connection.
+ ///
+ public string address;
+
+ ///
+ /// The last time that a message was received on this connection.
+ /// This includes internal system messages (such as Commands and ClientRpc calls) and user messages.
+ ///
+ public float lastMessageTime;
+
+ ///
+ /// The NetworkIdentity for this connection.
+ ///
+ public NetworkIdentity playerController { get; internal set; }
+
+ ///
+ /// A list of the NetworkIdentity objects owned by this connection. This list is read-only.
+ /// This includes the player object for the connection - if it has localPlayerAutority set, and any objects spawned with local authority or set with AssignLocalAuthority.
+ /// This list can be used to validate messages from clients, to ensure that clients are only trying to control objects that they own.
+ ///
+ public readonly HashSet clientOwnedObjects = new HashSet();
+
+ ///
+ /// Setting this to true will log the contents of network message to the console.
+ ///
+ ///
+ /// Warning: this can be a lot of data and can be very slow. Both incoming and outgoing messages are logged. The format of the logs is:
+ /// ConnectionSend con:1 bytes:11 msgId:5 FB59D743FD120000000000 ConnectionRecv con:1 bytes:27 msgId:8 14F21000000000016800AC3FE090C240437846403CDDC0BD3B0000
+ /// Note that these are application-level network messages, not protocol-level packets. There will typically be multiple network messages combined in a single protocol packet.
+ ///
+ public bool logNetworkMessages;
+
+ // this is always true for regular connections, false for local
+ // connections because it's set in the constructor and never reset.
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("isConnected will be removed because it's pointless. A NetworkConnection is always connected.")]
+ public bool isConnected { get; protected set; }
+
+ // this is always 0 for regular connections, -1 for local
+ // connections because it's set in the constructor and never reset.
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("hostId will be removed because it's not needed ever since we removed LLAPI as default. It's always 0 for regular connections and -1 for local connections. Use connection.GetType() == typeof(NetworkConnection) to check if it's a regular or local connection.")]
+ public int hostId = -1;
+
+ ///
+ /// Creates a new NetworkConnection with the specified address
+ ///
+ ///
+ public NetworkConnection(string networkAddress)
+ {
+ address = networkAddress;
+ }
+
+ ///
+ /// Creates a new NetworkConnection with the specified address and connectionId
+ ///
+ ///
+ ///
+ public NetworkConnection(string networkAddress, int networkConnectionId)
+ {
+ address = networkAddress;
+ connectionId = networkConnectionId;
+#pragma warning disable 618
+ isConnected = true;
+ hostId = 0;
+#pragma warning restore 618
+ }
+
+ ~NetworkConnection()
+ {
+ Dispose(false);
+ }
+
+ ///
+ /// Disposes of this connection, releasing channel buffers that it holds.
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ // Take yourself off the Finalization queue
+ // to prevent finalization code for this object
+ // from executing a second time.
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ foreach (uint netId in clientOwnedObjects)
+ {
+ if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity))
+ {
+ identity.clientAuthorityOwner = null;
+ }
+ }
+ clientOwnedObjects.Clear();
+ }
+
+ ///
+ /// Disconnects this connection.
+ ///
+ public void Disconnect()
+ {
+ // don't clear address so we can still access it in NetworkManager.OnServerDisconnect
+ // => it's reset in Initialize anyway and there is no address empty check anywhere either
+ //address = "";
+
+ // set not ready and handle clientscene disconnect in any case
+ // (might be client or host mode here)
+ isReady = false;
+ ClientScene.HandleClientDisconnect(this);
+
+ // server? then disconnect that client (not for host local player though)
+ if (Transport.activeTransport.ServerActive() && connectionId != 0)
+ {
+ Transport.activeTransport.ServerDisconnect(connectionId);
+ }
+ // not server and not host mode? then disconnect client
+ else
+ {
+ Transport.activeTransport.ClientDisconnect();
+ }
+
+ RemoveObservers();
+ }
+
+ internal void SetHandlers(Dictionary handlers)
+ {
+ messageHandlers = handlers;
+ }
+
+ ///
+ /// Obsolete: Use NetworkClient/NetworkServer.RegisterHandler{T} instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkClient/NetworkServer.RegisterHandler instead")]
+ public void RegisterHandler(short msgType, NetworkMessageDelegate handler)
+ {
+ if (messageHandlers.ContainsKey(msgType))
+ {
+ if (LogFilter.Debug) Debug.Log("NetworkConnection.RegisterHandler replacing " + msgType);
+ }
+ messageHandlers[msgType] = handler;
+ }
+
+ ///
+ /// Obsolete: Use and instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkClient/NetworkServer.UnregisterHandler instead")]
+ public void UnregisterHandler(short msgType)
+ {
+ messageHandlers.Remove(msgType);
+ }
+
+ ///
+ /// Obsolete: use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("use Send instead")]
+ public virtual bool Send(int msgType, MessageBase msg, int channelId = Channels.DefaultReliable)
+ {
+ // pack message and send
+ byte[] message = MessagePacker.PackMessage(msgType, msg);
+ return SendBytes(message, channelId);
+ }
+
+ ///
+ /// This sends a network message with a message ID on the connection. This message is sent on channel zero, which by default is the reliable channel.
+ ///
+ /// The message type to unregister.
+ /// The message to send.
+ /// The transport layer channel to send on.
+ ///
+ public virtual bool Send(T msg, int channelId = Channels.DefaultReliable) where T: IMessageBase
+ {
+ // pack message and send
+ byte[] message = MessagePacker.Pack(msg);
+ return SendBytes(message, channelId);
+ }
+
+ // internal because no one except Mirror should send bytes directly to
+ // the client. they would be detected as a message. send messages instead.
+ internal virtual bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable)
+ {
+ if (logNetworkMessages) Debug.Log("ConnectionSend con:" + connectionId + " bytes:" + BitConverter.ToString(bytes));
+
+ if (bytes.Length > Transport.activeTransport.GetMaxPacketSize(channelId))
+ {
+ Debug.LogError("NetworkConnection.SendBytes cannot send packet larger than " + Transport.activeTransport.GetMaxPacketSize(channelId) + " bytes");
+ return false;
+ }
+
+ if (bytes.Length == 0)
+ {
+ // zero length packets getting into the packet queues are bad.
+ Debug.LogError("NetworkConnection.SendBytes cannot send zero bytes");
+ return false;
+ }
+
+ return TransportSend(channelId, bytes);
+ }
+
+ public override string ToString()
+ {
+ return $"connectionId: {connectionId} isReady: {isReady}";
+ }
+
+ internal void AddToVisList(NetworkIdentity identity)
+ {
+ visList.Add(identity);
+
+ // spawn identity for this conn
+ NetworkServer.ShowForConnection(identity, this);
+ }
+
+ internal void RemoveFromVisList(NetworkIdentity identity, bool isDestroyed)
+ {
+ visList.Remove(identity);
+
+ if (!isDestroyed)
+ {
+ // hide identity for this conn
+ NetworkServer.HideForConnection(identity, this);
+ }
+ }
+
+ internal void RemoveObservers()
+ {
+ foreach (NetworkIdentity identity in visList)
+ {
+ identity.RemoveObserverInternal(this);
+ }
+ visList.Clear();
+ }
+
+ ///
+ /// Obsolete: Use instead
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use InvokeHandler instead")]
+ public bool InvokeHandlerNoData(int msgType)
+ {
+ return InvokeHandler(msgType, null);
+ }
+
+ internal bool InvokeHandler(int msgType, NetworkReader reader)
+ {
+ if (messageHandlers.TryGetValue(msgType, out NetworkMessageDelegate msgDelegate))
+ {
+ NetworkMessage message = new NetworkMessage
+ {
+ msgType = msgType,
+ reader = reader,
+ conn = this
+ };
+
+ msgDelegate(message);
+ return true;
+ }
+ Debug.LogError("Unknown message ID " + msgType + " connId:" + connectionId);
+ return false;
+ }
+
+ ///
+ /// This function invokes the registered handler function for a message.
+ /// Network connections used by the NetworkClient and NetworkServer use this function for handling network messages.
+ ///
+ /// The message type to unregister.
+ /// The message object to process.
+ ///
+ public bool InvokeHandler(T msg) where T : IMessageBase
+ {
+ int msgType = MessagePacker.GetId();
+ byte[] data = MessagePacker.Pack(msg);
+ return InvokeHandler(msgType, new NetworkReader(data));
+ }
+
+ // note: original HLAPI HandleBytes function handled >1 message in a while loop, but this wasn't necessary
+ // anymore because NetworkServer/NetworkClient Update both use while loops to handle >1 data events per
+ // frame already.
+ // -> in other words, we always receive 1 message per Receive call, never two.
+ // -> can be tested easily with a 1000ms send delay and then logging amount received in while loops here
+ // and in NetworkServer/Client Update. HandleBytes already takes exactly one.
+ ///
+ /// This virtual function allows custom network connection classes to process data from the network before it is passed to the application.
+ ///
+ /// The data recieved.
+ public virtual void TransportReceive(ArraySegment buffer)
+ {
+ // unpack message
+ NetworkReader reader = new NetworkReader(buffer);
+ if (MessagePacker.UnpackMessage(reader, out int msgType))
+ {
+ // logging
+ if (logNetworkMessages) Debug.Log("ConnectionRecv con:" + connectionId + " msgType:" + msgType + " content:" + BitConverter.ToString(buffer.Array, buffer.Offset, buffer.Count));
+
+ // try to invoke the handler for that message
+ if (InvokeHandler(msgType, reader))
+ {
+ lastMessageTime = Time.time;
+ }
+ }
+ else
+ {
+ Debug.LogError("Closed connection: " + connectionId + ". Invalid message header.");
+ Disconnect();
+ }
+ }
+
+ ///
+ /// This virtual function allows custom network connection classes to process data send by the application before it goes to the network transport layer.
+ ///
+ /// Channel to send data on.
+ /// Data to send.
+ ///
+ public virtual bool TransportSend(int channelId, byte[] bytes)
+ {
+ if (Transport.activeTransport.ClientConnected())
+ {
+ return Transport.activeTransport.ClientSend(channelId, bytes);
+ }
+ else if (Transport.activeTransport.ServerActive())
+ {
+ return Transport.activeTransport.ServerSend(connectionId, channelId, bytes);
+ }
+ return false;
+ }
+
+ internal void AddOwnedObject(NetworkIdentity obj)
+ {
+ clientOwnedObjects.Add(obj.netId);
+ }
+
+ internal void RemoveOwnedObject(NetworkIdentity obj)
+ {
+ clientOwnedObjects.Remove(obj.netId);
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/NetworkConnection.cs.meta b/Assets/Packages/Mirror/Runtime/NetworkConnection.cs.meta
new file mode 100644
index 0000000..3688d9c
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkConnection.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 11ea41db366624109af1f0834bcdde2f
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs b/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs
new file mode 100644
index 0000000..362b14c
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs
@@ -0,0 +1,1253 @@
+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
+{
+ ///
+ /// 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.
+ ///
+ ///
+ /// 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.
+ /// 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.
+ /// NetworkBehaviour scripts require a NetworkIdentity on the game object to be able to function.
+ /// 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.
+ /// The flow for serialization updates managed by the NetworkIdentity is:
+ /// * Each NetworkBehaviour has a dirty mask. This mask is available inside OnSerialize as syncVarDirtyBits
+ /// * Each SyncVar in a NetworkBehaviour script is assigned a bit in the dirty mask.
+ /// * Changing the value of SyncVars causes the bit for that SyncVar to be set in the dirty mask
+ /// * Alternatively, calling SetDirtyBit() writes directly to the dirty mask
+ /// * NetworkIdentity objects are checked on the server as part of it's update loop
+ /// * If any NetworkBehaviours on a NetworkIdentity are dirty, then an UpdateVars packet is created for that object
+ /// * The UpdateVars packet is populated by calling OnSerialize on each NetworkBehaviour on the object
+ /// * NetworkBehaviours that are NOT dirty write a zero to the packet for their dirty bits
+ /// * NetworkBehaviours that are dirty write their dirty mask, then the values for the SyncVars that have changed
+ /// * 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.
+ /// * The UpdateVars packet is sent to ready clients that are observing the object
+ /// On the client:
+ /// * an UpdateVars packet is received for an object
+ /// * The OnDeserialize function is called for each NetworkBehaviour script on the object
+ /// * Each NetworkBehaviour script on the object reads a dirty mask.
+ /// * If the dirty mask for a NetworkBehaviour is zero, the OnDeserialize functions returns without reading any more
+ /// * 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
+ /// * If there are SyncVar hook functions, those are invoked with the value read from the stream.
+ ///
+ [ExecuteInEditMode]
+ [DisallowMultipleComponent]
+ [AddComponentMenu("Network/NetworkIdentity")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkIdentity.html")]
+ public sealed class NetworkIdentity : MonoBehaviour
+ {
+ // configuration
+ bool m_IsServer;
+ NetworkBehaviour[] networkBehavioursCache;
+
+ // member used to mark a identity for future reset
+ // check MarkForReset for more information.
+ bool m_Reset;
+
+ ///
+ /// Returns true if running as a client and this object was spawned by a server.
+ ///
+ public bool isClient { get; internal set; }
+
+ ///
+ /// Returns true if NetworkServer.active and server is not stopped.
+ ///
+ public bool isServer
+ {
+ get => m_IsServer && NetworkServer.active && netId != 0;
+ internal set => m_IsServer = value;
+ }
+
+ ///
+ /// This returns true if this object is the one that represents the player on the local machine.
+ /// This is set when the server has spawned an object for this particular client.
+ ///
+ public bool isLocalPlayer { get; private set; }
+
+ internal bool pendingOwner { get; set; }
+
+ ///
+ /// This returns true if this object is the authoritative version of the object in the distributed network application.
+ /// This value is determined at runtime, as opposed to localPlayerAuthority which is set on the prefab. For most objects, authority is held by the server / host. For objects with localPlayerAuthority set, authority is held by the client of that player.
+ /// 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.
+ ///
+ public bool hasAuthority { get; private set; }
+
+ ///
+ /// The set of network connections (players) that can see this object.
+ /// null until OnStartServer was called. this is necessary for SendTo* to work properly in server-only mode.
+ ///
+ public Dictionary observers;
+
+ ///
+ /// Unique identifier for this particular object instance, used for tracking objects between networked clients and the server.
+ /// This is a unique identifier for this particular GameObject instance. Use it to track GameObjects between networked clients and the server.
+ ///
+ public uint netId { get; internal set; }
+
+ ///
+ /// A unique identifier for NetworkIdentity objects within a scene.
+ /// This is used for spawning scene objects on clients.
+ ///
+ public ulong sceneId => m_SceneId;
+
+ ///
+ /// Flag to make this object only exist when the game is running as a server (or host).
+ ///
+ [FormerlySerializedAs("m_ServerOnly")]
+ public bool serverOnly;
+
+ ///
+ /// localPlayerAuthority means that the client of the "owning" player has authority over their own player object.
+ /// Authority for this object will be on the player's client. So hasAuthority will be true on that client - and false on the server and on other clients.
+ ///
+ [FormerlySerializedAs("m_LocalPlayerAuthority")]
+ public bool localPlayerAuthority;
+
+ ///
+ /// The client that has authority for this object. This will be null if no client has authority.
+ /// This is set for player objects with localPlayerAuthority, and for objects set with AssignClientAuthority, and spawned with SpawnWithClientAuthority.
+ ///
+ public NetworkConnection clientAuthorityOwner { get; internal set; }
+
+ ///
+ /// The NetworkConnection associated with this NetworkIdentity. This is only valid for player objects on a local client.
+ ///
+ public NetworkConnection connectionToServer { get; internal set; }
+
+ ///
+ /// The NetworkConnection associated with this NetworkIdentity. This is only valid for player objects on the server.
+ /// Use it to return details such as the connection's identity, IP address and ready status.
+ ///
+ public NetworkConnection connectionToClient { get; internal set; }
+
+ ///
+ /// All spawned NetworkIdentities by netId. Available on server and client.
+ ///
+ public static readonly Dictionary spawned = new Dictionary();
+
+ public NetworkBehaviour[] NetworkBehaviours => networkBehavioursCache = networkBehavioursCache ?? GetComponents();
+
+ [SerializeField] 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
+ ///
+ /// Unique identifier used to find the source assets when server spawns the on clients.
+ ///
+ 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 already has an assetId <" + m_AssetId + ">");
+ }
+ }
+
+ // persistent scene id
+ // (see AssignSceneID comments)
+ // suppress "Field 'NetworkIdentity.m_SceneId' is never assigned to, and will always have its default value 0"
+ // when building standalone
+ #pragma warning disable CS0649
+ [SerializeField] ulong m_SceneId;
+ #pragma warning restore CS0649
+
+ // keep track of all sceneIds to detect scene duplicates
+ static readonly Dictionary sceneIds = new Dictionary();
+
+ // used when adding players
+ internal void SetClientOwner(NetworkConnection conn)
+ {
+ if (clientAuthorityOwner != null)
+ {
+ Debug.LogError("SetClientOwner m_ClientAuthorityOwner already set!");
+ }
+ clientAuthorityOwner = conn;
+ clientAuthorityOwner.AddOwnedObject(this);
+ }
+
+ internal void ForceAuthority(bool authority)
+ {
+ if (hasAuthority == authority)
+ {
+ return;
+ }
+
+ hasAuthority = authority;
+ if (authority)
+ {
+ OnStartAuthority();
+ }
+ else
+ {
+ OnStopAuthority();
+ }
+ }
+
+ static uint nextNetworkId = 1;
+ internal static uint GetNextNetworkId() => nextNetworkId++;
+
+ ///
+ /// Resets nextNetworkId = 1
+ ///
+ public static void ResetNextNetworkId() => nextNetworkId = 1;
+
+ ///
+ /// The delegate type for the clientAuthorityCallback.
+ ///
+ /// The network connection that is gaining or losing authority.
+ /// The object whose client authority status is being changed.
+ /// The new state of client authority of the object for the connection.
+ public delegate void ClientAuthorityCallback(NetworkConnection conn, NetworkIdentity identity, bool authorityState);
+
+ ///
+ /// A callback that can be populated to be notified when the client-authority state of objects changes.
+ /// 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.
+ /// This callback is used by the NetworkMigrationManager to distribute client authority state to peers for host migration. If the NetworkMigrationManager is not being used, this callback does not need to be populated.
+ ///
+ public static ClientAuthorityCallback clientAuthorityCallback;
+
+ // used when the player object for a connection changes
+ internal void SetNotLocalPlayer()
+ {
+ isLocalPlayer = false;
+
+ if (NetworkServer.active && NetworkServer.localClientActive)
+ {
+ // dont change authority for objects on the host
+ return;
+ }
+ hasAuthority = false;
+ }
+
+ // 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
+ if (serverOnly && localPlayerAuthority)
+ {
+ Debug.LogWarning("Disabling Local Player Authority for " + gameObject + " because it is server-only.");
+ localPlayerAuthority = false;
+ }
+
+ 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 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
+ 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(m_SceneId, out NetworkIdentity existing) && existing != null && existing != this;
+ if (m_SceneId == 0 || duplicate)
+ {
+ // clear in any case, because it might have been a duplicate
+ m_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)
+ {
+ m_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[m_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
+ m_SceneId = (m_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: " + m_SceneId.ToString("X"));
+ }
+
+ void SetupIDs()
+ {
+ if (ThisIsAPrefab())
+ {
+ m_SceneId = 0; // force 0 for prefabs
+ AssignAssetID(gameObject);
+ }
+ // check prefabstage BEFORE SceneObjectWithPrefabParent
+ // (fixes https://github.com/vis2k/Mirror/issues/976)
+ else if (PrefabStageUtility.GetCurrentPrefabStage() != null)
+ {
+ m_SceneId = 0; // force 0 for prefabs
+ string path = PrefabStageUtility.GetCurrentPrefabStage().prefabAssetPath;
+ AssignAssetID(path);
+ }
+ else if (ThisIsASceneObjectWithPrefabParent(out GameObject prefab))
+ {
+ AssignSceneID();
+ AssignAssetID(prefab);
+ }
+ else
+ {
+ AssignSceneID();
+ m_AssetId = "";
+ }
+ }
+#endif
+
+ void OnDestroy()
+ {
+ // remove from sceneIds
+ // -> remove with (0xFFFFFFFFFFFFFFFF) and without (0x00000000FFFFFFFF)
+ // sceneHash to be 100% safe.
+ sceneIds.Remove(sceneId);
+ sceneIds.Remove(sceneId & 0x00000000FFFFFFFF);
+
+ if (m_IsServer && NetworkServer.active)
+ {
+ NetworkServer.Destroy(gameObject);
+ }
+ }
+
+ internal void OnStartServer(bool allowNonZeroNetId)
+ {
+ if (m_IsServer)
+ {
+ return;
+ }
+ m_IsServer = true;
+ hasAuthority = !localPlayerAuthority;
+
+ observers = new Dictionary();
+
+ // 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
+ if (netId == 0)
+ {
+ netId = GetNextNetworkId();
+ }
+ else
+ {
+ if (!allowNonZeroNetId)
+ {
+ Debug.LogError("Object has non-zero netId " + netId + " for " + gameObject);
+ return;
+ }
+ }
+
+ 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;
+
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ try
+ {
+ comp.OnStartServer();
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("Exception in OnStartServer:" + e.Message + " " + e.StackTrace);
+ }
+ }
+
+ if (NetworkClient.active && NetworkServer.localClientActive)
+ {
+ // there will be no spawn message, so start the client here too
+ OnStartClient();
+ }
+
+ if (hasAuthority)
+ {
+ OnStartAuthority();
+ }
+ }
+
+ internal void OnStartClient()
+ {
+ isClient = true;
+
+ if (LogFilter.Debug) Debug.Log("OnStartClient " + gameObject + " netId:" + netId + " localPlayerAuthority:" + localPlayerAuthority);
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ try
+ {
+ comp.OnStartClient(); // user implemented startup
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("Exception in OnStartClient:" + e.Message + " " + e.StackTrace);
+ }
+ }
+ }
+
+ void OnStartAuthority()
+ {
+ if (networkBehavioursCache == null)
+ {
+ Debug.LogError("Network object " + name + " not initialized properly. Do you have more than one NetworkIdentity in the same object? Did you forget to spawn this object with NetworkServer?", this);
+ return;
+ }
+
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ try
+ {
+ comp.OnStartAuthority();
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("Exception in OnStartAuthority:" + e.Message + " " + e.StackTrace);
+ }
+ }
+ }
+
+ void OnStopAuthority()
+ {
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ try
+ {
+ comp.OnStopAuthority();
+ }
+ catch (Exception e)
+ {
+ Debug.LogError("Exception in OnStopAuthority:" + e.Message + " " + e.StackTrace);
+ }
+ }
+ }
+
+ internal void OnSetLocalVisibility(bool vis)
+ {
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ try
+ {
+ comp.OnSetLocalVisibility(vis);
+ }
+ 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;
+ }
+
+ // 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=" + m_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=" + m_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 before all the test behaviours
+ // were added)
+ ulong syncModeObserversMask = GetSyncModeObserversMask();
+
+ // write regular dirty mask for owner,
+ // writer 'dirty mask & syncMode==Everyone' for everyone else
+ // (WritePacked64 so we don't write full 8 bytes if we don't have to)
+ ownerWriter.WritePackedUInt64(dirtyComponentsMask);
+ observersWriter.WritePackedUInt64(dirtyComponentsMask & syncModeObserversMask);
+
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ // is this component dirty?
+ // -> always serialize if initialState so all components are included in spawn packet
+ // -> note: IsDirty() is false if the component isn't dirty or sendInterval isn't elapsed yet
+ if (initialState || comp.IsDirty())
+ {
+ if (LogFilter.Debug) Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState);
+
+ // serialize into ownerWriter first
+ // (owner always gets everything!)
+ int startPosition = ownerWriter.Position;
+ OnSerializeSafely(comp, ownerWriter, initialState);
+ ++ownerWritten;
+
+ // copy into observersWriter too if SyncMode.Observers
+ // -> we copy instead of calling OnSerialize again because
+ // we don't know what magic the user does in OnSerialize.
+ // -> it's not guaranteed that calling it twice gets the
+ // same result
+ // -> it's not guaranteed that calling it twice doesn't mess
+ // with the user's OnSerialize timing code etc.
+ // => so we just copy the result without touching
+ // OnSerialize again
+ if (comp.syncMode == SyncMode.Observers)
+ {
+ ArraySegment segment = ownerWriter.ToArraySegment();
+ int length = ownerWriter.Position - startPosition;
+ observersWriter.WriteBytes(segment.Array, startPosition, length);
+ ++observersWritten;
+ }
+ }
+ }
+ }
+
+ internal ulong GetDirtyMask(bool initialState)
+ {
+ // loop through all components only once and then write dirty+payload into the writer afterwards
+ ulong dirtyComponentsMask = 0L;
+ NetworkBehaviour[] components = NetworkBehaviours;
+ for (int i = 0; i < components.Length; ++i)
+ {
+ NetworkBehaviour comp = components[i];
+ if (initialState || comp.IsDirty())
+ {
+ dirtyComponentsMask |= (ulong)(1L << i);
+ }
+ }
+
+ return dirtyComponentsMask;
+ }
+
+ // a mask that contains all the components with SyncMode.Observers
+ internal ulong GetSyncModeObserversMask()
+ {
+ // loop through all components
+ ulong mask = 0UL;
+ NetworkBehaviour[] components = NetworkBehaviours;
+ for (int i = 0; i < NetworkBehaviours.Length; ++i)
+ {
+ NetworkBehaviour comp = components[i];
+ if (comp.syncMode == SyncMode.Observers)
+ {
+ mask |= 1UL << i;
+ }
+ }
+
+ return mask;
+ }
+
+ void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState)
+ {
+ // read header as 4 bytes and calculate this chunk's start+end
+ int contentSize = reader.ReadInt32();
+ int chunkStart = reader.Position;
+ int chunkEnd = reader.Position + contentSize;
+
+ // call OnDeserialize and wrap it in a try-catch block so there's no
+ // way to mess up another component's deserialization
+ try
+ {
+ if (LogFilter.Debug) Debug.Log("OnDeserializeSafely: " + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + " length=" + contentSize);
+ comp.OnDeserialize(reader, initialState);
+ }
+ catch (Exception e)
+ {
+ // show a detailed error and let the user know what went wrong
+ Debug.LogError("OnDeserialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + " length=" + contentSize + ". Possible Reasons:\n * Do " + comp.GetType() + "'s OnSerialize and OnDeserialize calls write the same amount of data(" + contentSize +" bytes)? \n * Was there an exception in " + comp.GetType() + "'s OnSerialize/OnDeserialize code?\n * Are the server and client the exact same project?\n * Maybe this OnDeserialize call was meant for another GameObject? The sceneIds can easily get out of sync if the Hierarchy was modified only in the client OR the server. Try rebuilding both.\n\n" + e);
+ }
+
+ // now the reader should be EXACTLY at 'before + size'.
+ // otherwise the component read too much / too less data.
+ if (reader.Position != chunkEnd)
+ {
+ // warn the user
+ int bytesRead = reader.Position - chunkStart;
+ Debug.LogWarning("OnDeserialize was expected to read " + contentSize + " instead of " + bytesRead + " bytes for object:" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + ". Make sure that OnSerialize and OnDeserialize write/read the same amount of data in all cases.");
+
+ // fix the position, so the following components don't all fail
+ reader.Position = chunkEnd;
+ }
+ }
+
+ internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState)
+ {
+ // read component dirty mask
+ ulong dirtyComponentsMask = reader.ReadPackedUInt64();
+
+ NetworkBehaviour[] components = NetworkBehaviours;
+ // loop through all components and deserialize the dirty ones
+ for (int i = 0; i < components.Length; ++i)
+ {
+ // is the dirty bit at position 'i' set to 1?
+ ulong dirtyBit = (ulong)(1L << i);
+ if ((dirtyComponentsMask & dirtyBit) != 0L)
+ {
+ OnDeserializeSafely(components[i], reader, initialState);
+ }
+ }
+ }
+
+ // happens on client
+ internal void HandleClientAuthority(bool authority)
+ {
+ if (!localPlayerAuthority)
+ {
+ Debug.LogError("HandleClientAuthority " + gameObject + " does not have localPlayerAuthority");
+ return;
+ }
+
+ ForceAuthority(authority);
+ }
+
+ // helper function to handle SyncEvent/Command/Rpc
+ void HandleRemoteCall(int componentIndex, int functionHash, MirrorInvokeType invokeType, NetworkReader reader)
+ {
+ if (gameObject == null)
+ {
+ Debug.LogWarning(invokeType + " [" + functionHash + "] received for deleted object [netId=" + netId + "]");
+ return;
+ }
+
+ // find the right component to invoke the function on
+ if (0 <= componentIndex && componentIndex < networkBehavioursCache.Length)
+ {
+ NetworkBehaviour invokeComponent = networkBehavioursCache[componentIndex];
+ if (!invokeComponent.InvokeHandlerDelegate(functionHash, invokeType, reader))
+ {
+ Debug.LogError("Found no receiver for incoming " + invokeType + " [" + functionHash + "] on " + gameObject + ", the server and client should have the same NetworkBehaviour instances [netId=" + netId + "].");
+ }
+ }
+ else
+ {
+ Debug.LogWarning("Component [" + componentIndex + "] not found for [netId=" + netId + "]");
+ }
+ }
+
+ // happens on client
+ internal void HandleSyncEvent(int componentIndex, int eventHash, NetworkReader reader)
+ {
+ HandleRemoteCall(componentIndex, eventHash, MirrorInvokeType.SyncEvent, reader);
+ }
+
+ // happens on server
+ internal void HandleCommand(int componentIndex, int cmdHash, NetworkReader reader)
+ {
+ HandleRemoteCall(componentIndex, cmdHash, MirrorInvokeType.Command, reader);
+ }
+
+ // happens on client
+ internal void HandleRPC(int componentIndex, int rpcHash, NetworkReader reader)
+ {
+ HandleRemoteCall(componentIndex, rpcHash, MirrorInvokeType.ClientRpc, reader);
+ }
+
+ internal void OnUpdateVars(NetworkReader reader, bool initialState)
+ {
+ OnDeserializeAllSafely(reader, initialState);
+ }
+
+ internal void SetLocalPlayer()
+ {
+ isLocalPlayer = true;
+
+ // there is an ordering issue here that originAuthority solves. OnStartAuthority should only be called if m_HasAuthority was false when this function began,
+ // or it will be called twice for this object. But that state is lost by the time OnStartAuthority is called below, so the original value is cached
+ // here to be checked below.
+ bool originAuthority = hasAuthority;
+ if (localPlayerAuthority)
+ {
+ hasAuthority = true;
+ }
+
+ foreach (NetworkBehaviour comp in networkBehavioursCache)
+ {
+ comp.OnStartLocalPlayer();
+
+ if (localPlayerAuthority && !originAuthority)
+ {
+ comp.OnStartAuthority();
+ }
+ }
+ }
+
+ internal void OnNetworkDestroy()
+ {
+ for (int i = 0; networkBehavioursCache != null && i < networkBehavioursCache.Length; i++)
+ {
+ NetworkBehaviour comp = networkBehavioursCache[i];
+ comp.OnNetworkDestroy();
+ }
+ m_IsServer = false;
+ }
+
+ internal void ClearObservers()
+ {
+ if (observers != null)
+ {
+ foreach (NetworkConnection conn in observers.Values)
+ {
+ conn.RemoveFromVisList(this, true);
+ }
+ observers.Clear();
+ }
+ }
+
+ internal void AddObserver(NetworkConnection conn)
+ {
+ if (observers == null)
+ {
+ Debug.LogError("AddObserver for " + gameObject + " observer list is null");
+ return;
+ }
+
+ if (observers.ContainsKey(conn.connectionId))
+ {
+ // if we try to add a connectionId that was already added, then
+ // we may have generated one that was already in use.
+ return;
+ }
+
+ if (LogFilter.Debug) Debug.Log("Added observer " + conn.address + " added for " + gameObject);
+
+ observers[conn.connectionId] = conn;
+ conn.AddToVisList(this);
+ }
+
+ static readonly HashSet newObservers = new HashSet();
+
+ ///
+ /// This causes the set of players that can see this object to be rebuild. The OnRebuildObservers callback function will be invoked on each NetworkBehaviour.
+ ///
+ /// True if this is the first time.
+ public void RebuildObservers(bool initialize)
+ {
+ if (observers == null)
+ return;
+
+ bool changed = false;
+ bool result = false;
+
+ newObservers.Clear();
+
+ // call OnRebuildObservers function in components
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ result |= comp.OnRebuildObservers(newObservers, initialize);
+ }
+
+ // if player connection: ensure player always see himself no matter what.
+ // -> fixes https://github.com/vis2k/Mirror/issues/692 where a
+ // player might teleport out of the ProximityChecker's cast,
+ // losing the own connection as observer.
+ if (connectionToClient != null && connectionToClient.isReady)
+ {
+ newObservers.Add(connectionToClient);
+ }
+
+ // if no component implemented OnRebuildObservers, then add all
+ // connections.
+ if (!result)
+ {
+ if (initialize)
+ {
+ foreach (NetworkConnection conn in NetworkServer.connections.Values)
+ {
+ if (conn.isReady)
+ AddObserver(conn);
+ }
+
+ if (NetworkServer.localConnection != null && NetworkServer.localConnection.isReady)
+ {
+ AddObserver(NetworkServer.localConnection);
+ }
+ }
+ return;
+ }
+
+ // apply changes from rebuild
+ foreach (NetworkConnection conn in newObservers)
+ {
+ if (conn == null)
+ {
+ continue;
+ }
+
+ if (!conn.isReady)
+ {
+ if (LogFilter.Debug) Debug.Log("Observer is not ready for " + gameObject + " " + conn);
+ continue;
+ }
+
+ if (initialize || !observers.ContainsKey(conn.connectionId))
+ {
+ // new observer
+ conn.AddToVisList(this);
+ if (LogFilter.Debug) Debug.Log("New Observer for " + gameObject + " " + conn);
+ changed = true;
+ }
+ }
+
+ foreach (NetworkConnection conn in observers.Values)
+ {
+ if (!newObservers.Contains(conn))
+ {
+ // removed observer
+ conn.RemoveFromVisList(this, false);
+ if (LogFilter.Debug) Debug.Log("Removed Observer for " + gameObject + " " + conn);
+ changed = true;
+ }
+ }
+
+ // special case for local client.
+ if (initialize)
+ {
+ if (!newObservers.Contains(NetworkServer.localConnection))
+ {
+ OnSetLocalVisibility(false);
+ }
+ }
+
+ if (changed)
+ {
+ observers.Clear();
+ foreach (NetworkConnection conn in newObservers)
+ {
+ if (conn.isReady)
+ observers.Add(conn.connectionId, conn);
+ }
+ }
+ }
+
+ ///
+ /// Removes ownership for an object for a client by its connection.
+ /// This applies to objects that had authority set by AssignClientAuthority, or NetworkServer.SpawnWithClientAuthority. Authority cannot be removed for player objects.
+ ///
+ /// The connection of the client to remove authority for.
+ /// True if authority is removed.
+ public bool RemoveClientAuthority(NetworkConnection conn)
+ {
+ if (!isServer)
+ {
+ Debug.LogError("RemoveClientAuthority can only be call on the server for spawned objects.");
+ return false;
+ }
+
+ if (connectionToClient != null)
+ {
+ Debug.LogError("RemoveClientAuthority cannot remove authority for a player object");
+ return false;
+ }
+
+ if (clientAuthorityOwner == null)
+ {
+ Debug.LogError("RemoveClientAuthority for " + gameObject + " has no clientAuthority owner.");
+ return false;
+ }
+
+ if (clientAuthorityOwner != conn)
+ {
+ Debug.LogError("RemoveClientAuthority for " + gameObject + " has different owner.");
+ return false;
+ }
+
+ clientAuthorityOwner.RemoveOwnedObject(this);
+ clientAuthorityOwner = null;
+
+ // server now has authority (this is only called on server)
+ ForceAuthority(true);
+
+ // send msg to that client
+ ClientAuthorityMessage msg = new ClientAuthorityMessage
+ {
+ netId = netId,
+ authority = false
+ };
+ conn.Send(msg);
+
+ clientAuthorityCallback?.Invoke(conn, this, false);
+ return true;
+ }
+
+ ///
+ /// Assign control of an object to a client via the client's NetworkConnection.
+ /// This causes hasAuthority to be set on the client that owns the object, and NetworkBehaviour.OnStartAuthority will be called on that client. This object then will be in the NetworkConnection.clientOwnedObjects list for the connection.
+ /// Authority can be removed with RemoveClientAuthority. Only one client can own an object at any time. Only NetworkIdentities with localPlayerAuthority set can have client authority assigned. This does not need to be called for player objects, as their authority is setup automatically.
+ ///
+ /// The connection of the client to assign authority to.
+ /// True if authority was assigned.
+ public bool AssignClientAuthority(NetworkConnection conn)
+ {
+ if (!isServer)
+ {
+ Debug.LogError("AssignClientAuthority can only be called on the server for spawned objects.");
+ return false;
+ }
+ if (!localPlayerAuthority)
+ {
+ Debug.LogError("AssignClientAuthority can only be used for NetworkIdentity components with LocalPlayerAuthority set.");
+ return false;
+ }
+
+ if (clientAuthorityOwner != null && conn != clientAuthorityOwner)
+ {
+ Debug.LogError("AssignClientAuthority for " + gameObject + " already has an owner. Use RemoveClientAuthority() first.");
+ return false;
+ }
+
+ if (conn == null)
+ {
+ Debug.LogError("AssignClientAuthority for " + gameObject + " owner cannot be null. Use RemoveClientAuthority() instead.");
+ return false;
+ }
+
+ clientAuthorityOwner = conn;
+ clientAuthorityOwner.AddOwnedObject(this);
+
+ // server no longer has authority (this is called on server). Note that local client could re-acquire authority below
+ ForceAuthority(false);
+
+ // send msg to that client
+ ClientAuthorityMessage msg = new ClientAuthorityMessage
+ {
+ netId = netId,
+ authority = true
+ };
+ conn.Send(msg);
+
+ clientAuthorityCallback?.Invoke(conn, this, true);
+ return true;
+ }
+
+ // marks the identity for future reset, this is because we cant reset the identity during destroy
+ // as people might want to be able to read the members inside OnDestroy(), and we have no way
+ // of invoking reset after OnDestroy is called.
+ internal void MarkForReset() => m_Reset = true;
+
+ // if we have marked an identity for reset we do the actual reset.
+ internal void Reset()
+ {
+ if (!m_Reset)
+ return;
+
+ m_Reset = false;
+ m_IsServer = false;
+ isClient = false;
+ hasAuthority = false;
+
+ netId = 0;
+ isLocalPlayer = false;
+ connectionToServer = null;
+ connectionToClient = null;
+ networkBehavioursCache = null;
+
+ ClearObservers();
+ clientAuthorityOwner = null;
+ }
+
+ // MirrorUpdate is a hot path. Caching the vars msg is really worth it to
+ // avoid large amounts of allocations.
+ static UpdateVarsMessage varsMessage = new UpdateVarsMessage();
+
+ // invoked by NetworkServer during Update()
+ internal void MirrorUpdate()
+ {
+ if (observers != null && observers.Count > 0)
+ {
+ // one writer for owner, one for observers
+ NetworkWriter ownerWriter = NetworkWriterPool.GetWriter();
+ NetworkWriter observersWriter = NetworkWriterPool.GetWriter();
+
+ // serialize all the dirty components and send (if any were dirty)
+ OnSerializeAllSafely(false, ownerWriter, out int ownerWritten, observersWriter, out int observersWritten);
+ if (ownerWritten > 0 || observersWritten > 0)
+ {
+ // populate cached UpdateVarsMessage and send
+ varsMessage.netId = netId;
+
+ // send ownerWriter to owner
+ // (only if we serialized anything for owner)
+ // (only if there is a connection (e.g. if not a monster),
+ // and if connection is ready because we use SendToReady
+ // below too)
+ if (ownerWritten > 0)
+ {
+ varsMessage.payload = ownerWriter.ToArraySegment();
+ if (connectionToClient != null && connectionToClient.isReady)
+ NetworkServer.SendToClientOfPlayer(this, varsMessage);
+ }
+
+ // send observersWriter to everyone but owner
+ // (only if we serialized anything for observers)
+ if (observersWritten > 0)
+ {
+ varsMessage.payload = observersWriter.ToArraySegment();
+ NetworkServer.SendToReady(this, varsMessage, false);
+ }
+
+ // only clear bits if we sent something
+ ClearDirtyBits();
+ }
+ NetworkWriterPool.Recycle(ownerWriter);
+ NetworkWriterPool.Recycle(observersWriter);
+ }
+ else
+ {
+ ClearDirtyBits();
+ }
+ }
+
+ private void ClearDirtyBits()
+ {
+ foreach (NetworkBehaviour comp in NetworkBehaviours)
+ {
+ comp.ClearAllDirtyBits();
+ }
+ }
+ }
+}
diff --git a/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs.meta b/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs.meta
new file mode 100644
index 0000000..85a8007
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkIdentity.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 9b91ecbcc199f4492b9a91e820070131
+MonoImporter:
+ externalObjects: {}
+ serializedVersion: 2
+ defaultReferences: []
+ executionOrder: 0
+ icon: {instanceID: 0}
+ userData:
+ assetBundleName:
+ assetBundleVariant:
diff --git a/Assets/Packages/Mirror/Runtime/NetworkManager.cs b/Assets/Packages/Mirror/Runtime/NetworkManager.cs
new file mode 100644
index 0000000..53e4534
--- /dev/null
+++ b/Assets/Packages/Mirror/Runtime/NetworkManager.cs
@@ -0,0 +1,1102 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.Rendering;
+using UnityEngine.SceneManagement;
+using UnityEngine.Serialization;
+
+namespace Mirror
+{
+ ///
+ /// Enumeration of methods of where to spawn player objects in multiplayer games.
+ ///
+ public enum PlayerSpawnMethod
+ {
+ Random,
+ RoundRobin
+ }
+
+ [AddComponentMenu("Network/NetworkManager")]
+ [HelpURL("https://mirror-networking.com/xmldocs/articles/Components/NetworkManager.html")]
+ public class NetworkManager : MonoBehaviour
+ {
+ [Header("Configuration")]
+
+ ///
+ /// A flag to control whether the NetworkManager object is destroyed when the scene changes.
+ /// This should be set if your game has a single NetworkManager that exists for the lifetime of the process. If there is a NetworkManager in each scene, then this should not be set.
+ ///
+ [FormerlySerializedAs("m_DontDestroyOnLoad")]
+ public bool dontDestroyOnLoad = true;
+
+ ///
+ /// Controls whether the program runs when it is in the background.
+ /// This is required when multiple instances of a program using networking are running on the same machine, such as when testing using localhost. But this is not recommended when deploying to mobile platforms.
+ ///
+ [FormerlySerializedAs("m_RunInBackground")]
+ public bool runInBackground = true;
+
+ ///
+ /// Automatically invoke StartServer()
+ /// If the application is a Server Build or run with the -batchMode command line arguement, StartServer is automatically invoked.
+ ///
+ public bool startOnHeadless = true;
+
+ ///
+ /// Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.
+ ///
+ [Tooltip("Server Update frequency, per second. Use around 60Hz for fast paced games like Counter-Strike to minimize latency. Use around 30Hz for games like WoW to minimize computations. Use around 1-10Hz for slow paced games like EVE.")]
+ public int serverTickRate = 30;
+
+ ///
+ /// Enables verbose debug messages in the console
+ ///
+ [FormerlySerializedAs("m_ShowDebugMessages")]
+ public bool showDebugMessages;
+
+ ///
+ /// The scene to switch to when offline.
+ /// Setting this makes the NetworkManager do scene management. This scene will be switched to when a network session is completed - such as a client disconnect, or a server shutdown.
+ ///
+ [Scene]
+ [FormerlySerializedAs("m_OfflineScene")]
+ public string offlineScene = "";
+
+ ///
+ /// The scene to switch to when online.
+ /// Setting this makes the NetworkManager do scene management. This scene will be switched to when a network session is started - such as a client connect, or a server listen.
+ ///
+ [Scene]
+ [FormerlySerializedAs("m_OnlineScene")]
+ public string onlineScene = "";
+
+ [Header("Network Info")]
+
+ // transport layer
+ [SerializeField]
+ protected Transport transport;
+
+ ///
+ /// The network address currently in use.
+ /// For clients, this is the address of the server that is connected to. For servers, this is the local address.
+ ///
+ [FormerlySerializedAs("m_NetworkAddress")]
+ public string networkAddress = "localhost";
+
+ ///
+ /// The maximum number of concurrent network connections to support.
+ /// This effects the memory usage of the network layer.
+ ///
+ [FormerlySerializedAs("m_MaxConnections")]
+ public int maxConnections = 4;
+
+ [Header("Spawn Info")]
+
+ ///
+ /// The default prefab to be used to create player objects on the server.
+ /// Player objects are created in the default handler for AddPlayer() on the server. Implementing OnServerAddPlayer overrides this behaviour.
+ ///
+ [FormerlySerializedAs("m_PlayerPrefab")]
+ public GameObject playerPrefab;
+
+ ///
+ /// A flag to control whether or not player objects are automatically created on connect, and on scene change.
+ ///
+ [FormerlySerializedAs("m_AutoCreatePlayer")]
+ public bool autoCreatePlayer = true;
+
+ ///
+ /// The current method of spawning players used by the NetworkManager.
+ ///
+ [FormerlySerializedAs("m_PlayerSpawnMethod")]
+ public PlayerSpawnMethod playerSpawnMethod;
+
+ ///
+ /// List of prefabs that will be registered with the spawning system.
+ /// For each of these prefabs, ClientManager.RegisterPrefab() will be automatically invoke.
+ ///
+ [FormerlySerializedAs("m_SpawnPrefabs"), HideInInspector]
+ public List spawnPrefabs = new List();
+
+ ///
+ /// List of transforms populted by NetworkStartPosition components found in the scene.
+ ///
+ public static List startPositions = new List();
+
+ ///
+ /// This is true if the client loaded a new scene when connecting to the server.
+ /// This is set before OnClientConnect is called, so it can be checked there to perform different logic if a scene load occurred.
+ ///
+ [NonSerialized]
+ public bool clientLoadedScene;
+
+ ///
+ /// Number of active player objects across all connections on the server.
+ /// This is only valid on the host / server.
+ ///
+ public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.playerController != null);
+
+ ///
+ /// The name of the current network scene.
+ ///
+ ///
+ /// This is populated if the NetworkManager is doing scene management. This should not be changed directly. Calls to ServerChangeScene() cause this to change. New clients that connect to a server will automatically load this scene.
+ /// This is used to make sure that all scene changes are initialized by Mirror.
+ /// Loading a scene manually wont set networkSceneName, so Mirror would still load it again on start.
+ ///
+ public static string networkSceneName = "";
+
+ ///
+ /// True if the server or client is started and running
+ /// This is set True in StartServer / StartClient, and set False in StopServer / StopClient
+ ///
+ [NonSerialized]
+ public bool isNetworkActive;
+
+ ///
+ /// Obsolete: Use directly
+ /// For example, use NetworkClient.Send(message) instead of NetworkManager.client.Send(message)
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use NetworkClient directly, it will be made static soon. For example, use NetworkClient.Send(message) instead of NetworkManager.client.Send(message)")]
+ public NetworkClient client => NetworkClient.singleton;
+
+ static int startPositionIndex;
+
+ ///
+ /// NetworkManager singleton
+ ///
+ public static NetworkManager singleton;
+
+ static UnityEngine.AsyncOperation loadingSceneAsync;
+ static NetworkConnection clientReadyConnection;
+
+ ///
+ /// virtual so that inheriting classes' Awake() can call base.Awake() too
+ ///
+ public virtual void Awake()
+ {
+ Debug.Log("Thank you for using Mirror! https://mirror-networking.com");
+
+ // Set the networkSceneName to prevent a scene reload
+ // if client connection to server fails.
+ networkSceneName = offlineScene;
+
+ InitializeSingleton();
+
+ // setup OnSceneLoaded callback
+ SceneManager.sceneLoaded += OnSceneLoaded;
+ }
+
+ ///
+ /// headless mode detection
+ ///
+ public static bool isHeadless => SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null;
+
+ ///
+ /// Obsolete: Use instead.
+ /// This is a static property now. This method will be removed by summer 2019.
+ ///
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never), Obsolete("Use isHeadless instead of IsHeadless()")]
+ public static bool IsHeadless()
+ {
+ return isHeadless;
+ }
+
+ void InitializeSingleton()
+ {
+ if (singleton != null && singleton == this)
+ {
+ return;
+ }
+
+ // do this early
+ LogFilter.Debug = showDebugMessages;
+
+ if (dontDestroyOnLoad)
+ {
+ if (singleton != null)
+ {
+ Debug.LogWarning("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will be destroyed.");
+ Destroy(gameObject);
+ return;
+ }
+ if (LogFilter.Debug) Debug.Log("NetworkManager created singleton (DontDestroyOnLoad)");
+ singleton = this;
+ if (Application.isPlaying) DontDestroyOnLoad(gameObject);
+ }
+ else
+ {
+ if (LogFilter.Debug) Debug.Log("NetworkManager created singleton (ForScene)");
+ singleton = this;
+ }
+
+ // set active transport AFTER setting singleton.
+ // so only if we didn't destroy ourselves.
+ Transport.activeTransport = transport;
+ }
+
+ ///
+ /// virtual so that inheriting classes' Start() can call base.Start() too
+ ///
+ public virtual void Start()
+ {
+ // headless mode? then start the server
+ // can't do this in Awake because Awake is for initialization.
+ // some transports might not be ready until Start.
+ //
+ // (tick rate is applied in StartServer!)
+ if (isHeadless && startOnHeadless)
+ {
+ StartServer();
+ }
+ }
+
+ // support additive scene loads:
+ // NetworkScenePostProcess disables all scene objects on load, and
+ // * NetworkServer.SpawnObjects enables them again on the server when
+ // calling OnStartServer
+ // * ClientScene.PrepareToSpawnSceneObjects enables them again on the
+ // client after the server sends ObjectSpawnStartedMessage to client
+ // in SpawnObserversForConnection. this is only called when the
+ // client joins, so we need to rebuild scene objects manually again
+ // TODO merge this with FinishLoadScene()?
+ void OnSceneLoaded(Scene scene, LoadSceneMode mode)
+ {
+ if (mode == LoadSceneMode.Additive)
+ {
+ if (NetworkServer.active)
+ {
+ // TODO only respawn the server objects from that scene later!
+ NetworkServer.SpawnObjects();
+ Debug.Log("Respawned Server objects after additive scene load: " + scene.name);
+ }
+ if (NetworkClient.active)
+ {
+ ClientScene.PrepareToSpawnSceneObjects();
+ Debug.Log("Rebuild Client spawnableObjects after additive scene load: " + scene.name);
+ }
+ }
+ }
+
+ // NetworkIdentity.UNetStaticUpdate is called from UnityEngine while LLAPI network is active.
+ // If we want TCP then we need to call it manually. Probably best from NetworkManager, although this means that we can't use NetworkServer/NetworkClient without a NetworkManager invoking Update anymore.
+ ///
+ /// virtual so that inheriting classes' LateUpdate() can call base.LateUpdate() too
+ ///
+ public virtual void LateUpdate()
+ {
+ // call it while the NetworkManager exists.
+ // -> we don't only call while Client/Server.Connected, because then we would stop if disconnected and the
+ // NetworkClient wouldn't receive the last Disconnect event, result in all kinds of issues
+ NetworkServer.Update();
+ NetworkClient.Update();
+ UpdateScene();
+ }
+
+ ///
+ /// called when quitting the application by closing the window / pressing stop in the editor
+ /// virtual so that inheriting classes' OnApplicationQuit() can call base.OnApplicationQuit() too
+ ///
+ public virtual void OnApplicationQuit()
+ {
+ // stop client first
+ // (we want to send the quit packet to the server instead of waiting
+ // for a timeout)
+ if (NetworkClient.isConnected)
+ {
+ StopClient();
+ print("OnApplicationQuit: stopped client");
+ }
+
+ // stop server after stopping client (for proper host mode stopping)
+ if (NetworkServer.active)
+ {
+ StopServer();
+ print("OnApplicationQuit: stopped server");
+ }
+
+ // stop transport (e.g. to shut down threads)
+ // (when pressing Stop in the Editor, Unity keeps threads alive
+ // until we press Start again. so if Transports use threads, we
+ // really want them to end now and not after next start)
+ Transport.activeTransport.Shutdown();
+ }
+
+ ///
+ /// virtual so that inheriting classes' OnValidate() can call base.OnValidate() too
+ ///
+ public virtual void OnValidate()
+ {
+ // add transport if there is none yet. makes upgrading easier.
+ if (transport == null)
+ {
+ // was a transport added yet? if not, add one
+ transport = GetComponent();
+ if (transport == null)
+ {
+ transport = gameObject.AddComponent();
+ Debug.Log("NetworkManager: added default Transport because there was none yet.");
+ }
+#if UNITY_EDITOR
+ UnityEditor.EditorUtility.SetDirty(gameObject);
+#endif
+ }
+
+ maxConnections = Mathf.Max(maxConnections, 0); // always >= 0
+
+ if (playerPrefab != null && playerPrefab.GetComponent() == null)
+ {
+ Debug.LogError("NetworkManager - playerPrefab must have a NetworkIdentity.");
+ playerPrefab = null;
+ }
+ }
+
+ void RegisterServerMessages()
+ {
+ NetworkServer.RegisterHandler(OnServerConnectInternal);
+ NetworkServer.RegisterHandler(OnServerDisconnectInternal);
+ NetworkServer.RegisterHandler(OnServerReadyMessageInternal);
+ NetworkServer.RegisterHandler(OnServerAddPlayerInternal);
+ NetworkServer.RegisterHandler(OnServerRemovePlayerMessageInternal);
+ NetworkServer.RegisterHandler(OnServerErrorInternal);
+ }
+
+ ///
+ /// Set the frame rate for a headless server.
+ /// Override if you wish to disable the behavior or set your own tick rate.
+ ///
+ public virtual void ConfigureServerFrameRate()
+ {
+ // set a fixed tick rate instead of updating as often as possible
+ // * if not in Editor (it doesn't work in the Editor)
+ // * if not in Host mode
+#if !UNITY_EDITOR
+ if (!NetworkClient.active && isHeadless)
+ {
+ Application.targetFrameRate = serverTickRate;
+ Debug.Log("Server Tick Rate set to: " + Application.targetFrameRate + " Hz.");
+ }
+#endif
+ }
+
+ ///
+ /// This starts a new server.
+ /// This uses the networkPort property as the listen port.
+ ///
+ ///
+ public bool StartServer()
+ {
+ InitializeSingleton();
+
+ if (runInBackground)
+ Application.runInBackground = true;
+
+ ConfigureServerFrameRate();
+
+ if (!NetworkServer.Listen(maxConnections))
+ {
+ Debug.LogError("StartServer listen failed.");
+ return false;
+ }
+
+ // call OnStartServer AFTER Listen, so that NetworkServer.active is
+ // true and we can call NetworkServer.Spawn in OnStartServer
+ // overrides.
+ // (useful for loading & spawning stuff from database etc.)
+ //
+ // note: there is no risk of someone connecting after Listen() and
+ // before OnStartServer() because this all runs in one thread
+ // and we don't start processing connects until Update.
+ OnStartServer();
+
+ // this must be after Listen(), since that registers the default message handlers
+ RegisterServerMessages();
+
+ if (LogFilter.Debug) Debug.Log("NetworkManager StartServer");
+ isNetworkActive = true;
+
+ // Only change scene if the requested online scene is not blank, and is not already loaded
+ string loadedSceneName = SceneManager.GetActiveScene().name;
+ if (!string.IsNullOrEmpty(onlineScene) && onlineScene != loadedSceneName && onlineScene != offlineScene)
+ {
+ ServerChangeScene(onlineScene);
+ }
+ else
+ {
+ NetworkServer.SpawnObjects();
+ }
+ return true;
+ }
+
+ void RegisterClientMessages()
+ {
+ NetworkClient.RegisterHandler(OnClientConnectInternal);
+ NetworkClient.RegisterHandler(OnClientDisconnectInternal);
+ NetworkClient.RegisterHandler(OnClientNotReadyMessageInternal);
+ NetworkClient.RegisterHandler(OnClientErrorInternal);
+ NetworkClient.RegisterHandler