diff --git a/.releaserc.yml b/.releaserc.yml new file mode 100644 index 000000000..08b0ce562 --- /dev/null +++ b/.releaserc.yml @@ -0,0 +1,13 @@ +{ + "plugins": [ + ["@semantic-release/commit-analyzer", { + "preset": "angular", + "releaseRules": [ + {"type": "breaking", "release": "major"}, + {"type": "feature", "release": "minor"}, + ] + }], + '@semantic-release/release-notes-generator', + '@semantic-release/github' + ] +} \ No newline at end of file diff --git a/Assets/Mirror/Components/NetworkAnimator.cs b/Assets/Mirror/Components/NetworkAnimator.cs index 0c8073b0d..450c12553 100644 --- a/Assets/Mirror/Components/NetworkAnimator.cs +++ b/Assets/Mirror/Components/NetworkAnimator.cs @@ -1,4 +1,3 @@ -using System; using UnityEngine; namespace Mirror @@ -6,18 +5,21 @@ namespace Mirror [DisallowMultipleComponent] [AddComponentMenu("Network/NetworkAnimator")] [RequireComponent(typeof(NetworkIdentity))] - [RequireComponent(typeof(Animator))] [HelpURL("https://vis2k.github.io/Mirror/Components/NetworkAnimator")] public class NetworkAnimator : NetworkBehaviour { // configuration - [SerializeField] Animator m_Animator; - [SerializeField] uint m_ParameterSendBits; + [SerializeField] Animator m_Animator; + [SerializeField] uint m_ParameterSendBits; + // Note: not an object[] array because otherwise initialization is real annoying + int[] lastIntParameters; + float[] lastFloatParameters; + bool[] lastBoolParameters; // properties public Animator animator { - get { return m_Animator; } + get => m_Animator; set { m_Animator = value; @@ -42,9 +44,9 @@ public bool GetParameterAutoSend(int index) return (m_ParameterSendBits & (uint)(1 << index)) != 0; } - int m_AnimationHash; - int m_TransitionHash; - float m_SendTimer; + int m_AnimationHash; + int m_TransitionHash; + float m_SendTimer; bool sendMessagesAllowed { @@ -135,9 +137,10 @@ void CheckSendRate() m_SendTimer = Time.time + syncInterval; NetworkWriter writer = new NetworkWriter(); - WriteParameters(writer, true); - - SendAnimationParametersMessage(writer.ToArray()); + if (WriteParameters(writer, true)) + { + SendAnimationParametersMessage(writer.ToArray()); + } } } @@ -194,12 +197,20 @@ internal void HandleAnimTriggerMsg(int hash) m_Animator.SetTrigger(hash); } - void WriteParameters(NetworkWriter writer, bool autoSend) + bool WriteParameters(NetworkWriter writer, bool autoSend) { // 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 AnimatorControllerParameter[] parameters = m_Animator.parameters; + if (lastIntParameters == null) lastIntParameters = new int[parameters.Length]; + if (lastFloatParameters == null) lastFloatParameters = new float[parameters.Length]; + if (lastBoolParameters == null) lastBoolParameters = new bool[parameters.Length]; + uint dirtyBits = 0; + // Save the position in the writer where to insert the dirty bits + int dirtyBitsPosition = writer.Position; + // Reserve the space for the bits + writer.Write(dirtyBits); for (int i = 0; i < parameters.Length; i++) { if (autoSend && !GetParameterAutoSend(i)) @@ -208,17 +219,43 @@ void WriteParameters(NetworkWriter writer, bool autoSend) AnimatorControllerParameter par = parameters[i]; if (par.type == AnimatorControllerParameterType.Int) { - writer.WritePackedUInt32((uint)m_Animator.GetInteger(par.nameHash)); + int newIntValue = m_Animator.GetInteger(par.nameHash); + if (newIntValue != lastIntParameters[i]) + { + writer.WritePackedUInt32((uint) newIntValue); + dirtyBits |= 1u << i; + lastIntParameters[i] = newIntValue; + } } else if (par.type == AnimatorControllerParameterType.Float) { - writer.Write(m_Animator.GetFloat(par.nameHash)); + float newFloatValue = m_Animator.GetFloat(par.nameHash); + if (Mathf.Abs(newFloatValue - lastFloatParameters[i]) > 0.001f) + { + writer.Write(newFloatValue); + dirtyBits |= 1u << i; + lastFloatParameters[i] = newFloatValue; + } } else if (par.type == AnimatorControllerParameterType.Bool) { - writer.Write(m_Animator.GetBool(par.nameHash)); + bool newBoolValue = m_Animator.GetBool(par.nameHash); + if (newBoolValue != lastBoolParameters[i]) + { + writer.Write(newBoolValue); + dirtyBits |= 1u << i; + lastBoolParameters[i] = newBoolValue; + } } } + // Save the position we were at to return to after writing dirtyBits + int messageEndPosition = writer.Position; + // Write the dirty bits into the reserved position + writer.Position = dirtyBitsPosition; + writer.Write(dirtyBits); + // Return to the end position, so that serialization includes parameter data. + writer.Position = messageEndPosition; + return dirtyBits != 0; } void ReadParameters(NetworkReader reader, bool autoSend) @@ -227,16 +264,19 @@ void ReadParameters(NetworkReader reader, bool autoSend) // a new parameter array every time it is accessed so we should avoid doing it in a loop AnimatorControllerParameter[] parameters = m_Animator.parameters; + uint dirtyBits = reader.ReadUInt32(); for (int i = 0; i < parameters.Length; i++) { if (autoSend && !GetParameterAutoSend(i)) continue; + if ((dirtyBits & (1 << i)) == 0) + continue; AnimatorControllerParameter par = parameters[i]; if (par.type == AnimatorControllerParameterType.Int) { - int newValue = (int)reader.ReadPackedUInt32(); - m_Animator.SetInteger(par.nameHash, newValue); + int newIntValue = (int)reader.ReadPackedUInt32(); + m_Animator.SetInteger(par.nameHash, newIntValue); } else if (par.type == AnimatorControllerParameterType.Float) { @@ -293,7 +333,7 @@ public void SetTrigger(int hash) { if (hasAuthority && localPlayerAuthority) { - if (NetworkClient.allClients.Count > 0 && ClientScene.readyConnection != null) + if (NetworkClient.singleton != null && ClientScene.readyConnection != null) { CmdOnAnimationTriggerServerMessage(hash); } @@ -306,12 +346,11 @@ public void SetTrigger(int hash) } } - // ------------------ server message handlers ------------------- - + #region server message handlers [Command] void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, byte[] parameters) { - if (LogFilter.Debug) { Debug.Log("OnAnimationMessage for netId=" + netId); } + if (LogFilter.Debug) Debug.Log("OnAnimationMessage for netId=" + netId); // handle and broadcast HandleAnimMsg(stateHash, normalizedTime, new NetworkReader(parameters)); @@ -333,8 +372,9 @@ void CmdOnAnimationTriggerServerMessage(int hash) HandleAnimTriggerMsg(hash); RpcOnAnimationTriggerClientMessage(hash); } + #endregion - // ------------------ client message handlers ------------------- + #region client message handlers [ClientRpc] void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, byte[] parameters) { @@ -353,5 +393,6 @@ void RpcOnAnimationTriggerClientMessage(int hash) { HandleAnimTriggerMsg(hash); } + #endregion } } diff --git a/Assets/Mirror/Components/NetworkLobbyManager.cs b/Assets/Mirror/Components/NetworkLobbyManager.cs index dd0b79a45..666e621ef 100644 --- a/Assets/Mirror/Components/NetworkLobbyManager.cs +++ b/Assets/Mirror/Components/NetworkLobbyManager.cs @@ -10,7 +10,7 @@ namespace Mirror [HelpURL("https://vis2k.github.io/Mirror/Components/NetworkLobbyManager")] public class NetworkLobbyManager : NetworkManager { - struct PendingPlayer + public struct PendingPlayer { public NetworkConnection conn; public GameObject lobbyPlayer; @@ -29,8 +29,8 @@ struct PendingPlayer public string GameplayScene; // runtime data - [FormerlySerializedAs("m_PendingPlayers")] List pendingPlayers = new List(); - List lobbySlots = new List(); + [FormerlySerializedAs("m_PendingPlayers")] public List pendingPlayers = new List(); + public List lobbySlots = new List(); public bool allPlayersReady; @@ -39,7 +39,7 @@ public override void OnValidate() // always >= 0 maxConnections = Mathf.Max(maxConnections, 0); - // always <= maxConnections + // always <= maxConnections minPlayers = Mathf.Min(minPlayers, maxConnections); // always >= 0 @@ -195,7 +195,13 @@ public override void OnServerDisconnect(NetworkConnection conn) OnLobbyServerDisconnect(conn); } + [System.Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")] public override void OnServerAddPlayer(NetworkConnection conn) + { + OnServerAddPlayer(conn, null); + } + + public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) { if (SceneManager.GetActiveScene().name != LobbyScene) return; @@ -203,7 +209,7 @@ public override void OnServerAddPlayer(NetworkConnection conn) allPlayersReady = false; - if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager:OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name); + if (LogFilter.Debug) Debug.LogFormat("NetworkLobbyManager.OnServerAddPlayer playerPrefab:{0}", lobbyPlayerPrefab.name); GameObject newLobbyGameObject = OnLobbyServerCreateLobbyPlayer(conn); if (newLobbyGameObject == null) @@ -400,17 +406,17 @@ public override void OnClientSceneChanged(NetworkConnection conn) #region lobby server virtuals - public virtual void OnLobbyStartHost() { } + public virtual void OnLobbyStartHost() {} - public virtual void OnLobbyStopHost() { } + public virtual void OnLobbyStopHost() {} - public virtual void OnLobbyStartServer() { } + public virtual void OnLobbyStartServer() {} - public virtual void OnLobbyServerConnect(NetworkConnection conn) { } + public virtual void OnLobbyServerConnect(NetworkConnection conn) {} - public virtual void OnLobbyServerDisconnect(NetworkConnection conn) { } + public virtual void OnLobbyServerDisconnect(NetworkConnection conn) {} - public virtual void OnLobbyServerSceneChanged(string sceneName) { } + public virtual void OnLobbyServerSceneChanged(string sceneName) {} public virtual GameObject OnLobbyServerCreateLobbyPlayer(NetworkConnection conn) { @@ -438,22 +444,22 @@ public virtual void OnLobbyServerPlayersReady() #region lobby client virtuals - public virtual void OnLobbyClientEnter() { } + public virtual void OnLobbyClientEnter() {} - public virtual void OnLobbyClientExit() { } + public virtual void OnLobbyClientExit() {} - public virtual void OnLobbyClientConnect(NetworkConnection conn) { } + public virtual void OnLobbyClientConnect(NetworkConnection conn) {} - public virtual void OnLobbyClientDisconnect(NetworkConnection conn) { } + public virtual void OnLobbyClientDisconnect(NetworkConnection conn) {} - public virtual void OnLobbyStartClient(NetworkClient lobbyClient) { } + public virtual void OnLobbyStartClient(NetworkClient lobbyClient) {} - public virtual void OnLobbyStopClient() { } + public virtual void OnLobbyStopClient() {} - public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) { } + public virtual void OnLobbyClientSceneChanged(NetworkConnection conn) {} // for users to handle adding a player failed on the server - public virtual void OnLobbyClientAddPlayerFailed() { } + public virtual void OnLobbyClientAddPlayerFailed() {} #endregion diff --git a/Assets/Mirror/Components/NetworkLobbyPlayer.cs b/Assets/Mirror/Components/NetworkLobbyPlayer.cs index 7bc831da1..718f65e8b 100644 --- a/Assets/Mirror/Components/NetworkLobbyPlayer.cs +++ b/Assets/Mirror/Components/NetworkLobbyPlayer.cs @@ -1,4 +1,3 @@ -using System; using UnityEngine; using UnityEngine.SceneManagement; @@ -62,11 +61,11 @@ public void CmdSendLevelLoaded() #region lobby client virtuals - public virtual void OnClientEnterLobby() { } + public virtual void OnClientEnterLobby() {} - public virtual void OnClientExitLobby() { } + public virtual void OnClientExitLobby() {} - public virtual void OnClientReady(bool readyState) { } + public virtual void OnClientReady(bool readyState) {} #endregion @@ -88,7 +87,7 @@ public virtual void OnGUI() GUILayout.BeginArea(new Rect(20f + (Index * 100), 200f, 90f, 130f)); - GUILayout.Label(String.Format("Player [{0}]", Index + 1)); + GUILayout.Label($"Player [{Index + 1}]"); if (ReadyToBegin) GUILayout.Label("Ready"); diff --git a/Assets/Mirror/Runtime/NetworkProximityChecker.cs b/Assets/Mirror/Components/NetworkProximityChecker.cs similarity index 73% rename from Assets/Mirror/Runtime/NetworkProximityChecker.cs rename to Assets/Mirror/Components/NetworkProximityChecker.cs index b7a34e3fd..b8f5643e7 100644 --- a/Assets/Mirror/Runtime/NetworkProximityChecker.cs +++ b/Assets/Mirror/Components/NetworkProximityChecker.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using UnityEngine; @@ -20,7 +19,7 @@ public enum CheckMethod // how often to refresh the list of observers, in seconds [Tooltip("How often (in seconds) that this object should update the set of players that can see it.")] - public float visUpdateInterval = 1.0f; + public float visUpdateInterval = 1; [Tooltip("Which method to use for checking proximity of players.\n\nPhysics3D uses 3D physics to determine proximity.\n\nPhysics2D uses 2D physics to determine proximity.")] public CheckMethod checkMethod = CheckMethod.Physics3D; @@ -30,10 +29,18 @@ public enum CheckMethod // ~0 means 'Everything'. layers are used anyway, might as well expose them to the user. [Tooltip("Select only the Player's layer to avoid unnecessary SphereCasts against the Terrain, etc.")] - public LayerMask castLayers = ~0; + public LayerMask castLayers = ~0; float m_VisUpdateTime; + // 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) @@ -78,10 +85,13 @@ public override bool OnRebuildObservers(HashSet observers, bo { case CheckMethod.Physics3D: { - Collider[] hits = Physics.OverlapSphere(transform.position, visRange, castLayers); - for (int i = 0; i < hits.Length; i++) + // 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 = hits[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(); @@ -96,10 +106,13 @@ public override bool OnRebuildObservers(HashSet observers, bo case CheckMethod.Physics2D: { - Collider2D[] hits = Physics2D.OverlapCircleAll(transform.position, visRange, castLayers); - for (int i = 0; i < hits.Length; i++) + // 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 = hits[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(); diff --git a/Assets/Mirror/Runtime/NetworkProximityChecker.cs.meta b/Assets/Mirror/Components/NetworkProximityChecker.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/NetworkProximityChecker.cs.meta rename to Assets/Mirror/Components/NetworkProximityChecker.cs.meta diff --git a/Assets/Mirror/Components/NetworkTransform.cs b/Assets/Mirror/Components/NetworkTransform.cs index 7e353c927..51166e142 100644 --- a/Assets/Mirror/Components/NetworkTransform.cs +++ b/Assets/Mirror/Components/NetworkTransform.cs @@ -9,4 +9,4 @@ public class NetworkTransform : NetworkTransformBase { protected override Transform targetComponent => transform; } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Components/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransformBase.cs index c3a542b95..e8f9628b2 100644 --- a/Assets/Mirror/Components/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransformBase.cs @@ -16,7 +16,6 @@ // * Only way for smooth movement is to use a fixed movement speed during // interpolation. interpolation over time is never that good. // -using System; using UnityEngine; namespace Mirror @@ -29,7 +28,7 @@ public abstract class NetworkTransformBase : NetworkBehaviour // 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, Some=6, Much=3, Lots=2 Byte")] + [Tooltip("Compresses 16 Byte Quaternion into None=12, Much=3, Lots=2 Byte")] [SerializeField] Compression compressRotation = Compression.Much; public enum Compression { None, Much, Lots }; // easily understandable and funny @@ -77,14 +76,14 @@ static void SerializeIntoWriter(NetworkWriter writer, Vector3 position, Quaterni else if (compressRotation == Compression.Much) { // write 3 byte. scaling [0,360] to [0,255] - writer.Write(Utils.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue)); - writer.Write(Utils.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue)); - writer.Write(Utils.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue)); + writer.Write(FloatBytePacker.ScaleFloatToByte(euler.x, 0, 360, byte.MinValue, byte.MaxValue)); + writer.Write(FloatBytePacker.ScaleFloatToByte(euler.y, 0, 360, byte.MinValue, byte.MaxValue)); + writer.Write(FloatBytePacker.ScaleFloatToByte(euler.z, 0, 360, byte.MinValue, byte.MaxValue)); } else if (compressRotation == Compression.Lots) { // write 2 byte, 5 bits for each float - writer.Write(Utils.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360)); + writer.Write(FloatBytePacker.PackThreeFloatsIntoUShort(euler.x, euler.y, euler.z, 0, 360)); } } @@ -128,19 +127,18 @@ void DeserializeFromReader(NetworkReader reader) else if (compressRotation == Compression.Much) { // read 3 byte. scaling [0,255] to [0,360] - float x = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); - float y = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 0, 360); - float z = Utils.ScaleByteToFloat(reader.ReadByte(), byte.MinValue, byte.MaxValue, 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.rotation = Quaternion.Euler(x, y, z); } else if (compressRotation == Compression.Lots) { // read 2 byte, 5 bits per float - float[] xyz = Utils.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360); + float[] xyz = FloatBytePacker.UnpackUShortIntoThreeFloats(reader.ReadUInt16(), 0, 360); temp.rotation = Quaternion.Euler(xyz[0], xyz[1], xyz[2]); } - // timestamp temp.timeStamp = Time.time; // movement speed: based on how far it moved since last time @@ -153,10 +151,10 @@ void DeserializeFromReader(NetworkReader reader) if (start == null) { start = new DataPoint{ - timeStamp=Time.time - syncInterval, - position=targetComponent.transform.position, - rotation=targetComponent.transform.rotation, - movementSpeed=temp.movementSpeed + timeStamp = Time.time - syncInterval, + position = targetComponent.transform.position, + rotation = targetComponent.transform.rotation, + movementSpeed = temp.movementSpeed }; } // -> second or nth data point? then update previous, but: @@ -298,10 +296,16 @@ bool HasMovedOrRotated() bool rotated = lastRotation != targetComponent.transform.rotation; // save last for next frame to compare - lastPosition = targetComponent.transform.position; - lastRotation = targetComponent.transform.rotation; - - return moved || rotated; + // (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; + if (change) + { + lastPosition = targetComponent.transform.position; + lastRotation = targetComponent.transform.rotation; + } + return change; } // set position carefully depending on the target component diff --git a/Assets/Mirror/Editor/NetworkAnimatorEditor.cs b/Assets/Mirror/Editor/NetworkAnimatorEditor.cs index 250a166c0..46558f326 100644 --- a/Assets/Mirror/Editor/NetworkAnimatorEditor.cs +++ b/Assets/Mirror/Editor/NetworkAnimatorEditor.cs @@ -13,7 +13,7 @@ public class NetworkAnimatorEditor : Editor [NonSerialized] bool m_Initialized; SerializedProperty m_AnimatorProperty; - GUIContent m_AnimatorLabel; + GUIContent m_AnimatorLabel = new GUIContent("Animator", "The Animator component to synchronize."); void Init() { @@ -24,7 +24,6 @@ void Init() m_AnimSync = target as NetworkAnimator; m_AnimatorProperty = serializedObject.FindProperty("m_Animator"); - m_AnimatorLabel = new GUIContent("Animator", "The Animator component to synchronize."); } public override void OnInspectorGUI() @@ -63,7 +62,7 @@ void DrawControls() } bool oldSend = m_AnimSync.GetParameterAutoSend(i); - bool send = EditorGUILayout.Toggle(p.name, oldSend); + bool send = EditorGUILayout.Toggle("Sync " + p.name, oldSend); if (send != oldSend) { m_AnimSync.SetParameterAutoSend(i, send); diff --git a/Assets/Mirror/Editor/NetworkIdentityEditor.cs b/Assets/Mirror/Editor/NetworkIdentityEditor.cs index af250af40..6583dd2fc 100644 --- a/Assets/Mirror/Editor/NetworkIdentityEditor.cs +++ b/Assets/Mirror/Editor/NetworkIdentityEditor.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -12,9 +11,9 @@ public class NetworkIdentityEditor : Editor SerializedProperty m_ServerOnlyProperty; SerializedProperty m_LocalPlayerAuthorityProperty; - GUIContent m_ServerOnlyLabel; - GUIContent m_LocalPlayerAuthorityLabel; - GUIContent m_SpawnLabel; + GUIContent m_ServerOnlyLabel = new GUIContent("Server Only", "True if the object should only exist on the server."); + GUIContent m_LocalPlayerAuthorityLabel = new GUIContent("Local Player Authority", "True if this object will be controlled by a player on a client."); + GUIContent m_SpawnLabel = new GUIContent("Spawn Object", "This causes an unspawned server object to be spawned on clients"); NetworkIdentity m_NetworkIdentity; bool m_Initialized; @@ -31,10 +30,6 @@ void Init() m_ServerOnlyProperty = serializedObject.FindProperty("m_ServerOnly"); m_LocalPlayerAuthorityProperty = serializedObject.FindProperty("m_LocalPlayerAuthority"); - - m_ServerOnlyLabel = new GUIContent("Server Only", "True if the object should only exist on the server."); - m_LocalPlayerAuthorityLabel = new GUIContent("Local Player Authority", "True if this object will be controlled by a player on a client."); - m_SpawnLabel = new GUIContent("Spawn Object", "This causes an unspawned server object to be spawned on clients"); } public override void OnInspectorGUI() diff --git a/Assets/Mirror/Editor/NetworkInformationPreview.cs b/Assets/Mirror/Editor/NetworkInformationPreview.cs index 71fb803a1..997d6347f 100644 --- a/Assets/Mirror/Editor/NetworkInformationPreview.cs +++ b/Assets/Mirror/Editor/NetworkInformationPreview.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using UnityEditor; using UnityEngine; @@ -216,7 +215,7 @@ void GetNetworkInformation(GameObject gameObject) m_Info = new List { GetAssetId(), - GetString("Scene ID", m_Identity.sceneId.ToString()) + GetString("Scene ID", m_Identity.sceneId.ToString("X")) }; if (!Application.isPlaying) @@ -278,4 +277,4 @@ static NetworkIdentityInfo GetBoolean(string name, bool value) return info; } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/NetworkManagerEditor.cs b/Assets/Mirror/Editor/NetworkManagerEditor.cs index 6a8a1443e..0552cfaea 100644 --- a/Assets/Mirror/Editor/NetworkManagerEditor.cs +++ b/Assets/Mirror/Editor/NetworkManagerEditor.cs @@ -1,10 +1,6 @@ -using System; -using System.IO; -using System.Reflection; using UnityEditor; using UnityEditorInternal; using UnityEngine; -using UnityObject = UnityEngine.Object; namespace Mirror { @@ -113,4 +109,4 @@ internal void RemoveButton(ReorderableList list) } } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/NetworkScenePostProcess.cs b/Assets/Mirror/Editor/NetworkScenePostProcess.cs index a246984f5..b5a2c7584 100644 --- a/Assets/Mirror/Editor/NetworkScenePostProcess.cs +++ b/Assets/Mirror/Editor/NetworkScenePostProcess.cs @@ -1,5 +1,3 @@ -using System.Text; -using System.Collections.Generic; using System.Linq; using UnityEditor; using UnityEditor.Callbacks; @@ -10,168 +8,20 @@ namespace Mirror { public class NetworkScenePostProcess : MonoBehaviour { - // persistent sceneId assignment to fix readstring bug that occurs when restarting the editor and - // connecting to a build again. sceneids were then different because FindObjectsOfType's order - // is not guranteed to be the same. - // -> we need something unique and persistent, aka always the same when pressing play/building the first time - // -> Unity has no built in unique id for GameObjects in the scene - - // helper function to figure out a unique, persistent scene id for a GameObject in the hierarchy - // -> Unity's instanceId is unique but not persistent - // -> hashing the whole GameObject is not enough either since a duplicate would have the same hash - // -> we definitely need the transform sibling index in the hierarchy - // -> so we might as well use just that - // -> transforms have children too so we need a list of sibling indices like 0->3->5 - public static List SiblingPathFor(Transform t) - { - List result = new List(); - while (t != null) - { - result.Add(t.GetSiblingIndex()); - t = t.parent; - } - - result.Reverse(); // parent to child instead of child to parent order - return result; - } - - // we need to compare by using the whole sibling list - // comparing the string won't work work because: - // "1->2" - // "20->2" - // would compare '1' vs '2', then '-' vs '0' - // - // tests: - // CompareSiblingPaths(new List(){0}, new List(){0}) => 0 - // CompareSiblingPaths(new List(){0}, new List(){1}) => -1 - // CompareSiblingPaths(new List(){1}, new List(){0}) => 1 - // CompareSiblingPaths(new List(){0,1}, new List(){0,2}) => -1 - // CompareSiblingPaths(new List(){0,2}, new List(){0,1}) => 1 - // CompareSiblingPaths(new List(){1}, new List(){0,1}) => 1 - // CompareSiblingPaths(new List(){1}, new List(){2,1}) => -1 - public static int CompareSiblingPaths(List left, List right) - { - // compare [0], remove it, compare next, etc. - while (left.Count > 0 && right.Count > 0) - { - if (left[0] < right[0]) - { - return -1; - } - else if (left[0] > right[0]) - { - return 1; - } - else - { - // equal, so they are both children of the same transform - // -> which also means that they both must have one more - // entry, so we can remove both without checking size - left.RemoveAt(0); - right.RemoveAt(0); - } - } - - // equal if both were empty or both had the same entry without any - // more children (should never happen in practice) - return 0; - } - - public static int CompareNetworkIdentitySiblingPaths(NetworkIdentity left, NetworkIdentity right) - { - return CompareSiblingPaths(SiblingPathFor(left.transform), SiblingPathFor(right.transform)); - } - - // we might have inactive scenes in the Editor's build settings, which - // aren't actually included in builds. - // so we have to only count the active ones when in Editor, otherwise - // editor and build sceneIds might get out of sync. - public static int GetSceneCount() - { -#if UNITY_EDITOR - return EditorBuildSettings.scenes.Count(scene => scene.enabled); -#else - return SceneManager.sceneCountInBuildSettings; -#endif - } + // helper function to check if a NetworkIdentity is in the active scene + static bool InActiveScene(NetworkIdentity identity) => + identity.gameObject.scene == SceneManager.GetActiveScene(); [PostProcessScene] public static void OnPostProcessScene() { - // vis2k: MISMATCHING SCENEID BUG FIX - // problem: - // * FindObjectsOfType order is not guaranteed. restarting the - // editor results in a different order - // * connecting to a build again would cause UNET to deserialize - // the wrong objects, causing all kinds of weird errors like - // 'ReadString out of range' - // - // solution: - // sort by sibling-index path, e.g. [0,1,2] vs [1] - // this is the only deterministic way to sort a list of objects in - // the scene. - // -> it's the same result every single time, even after restarts - // - // note: there is a reason why we 'sort by' sibling path instead of - // using it as sceneId directly. networkmanager etc. use Dont- - // DestroyOnLoad, which changes the hierarchy: - // - // World: - // NetworkManager - // Player - // - // ..becomes.. - // - // World: - // Player - // DontDestroyOnLoad: - // NetworkManager - // - // so the player's siblingindex would be decreased by one. - // -> this is a problem because when building, OnPostProcessScene - // is called before any dontdestroyonload happens, but when - // entering play mode, it's called after - // -> hence sceneids would differ by one - // - // => but if we only SORT it, then it doesn't matter if one - // inbetween disappeared. as long as no NetworkIdentity used - // DontDestroyOnLoad. - // - // note: assigning a GUID in NetworkIdentity.OnValidate would be way - // cooler, but OnValidate isn't called for other unopened scenes - // when building or pressing play, so the bug would still happen - // there. - // - // note: this can still fail if DontDestroyOnLoad is called for a - // NetworkIdentity - but no one should ever do that anyway. - List identities = FindObjectsOfType().ToList(); - identities.Sort(CompareNetworkIdentitySiblingPaths); - - // sceneId assignments need to work with additive scene loading, so - // it can't always start at 1,2,3,4,..., otherwise there will be - // sceneId duplicates. - // -> we need an offset to start at 1000+1,+2,+3, etc. - // -> the most robust way is to split uint value range by sceneCount - // -> only if more than one scene. otherwise use offset 0 to avoid - // DivisionByZero if no scene in build settings, and to avoid - // different offsets in editor/build if scene wasn't added to - // build settings. - uint offsetPerScene = 0; - if (SceneManager.sceneCountInBuildSettings > 1) - { - offsetPerScene = uint.MaxValue / (uint)GetSceneCount(); - - // make sure that there aren't more sceneIds than offsetPerScene - // -> only if we have multiple scenes. otherwise offset is 0, in - // which case it doesn't matter. - if (identities.Count >= offsetPerScene) - { - Debug.LogWarning(">=" + offsetPerScene + " NetworkIdentities in scene. Additive scene loading will cause duplicate ids."); - } - } - - uint nextSceneId = 1; - foreach (NetworkIdentity identity in identities) + // find all NetworkIdentities in this scene + // => but really only from this 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 + foreach (NetworkIdentity identity in FindObjectsOfType().Where(InActiveScene)) { // if we had a [ConflictComponent] attribute that would be better than this check. // also there is no context about which scene this is in. @@ -182,13 +32,22 @@ public static void OnPostProcessScene() if (identity.isClient || identity.isServer) continue; - uint offset = (uint)identity.gameObject.scene.buildIndex * offsetPerScene; - identity.ForceSceneId(offset + nextSceneId++); - if (LogFilter.Debug) { Debug.Log("PostProcess sceneid assigned: name=" + identity.name + " scene=" + identity.gameObject.scene.name + " sceneid=" + identity.sceneId); } + // valid scene id? then set scene path part + // 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. + // => throwing an exception would only show it for one object + // because this function would return afterwards. + if (identity.sceneId != 0) + { + identity.SetSceneIdSceneHashPartInternal(); + } + 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."); - // disable it AFTER assigning the sceneId. - // -> this way NetworkIdentity.OnDisable adds itself to the - // spawnableObjects dictionary (only if sceneId != 0) + // 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 diff --git a/Assets/Mirror/Editor/PreprocessorDefine.cs b/Assets/Mirror/Editor/PreprocessorDefine.cs index 724ae132a..2cf9b9d5b 100644 --- a/Assets/Mirror/Editor/PreprocessorDefine.cs +++ b/Assets/Mirror/Editor/PreprocessorDefine.cs @@ -1,7 +1,6 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using UnityEditor; -using UnityEngine; namespace Mirror { diff --git a/Assets/Mirror/Editor/SceneDrawer.cs b/Assets/Mirror/Editor/SceneDrawer.cs index 4d64cdea6..b6c04f4de 100644 --- a/Assets/Mirror/Editor/SceneDrawer.cs +++ b/Assets/Mirror/Editor/SceneDrawer.cs @@ -1,4 +1,4 @@ -using UnityEditor; +using UnityEditor; using UnityEngine; namespace Mirror @@ -53,4 +53,4 @@ protected SceneAsset GetSceneObject(string sceneObjectName) return null; } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2.meta b/Assets/Mirror/Editor/Weaver.meta similarity index 77% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2.meta rename to Assets/Mirror/Editor/Weaver.meta index cf26ff7eb..121fbf44b 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2.meta +++ b/Assets/Mirror/Editor/Weaver.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: cad1d2d7f70fb4e509408b6733c359d2 +guid: d9f8e6274119b4ce29e498cfb8aca8a4 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs b/Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs new file mode 100644 index 000000000..dfac8f6ab --- /dev/null +++ b/Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs @@ -0,0 +1,201 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using UnityEditor; +using UnityEditor.Compilation; +using UnityEngine; +using Assembly = System.Reflection.Assembly; + +namespace Mirror.Weaver +{ + public 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() + { + UnityEditor.Compilation.Assembly[] assemblies = CompilationPipeline.GetAssemblies(); + + foreach (UnityEditor.Compilation.Assembly assembly in assemblies) + { + if (assembly.name == MirrorRuntimeAssemblyName) + { + return assembly.outputPath; + } + } + return ""; + } + + // get all dependency directories + static HashSet GetDependencyDirectories(AssemblyName[] dependencies) + { + // Since this assembly is already loaded in the domain this is a + // no-op and returns the already loaded assembly + return new HashSet( + dependencies.Select(dependency => Path.GetDirectoryName(Assembly.Load(dependency).Location)) + ); + } + + // get all non-dynamic assembly directories + static HashSet GetNonDynamicAssemblyDirectories(Assembly[] assemblies) + { + HashSet paths = new HashSet(); + + foreach (Assembly assembly in assemblies) + { + if (!assembly.IsDynamic) + { + // need to check if file exists to avoid potential + // FileNotFoundException in Assembly.Load + string assemblyName = assembly.GetName().Name; + if (File.Exists(assemblyName)) + { + paths.Add(Path.GetDirectoryName(Assembly.Load(assemblyName).Location)); + } + } + } + + return paths; + } + + 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; + } + + // find all assemblies and the currently compiling assembly + Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); + Assembly targetAssembly = assemblies.FirstOrDefault(asm => asm.GetName().Name == Path.GetFileNameWithoutExtension(assemblyPath)); + + // prepare variables + HashSet dependencyPaths = new HashSet(); + + // found this assembly in assemblies? + if (targetAssembly != null) + { + // get all dependencies for the target assembly + AssemblyName[] dependencies = targetAssembly.GetReferencedAssemblies(); + + // does the target assembly depend on Mirror at all? + // otherwise there is nothing to weave anyway. + bool usesMirror = dependencies.Any(dependency => dependency.Name == MirrorRuntimeAssemblyName); + if (!usesMirror) + { + return; + } + + // get all the directories + dependencyPaths = GetDependencyDirectories(dependencies); + } + else + { + // Target assembly not found in current domain, trying to load it to check references + // will lead to trouble in the build pipeline, so lets assume it should go to weaver. + // Add all assemblies in current domain to dependency list since there could be a + // dependency lurking there (there might be generated assemblies so ignore file not found exceptions). + // (can happen in runtime test framework on editor platform and when doing full library reimport) + dependencyPaths = GetNonDynamicAssemblyDirectories(assemblies); + } + + // construct full path to Project/Library/ScriptAssemblies + string projectDirectory = Directory.GetParent(Application.dataPath).ToString(); + string outputDirectory = Path.Combine(projectDirectory, Path.GetDirectoryName(assemblyPath)); + + //if (UnityLogEnabled) Debug.Log("Weaving: " + assemblyPath); // uncomment to easily observe weave targets + if (Program.Process(unityEngineCoreModuleDLL, mirrorRuntimeDll, outputDirectory, 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/Mirror/Runtime/SceneAttribute.cs.meta b/Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta similarity index 83% rename from Assets/Mirror/Runtime/SceneAttribute.cs.meta rename to Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta index e90499928..ed537ab5c 100644 --- a/Assets/Mirror/Runtime/SceneAttribute.cs.meta +++ b/Assets/Mirror/Editor/Weaver/CompilationFinishedHook.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c764a8a81af7e41539c3cdc06aa036be +guid: de2aeb2e8068f421a9a1febe408f7051 MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Weaver/Weaver/Extensions.cs b/Assets/Mirror/Editor/Weaver/Extensions.cs similarity index 99% rename from Weaver/Weaver/Extensions.cs rename to Assets/Mirror/Editor/Weaver/Extensions.cs index 2e5267005..e49354140 100644 --- a/Weaver/Weaver/Extensions.cs +++ b/Assets/Mirror/Editor/Weaver/Extensions.cs @@ -102,4 +102,4 @@ public static bool CanBeResolved(this TypeReference parent) return true; } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Extensions.cs.meta b/Assets/Mirror/Editor/Weaver/Extensions.cs.meta new file mode 100644 index 000000000..78660f908 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Helpers.cs b/Assets/Mirror/Editor/Weaver/Helpers.cs similarity index 96% rename from Weaver/Weaver/Helpers.cs rename to Assets/Mirror/Editor/Weaver/Helpers.cs index e7df12ef8..f214b89b9 100644 --- a/Weaver/Weaver/Helpers.cs +++ b/Assets/Mirror/Editor/Weaver/Helpers.cs @@ -85,16 +85,16 @@ public static string PrettyPrintType(TypeReference type) return type.Name; } - public static ReaderParameters ReaderParameters(string assemblyPath, IEnumerable extraPaths, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string unityUNetDLLPath) + public static ReaderParameters ReaderParameters(string assemblyPath, IEnumerable extraPaths, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath) { - ReaderParameters parameters = new ReaderParameters(); + 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(unityUNetDLLPath)); + helper.AddSearchDirectory(Path.GetDirectoryName(mirrorNetDLLPath)); if (extraPaths != null) { foreach (var path in extraPaths) diff --git a/Assets/Mirror/Editor/Weaver/Helpers.cs.meta b/Assets/Mirror/Editor/Weaver/Helpers.cs.meta new file mode 100644 index 000000000..231f5396f --- /dev/null +++ b/Assets/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/Mirror/Editor/Weaver/Mirror.Weaver.asmdef b/Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef new file mode 100644 index 000000000..512242830 --- /dev/null +++ b/Assets/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/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta b/Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta new file mode 100644 index 000000000..8ab9fe675 --- /dev/null +++ b/Assets/Mirror/Editor/Weaver/Mirror.Weaver.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5710c1f859f1945b6bc9b33cff6b43fc +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Editor/Weaver/Processors.meta b/Assets/Mirror/Editor/Weaver/Processors.meta new file mode 100644 index 000000000..eb719b4f1 --- /dev/null +++ b/Assets/Mirror/Editor/Weaver/Processors.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e538d627280d2471b8c72fdea822ca49 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Weaver/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs similarity index 89% rename from Weaver/Weaver/Processors/CommandProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index 205282909..d1924eb3a 100644 --- a/Weaver/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -12,13 +12,6 @@ public static class CommandProcessor // generates code like: public void CallCmdThrust(float thrusting, int spin) { - Debug.LogError("Call Command function CmdThrust"); - if (!NetworkClient.active) - { - Debug.LogError("Command function CmdThrust called on server."); - return; - } - if (isServer) { // we are ON the server, invoke directly @@ -49,14 +42,12 @@ public static MethodDefinition ProcessCommandCall(TypeDefinition td, MethodDefin NetworkBehaviourProcessor.WriteSetupLocals(cmdWorker); - if (Weaver.generateLogErrors) + if (Weaver.GenerateLogErrors) { cmdWorker.Append(cmdWorker.Create(OpCodes.Ldstr, "Call Command function " + md.Name)); cmdWorker.Append(cmdWorker.Create(OpCodes.Call, Weaver.logErrorReference)); } - NetworkBehaviourProcessor.WriteClientActiveCheck(cmdWorker, md.Name, label, "Command function"); - // local client check Instruction localClientLabel = cmdWorker.Create(OpCodes.Nop); cmdWorker.Append(cmdWorker.Create(OpCodes.Ldarg_0)); @@ -143,15 +134,13 @@ public static bool ProcessMethodsValidateCommand(TypeDefinition td, MethodDefini { if (md.Name.Length > 2 && md.Name.Substring(0, 3) != "Cmd") { - Log.Error("Command function [" + td.FullName + ":" + md.Name + "] doesnt have 'Cmd' prefix"); - Weaver.fail = true; + Weaver.Error("Command function [" + td.FullName + ":" + md.Name + "] doesnt have 'Cmd' prefix"); return false; } if (md.IsStatic) { - Log.Error("Command function [" + td.FullName + ":" + md.Name + "] cant be a static method"); - Weaver.fail = true; + Weaver.Error("Command function [" + td.FullName + ":" + md.Name + "] cant be a static method"); return false; } @@ -160,4 +149,4 @@ public static bool ProcessMethodsValidateCommand(TypeDefinition td, MethodDefini NetworkBehaviourProcessor.ProcessMethodsValidateParameters(td, md, ca, "Command"); } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs.meta new file mode 100644 index 000000000..20c3e154f --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/MessageClassProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs similarity index 76% rename from Weaver/Weaver/Processors/MessageClassProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs index b997a0ed2..2ab044bbb 100644 --- a/Weaver/Weaver/Processors/MessageClassProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs @@ -13,7 +13,7 @@ public static void Process(TypeDefinition td) Weaver.ResetRecursionCount(); GenerateSerialization(td); - if (Weaver.fail) + if (Weaver.WeavingFailed) { return; } @@ -41,8 +41,7 @@ static void GenerateSerialization(TypeDefinition td) { if (field.FieldType.FullName == td.FullName) { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " [" + field.FullName + "]. [MessageBase] member cannot be self referencing."); + Weaver.Error("GenerateSerialization for " + td.Name + " [" + field.FullName + "]. [MessageBase] member cannot be self referencing."); return; } } @@ -51,7 +50,7 @@ static void GenerateSerialization(TypeDefinition td) MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); - serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType))); + 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) @@ -61,15 +60,13 @@ static void GenerateSerialization(TypeDefinition td) if (field.FieldType.Resolve().HasGenericParameters) { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot have generic parameters."); + Weaver.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot have generic parameters."); return; } if (field.FieldType.Resolve().IsInterface) { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot be an interface."); + Weaver.Error("GenerateSerialization for " + td.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member cannot be an interface."); return; } @@ -83,8 +80,7 @@ static void GenerateSerialization(TypeDefinition td) } else { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " unknown type [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member variables must be basic types."); + Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + field.FieldType + "/" + field.FieldType.FullName + "]. [MessageBase] member variables must be basic types."); return; } } @@ -111,7 +107,7 @@ static void GenerateDeSerialization(TypeDefinition td) MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); - serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType))); + 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) @@ -129,8 +125,7 @@ static void GenerateDeSerialization(TypeDefinition td) } else { - Weaver.fail = true; - Log.Error("GenerateDeSerialization for " + td.Name + " unknown type [" + field.FieldType + "]. [SyncVar] member variables must be basic types."); + Weaver.Error("GenerateDeSerialization for " + td.Name + " unknown type [" + field.FieldType + "]. [SyncVar] member variables must be basic types."); return; } } diff --git a/Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/MessageClassProcessor.cs.meta new file mode 100644 index 000000000..875cf9adc --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/MonoBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs similarity index 52% rename from Weaver/Weaver/Processors/MonoBehaviourProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs index b61cf9da6..3541af001 100644 --- a/Weaver/Weaver/Processors/MonoBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs @@ -20,15 +20,13 @@ static void ProcessSyncVars(TypeDefinition td) { if (ca.AttributeType.FullName == Weaver.SyncVarType.FullName) { - Log.Error("Script " + td.FullName + " uses [SyncVar] " + fd.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses [SyncVar] " + fd.Name + " but is not a NetworkBehaviour."); } } - if (SyncObjectProcessor.ImplementsSyncObject(fd.FieldType)) + if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { - Log.Error(string.Format("Script {0} defines field {1} with type {2}, but it's not a NetworkBehaviour", td.FullName, fd.Name, Helpers.PrettyPrintType(fd.FieldType))); - Weaver.fail = true; + Weaver.Error(string.Format("Script {0} defines field {1} with type {2}, but it's not a NetworkBehaviour", td.FullName, fd.Name, Helpers.PrettyPrintType(fd.FieldType))); } } } @@ -42,20 +40,17 @@ static void ProcessMethods(TypeDefinition td) { if (ca.AttributeType.FullName == Weaver.CommandType.FullName) { - Log.Error("Script " + td.FullName + " uses [Command] " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses [Command] " + md.Name + " but is not a NetworkBehaviour."); } if (ca.AttributeType.FullName == Weaver.ClientRpcType.FullName) { - Log.Error("Script " + td.FullName + " uses [ClientRpc] " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses [ClientRpc] " + md.Name + " but is not a NetworkBehaviour."); } if (ca.AttributeType.FullName == Weaver.TargetRpcType.FullName) { - Log.Error("Script " + td.FullName + " uses [TargetRpc] " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses [TargetRpc] " + md.Name + " but is not a NetworkBehaviour."); } string attributeName = ca.Constructor.DeclaringType.ToString(); @@ -63,24 +58,20 @@ static void ProcessMethods(TypeDefinition td) switch (attributeName) { case "Mirror.ServerAttribute": - Log.Error("Script " + td.FullName + " uses the attribute [Server] on the method " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses the attribute [Server] on the method " + md.Name + " but is not a NetworkBehaviour."); break; case "Mirror.ServerCallbackAttribute": - Log.Error("Script " + td.FullName + " uses the attribute [ServerCallback] on the method " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses the attribute [ServerCallback] on the method " + md.Name + " but is not a NetworkBehaviour."); break; case "Mirror.ClientAttribute": - Log.Error("Script " + td.FullName + " uses the attribute [Client] on the method " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses the attribute [Client] on the method " + md.Name + " but is not a NetworkBehaviour."); break; case "Mirror.ClientCallbackAttribute": - Log.Error("Script " + td.FullName + " uses the attribute [ClientCallback] on the method " + md.Name + " but is not a NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("Script " + td.FullName + " uses the attribute [ClientCallback] on the method " + md.Name + " but is not a NetworkBehaviour."); break; } } } } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/MonoBehaviourProcessor.cs.meta new file mode 100644 index 000000000..ef3f5f445 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs similarity index 87% rename from Weaver/Weaver/Processors/NetworkBehaviourProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index 764d9716c..2dfe0bb5f 100644 --- a/Weaver/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -37,8 +37,7 @@ public void Process() { if (m_td.HasGenericParameters) { - Weaver.fail = true; - Log.Error("NetworkBehaviour " + m_td.Name + " cannot have generic parameters"); + Weaver.Error("NetworkBehaviour " + m_td.Name + " cannot have generic parameters"); return; } Weaver.DLog(m_td, "Process Start"); @@ -49,7 +48,7 @@ public void Process() ProcessMethods(); SyncEventProcessor.ProcessEvents(m_td, m_Events, m_EventInvocationFuncs); - if (Weaver.fail) + if (Weaver.WeavingFailed) { return; } @@ -57,7 +56,7 @@ public void Process() Weaver.ResetRecursionCount(); GenerateSerialization(); - if (Weaver.fail) + if (Weaver.WeavingFailed) { return; } @@ -104,7 +103,7 @@ public static void WriteServerActiveCheck(ILProcessor worker, string mdName, Ins public static void WriteSetupLocals(ILProcessor worker) { worker.Body.InitLocals = true; - worker.Body.Variables.Add(new VariableDefinition(Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType))); + worker.Body.Variables.Add(new VariableDefinition(Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkWriterType))); } public static void WriteCreateWriter(ILProcessor worker) @@ -129,8 +128,7 @@ public static bool WriteArguments(ILProcessor worker, MethodDefinition md, strin MethodReference writeFunc = Weaver.GetWriteFunc(pd.ParameterType); if (writeFunc == null) { - Log.Error("WriteArguments for " + md.Name + " type " + pd.ParameterType + " not supported"); - Weaver.fail = true; + Weaver.Error("WriteArguments for " + md.Name + " type " + pd.ParameterType + " not supported"); return false; } // use built-in writer func on writer object @@ -142,7 +140,7 @@ public static bool WriteArguments(ILProcessor worker, MethodDefinition md, strin return true; } - // mark / check type as processed ////////////////////////////////////// + #region mark / check type as processed public const string ProcessedFunctionName = "MirrorProcessed"; // by adding an empty MirrorProcessed() function @@ -161,7 +159,7 @@ public static void MarkAsProcessed(TypeDefinition td) td.Methods.Add(versionMethod); } } - //////////////////////////////////////////////////////////////////////// + #endregion void GenerateConstants() { @@ -193,8 +191,7 @@ void GenerateConstants() } else { - Log.Error("No cctor for " + m_td.Name); - Weaver.fail = true; + Weaver.Error("No cctor for " + m_td.Name); return; } } @@ -226,8 +223,7 @@ void GenerateConstants() } else { - Weaver.fail = true; - Log.Error("No ctor for " + m_td.Name); + Weaver.Error("No ctor for " + m_td.Name); return; } @@ -237,8 +233,7 @@ void GenerateConstants() if (ctor == null) { - Weaver.fail = true; - Log.Error("No ctor for " + m_td.Name); + Weaver.Error("No ctor for " + m_td.Name); return; } @@ -267,8 +262,8 @@ void GenerateConstants() foreach (FieldDefinition fd in m_SyncObjects) { - SyncListProcessor.GenerateSyncListInstanceInitializer(ctorWorker, fd); - SyncObjectProcessor.GenerateSyncObjectInitializer(ctorWorker, fd); + SyncListInitializer.GenerateSyncListInstanceInitializer(ctorWorker, fd); + SyncObjectInitializer.GenerateSyncObjectInitializer(ctorWorker, fd); } cctorWorker.Append(cctorWorker.Create(OpCodes.Ret)); @@ -320,7 +315,7 @@ void GenerateSerialization() MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.boolType); - serialize.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType))); + 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(); @@ -330,7 +325,7 @@ void GenerateSerialization() VariableDefinition dirtyLocal = new VariableDefinition(Weaver.boolType); serialize.Body.Variables.Add(dirtyLocal); - MethodReference baseSerialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.scriptDef, "OnSerialize"); + MethodReference baseSerialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.CurrentAssembly, "OnSerialize"); if (baseSerialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base @@ -358,8 +353,7 @@ void GenerateSerialization() } else { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types."); + Weaver.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } } @@ -407,8 +401,7 @@ void GenerateSerialization() } else { - Log.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types."); - Weaver.fail = true; + Weaver.Error("GenerateSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } @@ -420,7 +413,7 @@ void GenerateSerialization() dirtyBit += 1; } - if (Weaver.generateLogErrors) + if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Serialize " + m_td.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference)); @@ -493,8 +486,7 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor serWorker) MethodReference readFunc = Weaver.GetReadFunc(syncVar.FieldType); if (readFunc == null) { - Log.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. UNet [SyncVar] member variables must be basic types."); - Weaver.fail = true; + Weaver.Error("GenerateDeSerialization for " + m_td.Name + " unknown type [" + syncVar.FieldType + "]. Mirror [SyncVar] member variables must be basic types."); return; } @@ -538,11 +530,11 @@ void GenerateDeSerialization() MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig, Weaver.voidType); - serialize.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType))); + 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(); - MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.scriptDef, "OnDeserialize"); + MethodReference baseDeserialize = Resolvers.ResolveMethodInParents(m_td.BaseType, Weaver.CurrentAssembly, "OnDeserialize"); if (baseDeserialize != null) { serWorker.Append(serWorker.Create(OpCodes.Ldarg_0)); // base @@ -595,7 +587,7 @@ void GenerateDeSerialization() dirtyBit += 1; } - if (Weaver.generateLogErrors) + if (Weaver.GenerateLogErrors) { serWorker.Append(serWorker.Create(OpCodes.Ldstr, "Injected Deserialize " + m_td.Name)); serWorker.Append(serWorker.Create(OpCodes.Call, Weaver.logErrorReference)); @@ -635,8 +627,7 @@ public static bool ProcessNetworkReaderParameters(TypeDefinition td, MethodDefin } else { - Log.Error("ProcessNetworkReaderParameters for " + td.Name + ":" + md.Name + " type " + arg.ParameterType + " not supported"); - Weaver.fail = true; + Weaver.Error("ProcessNetworkReaderParameters for " + td.Name + ":" + md.Name + " type " + arg.ParameterType + " not supported"); return false; } } @@ -646,27 +637,24 @@ public static bool ProcessNetworkReaderParameters(TypeDefinition td, MethodDefin public static void AddInvokeParameters(ICollection collection) { collection.Add(new ParameterDefinition("obj", ParameterAttributes.None, Weaver.NetworkBehaviourType2)); - collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType))); + collection.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); } public static bool ProcessMethodsValidateFunction(TypeDefinition td, MethodReference md, string actionType) { if (md.ReturnType.FullName == Weaver.IEnumeratorType.FullName) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot be a coroutine"); - Weaver.fail = true; + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot be a coroutine"); return false; } if (md.ReturnType.FullName != Weaver.voidType.FullName) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] must have a void return type."); - Weaver.fail = true; + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] must have a void return type."); return false; } if (md.HasGenericParameters) { - Log.Error(actionType + " [" + td.FullName + ":" + md.Name + "] cannot have generic parameters"); - Weaver.fail = true; + Weaver.Error(actionType + " [" + td.FullName + ":" + md.Name + "] cannot have generic parameters"); return false; } return true; @@ -679,46 +667,40 @@ public static bool ProcessMethodsValidateParameters(TypeDefinition td, MethodRef var p = md.Parameters[i]; if (p.IsOut) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have out parameters"); - Weaver.fail = true; + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have out parameters"); return false; } if (p.IsOptional) { - Log.Error(actionType + "function [" + td.FullName + ":" + md.Name + "] cannot have optional parameters"); - Weaver.fail = true; + Weaver.Error(actionType + "function [" + td.FullName + ":" + md.Name + "] cannot have optional parameters"); return false; } if (p.ParameterType.Resolve().IsAbstract) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have abstract parameters"); - Weaver.fail = true; + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have abstract parameters"); return false; } if (p.ParameterType.IsByReference) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have ref parameters"); - Weaver.fail = true; + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] cannot have ref parameters"); 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)) { - Log.Error(actionType + " [" + td.FullName + ":" + md.Name + "] cannot use a NetworkConnection as a parameter. To access a player object's connection on the server use connectionToClient"); - Log.Error("Name: " + ca.AttributeType.FullName + " parameter: " + md.Parameters[0].ParameterType.FullName); - Weaver.fail = true; + Weaver.Error(actionType + " [" + td.FullName + ":" + md.Name + "] cannot use a NetworkConnection as a parameter. To access a player object's connection on the server use connectionToClient"); + Weaver.Error("Name: " + ca.AttributeType.FullName + " parameter: " + md.Parameters[0].ParameterType.FullName); return false; } if (p.ParameterType.Resolve().IsDerivedFrom(Weaver.ComponentType)) { if (p.ParameterType.FullName != Weaver.NetworkIdentityType.FullName) { - Log.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] parameter [" + p.Name + + Weaver.Error(actionType + " function [" + td.FullName + ":" + md.Name + "] parameter [" + p.Name + "] is of the type [" + p.ParameterType.Name + "] which is a Component. You cannot pass a Component to a remote call. Try passing data from within the component."); - Weaver.fail = true; return false; } } @@ -743,8 +725,7 @@ void ProcessMethods() if (names.Contains(md.Name)) { - Log.Error("Duplicate Command name [" + m_td.FullName + ":" + md.Name + "]"); - Weaver.fail = true; + Weaver.Error("Duplicate Command name [" + m_td.FullName + ":" + md.Name + "]"); return; } names.Add(md.Name); @@ -760,7 +741,7 @@ void ProcessMethods() if (cmdCallFunc != null) { m_CmdCallFuncs.Add(cmdCallFunc); - Weaver.lists.replaceMethods[md.FullName] = cmdCallFunc; + Weaver.WeaveLists.replaceMethods[md.FullName] = cmdCallFunc; } break; } @@ -772,8 +753,7 @@ void ProcessMethods() if (names.Contains(md.Name)) { - Log.Error("Duplicate Target Rpc name [" + m_td.FullName + ":" + md.Name + "]"); - Weaver.fail = true; + Weaver.Error("Duplicate Target Rpc name [" + m_td.FullName + ":" + md.Name + "]"); return; } names.Add(md.Name); @@ -789,7 +769,7 @@ void ProcessMethods() if (rpcCallFunc != null) { m_TargetRpcCallFuncs.Add(rpcCallFunc); - Weaver.lists.replaceMethods[md.FullName] = rpcCallFunc; + Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc; } break; } @@ -801,8 +781,7 @@ void ProcessMethods() if (names.Contains(md.Name)) { - Log.Error("Duplicate ClientRpc name [" + m_td.FullName + ":" + md.Name + "]"); - Weaver.fail = true; + Weaver.Error("Duplicate ClientRpc name [" + m_td.FullName + ":" + md.Name + "]"); return; } names.Add(md.Name); @@ -818,7 +797,7 @@ void ProcessMethods() if (rpcCallFunc != null) { m_RpcCallFuncs.Add(rpcCallFunc); - Weaver.lists.replaceMethods[md.FullName] = rpcCallFunc; + Weaver.WeaveLists.replaceMethods[md.FullName] = rpcCallFunc; } break; } diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs.meta new file mode 100644 index 000000000..67c27dc00 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/RpcProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs similarity index 84% rename from Weaver/Weaver/Processors/RpcProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs index d3c88aa9c..529fe197e 100644 --- a/Weaver/Weaver/Processors/RpcProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs @@ -38,13 +38,9 @@ public static MethodDefinition ProcessRpcInvoke(TypeDefinition td, MethodDefinit /* generates code like: public void CallRpcTest (int param) { - if (!NetworkServer.get_active ()) { - Debug.LogError ((object)"RPC Function RpcTest called on client."); - } else { - NetworkWriter writer = new NetworkWriter (); - writer.WritePackedUInt32((uint)param); - base.SendRPCInternal(typeof(class),"RpcTest", writer, 0); - } + 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) @@ -64,8 +60,6 @@ public static MethodDefinition ProcessRpcCall(TypeDefinition td, MethodDefinitio NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker); - NetworkBehaviourProcessor.WriteServerActiveCheck(rpcWorker, md.Name, label, "RPC Function"); - NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker); // write all the arguments that the user passed to the Rpc call @@ -97,15 +91,13 @@ public static bool ProcessMethodsValidateRpc(TypeDefinition td, MethodDefinition { if (md.Name.Length > 2 && md.Name.Substring(0, 3) != "Rpc") { - Log.Error("Rpc function [" + td.FullName + ":" + md.Name + "] doesnt have 'Rpc' prefix"); - Weaver.fail = true; + Weaver.Error("Rpc function [" + td.FullName + ":" + md.Name + "] doesnt have 'Rpc' prefix"); return false; } if (md.IsStatic) { - Log.Error("ClientRpc function [" + td.FullName + ":" + md.Name + "] cant be a static method"); - Weaver.fail = true; + Weaver.Error("ClientRpc function [" + td.FullName + ":" + md.Name + "] cant be a static method"); return false; } diff --git a/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs.meta new file mode 100644 index 000000000..22375bacc --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/SyncEventProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs similarity index 90% rename from Weaver/Weaver/Processors/SyncEventProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs index 8fec69283..9e377d0b2 100644 --- a/Weaver/Weaver/Processors/SyncEventProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs @@ -21,8 +21,7 @@ public static MethodDefinition ProcessEventInvoke(TypeDefinition td, EventDefini } if (eventField == null) { - Weaver.DLog(td, "ERROR: no event field?!"); - Weaver.fail = true; + Weaver.Error("[" + td.Name + "] ERROR: no event field?!"); return null; } @@ -51,7 +50,7 @@ public static MethodDefinition ProcessEventInvoke(TypeDefinition td, EventDefini cmdWorker.Append(cmdWorker.Create(OpCodes.Ldfld, eventField)); // read the event arguments - MethodReference invoke = Resolvers.ResolveMethod(eventField.FieldType, Weaver.scriptDef, "Invoke"); + MethodReference invoke = Resolvers.ResolveMethod(eventField.FieldType, Weaver.CurrentAssembly, "Invoke"); if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(td, invoke.Resolve(), cmdWorker, false)) return null; @@ -66,7 +65,7 @@ public static MethodDefinition ProcessEventInvoke(TypeDefinition td, EventDefini public static MethodDefinition ProcessEventCall(TypeDefinition td, EventDefinition ed, CustomAttribute ca) { - MethodReference invoke = Resolvers.ResolveMethod(ed.EventType, Weaver.scriptDef, "Invoke"); + MethodReference invoke = Resolvers.ResolveMethod(ed.EventType, Weaver.CurrentAssembly, "Invoke"); MethodDefinition evt = new MethodDefinition("Call" + ed.Name, MethodAttributes.Public | MethodAttributes.HideBySig, Weaver.voidType); @@ -114,15 +113,13 @@ public static void ProcessEvents(TypeDefinition td, List events { if (ed.Name.Length > 4 && ed.Name.Substring(0, 5) != "Event") { - Log.Error("Event [" + td.FullName + ":" + ed.FullName + "] doesnt have 'Event' prefix"); - Weaver.fail = true; + Weaver.Error("Event [" + td.FullName + ":" + ed.FullName + "] doesnt have 'Event' prefix"); return; } if (ed.EventType.Resolve().HasGenericParameters) { - Log.Error("Event [" + td.FullName + ":" + ed.FullName + "] cannot have generic parameters"); - Weaver.fail = true; + Weaver.Error("Event [" + td.FullName + ":" + ed.FullName + "] cannot have generic parameters"); return; } @@ -141,7 +138,7 @@ public static void ProcessEvents(TypeDefinition td, List events MethodDefinition eventCallFunc = ProcessEventCall(td, ed, ca); td.Methods.Add(eventCallFunc); - Weaver.lists.replaceEvents[ed.Name] = eventCallFunc; // original weaver compares .Name, not EventDefinition. + Weaver.WeaveLists.replaceEvents[ed.Name] = eventCallFunc; // original weaver compares .Name, not EventDefinition. Weaver.DLog(td, " Event: " + ed.Name); break; @@ -150,4 +147,4 @@ public static void ProcessEvents(TypeDefinition td, List events } } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/SyncEventProcessor.cs.meta new file mode 100644 index 000000000..81b957684 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/SyncListProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs similarity index 81% rename from Weaver/Weaver/Processors/SyncListProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs index 40fd888ce..e449f880f 100644 --- a/Weaver/Weaver/Processors/SyncListProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs @@ -6,7 +6,7 @@ namespace Mirror.Weaver { - public static class SyncListProcessor + public static class SyncListInitializer { // generates 'syncListInt = new SyncListInt()' if user didn't do that yet public static void GenerateSyncListInstanceInitializer(ILProcessor ctorWorker, FieldDefinition fd) @@ -31,12 +31,11 @@ public static void GenerateSyncListInstanceInitializer(ILProcessor ctorWorker, F MethodReference listCtor; try { - listCtor = Weaver.scriptDef.MainModule.ImportReference(fd.FieldType.Resolve().Methods.First(x => x.Name == ".ctor" && !x.HasParameters)); + listCtor = Weaver.CurrentAssembly.MainModule.ImportReference(fd.FieldType.Resolve().Methods.First(x => x.Name == ".ctor" && !x.HasParameters)); } catch (Exception) { - Weaver.fail = true; - Log.Error("Missing parameter-less constructor for:" + fd.FieldType.Name); + Weaver.Error("Missing parameter-less constructor for:" + fd.FieldType.Name); return; } @@ -45,4 +44,4 @@ public static void GenerateSyncListInstanceInitializer(ILProcessor ctorWorker, F ctorWorker.Append(ctorWorker.Create(OpCodes.Stfld, fd)); } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/SyncListInitializer.cs.meta new file mode 100644 index 000000000..d3f5278de --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/SyncListStructProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs similarity index 75% rename from Weaver/Weaver/Processors/SyncListStructProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs index 4926d2b78..931dc67a3 100644 --- a/Weaver/Weaver/Processors/SyncListStructProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs @@ -12,17 +12,16 @@ public static void Process(TypeDefinition td) GenericInstanceType gt = (GenericInstanceType)td.BaseType; if (gt.GenericArguments.Count == 0) { - Weaver.fail = true; - Log.Error("SyncListStructProcessor no generic args"); + Weaver.Error("SyncListStructProcessor no generic args"); return; } - TypeReference itemType = Weaver.scriptDef.MainModule.ImportReference(gt.GenericArguments[0]); + TypeReference itemType = Weaver.CurrentAssembly.MainModule.ImportReference(gt.GenericArguments[0]); Weaver.DLog(td, "SyncListStructProcessor Start item:" + itemType.FullName); Weaver.ResetRecursionCount(); MethodReference writeItemFunc = GenerateSerialization(td, itemType); - if (Weaver.fail) + if (Weaver.WeavingFailed) { return; } @@ -51,14 +50,13 @@ static MethodReference GenerateSerialization(TypeDefinition td, TypeReference it MethodAttributes.HideBySig, Weaver.voidType); - serializeFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkWriterType))); + 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.fail = true; - Log.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Struct passed into SyncListStruct can't have generic parameters"); + Weaver.Error("GenerateSerialization for " + Helpers.PrettyPrintType(itemType) + " failed. Struct passed into SyncListStruct can't have generic parameters"); return null; } @@ -67,20 +65,18 @@ static MethodReference GenerateSerialization(TypeDefinition td, TypeReference it if (field.IsStatic || field.IsPrivate || field.IsSpecialName) continue; - FieldReference importedField = Weaver.scriptDef.MainModule.ImportReference(field); + FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field); TypeDefinition ft = importedField.FieldType.Resolve(); if (ft.HasGenericParameters) { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot have generic parameters."); + Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot have generic parameters."); return null; } if (ft.IsInterface) { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot be an interface."); + Weaver.Error("GenerateSerialization for " + td.Name + " [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member cannot be an interface."); return null; } @@ -94,8 +90,7 @@ static MethodReference GenerateSerialization(TypeDefinition td, TypeReference it } else { - Weaver.fail = true; - Log.Error("GenerateSerialization for " + td.Name + " unknown type [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member variables must be basic types."); + Weaver.Error("GenerateSerialization for " + td.Name + " unknown type [" + ft + "/" + ft.FullName + "]. [SyncListStruct] member variables must be basic types."); return null; } } @@ -120,7 +115,7 @@ static MethodReference GenerateDeserialization(TypeDefinition td, TypeReference MethodAttributes.HideBySig, itemType); - serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.scriptDef.MainModule.ImportReference(Weaver.NetworkReaderType))); + serializeFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, Weaver.CurrentAssembly.MainModule.ImportReference(Weaver.NetworkReaderType))); ILProcessor serWorker = serializeFunc.Body.GetILProcessor(); @@ -136,7 +131,7 @@ static MethodReference GenerateDeserialization(TypeDefinition td, TypeReference if (field.IsStatic || field.IsPrivate || field.IsSpecialName) continue; - FieldReference importedField = Weaver.scriptDef.MainModule.ImportReference(field); + FieldReference importedField = Weaver.CurrentAssembly.MainModule.ImportReference(field); TypeDefinition ft = importedField.FieldType.Resolve(); MethodReference readerFunc = Weaver.GetReadFunc(field.FieldType); @@ -149,8 +144,7 @@ static MethodReference GenerateDeserialization(TypeDefinition td, TypeReference } else { - Weaver.fail = true; - Log.Error("GenerateDeserialization for " + td.Name + " unknown type [" + ft + "]. [SyncListStruct] member variables must be basic types."); + Weaver.Error("GenerateDeserialization for " + td.Name + " unknown type [" + ft + "]. [SyncListStruct] member variables must be basic types."); return null; } } diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta new file mode 100644 index 000000000..4a96fc5d5 --- /dev/null +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncListStructProcessor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 93397916cae0248bc9294f863fa49f81 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Weaver/Weaver/Processors/SyncObjectProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs similarity index 96% rename from Weaver/Weaver/Processors/SyncObjectProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs index 1c562075e..02c9e1db0 100644 --- a/Weaver/Weaver/Processors/SyncObjectProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs @@ -5,7 +5,7 @@ namespace Mirror.Weaver { - public static class SyncObjectProcessor + public static class SyncObjectInitializer { public static bool ImplementsSyncObject(TypeReference typeRef) { @@ -40,4 +40,4 @@ public static void GenerateSyncObjectInitializer(ILProcessor methodWorker, Field methodWorker.Append(methodWorker.Create(OpCodes.Call, Weaver.InitSyncObjectReference)); } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/SyncObjectInitializer.cs.meta new file mode 100644 index 000000000..22f976e79 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/SyncVarProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs similarity index 83% rename from Weaver/Weaver/Processors/SyncVarProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs index 86eaea1d3..744e4f48b 100644 --- a/Weaver/Weaver/Processors/SyncVarProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs @@ -31,20 +31,17 @@ public static bool CheckForHookFunction(TypeDefinition td, FieldDefinition syncV { if (m.Parameters[0].ParameterType != syncVar.FieldType) { - Log.Error("SyncVar Hook function " + hookFunctionName + " has wrong type signature for " + td.Name); - Weaver.fail = true; + Weaver.Error("SyncVar Hook function " + hookFunctionName + " has wrong type signature for " + td.Name); return false; } foundMethod = m; return true; } - Log.Error("SyncVar Hook function " + hookFunctionName + " must have one argument " + td.Name); - Weaver.fail = true; + Weaver.Error("SyncVar Hook function " + hookFunctionName + " must have one argument " + td.Name); return false; } } - Log.Error("SyncVar Hook function " + hookFunctionName + " not found for " + td.Name); - Weaver.fail = true; + Weaver.Error("SyncVar Hook function " + hookFunctionName + " not found for " + td.Name); return false; } } @@ -222,7 +219,7 @@ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Diction td.Methods.Add(get); td.Methods.Add(set); td.Properties.Add(propertyDefinition); - Weaver.lists.replacementSetterProperties[fd] = set; + Weaver.WeaveLists.replacementSetterProperties[fd] = set; // replace getter field if GameObject/NetworkIdentity so it uses // netId instead @@ -231,7 +228,7 @@ public static void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Diction if (fd.FieldType.FullName == Weaver.gameObjectType.FullName || fd.FieldType.FullName == Weaver.NetworkIdentityType.FullName) { - Weaver.lists.replacementGetterProperties[fd] = get; + Weaver.WeaveLists.replacementGetterProperties[fd] = get; } } @@ -256,61 +253,54 @@ public static void ProcessSyncVars(TypeDefinition td, List sync if (resolvedField.IsDerivedFrom(Weaver.NetworkBehaviourType)) { - Log.Error("SyncVar [" + fd.FullName + "] cannot be derived from NetworkBehaviour."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot be derived from NetworkBehaviour."); return; } if (resolvedField.IsDerivedFrom(Weaver.ScriptableObjectType)) { - Log.Error("SyncVar [" + fd.FullName + "] cannot be derived from ScriptableObject."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot be derived from ScriptableObject."); return; } if ((fd.Attributes & FieldAttributes.Static) != 0) { - Log.Error("SyncVar [" + fd.FullName + "] cannot be static."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot be static."); return; } if (resolvedField.HasGenericParameters) { - Log.Error("SyncVar [" + fd.FullName + "] cannot have generic parameters."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot have generic parameters."); return; } if (resolvedField.IsInterface) { - Log.Error("SyncVar [" + fd.FullName + "] cannot be an interface."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot be an interface."); return; } var fieldModuleName = resolvedField.Module.Name; - if (fieldModuleName != Weaver.scriptDef.MainModule.Name && - fieldModuleName != Weaver.m_UnityAssemblyDefinition.MainModule.Name && - fieldModuleName != Weaver.m_UNetAssemblyDefinition.MainModule.Name && - fieldModuleName != Weaver.corLib.Name && + if (fieldModuleName != Weaver.CurrentAssembly.MainModule.Name && + fieldModuleName != Weaver.UnityAssembly.MainModule.Name && + fieldModuleName != Weaver.NetAssembly.MainModule.Name && + fieldModuleName != Weaver.CorLibModule.Name && fieldModuleName != "System.Runtime.dll" && // this is only for Metro, built-in types are not in corlib on metro fieldModuleName != "netstandard.dll" // handle built-in types when weaving new C#7 compiler assemblies ) { - Log.Error("SyncVar [" + fd.FullName + "] from " + resolvedField.Module.ToString() + " cannot be a different module."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] from " + resolvedField.Module.ToString() + " cannot be a different module."); return; } if (fd.FieldType.IsArray) { - Log.Error("SyncVar [" + fd.FullName + "] cannot be an array. Use a SyncList instead."); - Weaver.fail = true; + Weaver.Error("SyncVar [" + fd.FullName + "] cannot be an array. Use a SyncList instead."); return; } - if (SyncObjectProcessor.ImplementsSyncObject(fd.FieldType)) + if (SyncObjectInitializer.ImplementsSyncObject(fd.FieldType)) { Log.Warning(string.Format("Script class [{0}] has [SyncVar] attribute on SyncList field {1}, SyncLists should not be marked with SyncVar.", td.FullName, fd.Name)); break; @@ -324,8 +314,7 @@ public static void ProcessSyncVars(TypeDefinition td, List sync if (dirtyBitCounter == k_SyncVarLimit) { - Log.Error("Script class [" + td.FullName + "] has too many SyncVars (" + k_SyncVarLimit + "). (This could include base classes)"); - Weaver.fail = true; + Weaver.Error("Script class [" + td.FullName + "] has too many SyncVars (" + k_SyncVarLimit + "). (This could include base classes)"); return; } break; @@ -334,8 +323,7 @@ public static void ProcessSyncVars(TypeDefinition td, List sync if (fd.FieldType.FullName.Contains("Mirror.SyncListStruct")) { - Log.Error("SyncListStruct member variable [" + fd.FullName + "] must use a dervied class, like \"class MySyncList : SyncListStruct {}\"."); - Weaver.fail = true; + Weaver.Error("SyncListStruct member variable [" + fd.FullName + "] must use a dervied class, like \"class MySyncList : SyncListStruct {}\"."); return; } @@ -343,8 +331,7 @@ public static void ProcessSyncVars(TypeDefinition td, List sync { if (fd.IsStatic) { - Log.Error("SyncList [" + td.FullName + ":" + fd.FullName + "] cannot be a static"); - Weaver.fail = true; + Weaver.Error("SyncList [" + td.FullName + ":" + fd.FullName + "] cannot be a static"); return; } @@ -361,4 +348,4 @@ public static void ProcessSyncVars(TypeDefinition td, List sync Weaver.SetNumSyncVars(td.FullName, numSyncVars); } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/SyncVarProcessor.cs.meta new file mode 100644 index 000000000..982f7680e --- /dev/null +++ b/Assets/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/Weaver/Weaver/Processors/TargetRpcProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs similarity index 61% rename from Weaver/Weaver/Processors/TargetRpcProcessor.cs rename to Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs index 01d66b255..56a9fd9eb 100644 --- a/Weaver/Weaver/Processors/TargetRpcProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs @@ -8,6 +8,13 @@ public static class TargetRpcProcessor { const string k_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.k_RpcPrefix + md.Name, MethodAttributes.Family | @@ -24,10 +31,16 @@ public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodD rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); rpcWorker.Append(rpcWorker.Create(OpCodes.Castclass, td)); - //ClientScene.readyconnection - rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.ReadyConnectionReference)); + // NetworkConnection parameter is optional + bool hasNetworkConnection = HasNetworkConnectionParameter(md); + if (hasNetworkConnection) + { + //ClientScene.readyconnection + rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.ReadyConnectionReference)); + } - if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(td, md, rpcWorker, true)) + // process reader parameters and skip first one if first one is NetworkConnection + if (!NetworkBehaviourProcessor.ProcessNetworkReaderParameters(td, md, rpcWorker, hasNetworkConnection)) return null; // invoke actual command function @@ -42,15 +55,17 @@ public static MethodDefinition ProcessTargetRpcInvoke(TypeDefinition td, MethodD /* generates code like: public void CallTargetTest (NetworkConnection conn, int param) { - if (!NetworkServer.get_active ()) { - Debug.LogError((object)"TargetRPC Function TargetTest called on client."); - } else if (((?)conn) is ULocalConnectionToServer) { - Debug.LogError((object)"TargetRPC Function TargetTest called on connection to server"); - } else { - NetworkWriter writer = new NetworkWriter (); - writer.WritePackedUInt32 ((uint)param); - base.SendTargetRPCInternal (conn, typeof(class), "TargetTest", val); - } + 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) @@ -59,35 +74,24 @@ public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDef MethodAttributes.HideBySig, Weaver.voidType); - // add paramters + // add parameters foreach (ParameterDefinition pd in md.Parameters) { rpc.Parameters.Add(new ParameterDefinition(pd.Name, ParameterAttributes.None, pd.ParameterType)); } ILProcessor rpcWorker = rpc.Body.GetILProcessor(); - Instruction label = rpcWorker.Create(OpCodes.Nop); NetworkBehaviourProcessor.WriteSetupLocals(rpcWorker); - NetworkBehaviourProcessor.WriteServerActiveCheck(rpcWorker, md.Name, label, "TargetRPC Function"); - - Instruction labelConnectionCheck = rpcWorker.Create(OpCodes.Nop); - - // check specifically for ULocalConnectionToServer so a host is not trying to send - // an TargetRPC to the "server" from it's local client. - rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1)); - rpcWorker.Append(rpcWorker.Create(OpCodes.Isinst, Weaver.ULocalConnectionToServerType)); - rpcWorker.Append(rpcWorker.Create(OpCodes.Brfalse, labelConnectionCheck)); - rpcWorker.Append(rpcWorker.Create(OpCodes.Ldstr, string.Format("TargetRPC Function {0} called on connection to server", md.Name))); - rpcWorker.Append(rpcWorker.Create(OpCodes.Call, Weaver.logErrorReference)); - rpcWorker.Append(rpcWorker.Create(OpCodes.Ret)); - rpcWorker.Append(labelConnectionCheck); - NetworkBehaviourProcessor.WriteCreateWriter(rpcWorker); + // NetworkConnection parameter is optional + bool hasNetworkConnection = HasNetworkConnectionParameter(md); + // write all the arguments that the user passed to the TargetRpc call - if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, "TargetRPC", true)) + // (skip first one if first one is NetworkConnection) + if (!NetworkBehaviourProcessor.WriteArguments(rpcWorker, md, "TargetRPC", hasNetworkConnection)) return null; var rpcName = md.Name; @@ -99,7 +103,14 @@ public static MethodDefinition ProcessTargetRpcCall(TypeDefinition td, MethodDef // invoke SendInternal and return rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_0)); // this - rpcWorker.Append(rpcWorker.Create(OpCodes.Ldarg_1)); // connection + 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)); @@ -119,15 +130,13 @@ public static bool ProcessMethodsValidateTargetRpc(TypeDefinition td, MethodDefi if (md.Name.Length > prefixLen && md.Name.Substring(0, prefixLen) != targetPrefix) { - Log.Error("Target Rpc function [" + td.FullName + ":" + md.Name + "] doesnt have 'Target' prefix"); - Weaver.fail = true; + Weaver.Error("Target Rpc function [" + td.FullName + ":" + md.Name + "] doesnt have 'Target' prefix"); return false; } if (md.IsStatic) { - Log.Error("TargetRpc function [" + td.FullName + ":" + md.Name + "] cant be a static method"); - Weaver.fail = true; + Weaver.Error("TargetRpc function [" + td.FullName + ":" + md.Name + "] cant be a static method"); return false; } @@ -136,22 +145,8 @@ public static bool ProcessMethodsValidateTargetRpc(TypeDefinition td, MethodDefi return false; } - if (md.Parameters.Count < 1) - { - Log.Error("Target Rpc function [" + td.FullName + ":" + md.Name + "] must have a NetworkConnection as the first parameter"); - Weaver.fail = true; - return false; - } - - if (md.Parameters[0].ParameterType.FullName != Weaver.NetworkConnectionType.FullName) - { - Log.Error("Target Rpc function [" + td.FullName + ":" + md.Name + "] first parameter must be a NetworkConnection"); - Weaver.fail = true; - return false; - } - // validate return NetworkBehaviourProcessor.ProcessMethodsValidateParameters(td, md, ca, "Target Rpc"); } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs.meta b/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs.meta new file mode 100644 index 000000000..0ff7cc552 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Program.cs b/Assets/Mirror/Editor/Weaver/Program.cs similarity index 82% rename from Weaver/Weaver/Program.cs rename to Assets/Mirror/Editor/Weaver/Program.cs index 188f2b94b..ebfec5e9e 100644 --- a/Weaver/Weaver/Program.cs +++ b/Assets/Mirror/Editor/Weaver/Program.cs @@ -23,15 +23,15 @@ public static void Error(string msg) public class Program { - public static bool Process(string unityEngine, string unetDLL, string outputDirectory, string[] assemblies, string[] extraAssemblyPaths, IAssemblyResolver assemblyResolver, Action printWarning, Action printError) + public static bool Process(string unityEngine, string netDLL, string outputDirectory, string[] assemblies, string[] extraAssemblyPaths, Action printWarning, Action printError) { CheckDLLPath(unityEngine); - CheckDLLPath(unetDLL); + CheckDLLPath(netDLL); CheckOutputDirectory(outputDirectory); CheckAssemblies(assemblies); Log.WarningMethod = printWarning; Log.ErrorMethod = printError; - return Weaver.WeaveAssemblies(assemblies, extraAssemblyPaths, assemblyResolver, outputDirectory, unityEngine, unetDLL); + return Weaver.WeaveAssemblies(assemblies, extraAssemblyPaths, null, outputDirectory, unityEngine, netDLL); } private static void CheckDLLPath(string path) diff --git a/Assets/Mirror/Editor/Weaver/Program.cs.meta b/Assets/Mirror/Editor/Weaver/Program.cs.meta new file mode 100644 index 000000000..3f62978c1 --- /dev/null +++ b/Assets/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/Weaver/Weaver/Resolvers.cs b/Assets/Mirror/Editor/Weaver/Resolvers.cs similarity index 89% rename from Weaver/Weaver/Resolvers.cs rename to Assets/Mirror/Editor/Weaver/Resolvers.cs index d0fdae2b2..73958fb9f 100644 --- a/Weaver/Weaver/Resolvers.cs +++ b/Assets/Mirror/Editor/Weaver/Resolvers.cs @@ -15,8 +15,7 @@ public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition //Console.WriteLine("ResolveMethod " + t.ToString () + " " + name); if (tr == null) { - Log.Error("Type missing for " + name); - Weaver.fail = true; + Weaver.Error("Type missing for " + name); return null; } foreach (MethodDefinition methodRef in tr.Resolve().Methods) @@ -26,15 +25,14 @@ public static MethodReference ResolveMethod(TypeReference tr, AssemblyDefinition return scriptDef.MainModule.ImportReference(methodRef); } } - Log.Error("ResolveMethod failed " + tr.Name + "::" + name + " " + tr.Resolve()); + Weaver.Error("ResolveMethod failed " + tr.Name + "::" + name + " " + tr.Resolve()); // why did it fail!? foreach (MethodDefinition methodRef in tr.Resolve().Methods) { - Log.Error("Method " + methodRef.Name); + Weaver.Error("Method " + methodRef.Name); } - Weaver.fail = true; return null; } @@ -43,8 +41,7 @@ public static MethodReference ResolveMethodInParents(TypeReference tr, AssemblyD { if (tr == null) { - Log.Error("Type missing for " + name); - Weaver.fail = true; + Weaver.Error("Type missing for " + name); return null; } foreach (MethodDefinition methodRef in tr.Resolve().Methods) @@ -74,8 +71,7 @@ public static MethodReference ResolveMethodWithArg(TypeReference tr, AssemblyDef } } } - Log.Error("ResolveMethodWithArg failed " + tr.Name + "::" + name + " " + argTypeFullName); - Weaver.fail = true; + Weaver.Error("ResolveMethodWithArg failed " + tr.Name + "::" + name + " " + argTypeFullName); return null; } @@ -121,8 +117,7 @@ public static GenericInstanceMethod ResolveMethodGeneric(TypeReference t, Assemb } } - Log.Error("ResolveMethodGeneric failed " + t.Name + "::" + name + " " + genericType); - Weaver.fail = true; + Weaver.Error("ResolveMethodGeneric failed " + t.Name + "::" + name + " " + genericType); return null; } @@ -150,4 +145,4 @@ public static MethodReference ResolveProperty(TypeReference tr, AssemblyDefiniti return null; } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Editor/Weaver/Resolvers.cs.meta b/Assets/Mirror/Editor/Weaver/Resolvers.cs.meta new file mode 100644 index 000000000..f4f66029f --- /dev/null +++ b/Assets/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/Weaver/Weaver/Weaver.cs b/Assets/Mirror/Editor/Weaver/Weaver.cs similarity index 73% rename from Weaver/Weaver/Weaver.cs rename to Assets/Mirror/Editor/Weaver/Weaver.cs index ba2ae3162..97b924aea 100644 --- a/Weaver/Weaver/Weaver.cs +++ b/Assets/Mirror/Editor/Weaver/Weaver.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using System.IO; @@ -38,14 +38,29 @@ class WeaverLists class Weaver { - // UNetwork types + 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 bool DebugLogEnabled = true; + + // this is used to prevent stack overflows when generating serialization code when there are self-referencing types. + // All the utility classes use GetWriteFunc() to generate serialization code, so the recursion check is implemented there instead of in each utility class. + // A NetworkBehaviour with the max SyncVars (64) can legitimately increment this value to 65 - so max must be higher than that + const int MaxRecursionCount = 128; + static int RecursionCount; + + // 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 ULocalConnectionToServerType; - public static TypeReference ULocalConnectionToClientType; public static TypeReference MessageBaseType; public static TypeReference SyncListStructType; @@ -63,7 +78,7 @@ class Weaver public static MethodReference NetworkWriterCtor; public static MethodReference NetworkReaderCtor; public static MethodReference getComponentReference; - public static MethodReference getUNetIdReference; + public static MethodReference getNetIdReference; public static TypeReference NetworkIdentityType; public static TypeReference IEnumeratorType; @@ -156,60 +171,50 @@ class Weaver public static MethodReference sendTargetRpcInternal; public static MethodReference sendEventInternal; - public static WeaverLists lists; - - public static AssemblyDefinition scriptDef; - public static ModuleDefinition corLib; - public static AssemblyDefinition m_UnityAssemblyDefinition; - public static AssemblyDefinition m_UNetAssemblyDefinition; - - static bool m_DebugFlag = true; - - public static bool fail; - public static bool generateLogErrors = false; - - // this is used to prevent stack overflows when generating serialization code when there are self-referencing types. - // All the utility classes use GetWriteFunc() to generate serialization code, so the recursion check is implemented there instead of in each utility class. - // A NetworkBehaviour with the max SyncVars (32) can legitimately increment this value to 65 - so max must be higher than that - const int MaxRecursionCount = 128; - static int s_RecursionCount; public static void ResetRecursionCount() { - s_RecursionCount = 0; + RecursionCount = 0; } public static void DLog(TypeDefinition td, string fmt, params object[] args) { - if (!m_DebugFlag) + 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 lists.numSyncVars.ContainsKey(className) - ? lists.numSyncVars[className] + return WeaveLists.numSyncVars.ContainsKey(className) + ? WeaveLists.numSyncVars[className] : 0; } public static void SetNumSyncVars(string className, int num) { - lists.numSyncVars[className] = num; + WeaveLists.numSyncVars[className] = num; } public static MethodReference GetWriteFunc(TypeReference variable) { - if (s_RecursionCount++ > MaxRecursionCount) + if (RecursionCount++ > MaxRecursionCount) { - Log.Error("GetWriteFunc recursion depth exceeded for " + variable.Name + ". Check for self-referencing member variables."); - fail = true; + Error("GetWriteFunc recursion depth exceeded for " + variable.Name + ". Check for self-referencing member variables."); return null; } - if (lists.writeFuncs.ContainsKey(variable.FullName)) + if (WeaveLists.writeFuncs.ContainsKey(variable.FullName)) { - MethodReference foundFunc = lists.writeFuncs[variable.FullName]; + MethodReference foundFunc = WeaveLists.writeFuncs[variable.FullName]; if (foundFunc.Parameters[0].ParameterType.IsArray == variable.IsArray) { return foundFunc; @@ -219,7 +224,7 @@ public static MethodReference GetWriteFunc(TypeReference variable) if (variable.IsByReference) { // error?? - Log.Error("GetWriteFunc variable.IsByReference error."); + Error("GetWriteFunc variable.IsByReference error."); return null; } @@ -256,18 +261,18 @@ public static MethodReference GetWriteFunc(TypeReference variable) public static void RegisterWriteFunc(string name, MethodDefinition newWriterFunc) { - lists.writeFuncs[name] = newWriterFunc; - lists.generatedWriteFunctions.Add(newWriterFunc); + WeaveLists.writeFuncs[name] = newWriterFunc; + WeaveLists.generatedWriteFunctions.Add(newWriterFunc); - ConfirmGeneratedCodeClass(scriptDef.MainModule); - lists.generateContainerClass.Methods.Add(newWriterFunc); + ConfirmGeneratedCodeClass(CurrentAssembly.MainModule); + WeaveLists.generateContainerClass.Methods.Add(newWriterFunc); } public static MethodReference GetReadFunc(TypeReference variable) { - if (lists.readFuncs.ContainsKey(variable.FullName)) + if (WeaveLists.readFuncs.ContainsKey(variable.FullName)) { - MethodReference foundFunc = lists.readFuncs[variable.FullName]; + MethodReference foundFunc = WeaveLists.readFuncs[variable.FullName]; if (foundFunc.ReturnType.IsArray == variable.IsArray) { return foundFunc; @@ -277,14 +282,14 @@ public static MethodReference GetReadFunc(TypeReference variable) TypeDefinition td = variable.Resolve(); if (td == null) { - Log.Error("GetReadFunc unsupported type " + variable.FullName); + Error("GetReadFunc unsupported type " + variable.FullName); return null; } if (variable.IsByReference) { // error?? - Log.Error("GetReadFunc variable.IsByReference error."); + Error("GetReadFunc variable.IsByReference error."); return null; } @@ -321,11 +326,11 @@ public static MethodReference GetReadFunc(TypeReference variable) public static void RegisterReadFunc(string name, MethodDefinition newReaderFunc) { - lists.readFuncs[name] = newReaderFunc; - lists.generatedReadFunctions.Add(newReaderFunc); + WeaveLists.readFuncs[name] = newReaderFunc; + WeaveLists.generatedReadFunctions.Add(newReaderFunc); - ConfirmGeneratedCodeClass(scriptDef.MainModule); - lists.generateContainerClass.Methods.Add(newReaderFunc); + ConfirmGeneratedCodeClass(CurrentAssembly.MainModule); + WeaveLists.generateContainerClass.Methods.Add(newReaderFunc); } static MethodDefinition GenerateArrayReadFunc(TypeReference variable, MethodReference elementReadFunc) @@ -352,7 +357,7 @@ static MethodDefinition GenerateArrayReadFunc(TypeReference variable, MethodRefe MethodAttributes.HideBySig, variable); - readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, scriptDef.MainModule.ImportReference(NetworkReaderType))); + readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(NetworkReaderType))); readerFunc.Body.Variables.Add(new VariableDefinition(int32Type)); readerFunc.Body.Variables.Add(new VariableDefinition(variable)); @@ -435,8 +440,8 @@ static MethodDefinition GenerateArrayWriteFunc(TypeReference variable, MethodRef MethodAttributes.HideBySig, voidType); - writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, scriptDef.MainModule.ImportReference(NetworkWriterType))); - writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, scriptDef.MainModule.ImportReference(variable))); + writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(NetworkWriterType))); + writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(variable))); writerFunc.Body.Variables.Add(new VariableDefinition(uint16Type)); writerFunc.Body.Variables.Add(new VariableDefinition(uint16Type)); @@ -523,8 +528,8 @@ static MethodDefinition GenerateWriterFunction(TypeReference variable) MethodAttributes.HideBySig, voidType); - writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, scriptDef.MainModule.ImportReference(NetworkWriterType))); - writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, scriptDef.MainModule.ImportReference(variable))); + writerFunc.Parameters.Add(new ParameterDefinition("writer", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(NetworkWriterType))); + writerFunc.Parameters.Add(new ParameterDefinition("value", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(variable))); ILProcessor worker = writerFunc.Body.GetILProcessor(); @@ -536,15 +541,13 @@ static MethodDefinition GenerateWriterFunction(TypeReference variable) if (field.FieldType.Resolve().HasGenericParameters) { - Weaver.fail = true; - Log.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot have generic parameters."); + Weaver.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot have generic parameters."); return null; } if (field.FieldType.Resolve().IsInterface) { - Weaver.fail = true; - Log.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot be an interface."); + Weaver.Error("WriteReadFunc for " + field.Name + " [" + field.FieldType + "/" + field.FieldType.FullName + "]. Cannot be an interface."); return null; } @@ -559,8 +562,7 @@ static MethodDefinition GenerateWriterFunction(TypeReference variable) } else { - Log.Error("WriteReadFunc for " + field.Name + " type " + field.FieldType + " no supported"); - fail = true; + Weaver.Error("WriteReadFunc for " + field.Name + " type " + field.FieldType + " no supported"); return null; } } @@ -574,10 +576,9 @@ static MethodDefinition GenerateWriterFunction(TypeReference variable) static MethodDefinition GenerateReadFunction(TypeReference variable) { - if (s_RecursionCount++ > MaxRecursionCount) + if (RecursionCount++ > MaxRecursionCount) { - Log.Error("GetReadFunc recursion depth exceeded for " + variable.Name + ". Check for self-referencing member variables."); - fail = true; + Weaver.Error("GetReadFunc recursion depth exceeded for " + variable.Name + ". Check for self-referencing member variables."); return null; } @@ -607,7 +608,7 @@ static MethodDefinition GenerateReadFunction(TypeReference variable) readerFunc.Body.Variables.Add(new VariableDefinition(variable)); readerFunc.Body.InitLocals = true; - readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, scriptDef.MainModule.ImportReference(NetworkReaderType))); + readerFunc.Parameters.Add(new ParameterDefinition("reader", ParameterAttributes.None, CurrentAssembly.MainModule.ImportReference(NetworkReaderType))); ILProcessor worker = readerFunc.Body.GetILProcessor(); @@ -650,8 +651,7 @@ static MethodDefinition GenerateReadFunction(TypeReference variable) } else { - Log.Error("GetReadFunc for " + field.Name + " type " + field.FieldType + " no supported"); - fail = true; + Weaver.Error("GetReadFunc for " + field.Name + " type " + field.FieldType + " no supported"); return null; } @@ -693,7 +693,7 @@ static void ProcessInstructionMethod(ModuleDefinition moduleDef, TypeDefinition // that's why we use dict. // TODO maybe replaceEvents[md] would work too? MethodDefinition replacement; - if (lists.replaceEvents.TryGetValue(opField.Name, out replacement)) + if (WeaveLists.replaceEvents.TryGetValue(opField.Name, out replacement)) { instr.Operand = replacement; inst.OpCode = OpCodes.Nop; @@ -709,7 +709,7 @@ static void ProcessInstructionMethod(ModuleDefinition moduleDef, TypeDefinition // that's why we use dict. // TODO maybe replaceMethods[md] would work too? MethodDefinition replacement; - if (lists.replaceMethods.TryGetValue(opMethodRef.FullName, out replacement)) + if (WeaveLists.replaceMethods.TryGetValue(opMethodRef.FullName, out replacement)) { //DLog(td, " replacing " + md.Name + ":" + i); instr.Operand = replacement; @@ -720,19 +720,19 @@ static void ProcessInstructionMethod(ModuleDefinition moduleDef, TypeDefinition static void ConfirmGeneratedCodeClass(ModuleDefinition moduleDef) { - if (lists.generateContainerClass == null) + if (WeaveLists.generateContainerClass == null) { - lists.generateContainerClass = new TypeDefinition("Mirror", "GeneratedNetworkCode", + 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, scriptDef, ".ctor"))); + method.Body.Instructions.Add(Instruction.Create(OpCodes.Call, Resolvers.ResolveMethod(objectType, CurrentAssembly, ".ctor"))); method.Body.Instructions.Add(Instruction.Create(OpCodes.Ret)); - lists.generateContainerClass.Methods.Add(method); + WeaveLists.generateContainerClass.Methods.Add(method); } } @@ -745,7 +745,7 @@ static void ProcessInstructionSetterField(TypeDefinition td, MethodDefinition md // does it set a field that we replaced? MethodDefinition replacement; - if (lists.replacementSetterProperties.TryGetValue(opField, out replacement)) + if (WeaveLists.replacementSetterProperties.TryGetValue(opField, out replacement)) { //replace with property //DLog(td, " replacing " + md.Name + ":" + i); @@ -764,7 +764,7 @@ static void ProcessInstructionGetterField(TypeDefinition td, MethodDefinition md // does it set a field that we replaced? MethodDefinition replacement; - if (lists.replacementGetterProperties.TryGetValue(opField, out replacement)) + if (WeaveLists.replacementGetterProperties.TryGetValue(opField, out replacement)) { //replace with property //DLog(td, " replacing " + md.Name + ":" + i); @@ -972,19 +972,19 @@ static void ProcessSitesModule(ModuleDefinition moduleDef) ProcessSiteClass(moduleDef, td); } } - if (lists.generateContainerClass != null) + if (WeaveLists.generateContainerClass != null) { - moduleDef.Types.Add(lists.generateContainerClass); - scriptDef.MainModule.ImportReference(lists.generateContainerClass); + moduleDef.Types.Add(WeaveLists.generateContainerClass); + CurrentAssembly.MainModule.ImportReference(WeaveLists.generateContainerClass); - foreach (MethodDefinition f in lists.generatedReadFunctions) + foreach (MethodDefinition f in WeaveLists.generatedReadFunctions) { - scriptDef.MainModule.ImportReference(f); + CurrentAssembly.MainModule.ImportReference(f); } - foreach (MethodDefinition f in lists.generatedWriteFunctions) + foreach (MethodDefinition f in WeaveLists.generatedWriteFunctions) { - scriptDef.MainModule.ImportReference(f); + CurrentAssembly.MainModule.ImportReference(f); } } Console.WriteLine(" ProcessSitesModule " + moduleDef.Name + " elapsed time:" + (DateTime.Now - startTime)); @@ -992,7 +992,7 @@ static void ProcessSitesModule(ModuleDefinition moduleDef) static void ProcessPropertySites() { - ProcessSitesModule(scriptDef.MainModule); + ProcessSitesModule(CurrentAssembly.MainModule); } static bool ProcessNetworkBehaviourType(TypeDefinition td) @@ -1010,29 +1010,29 @@ static bool ProcessNetworkBehaviourType(TypeDefinition td) static void SetupUnityTypes() { - vector2Type = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Vector2"); - vector3Type = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Vector3"); - vector4Type = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Vector4"); - colorType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Color"); - color32Type = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Color32"); - quaternionType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Quaternion"); - rectType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Rect"); - planeType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Plane"); - rayType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Ray"); - matrixType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Matrix4x4"); - gameObjectType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.GameObject"); - transformType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Transform"); - unityObjectType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Object"); + vector2Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector2"); + vector3Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector3"); + vector4Type = UnityAssembly.MainModule.GetType("UnityEngine.Vector4"); + colorType = UnityAssembly.MainModule.GetType("UnityEngine.Color"); + color32Type = UnityAssembly.MainModule.GetType("UnityEngine.Color32"); + quaternionType = UnityAssembly.MainModule.GetType("UnityEngine.Quaternion"); + rectType = UnityAssembly.MainModule.GetType("UnityEngine.Rect"); + planeType = UnityAssembly.MainModule.GetType("UnityEngine.Plane"); + rayType = UnityAssembly.MainModule.GetType("UnityEngine.Ray"); + matrixType = UnityAssembly.MainModule.GetType("UnityEngine.Matrix4x4"); + gameObjectType = UnityAssembly.MainModule.GetType("UnityEngine.GameObject"); + transformType = UnityAssembly.MainModule.GetType("UnityEngine.Transform"); + unityObjectType = UnityAssembly.MainModule.GetType("UnityEngine.Object"); - NetworkClientType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkClient"); - NetworkServerType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkServer"); + NetworkClientType = NetAssembly.MainModule.GetType("Mirror.NetworkClient"); + NetworkServerType = NetAssembly.MainModule.GetType("Mirror.NetworkServer"); - SyncVarType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.SyncVarAttribute"); - CommandType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.CommandAttribute"); - ClientRpcType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.ClientRpcAttribute"); - TargetRpcType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.TargetRpcAttribute"); - SyncEventType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.SyncEventAttribute"); - SyncObjectType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.SyncObject"); + 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() @@ -1040,15 +1040,15 @@ static void SetupCorLib() AssemblyNameReference name = AssemblyNameReference.Parse("mscorlib"); ReaderParameters parameters = new ReaderParameters { - AssemblyResolver = scriptDef.MainModule.AssemblyResolver, + AssemblyResolver = CurrentAssembly.MainModule.AssemblyResolver, }; - corLib = scriptDef.MainModule.AssemblyResolver.Resolve(name, parameters).MainModule; + CorLibModule = CurrentAssembly.MainModule.AssemblyResolver.Resolve(name, parameters).MainModule; } static TypeReference ImportCorLibType(string fullName) { - TypeDefinition type = corLib.GetType(fullName) ?? corLib.ExportedTypes.First(t => t.FullName == fullName).Resolve(); - return scriptDef.MainModule.ImportReference(type); + TypeDefinition type = CorLibModule.GetType(fullName) ?? CorLibModule.ExportedTypes.First(t => t.FullName == fullName).Resolve(); + return CurrentAssembly.MainModule.ImportReference(type); } static void SetupTargetTypes() @@ -1076,108 +1076,102 @@ static void SetupTargetTypes() IEnumeratorType = ImportCorLibType("System.Collections.IEnumerator"); guidType = ImportCorLibType("System.Guid"); - NetworkReaderType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkReader"); + NetworkReaderType = NetAssembly.MainModule.GetType("Mirror.NetworkReader"); NetworkReaderDef = NetworkReaderType.Resolve(); - NetworkReaderCtor = Resolvers.ResolveMethod(NetworkReaderDef, scriptDef, ".ctor"); + NetworkReaderCtor = Resolvers.ResolveMethod(NetworkReaderDef, CurrentAssembly, ".ctor"); - NetworkWriterType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkWriter"); + NetworkWriterType = NetAssembly.MainModule.GetType("Mirror.NetworkWriter"); NetworkWriterDef = NetworkWriterType.Resolve(); - NetworkWriterCtor = Resolvers.ResolveMethod(NetworkWriterDef, scriptDef, ".ctor"); + NetworkWriterCtor = Resolvers.ResolveMethod(NetworkWriterDef, CurrentAssembly, ".ctor"); - NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, scriptDef, "get_active"); - NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, scriptDef, "get_localClientActive"); - NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, scriptDef, "get_active"); + NetworkServerGetActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_active"); + NetworkServerGetLocalClientActive = Resolvers.ResolveMethod(NetworkServerType, CurrentAssembly, "get_localClientActive"); + NetworkClientGetActive = Resolvers.ResolveMethod(NetworkClientType, CurrentAssembly, "get_active"); - NetworkReaderReadInt32 = Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadInt32"); + NetworkReaderReadInt32 = Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadInt32"); - NetworkWriterWriteInt32 = Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", int32Type); - NetworkWriterWriteInt16 = Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", int16Type); + NetworkWriterWriteInt32 = Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", int32Type); + NetworkWriterWriteInt16 = Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", int16Type); - NetworkReaderReadPacked32 = Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadPackedUInt32"); - NetworkReaderReadPacked64 = Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadPackedUInt64"); - NetworkReaderReadByte = Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadByte"); + NetworkReaderReadPacked32 = Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadPackedUInt32"); + NetworkReaderReadPacked64 = Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadPackedUInt64"); + NetworkReaderReadByte = Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadByte"); - NetworkWriterWritePacked32 = Resolvers.ResolveMethod(NetworkWriterType, scriptDef, "WritePackedUInt32"); - NetworkWriterWritePacked64 = Resolvers.ResolveMethod(NetworkWriterType, scriptDef, "WritePackedUInt64"); + NetworkWriterWritePacked32 = Resolvers.ResolveMethod(NetworkWriterType, CurrentAssembly, "WritePackedUInt32"); + NetworkWriterWritePacked64 = Resolvers.ResolveMethod(NetworkWriterType, CurrentAssembly, "WritePackedUInt64"); - NetworkReadUInt16 = Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadUInt16"); - NetworkWriteUInt16 = Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", uint16Type); + NetworkReadUInt16 = Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadUInt16"); + NetworkWriteUInt16 = Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", uint16Type); - CmdDelegateReference = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkBehaviour/CmdDelegate"); - CmdDelegateConstructor = Resolvers.ResolveMethod(CmdDelegateReference, scriptDef, ".ctor"); - scriptDef.MainModule.ImportReference(gameObjectType); - scriptDef.MainModule.ImportReference(transformType); + CmdDelegateReference = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour/CmdDelegate"); + CmdDelegateConstructor = Resolvers.ResolveMethod(CmdDelegateReference, CurrentAssembly, ".ctor"); + CurrentAssembly.MainModule.ImportReference(gameObjectType); + CurrentAssembly.MainModule.ImportReference(transformType); - TypeReference unetViewTmp = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkIdentity"); - NetworkIdentityType = scriptDef.MainModule.ImportReference(unetViewTmp); + TypeReference netViewTmp = NetAssembly.MainModule.GetType("Mirror.NetworkIdentity"); + NetworkIdentityType = CurrentAssembly.MainModule.ImportReference(netViewTmp); - NetworkBehaviourType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkBehaviour"); - NetworkBehaviourType2 = scriptDef.MainModule.ImportReference(NetworkBehaviourType); - NetworkConnectionType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkConnection"); + NetworkBehaviourType = NetAssembly.MainModule.GetType("Mirror.NetworkBehaviour"); + NetworkBehaviourType2 = CurrentAssembly.MainModule.ImportReference(NetworkBehaviourType); + NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection"); - MonoBehaviourType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.MonoBehaviour"); - ScriptableObjectType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.ScriptableObject"); + MonoBehaviourType = UnityAssembly.MainModule.GetType("UnityEngine.MonoBehaviour"); + ScriptableObjectType = UnityAssembly.MainModule.GetType("UnityEngine.ScriptableObject"); - NetworkConnectionType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.NetworkConnection"); - NetworkConnectionType = scriptDef.MainModule.ImportReference(NetworkConnectionType); + NetworkConnectionType = NetAssembly.MainModule.GetType("Mirror.NetworkConnection"); + NetworkConnectionType = CurrentAssembly.MainModule.ImportReference(NetworkConnectionType); - ULocalConnectionToServerType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.ULocalConnectionToServer"); - ULocalConnectionToServerType = scriptDef.MainModule.ImportReference(ULocalConnectionToServerType); + MessageBaseType = NetAssembly.MainModule.GetType("Mirror.MessageBase"); + SyncListStructType = NetAssembly.MainModule.GetType("Mirror.SyncListSTRUCT`1"); - ULocalConnectionToClientType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.ULocalConnectionToClient"); - ULocalConnectionToClientType = scriptDef.MainModule.ImportReference(ULocalConnectionToClientType); + NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, CurrentAssembly, "syncVarDirtyBits"); - MessageBaseType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.MessageBase"); - SyncListStructType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.SyncListSTRUCT`1"); - - NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, scriptDef, "syncVarDirtyBits"); - - ComponentType = m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Component"); - ClientSceneType = m_UNetAssemblyDefinition.MainModule.GetType("Mirror.ClientScene"); - ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, scriptDef, "get_readyConnection"); + ComponentType = UnityAssembly.MainModule.GetType("UnityEngine.Component"); + ClientSceneType = NetAssembly.MainModule.GetType("Mirror.ClientScene"); + ReadyConnectionReference = Resolvers.ResolveMethod(ClientSceneType, CurrentAssembly, "get_readyConnection"); // get specialized GetComponent() - getComponentReference = Resolvers.ResolveMethodGeneric(ComponentType, scriptDef, "GetComponent", NetworkIdentityType); + getComponentReference = Resolvers.ResolveMethodGeneric(ComponentType, CurrentAssembly, "GetComponent", NetworkIdentityType); - getUNetIdReference = Resolvers.ResolveMethod(unetViewTmp, scriptDef, "get_netId"); + getNetIdReference = Resolvers.ResolveMethod(netViewTmp, CurrentAssembly, "get_netId"); - gameObjectInequality = Resolvers.ResolveMethod(unityObjectType, scriptDef, "op_Inequality"); + gameObjectInequality = Resolvers.ResolveMethod(unityObjectType, CurrentAssembly, "op_Inequality"); - UBehaviourIsServer = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "get_isServer"); - setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SetSyncVar"); - setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "set_syncVarHookGuard"); - getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "get_syncVarHookGuard"); + UBehaviourIsServer = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "get_isServer"); + setSyncVarReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "SetSyncVar"); + setSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "set_syncVarHookGuard"); + getSyncVarHookGuard = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "get_syncVarHookGuard"); - setSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SetSyncVarGameObject"); - getSyncVarGameObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "GetSyncVarGameObject"); - setSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SetSyncVarNetworkIdentity"); - getSyncVarNetworkIdentityReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "GetSyncVarNetworkIdentity"); - registerCommandDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterCommandDelegate"); - registerRpcDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterRpcDelegate"); - registerEventDelegateReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "RegisterEventDelegate"); - getTypeReference = Resolvers.ResolveMethod(objectType, scriptDef, "GetType"); - getTypeFromHandleReference = Resolvers.ResolveMethod(typeType, scriptDef, "GetTypeFromHandle"); - logErrorReference = Resolvers.ResolveMethod(m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Debug"), scriptDef, "LogError"); - logWarningReference = Resolvers.ResolveMethod(m_UnityAssemblyDefinition.MainModule.GetType("UnityEngine.Debug"), scriptDef, "LogWarning"); - sendCommandInternal = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SendCommandInternal"); - sendRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SendRPCInternal"); - sendTargetRpcInternal = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SendTargetRPCInternal"); - sendEventInternal = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "SendEventInternal"); + 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 = scriptDef.MainModule.ImportReference(SyncObjectType); - InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, scriptDef, "InitSyncObject"); + SyncObjectType = CurrentAssembly.MainModule.ImportReference(SyncObjectType); + InitSyncObjectReference = Resolvers.ResolveMethod(NetworkBehaviourType, CurrentAssembly, "InitSyncObject"); } static void SetupReadFunctions() { - lists.readFuncs = new Dictionary + WeaveLists.readFuncs = new Dictionary { - { singleType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadSingle") }, - { doubleType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadDouble") }, - { boolType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadBoolean") }, - { stringType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadString") }, + { singleType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadSingle") }, + { doubleType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadDouble") }, + { boolType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadBoolean") }, + { stringType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadString") }, { int64Type.FullName, NetworkReaderReadPacked64 }, { uint64Type.FullName, NetworkReaderReadPacked64 }, { int32Type.FullName, NetworkReaderReadPacked32 }, @@ -1187,33 +1181,33 @@ static void SetupReadFunctions() { byteType.FullName, NetworkReaderReadPacked32 }, { sbyteType.FullName, NetworkReaderReadPacked32 }, { charType.FullName, NetworkReaderReadPacked32 }, - { decimalType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadDecimal") }, - { vector2Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadVector2") }, - { vector3Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadVector3") }, - { vector4Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadVector4") }, - { colorType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadColor") }, - { color32Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadColor32") }, - { quaternionType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadQuaternion") }, - { rectType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadRect") }, - { planeType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadPlane") }, - { rayType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadRay") }, - { matrixType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadMatrix4x4") }, - { guidType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadGuid") }, - { gameObjectType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadGameObject") }, - { NetworkIdentityType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadNetworkIdentity") }, - { transformType.FullName, Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadTransform") }, - { "System.Byte[]", Resolvers.ResolveMethod(NetworkReaderType, scriptDef, "ReadBytesAndSize") }, + { decimalType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadDecimal") }, + { vector2Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector2") }, + { vector3Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector3") }, + { vector4Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadVector4") }, + { colorType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor") }, + { color32Type.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadColor32") }, + { quaternionType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadQuaternion") }, + { rectType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadRect") }, + { planeType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadPlane") }, + { rayType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadRay") }, + { matrixType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadMatrix4x4") }, + { guidType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadGuid") }, + { gameObjectType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadGameObject") }, + { NetworkIdentityType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadNetworkIdentity") }, + { transformType.FullName, Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadTransform") }, + { "System.Byte[]", Resolvers.ResolveMethod(NetworkReaderType, CurrentAssembly, "ReadBytesAndSize") }, }; } static void SetupWriteFunctions() { - lists.writeFuncs = new Dictionary + WeaveLists.writeFuncs = new Dictionary { - { singleType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", singleType) }, - { doubleType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", doubleType) }, - { boolType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", boolType) }, - { stringType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", stringType) }, + { singleType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", singleType) }, + { doubleType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", doubleType) }, + { boolType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", boolType) }, + { stringType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", stringType) }, { int64Type.FullName, NetworkWriterWritePacked64 }, { uint64Type.FullName, NetworkWriterWritePacked64 }, { int32Type.FullName, NetworkWriterWritePacked32 }, @@ -1223,22 +1217,22 @@ static void SetupWriteFunctions() { byteType.FullName, NetworkWriterWritePacked32 }, { sbyteType.FullName, NetworkWriterWritePacked32 }, { charType.FullName, NetworkWriterWritePacked32 }, - { decimalType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", decimalType) }, - { vector2Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", vector2Type) }, - { vector3Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", vector3Type) }, - { vector4Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", vector4Type) }, - { colorType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", colorType) }, - { color32Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", color32Type) }, - { quaternionType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", quaternionType) }, - { rectType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", rectType) }, - { planeType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", planeType) }, - { rayType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", rayType) }, - { matrixType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", matrixType) }, - { guidType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", guidType) }, - { gameObjectType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", gameObjectType) }, - { NetworkIdentityType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", NetworkIdentityType) }, - { transformType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "Write", transformType) }, - { "System.Byte[]", Resolvers.ResolveMethodWithArg(NetworkWriterType, scriptDef, "WriteBytesAndSize", "System.Byte[]") } + { decimalType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", decimalType) }, + { vector2Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector2Type) }, + { vector3Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector3Type) }, + { vector4Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", vector4Type) }, + { colorType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", colorType) }, + { color32Type.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", color32Type) }, + { quaternionType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", quaternionType) }, + { rectType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", rectType) }, + { planeType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", planeType) }, + { rayType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", rayType) }, + { matrixType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", matrixType) }, + { guidType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", guidType) }, + { gameObjectType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", gameObjectType) }, + { NetworkIdentityType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", NetworkIdentityType) }, + { transformType.FullName, Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "Write", transformType) }, + { "System.Byte[]", Resolvers.ResolveMethodWithArg(NetworkWriterType, CurrentAssembly, "WriteBytesAndSize", "System.Byte[]") } }; } @@ -1252,14 +1246,13 @@ 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. - string assembly = scriptDef.MainModule.Name; + string assembly = CurrentAssembly.MainModule.Name; if (variable.Module.Name != assembly) { - Log.Error("parameter [" + variable.Name + + Weaver.Error("parameter [" + variable.Name + "] is of the type [" + variable.FullName + "] is not a valid type, please make sure to use a valid type."); - fail = true; return false; } return true; @@ -1392,128 +1385,123 @@ static bool CheckSyncListStruct(TypeDefinition td) return didWork; } - static bool Weave(string assName, IEnumerable dependencies, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string unityUNetDLLPath, string outputDir) + static bool Weave(string assName, IEnumerable dependencies, IAssemblyResolver assemblyResolver, string unityEngineDLLPath, string mirrorNetDLLPath, string outputDir) { - ReaderParameters readParams = Helpers.ReaderParameters(assName, dependencies, assemblyResolver, unityEngineDLLPath, unityUNetDLLPath); - scriptDef = AssemblyDefinition.ReadAssembly(assName, readParams); + ReaderParameters readParams = Helpers.ReaderParameters(assName, dependencies, assemblyResolver, unityEngineDLLPath, mirrorNetDLLPath); - SetupTargetTypes(); - SetupReadFunctions(); - SetupWriteFunctions(); - - ModuleDefinition moduleDefinition = scriptDef.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++) + string pdbToDelete = null; + using (CurrentAssembly = AssemblyDefinition.ReadAssembly(assName, readParams)) { - System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); - foreach (TypeDefinition td in moduleDefinition.Types) + SetupTargetTypes(); + SetupReadFunctions(); + SetupWriteFunctions(); + + 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++) { - if (td.IsClass && td.BaseType.CanBeResolved()) + System.Diagnostics.Stopwatch watch = System.Diagnostics.Stopwatch.StartNew(); + foreach (TypeDefinition td in moduleDefinition.Types) { - try + if (td.IsClass && td.BaseType.CanBeResolved()) { - if (pass == 0) + try { - didWork |= CheckSyncListStruct(td); + if (pass == 0) + { + didWork |= CheckSyncListStruct(td); + } + else + { + didWork |= CheckNetworkBehaviour(td); + didWork |= CheckMessageBase(td); + } } - else + catch (Exception ex) { - didWork |= CheckNetworkBehaviour(td); - didWork |= CheckMessageBase(td); + if (CurrentAssembly.MainModule.SymbolReader != null) + CurrentAssembly.MainModule.SymbolReader.Dispose(); + Weaver.Error(ex.Message); + throw ex; } } - catch (Exception ex) + + if (WeavingFailed) { - if (scriptDef.MainModule.SymbolReader != null) - scriptDef.MainModule.SymbolReader.Dispose(); - fail = true; - throw ex; + if (CurrentAssembly.MainModule.SymbolReader != null) + CurrentAssembly.MainModule.SymbolReader.Dispose(); + return false; } } - - if (fail) - { - if (scriptDef.MainModule.SymbolReader != null) - scriptDef.MainModule.SymbolReader.Dispose(); - 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 - { - ProcessPropertySites(); - } - catch (Exception e) - { - Log.Error("ProcessPropertySites exception: " + e); - if (scriptDef.MainModule.SymbolReader != null) - scriptDef.MainModule.SymbolReader.Dispose(); - return false; + watch.Stop(); + Console.WriteLine("Pass: " + pass + " took " + watch.ElapsedMilliseconds + " milliseconds"); } - if (fail) + if (didWork) { - //Log.Error("Failed phase II."); - if (scriptDef.MainModule.SymbolReader != null) - scriptDef.MainModule.SymbolReader.Dispose(); - return false; - } - - string dest = Helpers.DestinationFileFor(outputDir, assName); - //Console.WriteLine ("Output:" + dest); - - WriterParameters writeParams = Helpers.GetWriterParameters(readParams); - - // PdbWriterProvider uses ISymUnmanagedWriter2 COM interface but Mono can't invoke a method on it and crashes (actually it first throws the following exception and then crashes). - // One solution would be to convert UNetWeaver to exe file and run it on .NET on Windows (I have tested that and it works). - // However it's much more simple to just write mdb file. - // System.NullReferenceException: Object reference not set to an instance of an object - // at(wrapper cominterop - invoke) Mono.Cecil.Pdb.ISymUnmanagedWriter2:DefineDocument(string, System.Guid &, System.Guid &, System.Guid &, Mono.Cecil.Pdb.ISymUnmanagedDocumentWriter &) - // at Mono.Cecil.Pdb.SymWriter.DefineDocument(System.String url, Guid language, Guid languageVendor, Guid documentType)[0x00000] in < filename unknown >:0 - if (writeParams.SymbolWriterProvider is PdbWriterProvider) - { - writeParams.SymbolWriterProvider = new MdbWriterProvider(); - // old pdb file is out of date so delete it. symbols will be stored in mdb - string pdb = Path.ChangeExtension(assName, ".pdb"); - + // this must be done for ALL code, not just NetworkBehaviours try { - File.Delete(pdb); + ProcessPropertySites(); } - catch (Exception ex) + catch (Exception e) { - // workaround until Unity fixes C#7 compiler compability with the UNET weaver - UnityEngine.Debug.LogWarning(string.Format("Unable to delete file {0}: {1}", pdb, ex.Message)); + Log.Error("ProcessPropertySites exception: " + e); + if (CurrentAssembly.MainModule.SymbolReader != null) + CurrentAssembly.MainModule.SymbolReader.Dispose(); + return false; + } + + if (WeavingFailed) + { + //Log.Error("Failed phase II."); + if (CurrentAssembly.MainModule.SymbolReader != null) + CurrentAssembly.MainModule.SymbolReader.Dispose(); + return false; + } + + string dest = Helpers.DestinationFileFor(outputDir, assName); + //Console.WriteLine ("Output:" + dest); + + WriterParameters writeParams = Helpers.GetWriterParameters(readParams); + CurrentAssembly.Write(dest, writeParams); + + // PdbWriterProvider uses ISymUnmanagedWriter2 COM interface but Mono can't invoke a method on it and crashes (actually it first throws the following exception and then crashes). + // One solution would be to convert UNetWeaver to exe file and run it on .NET on Windows (I have tested that and it works). + // However it's much more simple to just write mdb file. + // System.NullReferenceException: Object reference not set to an instance of an object + // at(wrapper cominterop - invoke) Mono.Cecil.Pdb.ISymUnmanagedWriter2:DefineDocument(string, System.Guid &, System.Guid &, System.Guid &, Mono.Cecil.Pdb.ISymUnmanagedDocumentWriter &) + // at Mono.Cecil.Pdb.SymWriter.DefineDocument(System.String url, Guid language, Guid languageVendor, Guid documentType)[0x00000] in < filename unknown >:0 + if (writeParams.SymbolWriterProvider is PdbWriterProvider) + { + writeParams.SymbolWriterProvider = new MdbWriterProvider(); + // old pdb file is out of date so delete it. symbols will be stored in mdb + pdbToDelete = Path.ChangeExtension(assName, ".pdb"); } } - scriptDef.Write(dest, writeParams); + if (CurrentAssembly.MainModule.SymbolReader != null) + CurrentAssembly.MainModule.SymbolReader.Dispose(); } - if (scriptDef.MainModule.SymbolReader != null) - scriptDef.MainModule.SymbolReader.Dispose(); + if (pdbToDelete != null) + File.Delete(pdbToDelete); return true; } - public static bool WeaveAssemblies(IEnumerable assemblies, IEnumerable dependencies, IAssemblyResolver assemblyResolver, string outputDir, string unityEngineDLLPath, string unityUNetDLLPath) + public static bool WeaveAssemblies(IEnumerable assemblies, IEnumerable dependencies, IAssemblyResolver assemblyResolver, string outputDir, string unityEngineDLLPath, string mirrorNetDLLPath) { - fail = false; - lists = new WeaverLists(); + WeavingFailed = false; + WeaveLists = new WeaverLists(); - m_UnityAssemblyDefinition = AssemblyDefinition.ReadAssembly(unityEngineDLLPath); - m_UNetAssemblyDefinition = AssemblyDefinition.ReadAssembly(unityUNetDLLPath); + UnityAssembly = AssemblyDefinition.ReadAssembly(unityEngineDLLPath); + NetAssembly = AssemblyDefinition.ReadAssembly(mirrorNetDLLPath); SetupUnityTypes(); @@ -1521,7 +1509,7 @@ public static bool WeaveAssemblies(IEnumerable assemblies, IEnumerable assemblies, IEnumerable().simulated = true; - - // Initial Velocity GetComponent().velocity = Vector2.right * speed; } @@ -55,4 +52,4 @@ void OnCollisionEnter2D(Collision2D col) } } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs b/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs new file mode 100644 index 000000000..3dca95a26 --- /dev/null +++ b/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs @@ -0,0 +1,38 @@ +// custom NetworkManager that simply assigns the correct racket positions when +// spawning players. the built in RoundRobin spawn method wouldn't work after +// someone reconnects (both players would be on the same side). +using UnityEngine; +using Mirror; + +public class NetworkManagerPong : NetworkManager +{ + public Transform leftRacketSpawn; + public Transform rightRacketSpawn; + public GameObject ballPrefab; + GameObject ball; + + public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) + { + // add player at correct spawn position + Transform start = numPlayers == 0 ? leftRacketSpawn : rightRacketSpawn; + GameObject player = Instantiate(playerPrefab, start.position, start.rotation); + NetworkServer.AddPlayerForConnection(conn, player); + + // spawn ball if two players + if (numPlayers == 2) + { + ball = Instantiate(ballPrefab); + NetworkServer.Spawn(ball); + } + } + + public override void OnServerDisconnect(NetworkConnection conn) + { + // destroy ball + if (ball != null) + NetworkServer.Destroy(ball); + + // call base functionality (actually destroys the player) + base.OnServerDisconnect(conn); + } +} diff --git a/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs.meta b/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs.meta new file mode 100644 index 000000000..4b191ea13 --- /dev/null +++ b/Assets/Mirror/Examples/Pong/Scripts/NetworkManagerPong.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0aa3018bb284840d6a6d0acee29ab098 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Pong/Scripts/Player.cs b/Assets/Mirror/Examples/Pong/Scripts/Player.cs index 446653730..e7967cb20 100644 --- a/Assets/Mirror/Examples/Pong/Scripts/Player.cs +++ b/Assets/Mirror/Examples/Pong/Scripts/Player.cs @@ -1,5 +1,4 @@ -using UnityEngine; -using Mirror; +using UnityEngine; namespace Mirror.Examples.Pong { @@ -15,7 +14,7 @@ void FixedUpdate() if (!isLocalPlayer) return; float vertical = Input.GetAxisRaw("Vertical"); - GetComponent().velocity = new Vector2(0, vertical) * speed; + GetComponent().velocity = new Vector2(0, vertical) * speed * Time.fixedDeltaTime; } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Examples/Movement.meta b/Assets/Mirror/Examples/Tanks.meta similarity index 100% rename from Assets/Mirror/Examples/Movement.meta rename to Assets/Mirror/Examples/Tanks.meta diff --git a/Assets/Mirror/Examples/Movement/Models.meta b/Assets/Mirror/Examples/Tanks/Models.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Models.meta rename to Assets/Mirror/Examples/Tanks/Models.meta diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank.meta new file mode 100644 index 000000000..93b007545 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: d8b3e43538fc240feb28e07816c7e733 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png new file mode 100644 index 000000000..bcd4bee1a Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png differ diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/SPECULAR_Character.png.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png.meta similarity index 74% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/SPECULAR_Character.png.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png.meta index 8b2b3cd6c..6e069a18a 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/SPECULAR_Character.png.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/BaseColor.png.meta @@ -1,12 +1,12 @@ fileFormatVersion: 2 -guid: 029d4d06af6a74673b65859a0a3af0f6 +guid: ce00d67f9368944fa8ef4de6ccc77bfa TextureImporter: fileIDToRecycleName: {} externalObjects: {} - serializedVersion: 4 + serializedVersion: 9 mipmaps: mipMapMode: 0 - enableMipMap: 0 + enableMipMap: 1 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -21,6 +21,8 @@ TextureImporter: heightScale: 0.25 normalMapFilter: 0 isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -31,14 +33,14 @@ TextureImporter: serializedVersion: 2 filterMode: -1 aniso: -1 - mipBias: -1 - wrapU: 1 - wrapV: 1 - wrapW: 1 - nPOTScale: 0 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 0 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -47,15 +49,17 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 1 + alphaIsTransparency: 0 spriteTessellationDetail: -1 - textureType: 8 + textureType: 0 textureShape: 1 + singleChannelComponent: 0 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 platformSettings: - - buildTarget: DefaultTexturePlatform + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 @@ -70,7 +74,15 @@ TextureImporter: sprites: [] outline: [] physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller new file mode 100644 index 000000000..e05d28cf3 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller @@ -0,0 +1,272 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!91 &9100000 +AnimatorController: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Controller + serializedVersion: 5 + m_AnimatorParameters: + - m_Name: Moving + m_Type: 4 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 0} + - m_Name: Shoot + m_Type: 9 + m_DefaultFloat: 0 + m_DefaultInt: 0 + m_DefaultBool: 0 + m_Controller: {fileID: 0} + m_AnimatorLayers: + - serializedVersion: 5 + m_Name: Base Layer + m_StateMachine: {fileID: 1107772262116321704} + m_Mask: {fileID: 0} + m_Motions: [] + m_Behaviours: [] + m_BlendingMode: 0 + m_SyncedLayerIndex: -1 + m_DefaultWeight: 0 + m_IKPass: 0 + m_SyncedLayerAffectsTiming: 0 + m_Controller: {fileID: 9100000} +--- !u!1101 &1101104249963802978 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: Moving + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 1102824315819425342} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.6 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &1101366829127142966 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: Shoot + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 1102254808008813326} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0 + m_TransitionOffset: 0 + m_ExitTime: 1 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &1101806660142692138 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: Moving + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 1102207974245764242} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.6 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &1101862483397811748 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 2 + m_ConditionEvent: Moving + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 1102207974245764242} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1101 &1101947542735704306 +AnimatorStateTransition: + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + m_Conditions: + - m_ConditionMode: 1 + m_ConditionEvent: Moving + m_EventTreshold: 0 + m_DstStateMachine: {fileID: 0} + m_DstState: {fileID: 1102824315819425342} + m_Solo: 0 + m_Mute: 0 + m_IsExit: 0 + serializedVersion: 3 + m_TransitionDuration: 0.25 + m_TransitionOffset: 0 + m_ExitTime: 0.75 + m_HasExitTime: 0 + m_HasFixedDuration: 1 + m_InterruptionSource: 0 + m_OrderedInterruption: 1 + m_CanTransitionToSelf: 1 +--- !u!1102 &1102207974245764242 +AnimatorState: + serializedVersion: 5 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Idle + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 1101947542735704306} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 0} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &1102254808008813326 +AnimatorState: + serializedVersion: 5 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Shoot + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 1101104249963802978} + - {fileID: 1101806660142692138} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400006, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1102 &1102824315819425342 +AnimatorState: + serializedVersion: 5 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Moving + m_Speed: 1 + m_CycleOffset: 0 + m_Transitions: + - {fileID: 1101862483397811748} + m_StateMachineBehaviours: [] + m_Position: {x: 50, y: 50, z: 0} + m_IKOnFeet: 0 + m_WriteDefaultValues: 1 + m_Mirror: 0 + m_SpeedParameterActive: 0 + m_MirrorParameterActive: 0 + m_CycleOffsetParameterActive: 0 + m_TimeParameterActive: 0 + m_Motion: {fileID: 7400004, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_Tag: + m_SpeedParameter: + m_MirrorParameter: + m_CycleOffsetParameter: + m_TimeParameter: +--- !u!1107 &1107772262116321704 +AnimatorStateMachine: + serializedVersion: 5 + m_ObjectHideFlags: 1 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Base Layer + m_ChildStates: + - serializedVersion: 1 + m_State: {fileID: 1102207974245764242} + m_Position: {x: 252, y: 48, z: 0} + - serializedVersion: 1 + m_State: {fileID: 1102824315819425342} + m_Position: {x: 252, y: 204, z: 0} + - serializedVersion: 1 + m_State: {fileID: 1102254808008813326} + m_Position: {x: 420, y: 120, z: 0} + m_ChildStateMachines: [] + m_AnyStateTransitions: + - {fileID: 1101366829127142966} + m_EntryTransitions: [] + m_StateMachineTransitions: {} + m_StateMachineBehaviours: [] + m_AnyStatePosition: {x: 60, y: 132, z: 0} + m_EntryPosition: {x: 60, y: 168, z: 0} + m_ExitPosition: {x: 60, y: 96, z: 0} + m_ParentStateMachinePosition: {x: 800, y: 20, z: 0} + m_DefaultState: {fileID: 1102207974245764242} diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller.meta new file mode 100644 index 000000000..7e1061cc9 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Controller.controller.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: a7211483bbd794b6d85ed88576e7d85c +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 9100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png new file mode 100644 index 000000000..61ca0cbaf Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png differ diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Diffuse.png.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png.meta similarity index 74% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Diffuse.png.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png.meta index 85e78012e..333174c97 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Diffuse.png.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Emissive.png.meta @@ -1,12 +1,12 @@ fileFormatVersion: 2 -guid: e79a606e2465b49168ad7785fd79e9d3 +guid: 9b3e91ab0048a4aa3a17706a349c6bf5 TextureImporter: fileIDToRecycleName: {} externalObjects: {} - serializedVersion: 4 + serializedVersion: 9 mipmaps: mipMapMode: 0 - enableMipMap: 0 + enableMipMap: 1 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -21,6 +21,8 @@ TextureImporter: heightScale: 0.25 normalMapFilter: 0 isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -31,14 +33,14 @@ TextureImporter: serializedVersion: 2 filterMode: -1 aniso: -1 - mipBias: -1 - wrapU: 1 - wrapV: 1 - wrapW: 1 - nPOTScale: 0 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 0 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -47,15 +49,17 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 1 + alphaIsTransparency: 0 spriteTessellationDetail: -1 - textureType: 8 + textureType: 0 textureShape: 1 + singleChannelComponent: 0 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 platformSettings: - - buildTarget: DefaultTexturePlatform + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 @@ -70,7 +74,15 @@ TextureImporter: sprites: [] outline: [] physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png new file mode 100644 index 000000000..d5d5559fe Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png differ diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Emission.png.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png.meta similarity index 74% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Emission.png.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png.meta index 8a8c6bc82..91006e4d0 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Emission.png.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Metallic.png.meta @@ -1,12 +1,12 @@ fileFormatVersion: 2 -guid: 1df5a55dca9084efba7314d168c7106f +guid: a7467e18a834e4d1390091c8b1ea562c TextureImporter: fileIDToRecycleName: {} externalObjects: {} - serializedVersion: 4 + serializedVersion: 9 mipmaps: mipMapMode: 0 - enableMipMap: 0 + enableMipMap: 1 sRGBTexture: 1 linearTexture: 0 fadeOut: 0 @@ -21,6 +21,8 @@ TextureImporter: heightScale: 0.25 normalMapFilter: 0 isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -31,14 +33,14 @@ TextureImporter: serializedVersion: 2 filterMode: -1 aniso: -1 - mipBias: -1 - wrapU: 1 - wrapV: 1 - wrapW: 1 - nPOTScale: 0 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 0 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -47,15 +49,17 @@ TextureImporter: spriteBorder: {x: 0, y: 0, z: 0, w: 0} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 - alphaIsTransparency: 1 + alphaIsTransparency: 0 spriteTessellationDetail: -1 - textureType: 8 + textureType: 0 textureShape: 1 + singleChannelComponent: 0 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 platformSettings: - - buildTarget: DefaultTexturePlatform + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 @@ -70,7 +74,15 @@ TextureImporter: sprites: [] outline: [] physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png new file mode 100644 index 000000000..f1a448fa2 Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png differ diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Normal.png.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png.meta similarity index 77% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Normal.png.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png.meta index 96b51d8f9..acc128a45 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Turret-Normal.png.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Normal.png.meta @@ -1,9 +1,9 @@ fileFormatVersion: 2 -guid: a623522aaab404bfb886bdd6fea0df80 +guid: 2617bfecca4d44805a3a51a7aa215d7c TextureImporter: fileIDToRecycleName: {} externalObjects: {} - serializedVersion: 4 + serializedVersion: 9 mipmaps: mipMapMode: 0 enableMipMap: 1 @@ -21,6 +21,8 @@ TextureImporter: heightScale: 0.25 normalMapFilter: 0 isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 @@ -31,14 +33,14 @@ TextureImporter: serializedVersion: 2 filterMode: -1 aniso: -1 - mipBias: -1 - wrapU: 1 - wrapV: 1 - wrapW: 1 - nPOTScale: 0 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 lightmap: 0 compressionQuality: 50 - spriteMode: 1 + spriteMode: 0 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 @@ -51,11 +53,13 @@ TextureImporter: spriteTessellationDetail: -1 textureType: 1 textureShape: 1 + singleChannelComponent: 0 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 platformSettings: - - buildTarget: DefaultTexturePlatform + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform maxTextureSize: 2048 resizeAlgorithm: 0 textureFormat: -1 @@ -70,7 +74,15 @@ TextureImporter: sprites: [] outline: [] physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt new file mode 100644 index 000000000..ed1000633 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt @@ -0,0 +1,7 @@ +Recon Tank (update), by Mophs +https://opengameart.org/users/mophs + +Based on original work, Recon Tank, By MNDV.ecb, 2018 Eric Buisson +https://opengameart.org/content/recon-tank + +CC-BY 4.0 \ No newline at end of file diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/readme.txt.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt.meta similarity index 75% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/readme.txt.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt.meta index bf8b56e78..2168518dd 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/readme.txt.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/Recon_Tank - License.txt.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: c7df69c69208a455abb56f1ec2ac9906 +guid: 6e745106dbbc3412bbe43eaf25dabd4f TextScriptImporter: externalObjects: {} userData: diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat new file mode 100644 index 000000000..207b1f77e --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat @@ -0,0 +1,82 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: TankMaterial + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: _EMISSION _METALLICGLOSSMAP _NORMALMAP _SPECGLOSSMAP + m_LightmapFlags: 0 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 2800000, guid: 2617bfecca4d44805a3a51a7aa215d7c, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 2800000, guid: 9b3e91ab0048a4aa3a17706a349c6bf5, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 2800000, guid: ce00d67f9368944fa8ef4de6ccc77bfa, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 2800000, guid: a7467e18a834e4d1390091c8b1ea562c, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _SpecGlossMap: + m_Texture: {fileID: 2800000, guid: a7467e18a834e4d1390091c8b1ea562c, type: 3} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: + - _BumpScale: 1.75 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 0.09 + - _Glossiness: 0.5 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 1, g: 1, b: 1, a: 1} + - _EmissionColor: {r: 1, g: 1, b: 1, a: 1} + - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat.meta similarity index 79% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat.meta rename to Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat.meta index 1b1c7ca87..080f53453 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat.meta +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/TankMaterial.mat.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 2ef04982cb77c4fa984062fdcfc61d20 +guid: 2e67e42170aa64aa9a33424f8045ac89 NativeFormatImporter: externalObjects: {} mainObjectFileID: 2100000 diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx new file mode 100644 index 000000000..5877b0945 Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx differ diff --git a/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx.meta b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx.meta new file mode 100644 index 000000000..1ac1ee013 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Models/(Public Domain) Recon_Tank/reconTank.fbx.meta @@ -0,0 +1,239 @@ +fileFormatVersion: 2 +guid: 38b49695fc0a4418bbc350f2366660c5 +ModelImporter: + serializedVersion: 23 + fileIDToRecycleName: + 100000: Barrel + 100002: Barrel_end + 100004: Chasis + 100006: Recon_Tank + 100008: Recon_Tank_Rig + 100010: //RootNode + 100012: Root + 100014: Turret + 100016: Wheel_Front_L + 100018: Wheel_Front_L_end + 100020: Wheel_Middle_L + 100022: Wheel_Middle_L_end + 100024: Wheel_Rear_L + 100026: Wheel_Rear_L_end + 400000: Barrel + 400002: Barrel_end + 400004: Chasis + 400006: Recon_Tank + 400008: Recon_Tank_Rig + 400010: //RootNode + 400012: Root + 400014: Turret + 400016: Wheel_Front_L + 400018: Wheel_Front_L_end + 400020: Wheel_Middle_L + 400022: Wheel_Middle_L_end + 400024: Wheel_Rear_L + 400026: Wheel_Rear_L_end + 2100000: Recon_Tank + 4300000: Recon_Tank + 7400000: Recon_Tank_Rig|Drive + 7400002: Recon_Tank_Rig|Forward + 7400004: Recon_Tank_Rig|Idle + 7400006: Recon_Tank_Rig|Shoot + 9500000: //RootNode + 13700000: Recon_Tank + externalObjects: {} + materials: + importMaterials: 1 + materialName: 0 + materialSearch: 1 + materialLocation: 1 + animations: + legacyGenerateAnimations: 4 + bakeSimulation: 0 + resampleCurves: 1 + optimizeGameObjects: 0 + motionNodeName: + rigImportErrors: + rigImportWarnings: + animationImportErrors: + animationImportWarnings: + animationRetargetingWarnings: + animationDoRetargetingWarnings: 0 + importAnimatedCustomProperties: 0 + importConstraints: 0 + animationCompression: 1 + animationRotationError: 0.5 + animationPositionError: 0.5 + animationScaleError: 0.5 + animationWrapMode: 0 + extraExposedTransformPaths: [] + extraUserProperties: [] + clipAnimations: + - serializedVersion: 16 + name: Recon_Tank_Rig|Drive + takeName: Recon_Tank_Rig|Drive + firstFrame: 0 + lastFrame: 1 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 0 + loopBlendPositionY: 0 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 0 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 0 + heightFromFeet: 0 + mirror: 0 + bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + curves: [] + events: [] + transformMask: [] + maskType: 3 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: Recon_Tank_Rig|Forward + takeName: Recon_Tank_Rig|Forward + firstFrame: 0 + lastFrame: 25 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 0 + loopBlendPositionY: 0 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 0 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 0 + heightFromFeet: 0 + mirror: 0 + bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + curves: [] + events: [] + transformMask: [] + maskType: 3 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: Recon_Tank_Rig|Idle + takeName: Recon_Tank_Rig|Idle + firstFrame: 0 + lastFrame: 11 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 1 + loopBlend: 1 + loopBlendOrientation: 0 + loopBlendPositionY: 0 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 0 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 0 + heightFromFeet: 0 + mirror: 0 + bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + curves: [] + events: [] + transformMask: [] + maskType: 3 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 + - serializedVersion: 16 + name: Recon_Tank_Rig|Shoot + takeName: Recon_Tank_Rig|Shoot + firstFrame: 0 + lastFrame: 15 + wrapMode: 0 + orientationOffsetY: 0 + level: 0 + cycleOffset: 0 + loop: 0 + hasAdditiveReferencePose: 0 + loopTime: 0 + loopBlend: 0 + loopBlendOrientation: 0 + loopBlendPositionY: 0 + loopBlendPositionXZ: 0 + keepOriginalOrientation: 0 + keepOriginalPositionY: 1 + keepOriginalPositionXZ: 0 + heightFromFeet: 0 + mirror: 0 + bodyMask: 01000000010000000100000001000000010000000100000001000000010000000100000001000000010000000100000001000000 + curves: [] + events: [] + transformMask: [] + maskType: 3 + maskSource: {instanceID: 0} + additiveReferencePoseFrame: 0 + isReadable: 1 + meshes: + lODScreenPercentages: [] + globalScale: 0.15 + meshCompression: 0 + addColliders: 0 + useSRGBMaterialColor: 1 + importVisibility: 1 + importBlendShapes: 1 + importCameras: 1 + importLights: 1 + swapUVChannels: 0 + generateSecondaryUV: 0 + useFileUnits: 1 + optimizeMeshForGPU: 1 + keepQuads: 0 + weldVertices: 1 + preserveHierarchy: 0 + indexFormat: 0 + secondaryUVAngleDistortion: 8 + secondaryUVAreaDistortion: 15.000001 + secondaryUVHardAngle: 88 + secondaryUVPackMargin: 4 + useFileScale: 1 + previousCalculatedGlobalScale: 0.0015 + hasPreviousCalculatedGlobalScale: 1 + tangentSpace: + normalSmoothAngle: 60 + normalImportMode: 0 + tangentImportMode: 3 + normalCalculationMode: 4 + legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 + blendShapeNormalImportMode: 1 + normalSmoothingSource: 0 + importAnimation: 1 + copyAvatar: 0 + humanDescription: + serializedVersion: 2 + human: [] + skeleton: [] + armTwist: 0.5 + foreArmTwist: 0.5 + upperLegTwist: 0.5 + legTwist: 0.5 + armStretch: 0.05 + legStretch: 0.05 + feetSpacing: 0 + rootMotionBoneName: + hasTranslationDoF: 0 + hasExtraRoot: 0 + skeletonHasParents: 1 + lastHumanDescriptionAvatarSource: {instanceID: 0} + animationType: 2 + humanoidOversampling: 1 + additionalBone: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Movement/Prefabs.meta b/Assets/Mirror/Examples/Tanks/Prefabs.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Prefabs.meta rename to Assets/Mirror/Examples/Tanks/Prefabs.meta diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab new file mode 100644 index 000000000..938c99dd5 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab @@ -0,0 +1,246 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &63476987332307980 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8035186136109819211} + - component: {fileID: 9118274893554935717} + - component: {fileID: 69063397099238371} + m_Layer: 0 + m_Name: 3D Model + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8035186136109819211 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 63476987332307980} + m_LocalRotation: {x: 0.7071068, y: 0, z: 0, w: 0.7071068} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 0.05, y: 0.1, z: 0.05} + m_Children: [] + m_Father: {fileID: 24373266488650541} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 90, y: 0, z: 0} +--- !u!33 &9118274893554935717 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 63476987332307980} + m_Mesh: {fileID: 10208, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &69063397099238371 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 63476987332307980} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: cba1b63a0bccc4b12ac25f05d0ae2dd1, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 0 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 +--- !u!1 &5890560936853567077 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 24373266488650541} + - component: {fileID: 1713098107664522388} + - component: {fileID: 2355290524794870353} + - component: {fileID: 4629190479245867726} + - component: {fileID: 7082621516996595528} + m_Layer: 0 + m_Name: Projectile + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &24373266488650541 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5890560936853567077} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 8035186136109819211} + - {fileID: 7830988697844474908} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1713098107664522388 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5890560936853567077} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ServerOnly: 0 + m_LocalPlayerAuthority: 0 + m_AssetId: b7dd46dbf38c643f09e206f9fa4be008 + m_SceneId: 0 +--- !u!136 &2355290524794870353 +CapsuleCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5890560936853567077} + m_Material: {fileID: 0} + m_IsTrigger: 1 + m_Enabled: 1 + m_Radius: 0.05 + m_Height: 0.2 + m_Direction: 1 + m_Center: {x: 0, y: 0, z: 0} +--- !u!54 &4629190479245867726 +Rigidbody: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5890560936853567077} + serializedVersion: 2 + m_Mass: 1 + m_Drag: 0 + m_AngularDrag: 0.05 + m_UseGravity: 0 + m_IsKinematic: 0 + m_Interpolate: 1 + m_Constraints: 0 + m_CollisionDetection: 1 +--- !u!114 &7082621516996595528 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5890560936853567077} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8f49b83f111a64bc7a5275af4f6f930b, type: 3} + m_Name: + m_EditorClassIdentifier: + syncInterval: 0.1 + destroyAfter: 5 + rigidBody: {fileID: 4629190479245867726} + force: 1000 +--- !u!1 &9126921595194253319 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 7830988697844474908} + - component: {fileID: 4878977110396366525} + m_Layer: 0 + m_Name: Point Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &7830988697844474908 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9126921595194253319} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 24373266488650541} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!108 &4878977110396366525 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9126921595194253319} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 2 + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_Intensity: 5 + m_Range: 2 + m_SpotAngle: 30 + m_CookieSize: 10 + m_Shadows: + m_Type: 0 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 3 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab.meta b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab.meta new file mode 100644 index 000000000..f6d4068e9 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b7dd46dbf38c643f09e206f9fa4be008 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab new file mode 100644 index 000000000..f94b6000a --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab @@ -0,0 +1,312 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &1916082411674582 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4492442352427800} + - component: {fileID: 114118589361100106} + - component: {fileID: 2240606817507776182} + - component: {fileID: 114250499875391520} + - component: {fileID: 6900008319038825817} + - component: {fileID: 5194388907919410155} + - component: {fileID: 114654712548978148} + m_Layer: 0 + m_Name: Tank + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4492442352427800 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 7831918942946891954} + - {fileID: 6564220120147636086} + - {fileID: 5718089106632469514} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &114118589361100106 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + m_ServerOnly: 0 + m_LocalPlayerAuthority: 1 + m_AssetId: 6f43bf5488a7443d19ab2a83c6b91f35 + m_SceneId: 0 +--- !u!95 &2240606817507776182 +Animator: + serializedVersion: 3 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Enabled: 1 + m_Avatar: {fileID: 9000000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_Controller: {fileID: 9100000, guid: a7211483bbd794b6d85ed88576e7d85c, type: 2} + m_CullingMode: 0 + m_UpdateMode: 0 + m_ApplyRootMotion: 0 + m_LinearVelocityBlending: 0 + m_WarningMessage: + m_HasTransformHierarchy: 1 + m_AllowConstantClipSamplingOptimization: 1 + m_KeepAnimatorControllerStateOnDisable: 0 +--- !u!114 &114250499875391520 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 2f74aedd71d9a4f55b3ce499326d45fb, type: 3} + m_Name: + m_EditorClassIdentifier: + syncInterval: 0.1 + compressRotation: 1 +--- !u!195 &6900008319038825817 +NavMeshAgent: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Enabled: 1 + m_AgentTypeID: 0 + m_Radius: 0.5 + m_Speed: 1 + m_Acceleration: 1 + avoidancePriority: 50 + m_AngularSpeed: 120 + m_StoppingDistance: 0 + m_AutoTraverseOffMeshLink: 1 + m_AutoBraking: 1 + m_AutoRepath: 1 + m_Height: 0.5 + m_BaseOffset: 0 + m_WalkableMask: 4294967295 + m_ObstacleAvoidanceType: 0 +--- !u!135 &5194388907919410155 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Material: {fileID: 0} + m_IsTrigger: 1 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0.25, z: 0} +--- !u!114 &114654712548978148 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1916082411674582} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7deadf756194d461e9140e42d651693b, type: 3} + m_Name: + m_EditorClassIdentifier: + syncInterval: 0.1 + agent: {fileID: 6900008319038825817} + animator: {fileID: 2240606817507776182} + rotationSpeed: 80 + shootKey: 32 + projectilePrefab: {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, + type: 3} + projectileMount: {fileID: 5718089106632469514} +--- !u!1 &4426914200102054949 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6564220120147636086} + - component: {fileID: 7604806193092689376} + m_Layer: 0 + m_Name: Spot Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6564220120147636086 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4426914200102054949} + m_LocalRotation: {x: 0.02281505, y: -0, z: -0, w: 0.9997397} + m_LocalPosition: {x: 0.07, y: 0.46, z: 0.126} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 4492442352427800} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 2.615, y: 0, z: 0} +--- !u!108 &7604806193092689376 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4426914200102054949} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 0 + m_Color: {r: 1, g: 0.9205329, b: 0.7877358, a: 1} + m_Intensity: 3 + m_Range: 15 + m_SpotAngle: 80 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!1 &4730779867780281009 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5718089106632469514} + m_Layer: 0 + m_Name: ProjectileMount + m_TagString: Untagged + m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5718089106632469514 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4730779867780281009} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0.412, z: 0.936} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 4492442352427800} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1001 &7831918942947279416 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 4492442352427800} + m_Modifications: + - target: {fileID: 100010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_Name + value: 3D Model + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_RootOrder + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 13700000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + propertyPath: m_Materials.Array.data[0] + value: + objectReference: {fileID: 2100000, guid: 2e67e42170aa64aa9a33424f8045ac89, type: 2} + m_RemovedComponents: + - {fileID: 9500000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} + m_SourcePrefab: {fileID: 100100000, guid: 38b49695fc0a4418bbc350f2366660c5, type: 3} +--- !u!4 &7831918942946891954 stripped +Transform: + m_CorrespondingSourceObject: {fileID: 400010, guid: 38b49695fc0a4418bbc350f2366660c5, + type: 3} + m_PrefabInstance: {fileID: 7831918942947279416} + m_PrefabAsset: {fileID: 0} diff --git a/Assets/Mirror/Examples/Movement/Prefabs/Player.prefab.meta b/Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Prefabs/Player.prefab.meta rename to Assets/Mirror/Examples/Tanks/Prefabs/Tank.prefab.meta diff --git a/Assets/Mirror/Examples/Movement/Scenes.meta b/Assets/Mirror/Examples/Tanks/Scenes.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Scenes.meta rename to Assets/Mirror/Examples/Tanks/Scenes.meta diff --git a/Assets/Mirror/Examples/Tanks/Scenes/Scene.meta b/Assets/Mirror/Examples/Tanks/Scenes/Scene.meta new file mode 100644 index 000000000..01af5324b --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Scenes/Scene.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 81cfd31f234d94a0985fe946d2ce699f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Movement/Scenes/Scene.unity b/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity similarity index 73% rename from Assets/Mirror/Examples/Movement/Scenes/Scene.unity rename to Assets/Mirror/Examples/Tanks/Scenes/Scene.unity index 323db4edf..fc8e49e02 100644 --- a/Assets/Mirror/Examples/Movement/Scenes/Scene.unity +++ b/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity @@ -111,7 +111,7 @@ NavMeshSettings: accuratePlacement: 0 debug: m_Flags: 0 - m_NavMeshData: {fileID: 0} + m_NavMeshData: {fileID: 23800000, guid: 0bc607fa2e315482ebe98797e844e11f, type: 2} --- !u!1 &88936773 GameObject: m_ObjectHideFlags: 0 @@ -196,13 +196,99 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 88936773} - m_LocalRotation: {x: 0.49582607, y: -0, z: -0, w: 0.86842185} - m_LocalPosition: {x: 0, y: 8.17, z: -6.07} + m_LocalRotation: {x: 0, y: 0.92387956, z: -0.38268343, w: 0} + m_LocalPosition: {x: 0, y: 6.5, z: 8} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 - m_LocalEulerAnglesHint: {x: 59.448, y: 0, z: 0} + m_LocalEulerAnglesHint: {x: 45, y: 180, z: 0} +--- !u!1 &251893064 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 251893065} + - component: {fileID: 251893066} + m_Layer: 0 + m_Name: Spawn + m_TagString: Untagged + m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &251893065 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 251893064} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 3, y: 0, z: 3} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &251893066 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 251893064} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &535739935 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 535739936} + - component: {fileID: 535739937} + m_Layer: 0 + m_Name: Spawn + m_TagString: Untagged + m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &535739936 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 535739935} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 3, y: 0, z: -3} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &535739937 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 535739935} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &1107091652 GameObject: m_ObjectHideFlags: 0 @@ -215,16 +301,12 @@ GameObject: - component: {fileID: 1107091655} - component: {fileID: 1107091654} - component: {fileID: 1107091653} - - component: {fileID: 1107091660} - - component: {fileID: 1107091659} - - component: {fileID: 1107091658} - - component: {fileID: 1107091657} m_Layer: 0 m_Name: Ground m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 + m_StaticEditorFlags: 4294967295 m_IsActive: 1 --- !u!23 &1107091653 MeshRenderer: @@ -243,7 +325,7 @@ MeshRenderer: m_RenderingLayerMask: 4294967295 m_RendererPriority: 0 m_Materials: - - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + - {fileID: 2100000, guid: 29b49c27a74f145918356859bd7af511, type: 2} m_StaticBatchInfo: firstSubMesh: 0 subMeshCount: 0 @@ -293,64 +375,12 @@ Transform: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 1107091652} m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} - m_LocalPosition: {x: 0, y: -0.954, z: 0} + m_LocalPosition: {x: 0, y: 0, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} ---- !u!65 &1107091657 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1107091652} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 10, y: 3, z: 0.5} - m_Center: {x: 0, y: 0, z: 5.25} ---- !u!65 &1107091658 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1107091652} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 10, y: 3, z: 0.5} - m_Center: {x: 0, y: 0, z: -5.25} ---- !u!65 &1107091659 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1107091652} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 0.5, y: 3, z: 10} - m_Center: {x: 5.25, y: 0, z: 0} ---- !u!65 &1107091660 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 1107091652} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 0.5, y: 3, z: 10} - m_Center: {x: -5.25, y: 0, z: 0} --- !u!1 &1282001517 GameObject: m_ObjectHideFlags: 0 @@ -424,8 +454,9 @@ MonoBehaviour: playerPrefab: {fileID: 1916082411674582, guid: 6f43bf5488a7443d19ab2a83c6b91f35, type: 3} autoCreatePlayer: 1 - playerSpawnMethod: 0 - spawnPrefabs: [] + playerSpawnMethod: 1 + spawnPrefabs: + - {fileID: 5890560936853567077, guid: b7dd46dbf38c643f09e206f9fa4be008, type: 3} --- !u!114 &1282001521 MonoBehaviour: m_ObjectHideFlags: 0 @@ -478,6 +509,92 @@ MonoBehaviour: m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null port: 7777 NoDelay: 1 +--- !u!1 &1458789072 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1458789073} + - component: {fileID: 1458789074} + m_Layer: 0 + m_Name: Spawn + m_TagString: Untagged + m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1458789073 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1458789072} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -3, y: 0, z: 3} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 7 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1458789074 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1458789072} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &1501912662 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1501912663} + - component: {fileID: 1501912664} + m_Layer: 0 + m_Name: Spawn + m_TagString: Untagged + m_Icon: {fileID: -964228994112308473, guid: 0000000000000000d000000000000000, type: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1501912663 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1501912662} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -3, y: 0, z: -3} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1501912664 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1501912662} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 41f84591ce72545258ea98cb7518d8b9, type: 3} + m_Name: + m_EditorClassIdentifier: --- !u!1 &2054208274 GameObject: m_ObjectHideFlags: 0 @@ -540,10 +657,10 @@ Transform: m_PrefabInstance: {fileID: 0} m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 2054208274} - m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalRotation: {x: 0.10938167, y: 0.8754261, z: -0.40821788, w: 0.23456976} m_LocalPosition: {x: 0, y: 10, z: 0} m_LocalScale: {x: 1, y: 1, z: 1} m_Children: [] m_Father: {fileID: 0} m_RootOrder: 2 - m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} + m_LocalEulerAnglesHint: {x: 50, y: 150, z: 0} diff --git a/Assets/Mirror/Examples/Movement/Scenes/Scene.unity.meta b/Assets/Mirror/Examples/Tanks/Scenes/Scene.unity.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Scenes/Scene.unity.meta rename to Assets/Mirror/Examples/Tanks/Scenes/Scene.unity.meta diff --git a/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset b/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset new file mode 100644 index 000000000..3acffc80e Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset differ diff --git a/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset.meta b/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset.meta new file mode 100644 index 000000000..1997fe96a --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Scenes/Scene/NavMesh.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 0bc607fa2e315482ebe98797e844e11f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 23800000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Movement/Scripts.meta b/Assets/Mirror/Examples/Tanks/Scripts.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Scripts.meta rename to Assets/Mirror/Examples/Tanks/Scripts.meta diff --git a/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs new file mode 100644 index 000000000..83609ee56 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs @@ -0,0 +1,36 @@ +using UnityEngine; +using Mirror; + +public class Projectile : NetworkBehaviour +{ + public float destroyAfter = 5; + public Rigidbody rigidBody; + public float force = 1000; + + public override void OnStartServer() + { + Invoke(nameof(DestroySelf), destroyAfter); + } + + // set velocity for server and client. this way we don't have to sync the + // position, because both the server and the client simulate it. + void Start() + { + rigidBody.AddForce(transform.forward * force); + } + + // destroy for everyone on the server + [Server] + void DestroySelf() + { + NetworkServer.Destroy(gameObject); + } + + // ServerCallback because we don't want a warning if OnTriggerEnter is + // called on the client + [ServerCallback] + void OnTriggerEnter(Collider co) + { + NetworkServer.Destroy(gameObject); + } +} diff --git a/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs.meta b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs.meta new file mode 100644 index 000000000..387ece6ac --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f49b83f111a64bc7a5275af4f6f930b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Scripts/Tank.cs b/Assets/Mirror/Examples/Tanks/Scripts/Tank.cs new file mode 100644 index 000000000..b076780ae --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Scripts/Tank.cs @@ -0,0 +1,58 @@ +using UnityEngine; +using UnityEngine.AI; + +namespace Mirror.Examples.Movement +{ + public class Tank : NetworkBehaviour + { + [Header("Components")] + public NavMeshAgent agent; + public Animator animator; + + [Header("Movement")] + public float rotationSpeed = 100; + + [Header("Firing")] + public KeyCode shootKey = KeyCode.Space; + public GameObject projectilePrefab; + public Transform projectileMount; + + void Update() + { + // movement for local player + if (!isLocalPlayer) return; + + // rotate + float horizontal = Input.GetAxis("Horizontal"); + transform.Rotate(0, horizontal * rotationSpeed * Time.deltaTime, 0); + + // move + float vertical = Input.GetAxis("Vertical"); + Vector3 forward = transform.TransformDirection(Vector3.forward); + agent.velocity = forward * Mathf.Max(vertical, 0) * agent.speed; + animator.SetBool("Moving", agent.velocity != Vector3.zero); + + // shoot + if (Input.GetKeyDown(shootKey)) + { + CmdFire(); + } + } + + // this is called on the server + [Command] + void CmdFire() + { + GameObject projectile = Instantiate(projectilePrefab, projectileMount.position, transform.rotation); + NetworkServer.Spawn(projectile); + RpcOnFire(); + } + + // this is called on the tank that fired for all observers + [ClientRpc] + void RpcOnFire() + { + animator.SetTrigger("Shoot"); + } + } +} diff --git a/Assets/Mirror/Examples/Movement/Scripts/Move.cs.meta b/Assets/Mirror/Examples/Tanks/Scripts/Tank.cs.meta similarity index 100% rename from Assets/Mirror/Examples/Movement/Scripts/Move.cs.meta rename to Assets/Mirror/Examples/Tanks/Scripts/Tank.cs.meta diff --git a/Assets/Mirror/Examples/Tanks/Textures.meta b/Assets/Mirror/Examples/Tanks/Textures.meta new file mode 100644 index 000000000..fad816bd7 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8ce6821cc1c644d5595ce9fc1f61debc +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture.meta b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture.meta new file mode 100644 index 000000000..a77c1ef53 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4e08c21c1034445fc834851f9e4202fe +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt new file mode 100644 index 000000000..6091137a3 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt @@ -0,0 +1,5 @@ +Dirt Hand Painted Textures created by 'KIIRA' +https://opengameart.org/content/dirt-hand-painted-texture + +Licensed as CC-BY-3.0: +https://creativecommons.org/licenses/by/3.0/ \ No newline at end of file diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt.meta b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt.meta new file mode 100644 index 000000000..4682e6c4f --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt Hand Painted Texture - License.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 422e9f56a29ae488e836a23d1e0f21d0 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat similarity index 70% rename from Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat rename to Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat index aeb682e79..443aaffbf 100644 --- a/Assets/Mirror/Examples/Movement/Models/(Public Domain) Quandtum BA-2/Material.mat +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat @@ -4,12 +4,13 @@ Material: serializedVersion: 6 m_ObjectHideFlags: 0 - m_PrefabParentObject: {fileID: 0} - m_PrefabInternal: {fileID: 0} - m_Name: Material + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Dirt m_Shader: {fileID: 45, guid: 0000000000000000f000000000000000, type: 0} - m_ShaderKeywords: _EMISSION _NORMALMAP - m_LightmapFlags: 0 + m_ShaderKeywords: + m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 @@ -19,7 +20,7 @@ Material: serializedVersion: 3 m_TexEnvs: - _BumpMap: - m_Texture: {fileID: 2800000, guid: a623522aaab404bfb886bdd6fea0df80, type: 3} + m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _DetailAlbedoMap: @@ -35,12 +36,12 @@ Material: m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _EmissionMap: - m_Texture: {fileID: 2800000, guid: 1df5a55dca9084efba7314d168c7106f, type: 3} - m_Scale: {x: 1, y: 1} + m_Texture: {fileID: 0} + m_Scale: {x: 5, y: 5} m_Offset: {x: 0, y: 0} - _MainTex: - m_Texture: {fileID: 2800000, guid: e79a606e2465b49168ad7785fd79e9d3, type: 3} - m_Scale: {x: 1, y: 1} + m_Texture: {fileID: 2800000, guid: bcc8fc05f1f924531a65f39394c0b703, type: 3} + m_Scale: {x: 5, y: 5} m_Offset: {x: 0, y: 0} - _MetallicGlossMap: m_Texture: {fileID: 0} @@ -55,7 +56,7 @@ Material: m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} - _SpecGlossMap: - m_Texture: {fileID: 2800000, guid: 07a60ce056e2c4ec3a7e460c1c37fb79, type: 3} + m_Texture: {fileID: 0} m_Scale: {x: 1, y: 1} m_Offset: {x: 0, y: 0} m_Floats: @@ -63,8 +64,8 @@ Material: - _Cutoff: 0.5 - _DetailNormalMapScale: 1 - _DstBlend: 0 - - _GlossMapScale: 0 - - _Glossiness: 0.38 + - _GlossMapScale: 1 + - _Glossiness: 0 - _GlossyReflections: 1 - _Metallic: 0 - _Mode: 0 @@ -77,5 +78,5 @@ Material: - _ZWrite: 1 m_Colors: - _Color: {r: 1, g: 1, b: 1, a: 1} - - _EmissionColor: {r: 1, g: 1, b: 1, a: 1} - - _SpecColor: {r: 0.2, g: 0.2, b: 0.2, a: 1} + - _EmissionColor: {r: 0.13207549, g: 0.06631743, b: 0, a: 1} + - _SpecColor: {r: 0, g: 0, b: 0, a: 1} diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat.meta b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat.meta new file mode 100644 index 000000000..f571c8bf9 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/Dirt.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 29b49c27a74f145918356859bd7af511 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png new file mode 100644 index 000000000..3174ecc98 Binary files /dev/null and b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png differ diff --git a/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png.meta b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png.meta new file mode 100644 index 000000000..b00a62679 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/(Public Domain) Dirt Hand Painted Texture/dirt.png.meta @@ -0,0 +1,88 @@ +fileFormatVersion: 2 +guid: bcc8fc05f1f924531a65f39394c0b703 +TextureImporter: + fileIDToRecycleName: {} + externalObjects: {} + serializedVersion: 9 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: -1 + aniso: -1 + mipBias: -100 + wrapU: -1 + wrapV: -1 + wrapW: -1 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + platformSettings: + - serializedVersion: 2 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + vertices: [] + indices: + edges: [] + weights: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat b/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat new file mode 100644 index 000000000..d231b59c8 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat @@ -0,0 +1,77 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 6 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ProjectileMaterial + m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0} + m_ShaderKeywords: + m_LightmapFlags: 4 + m_EnableInstancingVariants: 0 + m_DoubleSidedGI: 0 + m_CustomRenderQueue: -1 + stringTagMap: {} + disabledShaderPasses: [] + m_SavedProperties: + serializedVersion: 3 + m_TexEnvs: + - _BumpMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailAlbedoMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailMask: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _DetailNormalMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _EmissionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MainTex: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _MetallicGlossMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _OcclusionMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + - _ParallaxMap: + m_Texture: {fileID: 0} + m_Scale: {x: 1, y: 1} + m_Offset: {x: 0, y: 0} + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _Glossiness: 0 + - _GlossyReflections: 1 + - _Metallic: 0 + - _Mode: 0 + - _OcclusionStrength: 1 + - _Parallax: 0.02 + - _SmoothnessTextureChannel: 0 + - _SpecularHighlights: 1 + - _SrcBlend: 1 + - _UVSec: 0 + - _ZWrite: 1 + m_Colors: + - _Color: {r: 0, g: 1, b: 0.8901961, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} diff --git a/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat.meta b/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat.meta new file mode 100644 index 000000000..84c957ed6 --- /dev/null +++ b/Assets/Mirror/Examples/Tanks/Textures/ProjectileMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: cba1b63a0bccc4b12ac25f05d0ae2dd1 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Plugins/Mirror.Weaver.dll b/Assets/Mirror/Plugins/Mirror.Weaver.dll deleted file mode 100644 index 0e7f6fe33..000000000 Binary files a/Assets/Mirror/Plugins/Mirror.Weaver.dll and /dev/null differ diff --git a/Assets/Mirror/Readme.txt b/Assets/Mirror/Readme.txt index c16e2d8e0..5c1da3833 100644 --- a/Assets/Mirror/Readme.txt +++ b/Assets/Mirror/Readme.txt @@ -1,7 +1,7 @@ Mirror is a MMO Scale Networking library for Unity, used in uMMORPG, uSurvival and several MMO projects in development. - https://github.com/vis2k/Mirror +*** IMPORTANT -- You must restart Unity after importing Mirror for the Components Menu to update! *** Documentation: https://vis2k.github.io/Mirror/ diff --git a/Assets/Mirror/Runtime/ClientScene.cs b/Assets/Mirror/Runtime/ClientScene.cs index 08d0da9d5..224431f9c 100644 --- a/Assets/Mirror/Runtime/ClientScene.cs +++ b/Assets/Mirror/Runtime/ClientScene.cs @@ -19,7 +19,7 @@ public static class ClientScene public static Dictionary prefabs = new Dictionary(); // scene id to NetworkIdentity - public static Dictionary spawnableObjects; + public static Dictionary spawnableObjects; // spawn handlers internal static Dictionary spawnHandlers = new Dictionary(); @@ -29,48 +29,42 @@ internal static void Shutdown() { NetworkIdentity.spawned.Clear(); ClearSpawners(); - s_PendingOwnerNetIds = new List(); + s_PendingOwnerNetIds.Clear(); spawnableObjects = null; readyConnection = null; ready = false; s_IsSpawnFinished = false; - NetworkManager.singleton.transport.ClientDisconnect(); + Transport.activeTransport.ClientDisconnect(); } // this is called from message handler for Owner message internal static void InternalAddPlayer(NetworkIdentity identity) { - if (LogFilter.Debug) { Debug.LogWarning("ClientScene::InternalAddPlayer"); } + 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) + if (readyConnection != null) { - Debug.LogWarning("No ready connection found for setting player controller during InternalAddPlayer"); + readyConnection.SetPlayerController(identity); } else { - readyConnection.SetPlayerController(identity); + Debug.LogWarning("No ready connection found for setting player controller during InternalAddPlayer"); } } // use this if already ready - public static bool AddPlayer() - { - return AddPlayer(null); - } + public static bool AddPlayer() => AddPlayer(null); // use this to implicitly become ready - public static bool AddPlayer(NetworkConnection readyConn) - { - return AddPlayer(readyConn, null); - } + public static bool AddPlayer(NetworkConnection readyConn) => AddPlayer(readyConn, null); // use this to implicitly become ready // -> extraMessage can contain character selection, etc. - public static bool AddPlayer(NetworkConnection readyConn, MessageBase extraMessage) + public static bool AddPlayer(NetworkConnection readyConn, byte[] extraData) { // ensure valid ready connection if (readyConn != null) @@ -87,31 +81,28 @@ public static bool AddPlayer(NetworkConnection readyConn, MessageBase extraMessa if (readyConnection.playerController != null) { - Debug.LogError("ClientScene::AddPlayer: a PlayerController was already added. Did you call AddPlayer twice?"); + 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 + "]"); } + if (LogFilter.Debug) Debug.Log("ClientScene.AddPlayer() called with connection [" + readyConnection + "]"); - AddPlayerMessage msg = new AddPlayerMessage(); - if (extraMessage != null) + AddPlayerMessage msg = new AddPlayerMessage() { - NetworkWriter writer = new NetworkWriter(); - extraMessage.Serialize(writer); - msg.value = writer.ToArray(); - } - readyConnection.Send((short)MsgType.AddPlayer, msg); + value = extraData + }; + readyConnection.Send(msg); return true; } public static bool RemovePlayer() { - if (LogFilter.Debug) { Debug.Log("ClientScene::RemovePlayer() called with connection [" + readyConnection + "]"); } + if (LogFilter.Debug) Debug.Log("ClientScene.RemovePlayer() called with connection [" + readyConnection + "]"); if (readyConnection.playerController != null) { RemovePlayerMessage msg = new RemovePlayerMessage(); - readyConnection.Send((short)MsgType.RemovePlayer, msg); + readyConnection.Send(msg); Object.Destroy(readyConnection.playerController.gameObject); @@ -131,12 +122,11 @@ public static bool Ready(NetworkConnection conn) return false; } - if (LogFilter.Debug) { Debug.Log("ClientScene::Ready() called with connection [" + conn + "]"); } + if (LogFilter.Debug) Debug.Log("ClientScene.Ready() called with connection [" + conn + "]"); if (conn != null) { - ReadyMessage msg = new ReadyMessage(); - conn.Send((short)MsgType.Ready, msg); + conn.Send(new ReadyMessage()); ready = true; readyConnection = conn; readyConnection.isReady = true; @@ -150,7 +140,7 @@ public static NetworkClient ConnectLocalServer() { LocalClient newClient = new LocalClient(); NetworkServer.ActivateLocalClientScene(); - newClient.InternalConnectLocalServer(true); + newClient.InternalConnectLocalServer(); return newClient; } @@ -182,50 +172,18 @@ public static void PrepareToSpawnSceneObjects() .ToDictionary(identity => identity.sceneId, identity => identity); } - internal static NetworkIdentity SpawnSceneObject(uint sceneId) + internal static NetworkIdentity SpawnSceneObject(ulong sceneId) { if (spawnableObjects.TryGetValue(sceneId, out NetworkIdentity identity)) { spawnableObjects.Remove(sceneId); return identity; } - else - { - Debug.LogWarning("Could not find scene object with sceneid:" + sceneId); - } + Debug.LogWarning("Could not find scene object with sceneid:" + sceneId.ToString("X")); return null; } - internal static void RegisterSystemHandlers(NetworkClient client, bool localClient) - { - if (localClient) - { - client.RegisterHandler(MsgType.ObjectDestroy, OnLocalClientObjectDestroy); - client.RegisterHandler(MsgType.ObjectHide, OnLocalClientObjectHide); - client.RegisterHandler(MsgType.SpawnPrefab, OnLocalClientSpawnPrefab); - client.RegisterHandler(MsgType.SpawnSceneObject, OnLocalClientSpawnSceneObject); - client.RegisterHandler(MsgType.LocalClientAuthority, OnClientAuthority); - } - else - { - // LocalClient shares the sim/scene with the server, no need for these events - client.RegisterHandler(MsgType.SpawnPrefab, OnSpawnPrefab); - client.RegisterHandler(MsgType.SpawnSceneObject, OnSpawnSceneObject); - client.RegisterHandler(MsgType.SpawnStarted, OnObjectSpawnStarted); - client.RegisterHandler(MsgType.SpawnFinished, OnObjectSpawnFinished); - client.RegisterHandler(MsgType.ObjectDestroy, OnObjectDestroy); - client.RegisterHandler(MsgType.ObjectHide, OnObjectDestroy); - client.RegisterHandler(MsgType.UpdateVars, OnUpdateVarsMessage); - client.RegisterHandler(MsgType.Owner, OnOwnerMessage); - client.RegisterHandler(MsgType.LocalClientAuthority, OnClientAuthority); - client.RegisterHandler(MsgType.Pong, NetworkTime.OnClientPong); - } - - client.RegisterHandler(MsgType.Rpc, OnRPCMessage); - client.RegisterHandler(MsgType.SyncEvent, OnSyncEventMessage); - } - - // spawn handlers and prefabs ////////////////////////////////////////// + // spawn handlers and prefabs internal static bool GetPrefab(Guid assetId, out GameObject prefab) { prefab = null; @@ -239,9 +197,9 @@ public static void RegisterPrefab(GameObject prefab, Guid newAssetId) NetworkIdentity identity = prefab.GetComponent(); if (identity) { - identity.SetDynamicAssetId(newAssetId); + identity.assetId = newAssetId; - if (LogFilter.Debug) { Debug.Log("Registering prefab '" + prefab.name + "' as asset:" + identity.assetId); } + if (LogFilter.Debug) Debug.Log("Registering prefab '" + prefab.name + "' as asset:" + identity.assetId); prefabs[identity.assetId] = prefab; } else @@ -255,7 +213,7 @@ 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); } + if (LogFilter.Debug) Debug.Log("Registering prefab '" + prefab.name + "' as asset:" + identity.assetId); prefabs[identity.assetId] = prefab; NetworkIdentity[] identities = prefab.GetComponentsInChildren(); @@ -292,7 +250,7 @@ public static void RegisterPrefab(GameObject prefab, SpawnDelegate spawnHandler, return; } - if (LogFilter.Debug) { Debug.Log("Registering custom prefab '" + prefab.name + "' as asset:" + identity.assetId + " " + spawnHandler.GetMethodName() + "/" + unspawnHandler.GetMethodName()); } + 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; @@ -318,7 +276,7 @@ public static void RegisterSpawnHandler(Guid assetId, SpawnDelegate spawnHandler return; } - if (LogFilter.Debug) { Debug.Log("RegisterSpawnHandler asset '" + assetId + "' " + spawnHandler.GetMethodName() + "/" + unspawnHandler.GetMethodName()); } + if (LogFilter.Debug) Debug.Log("RegisterSpawnHandler asset '" + assetId + "' " + spawnHandler.GetMethodName() + "/" + unspawnHandler.GetMethodName()); spawnHandlers[assetId] = spawnHandler; unspawnHandlers[assetId] = unspawnHandler; @@ -381,7 +339,7 @@ public static GameObject FindLocalObject(uint netId) return null; } - static void ApplySpawnPayload(NetworkIdentity identity, Vector3 position, Quaternion rotation, byte[] payload, uint netId) + static void ApplySpawnPayload(NetworkIdentity identity, Vector3 position, Quaternion rotation, Vector3 scale, byte[] payload, uint netId) { if (!identity.gameObject.activeSelf) { @@ -389,40 +347,39 @@ static void ApplySpawnPayload(NetworkIdentity identity, Vector3 position, Quater } identity.transform.position = position; identity.transform.rotation = rotation; + identity.transform.localScale = scale; if (payload != null && payload.Length > 0) { NetworkReader payloadReader = new NetworkReader(payload); identity.OnUpdateVars(payloadReader, true); } - identity.SetNetworkInstanceId(netId); + identity.netId = netId; NetworkIdentity.spawned[netId] = identity; // objects spawned as part of initial state are started on a second pass if (s_IsSpawnFinished) { - identity.EnableIsClient(); + identity.isClient = true; identity.OnStartClient(); CheckForOwner(identity); } } - static void OnSpawnPrefab(NetworkMessage netMsg) + internal static void OnSpawnPrefab(NetworkConnection conn, SpawnPrefabMessage msg) { - SpawnPrefabMessage msg = netMsg.ReadMessage(); - 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 + "]"); } + if (LogFilter.Debug) Debug.Log("Client spawn handler instantiating [netId:" + msg.netId + " asset ID:" + msg.assetId + " pos:" + msg.position + "]"); 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.payload, msg.netId); + ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId); return; } @@ -441,7 +398,7 @@ static void OnSpawnPrefab(NetworkMessage netMsg) return; } localObject.Reset(); - ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.payload, msg.netId); + 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)) @@ -459,8 +416,8 @@ static void OnSpawnPrefab(NetworkMessage netMsg) return; } localObject.Reset(); - localObject.SetDynamicAssetId(msg.assetId); - ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.payload, msg.netId); + localObject.assetId = msg.assetId; + ApplySpawnPayload(localObject, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId); } else { @@ -468,46 +425,44 @@ static void OnSpawnPrefab(NetworkMessage netMsg) } } - static void OnSpawnSceneObject(NetworkMessage netMsg) + internal static void OnSpawnSceneObject(NetworkConnection conn, SpawnSceneObjectMessage msg) { - SpawnSceneObjectMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("Client spawn scene handler instantiating [netId:" + msg.netId + " sceneId:" + msg.sceneId + " pos:" + msg.position); } + if (LogFilter.Debug) Debug.Log("Client spawn scene handler instantiating [netId:" + msg.netId + " sceneId:" + msg.sceneId + " pos:" + msg.position); 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.payload, msg.netId); + 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 + " SpawnableObjects.Count=" + spawnableObjects.Count); + 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 - foreach (KeyValuePair kvp in spawnableObjects) + 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); } + if (LogFilter.Debug) Debug.Log("Client spawn for [netId:" + msg.netId + "] [sceneId:" + msg.sceneId + "] obj:" + spawnedId.gameObject.name); spawnedId.Reset(); - ApplySpawnPayload(spawnedId, msg.position, msg.rotation, msg.payload, msg.netId); + ApplySpawnPayload(spawnedId, msg.position, msg.rotation, msg.scale, msg.payload, msg.netId); } - static void OnObjectSpawnStarted(NetworkMessage netMsg) + internal static void OnObjectSpawnStarted(NetworkConnection conn, ObjectSpawnStartedMessage msg) { - if (LogFilter.Debug) { Debug.Log("SpawnStarted"); } + if (LogFilter.Debug) Debug.Log("SpawnStarted"); PrepareToSpawnSceneObjects(); s_IsSpawnFinished = false; } - static void OnObjectSpawnFinished(NetworkMessage netMsg) + internal static void OnObjectSpawnFinished(NetworkConnection conn, ObjectSpawnFinishedMessage msg) { - if (LogFilter.Debug) { Debug.Log("SpawnFinished"); } + 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 @@ -523,12 +478,20 @@ static void OnObjectSpawnFinished(NetworkMessage netMsg) s_IsSpawnFinished = true; } - static void OnObjectDestroy(NetworkMessage netMsg) + internal static void OnObjectHide(NetworkConnection conn, ObjectHideMessage msg) { - ObjectDestroyMessage msg = netMsg.ReadMessage(); - if (LogFilter.Debug) { Debug.Log("ClientScene::OnObjDestroy netId:" + msg.netId); } + DestroyObject(msg.netId); + } + internal static void OnObjectDestroy(NetworkConnection conn, ObjectDestroyMessage msg) + { + DestroyObject(msg.netId); + } - if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) + 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(); @@ -546,27 +509,25 @@ static void OnObjectDestroy(NetworkMessage netMsg) spawnableObjects[localObject.sceneId] = localObject; } } - NetworkIdentity.spawned.Remove(msg.netId); + NetworkIdentity.spawned.Remove(netId); localObject.MarkForReset(); } else { - if (LogFilter.Debug) { Debug.LogWarning("Did not find target for destroy message for " + msg.netId); } + if (LogFilter.Debug) Debug.LogWarning("Did not find target for destroy message for " + netId); } } - static void OnLocalClientObjectDestroy(NetworkMessage netMsg) + internal static void OnLocalClientObjectDestroy(NetworkConnection conn, ObjectDestroyMessage msg) { - ObjectDestroyMessage msg = netMsg.ReadMessage(); - if (LogFilter.Debug) { Debug.Log("ClientScene::OnLocalObjectObjDestroy netId:" + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnLocalObjectObjDestroy netId:" + msg.netId); NetworkIdentity.spawned.Remove(msg.netId); } - static void OnLocalClientObjectHide(NetworkMessage netMsg) + internal static void OnLocalClientObjectHide(NetworkConnection conn, ObjectHideMessage msg) { - ObjectDestroyMessage msg = netMsg.ReadMessage(); - if (LogFilter.Debug) { Debug.Log("ClientScene::OnLocalObjectObjHide netId:" + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene::OnLocalObjectObjHide netId:" + msg.netId); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { @@ -574,31 +535,25 @@ static void OnLocalClientObjectHide(NetworkMessage netMsg) } } - static void OnLocalClientSpawnPrefab(NetworkMessage netMsg) + internal static void OnLocalClientSpawnPrefab(NetworkConnection conn, SpawnPrefabMessage msg) { - SpawnPrefabMessage msg = netMsg.ReadMessage(); - if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { localObject.OnSetLocalVisibility(true); } } - static void OnLocalClientSpawnSceneObject(NetworkMessage netMsg) + internal static void OnLocalClientSpawnSceneObject(NetworkConnection conn, SpawnSceneObjectMessage msg) { - SpawnSceneObjectMessage msg = netMsg.ReadMessage(); - if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { localObject.OnSetLocalVisibility(true); } } - static void OnUpdateVarsMessage(NetworkMessage netMsg) + internal static void OnUpdateVarsMessage(NetworkConnection conn, UpdateVarsMessage msg) { - UpdateVarsMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("ClientScene::OnUpdateVarsMessage " + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnUpdateVarsMessage " + msg.netId); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { @@ -610,11 +565,9 @@ static void OnUpdateVarsMessage(NetworkMessage netMsg) } } - static void OnRPCMessage(NetworkMessage netMsg) + internal static void OnRPCMessage(NetworkConnection conn, RpcMessage msg) { - RpcMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("ClientScene::OnRPCMessage hash:" + msg.functionHash + " netId:" + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnRPCMessage hash:" + msg.functionHash + " netId:" + msg.netId); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity)) { @@ -622,11 +575,9 @@ static void OnRPCMessage(NetworkMessage netMsg) } } - static void OnSyncEventMessage(NetworkMessage netMsg) + internal static void OnSyncEventMessage(NetworkConnection conn, SyncEventMessage msg) { - SyncEventMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("ClientScene::OnSyncEventMessage " + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnSyncEventMessage " + msg.netId); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity)) { @@ -638,11 +589,9 @@ static void OnSyncEventMessage(NetworkMessage netMsg) } } - static void OnClientAuthority(NetworkMessage netMsg) + internal static void OnClientAuthority(NetworkConnection conn, ClientAuthorityMessage msg) { - ClientAuthorityMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("ClientScene::OnClientAuthority for connectionId=" + netMsg.conn.connectionId + " netId: " + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnClientAuthority for netId: " + msg.netId); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity)) { @@ -651,19 +600,17 @@ static void OnClientAuthority(NetworkMessage netMsg) } // OnClientAddedPlayer? - static void OnOwnerMessage(NetworkMessage netMsg) + internal static void OnOwnerMessage(NetworkConnection conn, OwnerMessage msg) { - OwnerMessage msg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("ClientScene::OnOwnerMessage - connectionId=" + netMsg.conn.connectionId + " netId: " + msg.netId); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnOwnerMessage - connectionId=" + readyConnection.connectionId + " netId: " + msg.netId); // is there already an owner that is a different object?? - netMsg.conn.playerController?.SetNotLocalPlayer(); + readyConnection.playerController?.SetNotLocalPlayer(); if (NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity localObject) && localObject != null) { // this object already exists - localObject.SetConnectionToServer(netMsg.conn); + localObject.connectionToServer = readyConnection; localObject.SetLocalPlayer(); InternalAddPlayer(localObject); } @@ -683,10 +630,10 @@ static void CheckForOwner(NetworkIdentity identity) // 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.SetConnectionToServer(readyConnection); + identity.connectionToServer = readyConnection; identity.SetLocalPlayer(); - if (LogFilter.Debug) { Debug.Log("ClientScene::OnOwnerMessage - player=" + identity.name); } + if (LogFilter.Debug) Debug.Log("ClientScene.OnOwnerMessage - player=" + identity.name); if (readyConnection.connectionId < 0) { Debug.LogError("Owner message received on a local client."); diff --git a/Assets/Mirror/Runtime/CustomAttributes.cs b/Assets/Mirror/Runtime/CustomAttributes.cs index a9666259b..6b3b1f45d 100644 --- a/Assets/Mirror/Runtime/CustomAttributes.cs +++ b/Assets/Mirror/Runtime/CustomAttributes.cs @@ -41,22 +41,17 @@ public class SyncEventAttribute : Attribute } [AttributeUsage(AttributeTargets.Method)] - public class ServerAttribute : Attribute - { - } + public class ServerAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method)] - public class ServerCallbackAttribute : Attribute - { - } + public class ServerCallbackAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method)] - public class ClientAttribute : Attribute - { - } + public class ClientAttribute : Attribute {} [AttributeUsage(AttributeTargets.Method)] - public class ClientCallbackAttribute : Attribute - { - } + public class ClientCallbackAttribute : Attribute {} + + // For Scene property Drawer + public class SceneAttribute : PropertyAttribute {} } diff --git a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs index 15e1ec6bc..edf929acb 100644 --- a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs +++ b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs @@ -1,4 +1,3 @@ -using System; namespace Mirror { // implementation of N-day EMA diff --git a/Assets/Mirror/Runtime/FloatBytePacker.cs b/Assets/Mirror/Runtime/FloatBytePacker.cs new file mode 100644 index 000000000..f13f33267 --- /dev/null +++ b/Assets/Mirror/Runtime/FloatBytePacker.cs @@ -0,0 +1,58 @@ +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 * (float)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 + ((float)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 float[] 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 float[]{u, v, w}; + } + } +} diff --git a/Assets/Mirror/Runtime/FloatBytePacker.cs.meta b/Assets/Mirror/Runtime/FloatBytePacker.cs.meta new file mode 100644 index 000000000..92145fe5c --- /dev/null +++ b/Assets/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/Mirror/Runtime/LocalClient.cs b/Assets/Mirror/Runtime/LocalClient.cs index b62e1dca7..316f55bfe 100644 --- a/Assets/Mirror/Runtime/LocalClient.cs +++ b/Assets/Mirror/Runtime/LocalClient.cs @@ -1,4 +1,3 @@ -using System; using System.Collections.Generic; using UnityEngine; @@ -6,96 +5,59 @@ namespace Mirror { sealed class LocalClient : NetworkClient { - Queue m_InternalMsgs = new Queue(); - bool m_Connected; + // 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 Queue packetQueue = new Queue(); + + internal void InternalConnectLocalServer() + { + connection = new ULocalConnectionToServer(); + SetHandlers(connection); + connection.connectionId = NetworkServer.AddLocalClient(this); + connectState = ConnectState.Connected; + + active = true; + RegisterSystemHandlers(true); + + packetQueue.Enqueue(MessagePacker.Pack(new ConnectMessage())); + } public override void Disconnect() { - ClientScene.HandleClientDisconnect(m_Connection); - if (m_Connected) - { - PostInternalMessage((short)MsgType.Disconnect); - m_Connected = false; - } connectState = ConnectState.Disconnected; - NetworkServer.RemoveLocalClient(m_Connection); - } - - internal void InternalConnectLocalServer(bool generateConnectMsg) - { - m_Connection = new ULocalConnectionToServer(); - SetHandlers(m_Connection); - m_Connection.connectionId = NetworkServer.AddLocalClient(this); - connectState = ConnectState.Connected; - - SetActive(true); - RegisterSystemHandlers(true); - - if (generateConnectMsg) + ClientScene.HandleClientDisconnect(connection); + if (isConnected) { - PostInternalMessage((short)MsgType.Connect); + packetQueue.Enqueue(MessagePacker.Pack(new DisconnectMessage())); } - m_Connected = true; + NetworkServer.RemoveLocalClient(); } internal override void Update() { - ProcessInternalMessages(); + // process internal messages so they are applied at the correct time + while (packetQueue.Count > 0) + { + byte[] packet = packetQueue.Dequeue(); + OnDataReceived(packet); + } } // Called by the server to set the LocalClient's LocalPlayer object during NetworkServer.AddPlayer() internal void AddLocalPlayer(NetworkIdentity localPlayer) { - if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + m_Connection.connectionId); - m_Connection.isReady = true; - m_Connection.SetPlayerController(localPlayer); + if (LogFilter.Debug) Debug.Log("Local client AddLocalPlayer " + localPlayer.gameObject.name + " conn=" + connection.connectionId); + connection.isReady = true; + connection.SetPlayerController(localPlayer); if (localPlayer != null) { - localPlayer.EnableIsClient(); + localPlayer.isClient = true; NetworkIdentity.spawned[localPlayer.netId] = localPlayer; - localPlayer.SetConnectionToServer(m_Connection); + localPlayer.connectionToServer = connection; } // there is no SystemOwnerMessage for local client. add to ClientScene here instead ClientScene.InternalAddPlayer(localPlayer); } - - void PostInternalMessage(short msgType, byte[] content) - { - NetworkMessage msg = new NetworkMessage - { - msgType = msgType, - reader = new NetworkReader(content), - conn = connection - }; - m_InternalMsgs.Enqueue(msg); - } - - void PostInternalMessage(short msgType) - { - // call PostInternalMessage with empty content array if we just want to call a message like Connect - // -> original NetworkTransport used empty [] and not null array for those messages too - PostInternalMessage(msgType, new byte[0]); - } - - void ProcessInternalMessages() - { - while (m_InternalMsgs.Count > 0) - { - NetworkMessage internalMessage = m_InternalMsgs.Dequeue(); - m_Connection.InvokeHandler(internalMessage); - connection.lastMessageTime = Time.time; - } - } - - // called by the server, to bypass network - internal void InvokeBytesOnClient(byte[] buffer) - { - // unpack message and post to internal list for processing - if (Protocol.UnpackMessage(buffer, out ushort msgType, out byte[] content)) - { - PostInternalMessage((short)msgType, content); - } - else Debug.LogError("InvokeBytesOnClient failed to unpack message: " + BitConverter.ToString(buffer)); - } } } diff --git a/Assets/Mirror/Runtime/LocalConnections.cs b/Assets/Mirror/Runtime/LocalConnections.cs index ab6a02489..2fd36b367 100644 --- a/Assets/Mirror/Runtime/LocalConnections.cs +++ b/Assets/Mirror/Runtime/LocalConnections.cs @@ -1,4 +1,3 @@ -using System; using UnityEngine; namespace Mirror @@ -7,7 +6,7 @@ namespace Mirror // sending messages on this connection causes the client's handler function to be invoked directly class ULocalConnectionToClient : NetworkConnection { - public LocalClient localClient { get; } + public LocalClient localClient; public ULocalConnectionToClient(LocalClient localClient) : base ("localClient") { @@ -16,7 +15,7 @@ public ULocalConnectionToClient(LocalClient localClient) : base ("localClient") internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable) { - localClient.InvokeBytesOnClient(bytes); + localClient.packetQueue.Enqueue(bytes); return true; } } @@ -25,18 +24,20 @@ internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultR // send messages on this connection causes the server's handler function to be invoked directly. internal class ULocalConnectionToServer : NetworkConnection { - public ULocalConnectionToServer() : base("localServer") - { - } + public ULocalConnectionToServer() : base("localServer") {} internal override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable) { if (bytes.Length == 0) { - Debug.LogError("LocalConnection:SendBytes cannot send zero bytes"); + Debug.LogError("LocalConnection.SendBytes cannot send zero bytes"); return false; } - return NetworkServer.InvokeBytes(this, bytes); + + // handle the server's message directly + // TODO any way to do this without NetworkServer.localConnection? + NetworkServer.localConnection.TransportReceive(bytes); + return true; } } } diff --git a/Assets/Mirror/Runtime/MessagePacker.cs b/Assets/Mirror/Runtime/MessagePacker.cs new file mode 100644 index 000000000..20c9f15ae --- /dev/null +++ b/Assets/Mirror/Runtime/MessagePacker.cs @@ -0,0 +1,92 @@ +using System; + +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 + { + // PackMessage is in hot path. caching the writer is really worth it to + // avoid large amounts of allocations. + static NetworkWriter packWriter = new NetworkWriter(); + + public static int GetId() where T : MessageBase + { + // 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 + [Obsolete("Use Pack instead")] + public static byte[] PackMessage(int msgType, MessageBase msg) + { + // reset cached writer length and position + packWriter.SetLength(0); + + // write message type + packWriter.Write((short)msgType); + + // serialize message into writer + msg.Serialize(packWriter); + + // return byte[] + return packWriter.ToArray(); + } + + // pack message before sending + public static byte[] Pack(T message) where T : MessageBase + { + // reset cached writer length and position + packWriter.SetLength(0); + + // write message type + int msgType = GetId(); + packWriter.Write((ushort)msgType); + + // serialize message into writer + message.Serialize(packWriter); + + // return byte[] + return packWriter.ToArray(); + } + + // unpack a message we received + public static T Unpack(byte[] data) where T : MessageBase, 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) + msgType = (int)messageReader.ReadUInt16(); + return true; + } + } +} diff --git a/Assets/Mirror/Runtime/MessagePacker.cs.meta b/Assets/Mirror/Runtime/MessagePacker.cs.meta new file mode 100644 index 000000000..7ca61aa7f --- /dev/null +++ b/Assets/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/Mirror/Runtime/Messages.cs b/Assets/Mirror/Runtime/Messages.cs index b91760c5d..df7bb8bfd 100644 --- a/Assets/Mirror/Runtime/Messages.cs +++ b/Assets/Mirror/Runtime/Messages.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System; using UnityEngine; namespace Mirror @@ -15,15 +14,12 @@ public virtual void Deserialize(NetworkReader reader) {} public virtual void Serialize(NetworkWriter writer) {} } - // ---------- General Typed Messages ------------------- - + #region General Typed Messages public class StringMessage : MessageBase { public string value; - public StringMessage() - { - } + public StringMessage() {} public StringMessage(string v) { @@ -45,9 +41,7 @@ public class ByteMessage : MessageBase { public byte value; - public ByteMessage() - { - } + public ByteMessage() {} public ByteMessage(byte v) { @@ -69,9 +63,7 @@ public class BytesMessage : MessageBase { public byte[] value; - public BytesMessage() - { - } + public BytesMessage() {} public BytesMessage(byte[] v) { @@ -93,9 +85,7 @@ public class IntegerMessage : MessageBase { public int value; - public IntegerMessage() - { - } + public IntegerMessage() {} public IntegerMessage(int v) { @@ -117,9 +107,7 @@ public class DoubleMessage : MessageBase { public double value; - public DoubleMessage() - { - } + public DoubleMessage() {} public DoubleMessage(double v) { @@ -139,17 +127,13 @@ public override void Serialize(NetworkWriter writer) public class EmptyMessage : MessageBase { - public override void Deserialize(NetworkReader reader) - { - } + public override void Deserialize(NetworkReader reader) {} - public override void Serialize(NetworkWriter writer) - { - } + public override void Serialize(NetworkWriter writer) {} } + #endregion - // ---------- Public System Messages ------------------- - + #region Public System Messages public class ErrorMessage : ByteMessage {} public class ReadyMessage : EmptyMessage {} @@ -160,8 +144,19 @@ public class AddPlayerMessage : BytesMessage {} public class RemovePlayerMessage : EmptyMessage {} - // ---------- System Messages requried for code gen path ------------------- + public class DisconnectMessage : EmptyMessage {} + public class ConnectMessage : EmptyMessage {} + + public class SceneMessage : StringMessage + { + public SceneMessage(string value) : base(value) {} + + public SceneMessage() {} + } + #endregion + + #region System Messages requried for code gen path // remote calls like Rpc/Cmd/SyncEvent all use the same message type class RemoteCallMessage : MessageBase { @@ -192,15 +187,16 @@ class CommandMessage : RemoteCallMessage {} class RpcMessage : RemoteCallMessage {} class SyncEventMessage : RemoteCallMessage {} + #endregion - // ---------- Internal System Messages ------------------- - + #region Internal System Messages class SpawnPrefabMessage : MessageBase { public uint netId; public Guid assetId; public Vector3 position; public Quaternion rotation; + public Vector3 scale; public byte[] payload; public override void Deserialize(NetworkReader reader) @@ -209,6 +205,7 @@ public override void Deserialize(NetworkReader reader) assetId = reader.ReadGuid(); position = reader.ReadVector3(); rotation = reader.ReadQuaternion(); + scale = reader.ReadVector3(); payload = reader.ReadBytesAndSize(); } @@ -218,6 +215,7 @@ public override void Serialize(NetworkWriter writer) writer.Write(assetId); writer.Write(position); writer.Write(rotation); + writer.Write(scale); writer.WriteBytesAndSize(payload); } } @@ -225,26 +223,29 @@ public override void Serialize(NetworkWriter writer) class SpawnSceneObjectMessage : MessageBase { public uint netId; - public uint sceneId; + public ulong sceneId; public Vector3 position; public Quaternion rotation; + public Vector3 scale; public byte[] payload; public override void Deserialize(NetworkReader reader) { netId = reader.ReadPackedUInt32(); - sceneId = reader.ReadPackedUInt32(); + sceneId = reader.ReadUInt64(); position = reader.ReadVector3(); rotation = reader.ReadQuaternion(); + scale = reader.ReadVector3(); payload = reader.ReadBytesAndSize(); } public override void Serialize(NetworkWriter writer) { writer.WritePackedUInt32(netId); - writer.WritePackedUInt32(sceneId); + writer.Write(sceneId); writer.Write(position); writer.Write(rotation); + writer.Write(scale); writer.WriteBytesAndSize(payload); } } @@ -268,6 +269,21 @@ public override void Serialize(NetworkWriter writer) } } + class ObjectHideMessage : MessageBase + { + public uint netId; + + public override void Deserialize(NetworkReader reader) + { + netId = reader.ReadPackedUInt32(); + } + + public override void Serialize(NetworkWriter writer) + { + writer.WritePackedUInt32(netId); + } + } + class OwnerMessage : MessageBase { public uint netId; @@ -323,13 +339,9 @@ public override void Serialize(NetworkWriter writer) // to calculate RTT and synchronize time class NetworkPingMessage : DoubleMessage { - public NetworkPingMessage() - { - } + public NetworkPingMessage() {} - public NetworkPingMessage(double value) : base(value) - { - } + public NetworkPingMessage(double value) : base(value) {} } // The server responds with this message @@ -351,4 +363,5 @@ public override void Serialize(NetworkWriter writer) writer.Write(serverTime); } } + #endregion } diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index 2be5ffa93..9f3d32d75 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.ComponentModel; using UnityEngine; -using System.Linq; namespace Mirror { @@ -17,13 +16,13 @@ public class NetworkBehaviour : MonoBehaviour [HideInInspector] public float syncInterval = 0.1f; public bool localPlayerAuthority => netIdentity.localPlayerAuthority; - public bool isServer => netIdentity.isServer; - public bool isClient => netIdentity.isClient; - public bool isLocalPlayer => netIdentity.isLocalPlayer; + public bool isServer => netIdentity.isServer; + public bool isClient => netIdentity.isClient; + public bool isLocalPlayer => netIdentity.isLocalPlayer; public bool isServerOnly => isServer && !isClient; public bool isClientOnly => isClient && !isServer; public bool hasAuthority => netIdentity.hasAuthority; - public uint netId => netIdentity.netId; + public uint netId => netIdentity.netId; public NetworkConnection connectionToServer => netIdentity.connectionToServer; public NetworkConnection connectionToClient => netIdentity.connectionToClient; protected ulong syncVarDirtyBits { get; private set; } @@ -70,11 +69,18 @@ protected void InitSyncObject(SyncObject syncObject) m_SyncObjects.Add(syncObject); } - // ----------------------------- Commands -------------------------------- - + #region Commands [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)) { @@ -97,20 +103,26 @@ protected void SendCommandInternal(Type invokeClass, string cmdName, NetworkWrit payload = writer.ToArray() }; - ClientScene.readyConnection.Send((short)MsgType.Command, message, channelId); + ClientScene.readyConnection.Send(message, channelId); } [EditorBrowsable(EditorBrowsableState.Never)] public virtual bool InvokeCommand(int cmdHash, NetworkReader reader) { - return InvokeHandlerDelegate(cmdHash, UNetInvokeType.Command, reader); + return InvokeHandlerDelegate(cmdHash, MirrorInvokeType.Command, reader); } + #endregion - // ----------------------------- Client RPCs -------------------------------- - + #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) { @@ -127,12 +139,29 @@ protected void SendRPCInternal(Type invokeClass, string rpcName, NetworkWriter w payload = writer.ToArray() }; - NetworkServer.SendToReady(netIdentity, (short)MsgType.Rpc, message, channelId); + 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) { @@ -149,17 +178,17 @@ protected void SendTargetRPCInternal(NetworkConnection conn, Type invokeClass, s payload = writer.ToArray() }; - conn.Send((short)MsgType.Rpc, message, channelId); + conn.Send(message, channelId); } [EditorBrowsable(EditorBrowsableState.Never)] public virtual bool InvokeRPC(int rpcHash, NetworkReader reader) { - return InvokeHandlerDelegate(rpcHash, UNetInvokeType.ClientRpc, reader); + return InvokeHandlerDelegate(rpcHash, MirrorInvokeType.ClientRpc, reader); } + #endregion - // ----------------------------- Sync Events -------------------------------- - + #region Sync Events [EditorBrowsable(EditorBrowsableState.Never)] protected void SendEventInternal(Type invokeClass, string eventName, NetworkWriter writer, int channelId) { @@ -178,22 +207,22 @@ protected void SendEventInternal(Type invokeClass, string eventName, NetworkWrit payload = writer.ToArray() }; - NetworkServer.SendToReady(netIdentity, (short)MsgType.SyncEvent, message, channelId); + NetworkServer.SendToReady(netIdentity,message, channelId); } [EditorBrowsable(EditorBrowsableState.Never)] public virtual bool InvokeSyncEvent(int eventHash, NetworkReader reader) { - return InvokeHandlerDelegate(eventHash, UNetInvokeType.SyncEvent, reader); + return InvokeHandlerDelegate(eventHash, MirrorInvokeType.SyncEvent, reader); } + #endregion - // ----------------------------- Code Gen Path Helpers -------------------------------- - + #region Code Gen Path Helpers public delegate void CmdDelegate(NetworkBehaviour obj, NetworkReader reader); protected class Invoker { - public UNetInvokeType invokeType; + public MirrorInvokeType invokeType; public Type invokeClass; public CmdDelegate invokeFunction; } @@ -202,7 +231,7 @@ protected class Invoker // helper function register a Command/Rpc/SyncEvent delegate [EditorBrowsable(EditorBrowsableState.Never)] - protected static void RegisterDelegate(Type invokeClass, string cmdName, UNetInvokeType invokerType, CmdDelegate func) + protected static void RegisterDelegate(Type invokeClass, string cmdName, MirrorInvokeType invokerType, CmdDelegate func) { int cmdHash = (invokeClass + ":" + cmdName).GetStableHashCode(); // type+func so Inventory.RpcUse != Equipment.RpcUse @@ -216,12 +245,7 @@ protected static void RegisterDelegate(Type invokeClass, string cmdName, UNetInv return; } - Debug.LogError(string.Format( - "Function {0}.{1} and {2}.{3} have the same hash. Please rename one of them", - oldInvoker.invokeClass, - oldInvoker.invokeFunction.GetMethodName(), - invokeClass, - oldInvoker.invokeFunction.GetMethodName())); + 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 { @@ -230,28 +254,28 @@ protected static void RegisterDelegate(Type invokeClass, string cmdName, UNetInv invokeFunction = func }; s_CmdHandlerDelegates[cmdHash] = invoker; - if (LogFilter.Debug) { Debug.Log("RegisterDelegate hash:" + cmdHash + " invokerType: " + invokerType + " method:" + func.GetMethodName()); } + 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, UNetInvokeType.Command, func); + RegisterDelegate(invokeClass, cmdName, MirrorInvokeType.Command, func); } [EditorBrowsable(EditorBrowsableState.Never)] protected static void RegisterRpcDelegate(Type invokeClass, string rpcName, CmdDelegate func) { - RegisterDelegate(invokeClass, rpcName, UNetInvokeType.ClientRpc, func); + RegisterDelegate(invokeClass, rpcName, MirrorInvokeType.ClientRpc, func); } [EditorBrowsable(EditorBrowsableState.Never)] protected static void RegisterEventDelegate(Type invokeClass, string eventName, CmdDelegate func) { - RegisterDelegate(invokeClass, eventName, UNetInvokeType.SyncEvent, func); + RegisterDelegate(invokeClass, eventName, MirrorInvokeType.SyncEvent, func); } - static bool GetInvokerForHash(int cmdHash, UNetInvokeType invokeType, out Invoker invoker) + static bool GetInvokerForHash(int cmdHash, MirrorInvokeType invokeType, out Invoker invoker) { if (s_CmdHandlerDelegates.TryGetValue(cmdHash, out invoker) && invoker != null && @@ -263,12 +287,12 @@ static bool GetInvokerForHash(int cmdHash, UNetInvokeType invokeType, out Invoke // 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"); } + 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, UNetInvokeType invokeType, NetworkReader reader) + internal bool InvokeHandlerDelegate(int cmdHash, MirrorInvokeType invokeType, NetworkReader reader) { if (GetInvokerForHash(cmdHash, invokeType, out Invoker invoker) && invoker.invokeClass.IsInstanceOfType(this)) @@ -278,9 +302,9 @@ internal bool InvokeHandlerDelegate(int cmdHash, UNetInvokeType invokeType, Netw } return false; } + #endregion - // ----------------------------- Helpers -------------------------------- - + #region Helpers // helper function for [SyncVar] GameObjects. [EditorBrowsable(EditorBrowsableState.Never)] protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gameObjectField, ulong dirtyBit, ref uint netIdField) @@ -305,7 +329,7 @@ protected void SetSyncVarGameObject(GameObject newGameObject, ref GameObject gam // netId changed? if (newNetId != netIdField) { - if (LogFilter.Debug) { Debug.Log("SetSyncVar GameObject " + GetType().Name + " bit [" + dirtyBit + "] netfieldId:" + netIdField + "->" + newNetId); } + 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; @@ -350,7 +374,7 @@ protected void SetSyncVarNetworkIdentity(NetworkIdentity newIdentity, ref Networ // netId changed? if (newNetId != netIdField) { - if (LogFilter.Debug) { Debug.Log("SetSyncVarNetworkIdentity NetworkIdentity " + GetType().Name + " bit [" + dirtyBit + "] netIdField:" + netIdField + "->" + newNetId); } + 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 @@ -381,11 +405,12 @@ protected void SetSyncVar(T value, ref T fieldValue, ulong dirtyBit) if ((value == null && fieldValue != null) || (value != null && !value.Equals(fieldValue))) { - if (LogFilter.Debug) { Debug.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value); } + if (LogFilter.Debug) Debug.Log("SetSyncVar " + GetType().Name + " bit [" + dirtyBit + "] " + fieldValue + "->" + value); SetDirtyBit(dirtyBit); fieldValue = value; } } + #endregion // these are masks, not bit numbers, ie. 0x004 not 2 public void SetDirtyBit(ulong dirtyBit) @@ -399,7 +424,13 @@ public void ClearAllDirtyBits() syncVarDirtyBits = 0L; // flush all unsynchronized changes in syncobjects - m_SyncObjects.ForEach(obj => obj.Flush()); + // note: don't use List.ForEach here, this is a hot path + // List.ForEach: 432b/frame + // for: 231b/frame + for (int i = 0; i < m_SyncObjects.Count; ++i) + { + m_SyncObjects[i].Flush(); + } } internal bool AnySyncObjectDirty() @@ -531,9 +562,7 @@ public virtual bool OnRebuildObservers(HashSet observers, boo return false; } - public virtual void OnSetLocalVisibility(bool vis) - { - } + public virtual void OnSetLocalVisibility(bool vis) {} public virtual bool OnCheckObserver(NetworkConnection conn) { diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 4a56d8bcb..4967a239a 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -6,75 +6,75 @@ namespace Mirror { public class NetworkClient { - public static List allClients = new List(); - public static bool active { get; private set; } + // the client (can be a regular NetworkClient or a LocalClient) + public static NetworkClient singleton; - int m_ClientId = -1; + [Obsolete("Use NetworkClient.singleton instead. There is always exactly one client.")] + public static List allClients => new List{singleton}; - public readonly Dictionary handlers = new Dictionary(); - protected NetworkConnection m_Connection; + public readonly Dictionary handlers = new Dictionary(); + + public NetworkConnection connection { get; protected set; } protected enum ConnectState { None, Connecting, Connected, - Disconnected, + Disconnected } protected ConnectState connectState = ConnectState.None; + public string serverIp { get; private set; } = ""; + + // active is true while a client is connecting/connected + // (= while the network is active) + public static bool active { get; protected set; } + + public bool isConnected => connectState == ConnectState.Connected; + + public NetworkClient() + { + if (LogFilter.Debug) Debug.Log("Client created version " + Version.Current); + + if (singleton != null) + { + Debug.LogError("NetworkClient: can only create one!"); + return; + } + singleton = this; + } + internal void SetHandlers(NetworkConnection conn) { conn.SetHandlers(handlers); } - public string serverIp { get; private set; } = ""; - public NetworkConnection connection => m_Connection; - - public bool isConnected => connectState == ConnectState.Connected; - - public NetworkClient() + public void Connect(string ip) { - if (LogFilter.Debug) { Debug.Log("Client created version " + Version.Current); } - AddClient(this); - } + if (LogFilter.Debug) Debug.Log("Client Connect: " + ip); - public NetworkClient(NetworkConnection conn) - { - if (LogFilter.Debug) { Debug.Log("Client created version " + Version.Current); } - AddClient(this); - - SetActive(true); - m_Connection = conn; - connectState = ConnectState.Connected; - conn.SetHandlers(handlers); + active = true; RegisterSystemHandlers(false); - } + Transport.activeTransport.enabled = true; + InitializeTransportHandlers(); - public void Connect(string serverIp) - { - PrepareForConnect(); - - if (LogFilter.Debug) { Debug.Log("Client Connect: " + serverIp); } - - string hostnameOrIp = serverIp; - this.serverIp = hostnameOrIp; + serverIp = ip; connectState = ConnectState.Connecting; - NetworkManager.singleton.transport.ClientConnect(serverIp); + Transport.activeTransport.ClientConnect(ip); // setup all the handlers - m_Connection = new NetworkConnection(this.serverIp, m_ClientId, 0); - m_Connection.SetHandlers(handlers); + connection = new NetworkConnection(serverIp, 0); + connection.SetHandlers(handlers); } private void InitializeTransportHandlers() { - // TODO do this in inspector? - NetworkManager.singleton.transport.OnClientConnected.AddListener(OnConnected); - NetworkManager.singleton.transport.OnClientDataReceived.AddListener(OnDataReceived); - NetworkManager.singleton.transport.OnClientDisconnected.AddListener(OnDisconnected); - NetworkManager.singleton.transport.OnClientError.AddListener(OnError); + Transport.activeTransport.OnClientConnected.AddListener(OnConnected); + Transport.activeTransport.OnClientDataReceived.AddListener(OnDataReceived); + Transport.activeTransport.OnClientDisconnected.AddListener(OnDisconnected); + Transport.activeTransport.OnClientError.AddListener(OnError); } void OnError(Exception exception) @@ -86,23 +86,23 @@ void OnDisconnected() { connectState = ConnectState.Disconnected; - ClientScene.HandleClientDisconnect(m_Connection); - - m_Connection?.InvokeHandlerNoData((short)MsgType.Disconnect); + ClientScene.HandleClientDisconnect(connection); + + connection?.InvokeHandler(new DisconnectMessage()); } - void OnDataReceived(byte[] data) + protected void OnDataReceived(byte[] data) { - if (m_Connection != null) + if (connection != null) { - m_Connection.TransportReceive(data); + connection.TransportReceive(data); } else Debug.LogError("Skipped Data message handling because m_Connection is null."); } void OnConnected() { - if (m_Connection != null) + if (connection != null) { // reset network time stats NetworkTime.Reset(); @@ -111,116 +111,101 @@ void OnConnected() // thus we should set the connected state before calling the handler connectState = ConnectState.Connected; NetworkTime.UpdateClient(this); - m_Connection.InvokeHandlerNoData((short)MsgType.Connect); + connection.InvokeHandler(new ConnectMessage()); } else Debug.LogError("Skipped Connect message handling because m_Connection is null."); } - void PrepareForConnect() - { - SetActive(true); - RegisterSystemHandlers(false); - m_ClientId = 0; - NetworkManager.singleton.transport.enabled = true; - InitializeTransportHandlers(); - } - public virtual void Disconnect() { connectState = ConnectState.Disconnected; - ClientScene.HandleClientDisconnect(m_Connection); - if (m_Connection != null) + ClientScene.HandleClientDisconnect(connection); + if (connection != null) { - m_Connection.Disconnect(); - m_Connection.Dispose(); - m_Connection = null; - m_ClientId = -1; + connection.Disconnect(); + connection.Dispose(); + connection = null; RemoveTransportHandlers(); } + + // the client's network is not active anymore. + active = false; } void RemoveTransportHandlers() { // so that we don't register them more than once - NetworkManager.singleton.transport.OnClientConnected.RemoveListener(OnConnected); - NetworkManager.singleton.transport.OnClientDataReceived.RemoveListener(OnDataReceived); - NetworkManager.singleton.transport.OnClientDisconnected.RemoveListener(OnDisconnected); - NetworkManager.singleton.transport.OnClientError.RemoveListener(OnError); + Transport.activeTransport.OnClientConnected.RemoveListener(OnConnected); + Transport.activeTransport.OnClientDataReceived.RemoveListener(OnDataReceived); + Transport.activeTransport.OnClientDisconnected.RemoveListener(OnDisconnected); + Transport.activeTransport.OnClientError.RemoveListener(OnError); } + [Obsolete("Use SendMessage instead with no message id instead")] public bool Send(short msgType, MessageBase msg) { - if (m_Connection != null) + if (connection != null) { if (connectState != ConnectState.Connected) { Debug.LogError("NetworkClient Send when not connected to a server"); return false; } - return m_Connection.Send(msgType, msg); + return connection.Send(msgType, msg); } Debug.LogError("NetworkClient Send with no connection"); return false; } - public void Shutdown() + public bool Send(T message) where T : MessageBase { - if (LogFilter.Debug) Debug.Log("Shutting down client " + m_ClientId); - m_ClientId = -1; - RemoveClient(this); - if (allClients.Count == 0) + if (connection != null) { - SetActive(false); + if (connectState != ConnectState.Connected) + { + Debug.LogError("NetworkClient Send when not connected to a server"); + return false; + } + return connection.Send(message); } + Debug.LogError("NetworkClient Send with no connection"); + return false; } internal virtual void Update() { - if (m_ClientId == -1) - { - return; - } - - // don't do anything if we aren't fully connected - // -> we don't check Client.Connected because then we wouldn't - // process the last disconnect message. - if (connectState != ConnectState.Connecting && - connectState != ConnectState.Connected) - { - return; - } - - if (connectState == ConnectState.Connected) + // only update things while connected + if (active && connectState == ConnectState.Connected) { NetworkTime.UpdateClient(this); } } + /* TODO use or remove void GenerateConnectError(byte error) { - Debug.LogError("UNet Client Error Connect Error: " + error); + Debug.LogError("Mirror Client Error Connect Error: " + error); GenerateError(error); } - /* TODO use or remove void GenerateDataError(byte error) { NetworkError dataError = (NetworkError)error; - Debug.LogError("UNet Client Data Error: " + dataError); + Debug.LogError("Mirror Client Data Error: " + dataError); GenerateError(error); } void GenerateDisconnectError(byte error) { NetworkError disconnectError = (NetworkError)error; - Debug.LogError("UNet Client Disconnect Error: " + disconnectError); + Debug.LogError("Mirror Client Disconnect Error: " + disconnectError); GenerateError(error); } - */ void GenerateError(byte error) { - if (handlers.TryGetValue((short)MsgType.Error, out NetworkMessageDelegate msgDelegate)) + int msgId = MessageBase.GetId(); + if (handlers.TryGetValue(msgId, out NetworkMessageDelegate msgDelegate)) { ErrorMessage msg = new ErrorMessage { @@ -233,13 +218,14 @@ void GenerateError(byte error) NetworkMessage netMsg = new NetworkMessage { - msgType = (short)MsgType.Error, + msgType = msgId, reader = new NetworkReader(writer.ToArray()), - conn = m_Connection + conn = connection }; msgDelegate(netMsg); } } + */ [Obsolete("Use NetworkTime.rtt instead")] public float GetRTT() @@ -249,71 +235,104 @@ public float GetRTT() internal void RegisterSystemHandlers(bool localClient) { - ClientScene.RegisterSystemHandlers(this, 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((conn, msg) => {}); + RegisterHandler(ClientScene.OnLocalClientSpawnPrefab); + RegisterHandler(ClientScene.OnLocalClientSpawnSceneObject); + RegisterHandler((conn, msg) => {}); + RegisterHandler((conn, msg) => {}); + RegisterHandler((conn, msg) => {}); + } + else + { + RegisterHandler(ClientScene.OnObjectDestroy); + RegisterHandler(ClientScene.OnObjectHide); + RegisterHandler(ClientScene.OnOwnerMessage); + 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); } - public void RegisterHandler(short msgType, NetworkMessageDelegate handler) + [Obsolete("Use RegisterHandler instead")] + public void RegisterHandler(int msgType, NetworkMessageDelegate handler) { if (handlers.ContainsKey(msgType)) { - if (LogFilter.Debug) { Debug.Log("NetworkClient.RegisterHandler replacing " + msgType); } + if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + msgType); } handlers[msgType] = handler; } + [Obsolete("Use RegisterHandler instead")] public void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler) { - RegisterHandler((short)msgType, handler); + RegisterHandler((int)msgType, handler); } - public void UnregisterHandler(short msgType) + public void RegisterHandler(Action handler) where T : MessageBase, new() + { + int msgType = MessagePacker.GetId(); + if (handlers.ContainsKey(msgType)) + { + if (LogFilter.Debug) Debug.Log("NetworkClient.RegisterHandler replacing " + msgType); + } + handlers[msgType] = (networkMessage) => + { + handler(networkMessage.conn, networkMessage.ReadMessage()); + }; + } + + [Obsolete("Use UnregisterHandler instead")] + public void UnregisterHandler(int msgType) { handlers.Remove(msgType); } + [Obsolete("Use UnregisterHandler instead")] public void UnregisterHandler(MsgType msgType) { - UnregisterHandler((short)msgType); + UnregisterHandler((int)msgType); } - internal static void AddClient(NetworkClient client) + public void UnregisterHandler() where T : MessageBase { - allClients.Add(client); + // use int to minimize collisions + int msgType = MessagePacker.GetId(); + handlers.Remove(msgType); } - internal static bool RemoveClient(NetworkClient client) + internal static void UpdateClient() { - return allClients.Remove(client); + singleton?.Update(); } - internal static void UpdateClients() + public void Shutdown() { - // remove null clients first - allClients.RemoveAll(cl => cl == null); - - // now update valid clients - // IMPORTANT: no foreach, otherwise we get an InvalidOperationException - // when stopping the client. - for (int i = 0; i < allClients.Count; ++i) - { - allClients[i].Update(); - } + if (LogFilter.Debug) Debug.Log("Shutting down client."); + singleton = null; + active = false; } public static void ShutdownAll() { - while (allClients.Count != 0) - { - allClients[0].Shutdown(); - } - allClients = new List(); + singleton?.Shutdown(); + singleton = null; active = false; ClientScene.Shutdown(); } - - internal static void SetActive(bool state) - { - active = state; - } } } diff --git a/Assets/Mirror/Runtime/NetworkConnection.cs b/Assets/Mirror/Runtime/NetworkConnection.cs index 7b7e7b44e..6bf214f64 100644 --- a/Assets/Mirror/Runtime/NetworkConnection.cs +++ b/Assets/Mirror/Runtime/NetworkConnection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using UnityEngine; @@ -6,30 +6,40 @@ namespace Mirror { public class NetworkConnection : IDisposable { - NetworkIdentity m_PlayerController; public HashSet visList = new HashSet(); - Dictionary m_MessageHandlers; + Dictionary m_MessageHandlers; - public int hostId = -1; public int connectionId = -1; public bool isReady; public string address; public float lastMessageTime; - public NetworkIdentity playerController => m_PlayerController; + public NetworkIdentity playerController { get; private set; } public HashSet clientOwnedObjects; public bool logNetworkMessages; - public bool isConnected => hostId != -1; + + // this is always true for regular connections, false for local + // connections because it's set in the constructor and never reset. + [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. + [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; public NetworkConnection(string networkAddress) { address = networkAddress; } - public NetworkConnection(string networkAddress, int networkHostId, int networkConnectionId) + public NetworkConnection(string networkAddress, int networkConnectionId) { address = networkAddress; - hostId = networkHostId; connectionId = networkConnectionId; +#pragma warning disable 618 + isConnected = true; + hostId = 0; +#pragma warning restore 618 } ~NetworkConnection() @@ -54,7 +64,7 @@ protected virtual void Dispose(bool disposing) { if (NetworkIdentity.spawned.TryGetValue(netId, out NetworkIdentity identity)) { - identity.ClearClientOwner(); + identity.clientAuthorityOwner = null; } } } @@ -72,66 +82,31 @@ public void Disconnect() isReady = false; ClientScene.HandleClientDisconnect(this); - // paul: we may be connecting or connected, either way, we need to disconnect - // transport should not do anything if it is not connecting/connected - NetworkManager.singleton.transport.ClientDisconnect(); - - // server? then disconnect that client - if (NetworkManager.singleton.transport.ServerActive()) + // server? then disconnect that client (not for host local player though) + if (Transport.activeTransport.ServerActive() && connectionId != 0) { - NetworkManager.singleton.transport.ServerDisconnect(connectionId); + Transport.activeTransport.ServerDisconnect(connectionId); + } + // not server and not host mode? then disconnect client + else + { + Transport.activeTransport.ClientDisconnect(); } - // remove observers. original HLAPI has hostId check for that too. - if (hostId != -1) - { - RemoveObservers(); - } + // remove observers + RemoveObservers(); } - internal void SetHandlers(Dictionary handlers) + internal void SetHandlers(Dictionary handlers) { m_MessageHandlers = handlers; } - public bool InvokeHandlerNoData(short msgType) - { - return InvokeHandler(msgType, null); - } - - public bool InvokeHandler(short msgType, NetworkReader reader) - { - if (m_MessageHandlers.TryGetValue(msgType, out NetworkMessageDelegate msgDelegate)) - { - NetworkMessage message = new NetworkMessage - { - msgType = msgType, - conn = this, - reader = reader - }; - - msgDelegate(message); - return true; - } - Debug.LogError("NetworkConnection InvokeHandler no handler for " + msgType); - return false; - } - - public bool InvokeHandler(NetworkMessage netMsg) - { - if (m_MessageHandlers.TryGetValue(netMsg.msgType, out NetworkMessageDelegate msgDelegate)) - { - msgDelegate(netMsg); - return true; - } - return false; - } - public void RegisterHandler(short msgType, NetworkMessageDelegate handler) { if (m_MessageHandlers.ContainsKey(msgType)) { - if (LogFilter.Debug) { Debug.Log("NetworkConnection.RegisterHandler replacing " + msgType); } + if (LogFilter.Debug) Debug.Log("NetworkConnection.RegisterHandler replacing " + msgType); } m_MessageHandlers[msgType] = handler; } @@ -143,18 +118,26 @@ public void UnregisterHandler(short msgType) internal void SetPlayerController(NetworkIdentity player) { - m_PlayerController = player; + playerController = player; } internal void RemovePlayerController() { - m_PlayerController = null; + playerController = null; } - public virtual bool Send(short msgType, MessageBase msg, int channelId = Channels.DefaultReliable) + [Obsolete("use Send instead")] + public virtual bool Send(int msgType, MessageBase msg, int channelId = Channels.DefaultReliable) { // pack message and send - byte[] message = Protocol.PackMessage((ushort)msgType, msg); + byte[] message = MessagePacker.PackMessage(msgType, msg); + return SendBytes(message, channelId); + } + + public virtual bool Send(T msg, int channelId = Channels.DefaultReliable) where T: MessageBase + { + // pack message and send + byte[] message = MessagePacker.Pack(msg); return SendBytes(message, channelId); } @@ -162,85 +145,34 @@ public virtual bool Send(short msgType, MessageBase msg, int channelId = Channel // 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 (logNetworkMessages) Debug.Log("ConnectionSend con:" + connectionId + " bytes:" + BitConverter.ToString(bytes)); - if (bytes.Length > NetworkManager.singleton.transport.GetMaxPacketSize(channelId)) + if (bytes.Length > Transport.activeTransport.GetMaxPacketSize(channelId)) { - Debug.LogError("NetworkConnection:SendBytes cannot send packet larger than " + NetworkManager.singleton.transport.GetMaxPacketSize(channelId) + " bytes"); + 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"); + Debug.LogError("NetworkConnection.SendBytes cannot send zero bytes"); return false; } return TransportSend(channelId, bytes, out byte error); } - // handle this message - // 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. - protected void HandleBytes(byte[] buffer) - { - // unpack message - if (Protocol.UnpackMessage(buffer, out ushort msgType, out byte[] content)) - { - if (logNetworkMessages) - { - if (Enum.IsDefined(typeof(MsgType), msgType)) - { - // one of Mirror mesage types, display the message name - Debug.Log("ConnectionRecv con:" + connectionId + " msgType:" + (MsgType)msgType + " content:" + BitConverter.ToString(content)); - } - else - { - // user defined message, display the number - Debug.Log("ConnectionRecv con:" + connectionId + " msgType:" + msgType + " content:" + BitConverter.ToString(content)); - } - } - - if (m_MessageHandlers.TryGetValue((short)msgType, out NetworkMessageDelegate msgDelegate)) - { - // create message here instead of caching it. so we can add it to queue more easily. - NetworkMessage msg = new NetworkMessage - { - msgType = (short)msgType, - reader = new NetworkReader(content), - conn = this - }; - - msgDelegate(msg); - lastMessageTime = Time.time; - } - else - { - //NOTE: this throws away the rest of the buffer. Need moar error codes - Debug.LogError("Unknown message ID " + msgType + " connId:" + connectionId); - } - } - else - { - Debug.LogError("HandleBytes UnpackMessage failed for: " + BitConverter.ToString(buffer)); - } - } - public override string ToString() { - return string.Format("hostId: {0} connectionId: {1} isReady: {2}", hostId, connectionId, isReady); + return $"connectionId: {connectionId} isReady: {isReady}"; } internal void AddToVisList(NetworkIdentity identity) { visList.Add(identity); - // spawn uv for this conn + // spawn identity for this conn NetworkServer.ShowForConnection(identity, this); } @@ -250,7 +182,7 @@ internal void RemoveFromVisList(NetworkIdentity identity, bool isDestroyed) if (!isDestroyed) { - // hide uv for this conn + // hide identity for this conn NetworkServer.HideForConnection(identity, this); } } @@ -264,21 +196,73 @@ internal void RemoveObservers() visList.Clear(); } - public virtual void TransportReceive(byte[] bytes) + public bool InvokeHandlerNoData(int msgType) { - HandleBytes(bytes); + return InvokeHandler(msgType, null); + } + + public bool InvokeHandler(int msgType, NetworkReader reader) + { + if (m_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; + } + + public bool InvokeHandler(T msg) where T : MessageBase + { + int msgType = MessagePacker.GetId(); + byte[] data = MessagePacker.Pack(msg); + return InvokeHandler(msgType, new NetworkReader(data)); + } + + // handle this message + // 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. + public virtual void TransportReceive(byte[] buffer) + { + // unpack message + NetworkReader reader = new NetworkReader(buffer); + if (MessagePacker.UnpackMessage(reader, out int msgType)) + { + if (logNetworkMessages) + { + Debug.Log("ConnectionRecv con:" + connectionId + " msgType:" + msgType + " content:" + BitConverter.ToString(buffer)); + } + + // try to invoke the handler for that message + if (InvokeHandler(msgType, reader)) + { + lastMessageTime = Time.time; + } + } + else Debug.LogError("HandleBytes UnpackMessage failed for: " + BitConverter.ToString(buffer)); } public virtual bool TransportSend(int channelId, byte[] bytes, out byte error) { error = 0; - if (NetworkManager.singleton.transport.ClientConnected()) + if (Transport.activeTransport.ClientConnected()) { - return NetworkManager.singleton.transport.ClientSend(channelId, bytes); + return Transport.activeTransport.ClientSend(channelId, bytes); } - else if (NetworkManager.singleton.transport.ServerActive()) + else if (Transport.activeTransport.ServerActive()) { - return NetworkManager.singleton.transport.ServerSend(connectionId, channelId, bytes); + return Transport.activeTransport.ServerSend(connectionId, channelId, bytes); } return false; } diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 4307d1c82..9d53d3715 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; +using System.ComponentModel; using System.Linq; using UnityEngine; - #if UNITY_EDITOR using UnityEditor; #if UNITY_2018_3_OR_NEWER - using UnityEditor.Experimental.SceneManagement; - #endif #endif @@ -22,7 +19,6 @@ namespace Mirror public sealed class NetworkIdentity : MonoBehaviour { // configuration - [SerializeField] uint m_SceneId; [SerializeField] bool m_ServerOnly; [SerializeField] bool m_LocalPlayerAuthority; bool m_IsServer; @@ -33,33 +29,31 @@ public sealed class NetworkIdentity : MonoBehaviour bool m_Reset; // properties - public bool isClient { get; private set; } - public bool isServer => m_IsServer && NetworkServer.active; // dont return true if server stopped. + public bool isClient { get; internal set; } + // dont return true if server stopped. + public bool isServer + { + get => m_IsServer && NetworkServer.active && netId != 0; + internal set => m_IsServer = value; + } public bool isLocalPlayer { get; private set; } public bool hasAuthority { get; private set; } // public Dictionary observers; - public uint netId { get; private set; } - public uint sceneId => m_SceneId; + public uint netId { get; internal set; } + public ulong sceneId => m_SceneId; public bool serverOnly { get { return m_ServerOnly; } set { m_ServerOnly = value; } } public bool localPlayerAuthority { get { return m_LocalPlayerAuthority; } set { m_LocalPlayerAuthority = value; } } - public NetworkConnection clientAuthorityOwner { get; private set; } - public NetworkConnection connectionToServer { get; private set; } - public NetworkConnection connectionToClient { get; private set; } + public NetworkConnection clientAuthorityOwner { get; internal set; } + public NetworkConnection connectionToServer { get; internal set; } + public NetworkConnection connectionToClient { get; internal set; } // all spawned NetworkIdentities by netId. needed on server and client. public static Dictionary spawned = new Dictionary(); - public NetworkBehaviour[] NetworkBehaviours - { - get - { - m_NetworkBehaviours = m_NetworkBehaviours ?? GetComponents(); - return m_NetworkBehaviours; - } - } + public NetworkBehaviour[] NetworkBehaviours => m_NetworkBehaviours = m_NetworkBehaviours ?? GetComponents(); // the AssetId trick: // - ideally we would have a serialized 'Guid m_AssetId' but Unity can't @@ -84,20 +78,23 @@ public Guid assetId // 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 + ">"); + } } - internal void SetDynamicAssetId(Guid newAssetId) - { - string newAssetIdString = newAssetId.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) + [SerializeField] ulong m_SceneId = 0; + + // keep track of all sceneIds to detect scene duplicates + static Dictionary sceneIds = new Dictionary(); // used when adding players internal void SetClientOwner(NetworkConnection conn) @@ -110,12 +107,6 @@ internal void SetClientOwner(NetworkConnection conn) clientAuthorityOwner.AddOwnedObject(this); } - // used during dispose after disconnect - internal void ClearClientOwner() - { - clientAuthorityOwner = null; - } - internal void ForceAuthority(bool authority) { if (hasAuthority == authority) @@ -135,40 +126,11 @@ internal void ForceAuthority(bool authority) } static uint s_NextNetworkId = 1; - internal static uint GetNextNetworkId() - { - return s_NextNetworkId++; - } + internal static uint GetNextNetworkId() => s_NextNetworkId++; public delegate void ClientAuthorityCallback(NetworkConnection conn, NetworkIdentity identity, bool authorityState); public static ClientAuthorityCallback clientAuthorityCallback; - // only used during spawning on clients to set the identity. - internal void SetNetworkInstanceId(uint newNetId) - { - netId = newNetId; - if (newNetId == 0) - { - m_IsServer = false; - } - } - - // only used when fixing duplicate scene IDs during post-processing - public void ForceSceneId(uint newSceneId) - { - m_SceneId = newSceneId; - } - - internal void EnableIsClient() - { - isClient = true; - } - - internal void EnableIsServer() - { - m_IsServer = true; - } - // used when the player object for a connection changes internal void SetNotLocalPlayer() { @@ -188,9 +150,9 @@ internal void RemoveObserverInternal(NetworkConnection conn) observers?.Remove(conn.connectionId); } -#if UNITY_EDITOR void OnValidate() { +#if UNITY_EDITOR if (m_ServerOnly && m_LocalPlayerAuthority) { Debug.LogWarning("Disabling Local Player Authority for " + gameObject + " because it is server-only."); @@ -198,23 +160,14 @@ void OnValidate() } SetupIDs(); +#endif } - void AssignAssetID(GameObject prefab) - { - string path = AssetDatabase.GetAssetPath(prefab); - AssignAssetID(path); - } +#if UNITY_EDITOR + void AssignAssetID(GameObject prefab) => AssignAssetID(AssetDatabase.GetAssetPath(prefab)); + void AssignAssetID(string path) => m_AssetId = AssetDatabase.AssetPathToGUID(path); - void AssignAssetID(string path) - { - m_AssetId = AssetDatabase.AssetPathToGUID(path); - } - - bool ThisIsAPrefab() - { - return PrefabUtility.IsPartOfPrefabAsset(gameObject); - } + bool ThisIsAPrefab() => PrefabUtility.IsPartOfPrefabAsset(gameObject); bool ThisIsASceneObjectWithPrefabParent(out GameObject prefab) { @@ -234,30 +187,146 @@ bool ThisIsASceneObjectWithPrefabParent(out GameObject prefab) return true; } + // 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? + NetworkIdentity existing; + bool duplicate = sceneIds.TryGetValue(m_SceneId, out existing) && existing != null && existing != this; + if (m_SceneId == 0 || duplicate) + { + // 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) + // -> exclude '0' because that's for unassigned sceneIDs + // TODO use 0,uint.max later. Random.Range only has int version. + m_SceneId = (uint)UnityEngine.Random.Range(1, int.MaxValue); + Debug.Log(name + " in scene=" + gameObject.scene.name + " sceneId assigned to: " + m_SceneId.ToString("X") + (duplicate ? " because duplicated" : "")); + } + + // 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! + 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. + 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()) { - ForceSceneId(0); + m_SceneId = 0; // force 0 for prefabs AssignAssetID(gameObject); } else if (ThisIsASceneObjectWithPrefabParent(out GameObject prefab)) { + AssignSceneID(); AssignAssetID(prefab); } else if (PrefabStageUtility.GetCurrentPrefabStage() != null) { - ForceSceneId(0); + m_SceneId = 0; // force 0 for prefabs string path = PrefabStageUtility.GetCurrentPrefabStage().prefabAssetPath; AssignAssetID(path); } else { + AssignSceneID(); m_AssetId = ""; } } - #endif + void OnDestroy() { if (m_IsServer && NetworkServer.active) @@ -291,7 +360,7 @@ internal void OnStartServer(bool allowNonZeroNetId) } } - if (LogFilter.Debug) { Debug.Log("OnStartServer " + this + " GUID:" + netId); } + if (LogFilter.Debug) Debug.Log("OnStartServer " + this + " GUID:" + netId); // add to spawned (note: the original EnableIsServer isn't needed // because we already set m_isServer=true above) @@ -312,7 +381,7 @@ internal void OnStartServer(bool allowNonZeroNetId) if (NetworkClient.active && NetworkServer.localClientActive) { // there will be no spawn message, so start the client here too - EnableIsClient(); + isClient = true; OnStartClient(); } @@ -326,7 +395,7 @@ internal void OnStartClient() { isClient = true; - if (LogFilter.Debug) { Debug.Log("OnStartClient " + gameObject + " GUID:" + netId + " localPlayerAuthority:" + localPlayerAuthority); } + if (LogFilter.Debug) Debug.Log("OnStartClient " + gameObject + " GUID:" + netId + " localPlayerAuthority:" + localPlayerAuthority); foreach (NetworkBehaviour comp in NetworkBehaviours) { try @@ -408,8 +477,6 @@ internal bool OnCheckObserver(NetworkConnection conn) 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 @@ -433,7 +500,7 @@ internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, boo 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 + "\n\n" + e.ToString()); + Debug.LogError("OnSerialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + "\n\n" + e.ToString()); } int endPosition = writer.Position; @@ -442,7 +509,7 @@ internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, boo writer.Write(endPosition - contentPosition); writer.Position = endPosition; - if (LogFilter.Debug) { Debug.Log("OnSerializeSafely written for object=" + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + "header@" + headerPosition + " content@" + contentPosition + " end@" + endPosition + " contentSize=" + (endPosition - contentPosition)); } + 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; } @@ -455,8 +522,8 @@ internal bool OnSerializeSafely(NetworkBehaviour comp, NetworkWriter writer, boo // -> returns serialized data of everything dirty, null if nothing was dirty internal byte[] OnSerializeAllSafely(bool initialState) { - // reset cached writer's position - onSerializeWriter.Position = 0; + // reset cached writer length and position + onSerializeWriter.SetLength(0); if (m_NetworkBehaviours.Length > 64) { @@ -478,7 +545,7 @@ internal byte[] OnSerializeAllSafely(bool initialState) if (initialState || comp.IsDirty()) { // serialize the data - if (LogFilter.Debug) { Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState); } + if (LogFilter.Debug) Debug.Log("OnSerializeAllSafely: " + name + " -> " + comp.GetType() + " initial=" + initialState); OnSerializeSafely(comp, onSerializeWriter, initialState); // Clear dirty bits only if we are synchronizing data and not sending a spawn message. @@ -509,8 +576,6 @@ private ulong GetDirtyMask(NetworkBehaviour[] components, bool initialState) return dirtyComponentsMask; } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, bool initialState) { // read header as 4 bytes @@ -518,7 +583,7 @@ internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, b // read content byte[] bytes = reader.ReadBytes(contentSize); - if (LogFilter.Debug) { Debug.Log("OnDeserializeSafely extracted: " + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + " length=" + bytes.Length); } + if (LogFilter.Debug) Debug.Log("OnDeserializeSafely extracted: " + comp.name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + " length=" + bytes.Length); // call OnDeserialize with a temporary reader, so that the // original one can't be messed with. we also wrap it in a @@ -530,13 +595,13 @@ internal void OnDeserializeSafely(NetworkBehaviour comp, NetworkReader reader, b comp.OnDeserialize(componentReader, initialState); if (componentReader.Position != componentReader.Length) { - Debug.LogWarning("OnDeserialize didn't read the full " + bytes.Length + " bytes for object:" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId + ". Make sure that OnSerialize and OnDeserialize write/read the same amount of data in all cases."); + Debug.LogWarning("OnDeserialize didn't read the full " + bytes.Length + " 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."); } } 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 + " length=" + bytes.Length + ". Possible Reasons:\n * Do " + comp.GetType() + "'s OnSerialize and OnDeserialize calls write the same amount of data(" + bytes.Length +" 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.ToString()); + Debug.LogError("OnDeserialize failed for: object=" + name + " component=" + comp.GetType() + " sceneId=" + m_SceneId.ToString("X") + " length=" + bytes.Length + ". Possible Reasons:\n * Do " + comp.GetType() + "'s OnSerialize and OnDeserialize calls write the same amount of data(" + bytes.Length +" 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.ToString()); } } @@ -557,8 +622,6 @@ internal void OnDeserializeAllSafely(NetworkBehaviour[] components, NetworkReade } } - //////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // happens on client internal void HandleClientAuthority(bool authority) { @@ -572,7 +635,7 @@ internal void HandleClientAuthority(bool authority) } // helper function to handle SyncEvent/Command/Rpc - internal void HandleRemoteCall(int componentIndex, int functionHash, UNetInvokeType invokeType, NetworkReader reader) + internal void HandleRemoteCall(int componentIndex, int functionHash, MirrorInvokeType invokeType, NetworkReader reader) { if (gameObject == null) { @@ -598,19 +661,19 @@ internal void HandleRemoteCall(int componentIndex, int functionHash, UNetInvokeT // happens on client internal void HandleSyncEvent(int componentIndex, int eventHash, NetworkReader reader) { - HandleRemoteCall(componentIndex, eventHash, UNetInvokeType.SyncEvent, reader); + HandleRemoteCall(componentIndex, eventHash, MirrorInvokeType.SyncEvent, reader); } // happens on server internal void HandleCommand(int componentIndex, int cmdHash, NetworkReader reader) { - HandleRemoteCall(componentIndex, cmdHash, UNetInvokeType.Command, reader); + HandleRemoteCall(componentIndex, cmdHash, MirrorInvokeType.Command, reader); } // happens on client internal void HandleRPC(int componentIndex, int rpcHash, NetworkReader reader) { - HandleRemoteCall(componentIndex, rpcHash, UNetInvokeType.ClientRpc, reader); + HandleRemoteCall(componentIndex, rpcHash, MirrorInvokeType.ClientRpc, reader); } internal void OnUpdateVars(NetworkReader reader, bool initialState) @@ -648,16 +711,6 @@ internal void SetLocalPlayer() } } - internal void SetConnectionToServer(NetworkConnection conn) - { - connectionToServer = conn; - } - - internal void SetConnectionToClient(NetworkConnection conn) - { - connectionToClient = conn; - } - internal void OnNetworkDestroy() { for (int i = 0; m_NetworkBehaviours != null && i < m_NetworkBehaviours.Length; i++) @@ -695,7 +748,7 @@ internal void AddObserver(NetworkConnection conn) return; } - if (LogFilter.Debug) { Debug.Log("Added observer " + conn.address + " added for " + gameObject); } + if (LogFilter.Debug) Debug.Log("Added observer " + conn.address + " added for " + gameObject); observers[conn.connectionId] = conn; conn.AddToVisList(this); @@ -762,7 +815,7 @@ public void RebuildObservers(bool initialize) { // new observer conn.AddToVisList(this); - if (LogFilter.Debug) { Debug.Log("New Observer for " + gameObject + " " + conn); } + if (LogFilter.Debug) Debug.Log("New Observer for " + gameObject + " " + conn); changed = true; } } @@ -773,7 +826,7 @@ public void RebuildObservers(bool initialize) { // removed observer conn.RemoveFromVisList(this, false); - if (LogFilter.Debug) { Debug.Log("Removed Observer for " + gameObject + " " + conn); } + if (LogFilter.Debug) Debug.Log("Removed Observer for " + gameObject + " " + conn); changed = true; } } @@ -831,7 +884,7 @@ public bool RemoveClientAuthority(NetworkConnection conn) netId = netId, authority = false }; - conn.Send((short)MsgType.LocalClientAuthority, msg); + conn.Send(msg); clientAuthorityCallback?.Invoke(conn, this, false); return true; @@ -841,12 +894,12 @@ public bool AssignClientAuthority(NetworkConnection conn) { if (!isServer) { - Debug.LogError("AssignClientAuthority can only be call on the server for spawned objects."); + 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 component with LocalPlayerAuthority set."); + Debug.LogError("AssignClientAuthority can only be used for NetworkIdentity components with LocalPlayerAuthority set."); return false; } @@ -874,7 +927,7 @@ public bool AssignClientAuthority(NetworkConnection conn) netId = netId, authority = true }; - conn.Send((short)MsgType.LocalClientAuthority, msg); + conn.Send(msg); clientAuthorityCallback?.Invoke(conn, this, true); return true; @@ -883,10 +936,7 @@ public bool AssignClientAuthority(NetworkConnection conn) // 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; - } + internal void MarkForReset() => m_Reset = true; // if we have marked an identity for reset we do the actual reset. internal void Reset() @@ -910,12 +960,12 @@ internal void Reset() } - // UNetUpdate is in hot path. caching the vars msg is really worth it to + // 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 unity runtime immediately after the regular "Update()" function. - internal void UNetUpdate() + // invoked by NetworkServer during Update() + internal void MirrorUpdate() { // SendToReady sends to all observers. no need to serialize if we // don't have any. @@ -929,16 +979,8 @@ internal void UNetUpdate() // populate cached UpdateVarsMessage and send varsMessage.netId = netId; varsMessage.payload = payload; - NetworkServer.SendToReady(this, (short)MsgType.UpdateVars, varsMessage); + NetworkServer.SendToReady(this, varsMessage); } } - - // this is invoked by the UnityEngine - public static void UNetStaticUpdate() - { - NetworkServer.Update(); - NetworkClient.UpdateClients(); - NetworkManager.UpdateScene(); - } } } diff --git a/Assets/Mirror/Runtime/NetworkManager.cs b/Assets/Mirror/Runtime/NetworkManager.cs index 5e81aa35f..225b1ca42 100644 --- a/Assets/Mirror/Runtime/NetworkManager.cs +++ b/Assets/Mirror/Runtime/NetworkManager.cs @@ -1,11 +1,10 @@ +using System; using System.Collections.Generic; -using System.Net; using System.Linq; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; using UnityEngine.Serialization; -using System; namespace Mirror { @@ -36,7 +35,7 @@ public class NetworkManager : MonoBehaviour [Header("Network Info")] // transport layer - public Transport transport; + [SerializeField] protected Transport transport; [FormerlySerializedAs("m_NetworkAddress")] public string networkAddress = "localhost"; [FormerlySerializedAs("m_MaxConnections")] public int maxConnections = 4; @@ -57,8 +56,8 @@ public class NetworkManager : MonoBehaviour public int numPlayers => NetworkServer.connections.Count(kv => kv.Value.playerController != null); // runtime data - // this is used to make sure that all scene changes are initialized by UNET. - // Loading a scene manually wont set networkSceneName, so UNET would still load it again on start. + // 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 = ""; [NonSerialized] public bool isNetworkActive; @@ -83,13 +82,12 @@ public virtual void Awake() networkSceneName = offlineScene; InitializeSingleton(); + } - // headless mode? then start the server - if (Utils.IsHeadless() && startOnHeadless) - { - Application.targetFrameRate = 60; - StartServer(); - } + // headless mode detection + public static bool IsHeadless() + { + return SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null; } void InitializeSingleton() @@ -106,20 +104,24 @@ void InitializeSingleton() { if (singleton != null) { - Debug.LogError("Multiple NetworkManagers detected in the scene. Only one NetworkManager can exist at a time. The duplicate NetworkManager will not be used."); + 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)"); } + 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)"); } + 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; + // persistent network address between scene changes if (networkAddress != "") { @@ -131,6 +133,19 @@ void InitializeSingleton() } } + 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(); + } + } + // 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. @@ -141,7 +156,9 @@ 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 - NetworkIdentity.UNetStaticUpdate(); + NetworkServer.Update(); + NetworkClient.UpdateClient(); + UpdateScene(); } // When pressing Stop in the Editor, Unity keeps threads alive until we @@ -153,7 +170,7 @@ public virtual void LateUpdate() // virtual so that inheriting classes' OnApplicationQuit() can call base.OnApplicationQuit() too public virtual void OnApplicationQuit() { - transport.Shutdown(); + Transport.activeTransport.Shutdown(); } // virtual so that inheriting classes' OnValidate() can call base.OnValidate() too @@ -185,12 +202,12 @@ public virtual void OnValidate() internal void RegisterServerMessages() { - NetworkServer.RegisterHandler(MsgType.Connect, OnServerConnectInternal); - NetworkServer.RegisterHandler(MsgType.Disconnect, OnServerDisconnectInternal); - NetworkServer.RegisterHandler(MsgType.Ready, OnServerReadyMessageInternal); - NetworkServer.RegisterHandler(MsgType.AddPlayer, OnServerAddPlayerMessageInternal); - NetworkServer.RegisterHandler(MsgType.RemovePlayer, OnServerRemovePlayerMessageInternal); - NetworkServer.RegisterHandler(MsgType.Error, OnServerErrorInternal); + NetworkServer.RegisterHandler(OnServerConnectInternal); + NetworkServer.RegisterHandler(OnServerDisconnectInternal); + NetworkServer.RegisterHandler(OnServerReadyMessageInternal); + NetworkServer.RegisterHandler(OnServerAddPlayerMessageInternal); + NetworkServer.RegisterHandler(OnServerRemovePlayerMessageInternal); + NetworkServer.RegisterHandler(OnServerErrorInternal); } public bool StartServer() @@ -230,7 +247,7 @@ public bool StartServer() // this must be after Listen(), since that registers the default message handlers RegisterServerMessages(); - if (LogFilter.Debug) { Debug.Log("NetworkManager StartServer"); } + 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 @@ -248,11 +265,11 @@ public bool StartServer() internal void RegisterClientMessages(NetworkClient client) { - client.RegisterHandler(MsgType.Connect, OnClientConnectInternal); - client.RegisterHandler(MsgType.Disconnect, OnClientDisconnectInternal); - client.RegisterHandler(MsgType.NotReady, OnClientNotReadyMessageInternal); - client.RegisterHandler(MsgType.Error, OnClientErrorInternal); - client.RegisterHandler(MsgType.Scene, OnClientSceneInternal); + client.RegisterHandler(OnClientConnectInternal); + client.RegisterHandler(OnClientDisconnectInternal); + client.RegisterHandler(OnClientNotReadyMessageInternal); + client.RegisterHandler(OnClientErrorInternal); + client.RegisterHandler(OnClientSceneInternal); if (playerPrefab != null) { @@ -286,7 +303,7 @@ public NetworkClient StartClient() Debug.LogError("Must set the Network Address field in the manager"); return null; } - if (LogFilter.Debug) { Debug.Log("NetworkManager StartClient address:" + networkAddress); } + if (LogFilter.Debug) Debug.Log("NetworkManager StartClient address:" + networkAddress); client.Connect(networkAddress); @@ -309,7 +326,7 @@ public virtual NetworkClient StartHost() NetworkClient ConnectLocalClient() { - if (LogFilter.Debug) { Debug.Log("NetworkManager StartHost"); } + if (LogFilter.Debug) Debug.Log("NetworkManager StartHost"); networkAddress = "localhost"; client = ClientScene.ConnectLocalServer(); RegisterClientMessages(client); @@ -331,7 +348,7 @@ public void StopServer() OnStopServer(); - if (LogFilter.Debug) { Debug.Log("NetworkManager StopServer"); } + if (LogFilter.Debug) Debug.Log("NetworkManager StopServer"); isNetworkActive = false; NetworkServer.Shutdown(); if (!string.IsNullOrEmpty(offlineScene)) @@ -345,7 +362,7 @@ public void StopClient() { OnStopClient(); - if (LogFilter.Debug) { Debug.Log("NetworkManager StopClient"); } + if (LogFilter.Debug) Debug.Log("NetworkManager StopClient"); isNetworkActive = false; if (client != null) { @@ -358,7 +375,8 @@ public void StopClient() ClientScene.DestroyAllClientObjects(); if (!string.IsNullOrEmpty(offlineScene)) { - ClientChangeScene(offlineScene, false); + // Must pass true or offlineScene will not be loaded + ClientChangeScene(offlineScene, true); } CleanupNetworkIdentities(); } @@ -371,14 +389,14 @@ public virtual void ServerChangeScene(string newSceneName) return; } - if (LogFilter.Debug) { Debug.Log("ServerChangeScene " + newSceneName); } + if (LogFilter.Debug) Debug.Log("ServerChangeScene " + newSceneName); NetworkServer.SetAllClientsNotReady(); networkSceneName = newSceneName; s_LoadingSceneAsync = SceneManager.LoadSceneAsync(newSceneName); - StringMessage msg = new StringMessage(networkSceneName); - NetworkServer.SendToAll((short)MsgType.Scene, msg); + SceneMessage msg = new SceneMessage(networkSceneName); + NetworkServer.SendToAll(msg); s_StartPositionIndex = 0; startPositions.Clear(); @@ -400,7 +418,7 @@ internal void ClientChangeScene(string newSceneName, bool forceReload) return; } - if (LogFilter.Debug) { Debug.Log("ClientChangeScene newSceneName:" + newSceneName + " networkSceneName:" + networkSceneName); } + if (LogFilter.Debug) Debug.Log("ClientChangeScene newSceneName:" + newSceneName + " networkSceneName:" + networkSceneName); if (newSceneName == networkSceneName) { @@ -416,8 +434,8 @@ internal void ClientChangeScene(string newSceneName, bool forceReload) // (client may be null after StopClient etc.) if (client != null) { - if (LogFilter.Debug) { Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded."); } - NetworkManager.singleton.transport.enabled = false; + if (LogFilter.Debug) Debug.Log("ClientChangeScene: pausing handlers while scene is loading to avoid data loss after scene was loaded."); + Transport.activeTransport.enabled = false; } // Let client prepare for scene change @@ -434,8 +452,8 @@ void FinishLoadScene() if (client != null) { // process queued messages that we received while loading the scene - if (LogFilter.Debug) { Debug.Log("FinishLoadScene: resuming handlers after scene was loading."); } - NetworkManager.singleton.transport.enabled = true; + if (LogFilter.Debug) Debug.Log("FinishLoadScene: resuming handlers after scene was loading."); + Transport.activeTransport.enabled = true; if (s_ClientReadyConnection != null) { @@ -446,7 +464,7 @@ void FinishLoadScene() } else { - if (LogFilter.Debug) { Debug.Log("FinishLoadScene client is null"); } + if (LogFilter.Debug) Debug.Log("FinishLoadScene client is null"); } if (NetworkServer.active) @@ -466,7 +484,7 @@ internal static void UpdateScene() { if (singleton != null && s_LoadingSceneAsync != null && s_LoadingSceneAsync.isDone) { - if (LogFilter.Debug) { Debug.Log("ClientChangeScene done readyCon:" + s_ClientReadyConnection); } + if (LogFilter.Debug) Debug.Log("ClientChangeScene done readyCon:" + s_ClientReadyConnection); singleton.FinishLoadScene(); s_LoadingSceneAsync.allowSceneActivation = true; s_LoadingSceneAsync = null; @@ -476,18 +494,18 @@ internal static void UpdateScene() // virtual so that inheriting classes' OnDestroy() can call base.OnDestroy() too public virtual void OnDestroy() { - if (LogFilter.Debug) { Debug.Log("NetworkManager destroyed"); } + if (LogFilter.Debug) Debug.Log("NetworkManager destroyed"); } public static void RegisterStartPosition(Transform start) { - if (LogFilter.Debug) { Debug.Log("RegisterStartPosition: (" + start.gameObject.name + ") " + start.position); } + if (LogFilter.Debug) Debug.Log("RegisterStartPosition: (" + start.gameObject.name + ") " + start.position); startPositions.Add(start); } public static void UnRegisterStartPosition(Transform start) { - if (LogFilter.Debug) { Debug.Log("UnRegisterStartPosition: (" + start.gameObject.name + ") " + start.position); } + if (LogFilter.Debug) Debug.Log("UnRegisterStartPosition: (" + start.gameObject.name + ") " + start.position); startPositions.Remove(start); } @@ -510,141 +528,117 @@ public static void Shutdown() singleton = null; } - // ----------------------------- Server Internal Message Handlers -------------------------------- - - internal void OnServerConnectInternal(NetworkMessage netMsg) + #region Server Internal Message Handlers + internal void OnServerConnectInternal(NetworkConnection conn, ConnectMessage connectMsg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerConnectInternal"); } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerConnectInternal"); if (networkSceneName != "" && networkSceneName != offlineScene) { - StringMessage msg = new StringMessage(networkSceneName); - netMsg.conn.Send((short)MsgType.Scene, msg); + SceneMessage msg = new SceneMessage(networkSceneName); + conn.Send(msg); } - OnServerConnect(netMsg.conn); + OnServerConnect(conn); } - internal void OnServerDisconnectInternal(NetworkMessage netMsg) + internal void OnServerDisconnectInternal(NetworkConnection conn, DisconnectMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerDisconnectInternal"); } - OnServerDisconnect(netMsg.conn); + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerDisconnectInternal"); + OnServerDisconnect(conn); } - internal void OnServerReadyMessageInternal(NetworkMessage netMsg) + internal void OnServerReadyMessageInternal(NetworkConnection conn, ReadyMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerReadyMessageInternal"); } - OnServerReady(netMsg.conn); + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerReadyMessageInternal"); + OnServerReady(conn); } - internal void OnServerAddPlayerMessageInternal(NetworkMessage netMsg) + internal void OnServerAddPlayerMessageInternal(NetworkConnection conn, AddPlayerMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerAddPlayerMessageInternal"); } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerAddPlayerMessageInternal"); - AddPlayerMessage msg = netMsg.ReadMessage(); + OnServerAddPlayer(conn, msg); + } - if (msg.value != null && msg.value.Length > 0) + internal void OnServerRemovePlayerMessageInternal(NetworkConnection conn, RemovePlayerMessage msg) + { + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerRemovePlayerMessageInternal"); + + if (conn.playerController != null) { - // convert payload to extra message and call OnServerAddPlayer - // (usually for character selection information) - NetworkMessage extraMessage = new NetworkMessage - { - reader = new NetworkReader(msg.value), - conn = netMsg.conn - }; - OnServerAddPlayer(netMsg.conn, extraMessage); - } - else - { - OnServerAddPlayer(netMsg.conn); + OnServerRemovePlayer(conn, conn.playerController); + conn.RemovePlayerController(); } } - internal void OnServerRemovePlayerMessageInternal(NetworkMessage netMsg) + internal void OnServerErrorInternal(NetworkConnection conn, ErrorMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerRemovePlayerMessageInternal"); } - - if (netMsg.conn.playerController != null) - { - OnServerRemovePlayer(netMsg.conn, netMsg.conn.playerController); - netMsg.conn.RemovePlayerController(); - } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnServerErrorInternal"); + OnServerError(conn, msg.value); } + #endregion - internal void OnServerErrorInternal(NetworkMessage netMsg) + #region Client Internal Message Handlers + internal void OnClientConnectInternal(NetworkConnection conn, ConnectMessage message) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnServerErrorInternal"); } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnClientConnectInternal"); - ErrorMessage msg = netMsg.ReadMessage(); - OnServerError(netMsg.conn, msg.value); - } - - // ----------------------------- Client Internal Message Handlers -------------------------------- - - internal void OnClientConnectInternal(NetworkMessage netMsg) - { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnClientConnectInternal"); } - - string loadedSceneName = SceneManager.GetSceneAt(0).name; + string loadedSceneName = SceneManager.GetActiveScene().name; if (string.IsNullOrEmpty(onlineScene) || onlineScene == offlineScene || loadedSceneName == onlineScene) { clientLoadedScene = false; - OnClientConnect(netMsg.conn); + OnClientConnect(conn); } else { // will wait for scene id to come from the server. - s_ClientReadyConnection = netMsg.conn; + s_ClientReadyConnection = conn; } } - internal void OnClientDisconnectInternal(NetworkMessage netMsg) + internal void OnClientDisconnectInternal(NetworkConnection conn, DisconnectMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnClientDisconnectInternal"); } - - OnClientDisconnect(netMsg.conn); + if (LogFilter.Debug) Debug.Log("NetworkManager.OnClientDisconnectInternal"); + OnClientDisconnect(conn); } - internal void OnClientNotReadyMessageInternal(NetworkMessage netMsg) + internal void OnClientNotReadyMessageInternal(NetworkConnection conn, NotReadyMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnClientNotReadyMessageInternal"); } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnClientNotReadyMessageInternal"); ClientScene.ready = false; - OnClientNotReady(netMsg.conn); + OnClientNotReady(conn); // NOTE: s_ClientReadyConnection is not set here! don't want OnClientConnect to be invoked again after scene changes. } - internal void OnClientErrorInternal(NetworkMessage netMsg) + internal void OnClientErrorInternal(NetworkConnection conn, ErrorMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnClientErrorInternal"); } - - ErrorMessage msg = netMsg.ReadMessage(); - OnClientError(netMsg.conn, msg.value); + if (LogFilter.Debug) Debug.Log("NetworkManager:OnClientErrorInternal"); + OnClientError(conn, msg.value); } - internal void OnClientSceneInternal(NetworkMessage netMsg) + internal void OnClientSceneInternal(NetworkConnection conn, SceneMessage msg) { - if (LogFilter.Debug) { Debug.Log("NetworkManager:OnClientSceneInternal"); } + if (LogFilter.Debug) Debug.Log("NetworkManager.OnClientSceneInternal"); - string newSceneName = netMsg.reader.ReadString(); + string newSceneName = msg.value; if (IsClientConnected() && !NetworkServer.active) { ClientChangeScene(newSceneName, true); } } + #endregion - // ----------------------------- Server System Callbacks -------------------------------- - - public virtual void OnServerConnect(NetworkConnection conn) - { - } + #region Server System Callbacks + public virtual void OnServerConnect(NetworkConnection conn) {} public virtual void OnServerDisconnect(NetworkConnection conn) { NetworkServer.DestroyPlayerForConnection(conn); - if (LogFilter.Debug) { Debug.Log("OnServerDisconnect: Client disconnected."); } + if (LogFilter.Debug) Debug.Log("OnServerDisconnect: Client disconnected."); } public virtual void OnServerReady(NetworkConnection conn) @@ -652,16 +646,23 @@ public virtual void OnServerReady(NetworkConnection conn) if (conn.playerController == null) { // this is now allowed (was not for a while) - if (LogFilter.Debug) { Debug.Log("Ready with no player object"); } + if (LogFilter.Debug) Debug.Log("Ready with no player object"); } NetworkServer.SetClientReady(conn); } + [Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")] public virtual void OnServerAddPlayer(NetworkConnection conn, NetworkMessage extraMessage) { OnServerAddPlayerInternal(conn); } + public virtual void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) + { + OnServerAddPlayerInternal(conn); + } + + [Obsolete("Use OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) instead")] public virtual void OnServerAddPlayer(NetworkConnection conn) { OnServerAddPlayerInternal(conn); @@ -728,16 +729,12 @@ public virtual void OnServerRemovePlayer(NetworkConnection conn, NetworkIdentity } } - public virtual void OnServerError(NetworkConnection conn, int errorCode) - { - } + public virtual void OnServerError(NetworkConnection conn, int errorCode) {} - public virtual void OnServerSceneChanged(string sceneName) - { - } - - // ----------------------------- Client System Callbacks -------------------------------- + public virtual void OnServerSceneChanged(string sceneName) {} + #endregion + #region Client System Callbacks public virtual void OnClientConnect(NetworkConnection conn) { if (!clientLoadedScene) @@ -756,17 +753,13 @@ public virtual void OnClientDisconnect(NetworkConnection conn) StopClient(); } - public virtual void OnClientError(NetworkConnection conn, int errorCode) - { - } + public virtual void OnClientError(NetworkConnection conn, int errorCode) {} - public virtual void OnClientNotReady(NetworkConnection conn) - { - } + public virtual void OnClientNotReady(NetworkConnection conn) {} // Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed // This allows client to do work / cleanup / prep before the scene changes. - public virtual void OnClientChangeScene(string newSceneName) { } + public virtual void OnClientChangeScene(string newSceneName) {} public virtual void OnClientSceneChanged(NetworkConnection conn) { @@ -783,9 +776,9 @@ public virtual void OnClientSceneChanged(NetworkConnection conn) } } } + #endregion - //------------------------------ Start & Stop callbacks ----------------------------------- - + #region Start & Stop callbacks // Since there are multiple versions of StartServer, StartClient and StartHost, to reliably customize // their functionality, users would need override all the versions. Instead these callbacks are invoked // from all versions, so users only need to implement this one case. @@ -796,5 +789,6 @@ public virtual void OnStartClient(NetworkClient client) {} public virtual void OnStopServer() {} public virtual void OnStopClient() {} public virtual void OnStopHost() {} + #endregion } } diff --git a/Assets/Mirror/Runtime/NetworkManagerHUD.cs b/Assets/Mirror/Runtime/NetworkManagerHUD.cs index 2c8ec9919..4c546608b 100644 --- a/Assets/Mirror/Runtime/NetworkManagerHUD.cs +++ b/Assets/Mirror/Runtime/NetworkManagerHUD.cs @@ -1,9 +1,7 @@ // vis2k: GUILayout instead of spacey += ...; removed Update hotkeys to avoid // confusion if someone accidentally presses one. -using System; using System.ComponentModel; using UnityEngine; -using UnityEngine.Rendering; namespace Mirror { @@ -80,7 +78,7 @@ void OnGUI() // server / client status message if (NetworkServer.active) { - GUILayout.Label("Server: active. Transport: " + manager.transport); + GUILayout.Label("Server: active. Transport: " + Transport.activeTransport); } if (manager.IsClientConnected()) { diff --git a/Assets/Mirror/Runtime/NetworkMessage.cs b/Assets/Mirror/Runtime/NetworkMessage.cs new file mode 100644 index 000000000..99e3a3524 --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkMessage.cs @@ -0,0 +1,21 @@ +namespace Mirror +{ + public struct NetworkMessage + { + public int msgType; + public NetworkConnection conn; + public NetworkReader reader; + + public TMsg ReadMessage() where TMsg : MessageBase, new() + { + TMsg msg = new TMsg(); + msg.Deserialize(reader); + return msg; + } + + public void ReadMessage(TMsg msg) where TMsg : MessageBase + { + msg.Deserialize(reader); + } + } +} diff --git a/Assets/Mirror/Runtime/NetworkMessage.cs.meta b/Assets/Mirror/Runtime/NetworkMessage.cs.meta new file mode 100644 index 000000000..370b0a6c2 --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkMessage.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb04e4848a2e4452aa2dbd7adb801c51 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/NetworkReader.cs b/Assets/Mirror/Runtime/NetworkReader.cs index 15447fe97..0073521c1 100644 --- a/Assets/Mirror/Runtime/NetworkReader.cs +++ b/Assets/Mirror/Runtime/NetworkReader.cs @@ -1,22 +1,28 @@ -using System; +using System; using System.IO; +using System.Text; using UnityEngine; namespace Mirror { public class NetworkReader { + // cache encoding instead of creating it with BinaryWriter each time + // 1000 readers before: 1MB GC, 30ms + // 1000 readers after: 0.8MB GC, 18ms + static readonly UTF8Encoding encoding = new UTF8Encoding(false, true); + readonly BinaryReader reader; public NetworkReader(byte[] buffer) { - reader = new BinaryReader(new MemoryStream(buffer)); + reader = new BinaryReader(new MemoryStream(buffer, false), encoding); } // 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position // -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here public int Position { get { return (int)reader.BaseStream.Position; } set { reader.BaseStream.Position = value; } } - public int Length => (int)reader.BaseStream.Length; + public int Length => (int)reader.BaseStream.Length; public byte ReadByte() => reader.ReadByte(); public sbyte ReadSByte() => reader.ReadSByte(); @@ -53,17 +59,17 @@ public byte[] ReadBytesAndSize() // http://sqlite.org/src4/doc/trunk/www/varint.wiki // NOTE: big endian. - public UInt32 ReadPackedUInt32() + public uint ReadPackedUInt32() { - UInt64 value = ReadPackedUInt64(); - if (value > UInt32.MaxValue) + ulong value = ReadPackedUInt64(); + if (value > uint.MaxValue) { throw new IndexOutOfRangeException("ReadPackedUInt32() failure, value too large"); } - return (UInt32)value; + return (uint)value; } - public UInt64 ReadPackedUInt64() + public ulong ReadPackedUInt64() { byte a0 = ReadByte(); if (a0 < 241) @@ -74,49 +80,49 @@ public UInt64 ReadPackedUInt64() byte a1 = ReadByte(); if (a0 >= 241 && a0 <= 248) { - return 240 + 256 * (a0 - ((UInt64)241)) + a1; + return 240 + 256 * (a0 - ((ulong)241)) + a1; } byte a2 = ReadByte(); if (a0 == 249) { - return 2288 + (((UInt64)256) * a1) + a2; + return 2288 + (((ulong)256) * a1) + a2; } byte a3 = ReadByte(); if (a0 == 250) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16); } byte a4 = ReadByte(); if (a0 == 251) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16) + (((UInt64)a4) << 24); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24); } byte a5 = ReadByte(); if (a0 == 252) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16) + (((UInt64)a4) << 24) + (((UInt64)a5) << 32); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32); } byte a6 = ReadByte(); if (a0 == 253) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16) + (((UInt64)a4) << 24) + (((UInt64)a5) << 32) + (((UInt64)a6) << 40); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40); } byte a7 = ReadByte(); if (a0 == 254) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16) + (((UInt64)a4) << 24) + (((UInt64)a5) << 32) + (((UInt64)a6) << 40) + (((UInt64)a7) << 48); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48); } byte a8 = ReadByte(); if (a0 == 255) { - return a1 + (((UInt64)a2) << 8) + (((UInt64)a3) << 16) + (((UInt64)a4) << 24) + (((UInt64)a5) << 32) + (((UInt64)a6) << 40) + (((UInt64)a7) << 48) + (((UInt64)a8) << 56); + return a1 + (((ulong)a2) << 8) + (((ulong)a3) << 16) + (((ulong)a4) << 24) + (((ulong)a5) << 32) + (((ulong)a6) << 40) + (((ulong)a7) << 48) + (((ulong)a8) << 56); } throw new IndexOutOfRangeException("ReadPackedUInt64() failure: " + a0); @@ -210,7 +216,7 @@ public Transform ReadTransform() return identity.transform; } - if (LogFilter.Debug) { Debug.Log("ReadTransform netId:" + netId + " not found in spawned"); } + if (LogFilter.Debug) Debug.Log("ReadTransform netId:" + netId + " not found in spawned"); return null; } @@ -227,7 +233,7 @@ public GameObject ReadGameObject() return identity.gameObject; } - if (LogFilter.Debug) { Debug.Log("ReadGameObject netId:" + netId + " not found in spawned"); } + if (LogFilter.Debug) Debug.Log("ReadGameObject netId:" + netId + " not found in spawned"); return null; } @@ -244,7 +250,7 @@ public NetworkIdentity ReadNetworkIdentity() return identity; } - if (LogFilter.Debug) { Debug.Log("ReadNetworkIdentity netId:" + netId + " not found in spawned"); } + if (LogFilter.Debug) Debug.Log("ReadNetworkIdentity netId:" + netId + " not found in spawned"); return null; } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 6c9c7ce74..dd95503d1 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -1,7 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Net; using UnityEngine; namespace Mirror @@ -17,11 +15,9 @@ public static class NetworkServer // => removed it for easier code. use .localConection now! public static NetworkConnection localConnection => s_LocalConnection; - public static int serverHostId { get; private set; } = -1; - // public static Dictionary connections = new Dictionary(); - public static Dictionary handlers = new Dictionary(); + public static Dictionary handlers = new Dictionary(); public static bool dontListen; @@ -45,14 +41,13 @@ public static void Shutdown() } else { - NetworkManager.singleton.transport.ServerStop(); - serverHostId = -1; + Transport.activeTransport.ServerStop(); } - NetworkManager.singleton.transport.OnServerDisconnected.RemoveListener(OnDisconnected); - NetworkManager.singleton.transport.OnServerConnected.RemoveListener(OnConnected); - NetworkManager.singleton.transport.OnServerDataReceived.RemoveListener(OnDataReceived); - NetworkManager.singleton.transport.OnServerError.RemoveListener(OnError); + Transport.activeTransport.OnServerDisconnected.RemoveListener(OnDisconnected); + Transport.activeTransport.OnServerConnected.RemoveListener(OnConnected); + Transport.activeTransport.OnServerDataReceived.RemoveListener(OnDataReceived); + Transport.activeTransport.OnServerError.RemoveListener(OnError); s_Initialized = false; } @@ -66,23 +61,23 @@ static void Initialize() return; s_Initialized = true; - if (LogFilter.Debug) { Debug.Log("NetworkServer Created version " + Version.Current); } + if (LogFilter.Debug) Debug.Log("NetworkServer Created version " + Version.Current); //Make sure connections are cleared in case any old connections references exist from previous sessions connections.Clear(); - NetworkManager.singleton.transport.OnServerDisconnected.AddListener(OnDisconnected); - NetworkManager.singleton.transport.OnServerConnected.AddListener(OnConnected); - NetworkManager.singleton.transport.OnServerDataReceived.AddListener(OnDataReceived); - NetworkManager.singleton.transport.OnServerError.AddListener(OnError); + Transport.activeTransport.OnServerDisconnected.AddListener(OnDisconnected); + Transport.activeTransport.OnServerConnected.AddListener(OnConnected); + Transport.activeTransport.OnServerDataReceived.AddListener(OnDataReceived); + Transport.activeTransport.OnServerError.AddListener(OnError); } internal static void RegisterMessageHandlers() { - RegisterHandler(MsgType.Ready, OnClientReadyMessage); - RegisterHandler(MsgType.Command, OnCommandMessage); - RegisterHandler(MsgType.RemovePlayer, OnRemovePlayerMessage); - RegisterHandler(MsgType.Ping, NetworkTime.OnServerPing); + RegisterHandler(OnClientReadyMessage); + RegisterHandler(OnCommandMessage); + RegisterHandler(OnRemovePlayerMessage); + RegisterHandler(NetworkTime.OnServerPing); } public static bool Listen(int maxConnections) @@ -93,15 +88,8 @@ public static bool Listen(int maxConnections) // only start server if we want to listen if (!dontListen) { - NetworkManager.singleton.transport.ServerStart(); - serverHostId = 0; // so it doesn't return false - - if (serverHostId == -1) - { - return false; - } - - if (LogFilter.Debug) { Debug.Log("Server started listening"); } + Transport.activeTransport.ServerStart(); + if (LogFilter.Debug) Debug.Log("Server started listening"); } active = true; @@ -141,13 +129,11 @@ internal static int AddLocalClient(LocalClient localClient) { connectionId = 0 }; - AddConnection(s_LocalConnection); - - s_LocalConnection.InvokeHandlerNoData((short)MsgType.Connect); + OnConnected(s_LocalConnection); return 0; } - internal static void RemoveLocalClient(NetworkConnection localClientConnection) + internal static void RemoveLocalClient() { if (s_LocalConnection != null) { @@ -170,9 +156,9 @@ internal static void ActivateLocalClientScene() { if (!identity.isClient) { - if (LogFilter.Debug) { Debug.Log("ActivateClientScene " + identity.netId + " " + identity); } + if (LogFilter.Debug) Debug.Log("ActivateClientScene " + identity.netId + " " + identity); - identity.EnableIsClient(); + identity.isClient = true; identity.OnStartClient(); } } @@ -180,14 +166,15 @@ internal static void ActivateLocalClientScene() // this is like SendToReady - but it doesn't check the ready flag on the connection. // this is used for ObjectDestroy messages. + [Obsolete("use SendToObservers instead")] static bool SendToObservers(NetworkIdentity identity, short msgType, MessageBase msg) { - if (LogFilter.Debug) { Debug.Log("Server.SendToObservers id:" + msgType); } + if (LogFilter.Debug) Debug.Log("Server.SendToObservers id:" + msgType); if (identity != null && identity.observers != null) { // pack message into byte[] once - byte[] bytes = Protocol.PackMessage((ushort)msgType, msg); + byte[] bytes = MessagePacker.PackMessage((ushort)msgType, msg); // send to all observers bool result = true; @@ -200,12 +187,34 @@ static bool SendToObservers(NetworkIdentity identity, short msgType, MessageBase return false; } - public static bool SendToAll(short msgType, MessageBase msg, int channelId = Channels.DefaultReliable) + // this is like SendToReady - but it doesn't check the ready flag on the connection. + // this is used for ObjectDestroy messages. + static bool SendToObservers(NetworkIdentity identity, T msg) where T: MessageBase { - if (LogFilter.Debug) { Debug.Log("Server.SendToAll id:" + msgType); } + if (LogFilter.Debug) Debug.Log("Server.SendToObservers id:" + typeof(T)); + + if (identity != null && identity.observers != null) + { + // pack message into byte[] once + byte[] bytes = MessagePacker.Pack(msg); + + bool result = true; + foreach (KeyValuePair kvp in identity.observers) + { + result &= kvp.Value.SendBytes(bytes); + } + return result; + } + return false; + } + + [Obsolete("Use SendToAll instead")] + public static bool SendToAll(int msgType, MessageBase msg, int channelId = Channels.DefaultReliable) + { + if (LogFilter.Debug) Debug.Log("Server.SendToAll id:" + msgType); // pack message into byte[] once - byte[] bytes = Protocol.PackMessage((ushort)msgType, msg); + byte[] bytes = MessagePacker.PackMessage((ushort)msgType, msg); // send to all bool result = true; @@ -216,14 +225,30 @@ public static bool SendToAll(short msgType, MessageBase msg, int channelId = Cha return result; } + public static bool SendToAll(T msg, int channelId = Channels.DefaultReliable) where T : MessageBase + { + if (LogFilter.Debug) Debug.Log("Server.SendToAll id:" + typeof(T)); + + // pack message into byte[] once + byte[] bytes = MessagePacker.Pack(msg); + + bool result = true; + foreach (KeyValuePair kvp in connections) + { + result &= kvp.Value.SendBytes(bytes, channelId); + } + return result; + } + + [Obsolete("Use SendToReady instead")] public static bool SendToReady(NetworkIdentity identity, short msgType, MessageBase msg, int channelId = Channels.DefaultReliable) { - if (LogFilter.Debug) { Debug.Log("Server.SendToReady msgType:" + msgType); } + if (LogFilter.Debug) Debug.Log("Server.SendToReady msgType:" + msgType); if (identity != null && identity.observers != null) { // pack message into byte[] once - byte[] bytes = Protocol.PackMessage((ushort)msgType, msg); + byte[] bytes = MessagePacker.PackMessage((ushort)msgType, msg); // send to all ready observers bool result = true; @@ -239,16 +264,32 @@ public static bool SendToReady(NetworkIdentity identity, short msgType, MessageB return false; } + public static bool SendToReady(NetworkIdentity identity,T msg, int channelId = Channels.DefaultReliable) where T: MessageBase + { + if (LogFilter.Debug) Debug.Log("Server.SendToReady msgType:" + typeof(T)); + + if (identity != null && identity.observers != null) + { + // pack message into byte[] once + byte[] bytes = MessagePacker.Pack(msg); + + bool result = true; + foreach (KeyValuePair kvp in identity.observers) + { + if (kvp.Value.isReady) + { + result &= kvp.Value.SendBytes(bytes, channelId); + } + } + return result; + } + return false; + } + public static void DisconnectAll() { DisconnectAllConnections(); - - if (s_LocalConnection != null) - { - s_LocalConnection.Disconnect(); - s_LocalConnection.Dispose(); - s_LocalConnection = null; - } + s_LocalConnection = null; active = false; localClientActive = false; @@ -260,7 +301,9 @@ public static void DisconnectAllConnections() { NetworkConnection conn = kvp.Value; conn.Disconnect(); - OnDisconnected(conn); + // call OnDisconnected unless local player in host mode + if (conn.connectionId != 0) + OnDisconnected(conn); conn.Dispose(); } connections.Clear(); @@ -272,7 +315,7 @@ static void UpdateServerObjects() { if (kvp.Value != null && kvp.Value.gameObject != null) { - kvp.Value.UNetUpdate(); + kvp.Value.MirrorUpdate(); } else { @@ -286,29 +329,29 @@ static void UpdateServerObjects() // The user should never need to pump the update loop manually internal static void Update() { - if (serverHostId == -1) - return; - - UpdateServerObjects(); + if (active) + { + UpdateServerObjects(); + } } static void OnConnected(int connectionId) { - if (LogFilter.Debug) { Debug.Log("Server accepted client:" + connectionId); } + if (LogFilter.Debug) Debug.Log("Server accepted client:" + connectionId); // connectionId needs to be > 0 because 0 is reserved for local player if (connectionId <= 0) { Debug.LogError("Server.HandleConnect: invalid connectionId: " + connectionId + " . Needs to be >0, because 0 is reserved for local player."); - NetworkManager.singleton.transport.ServerDisconnect(connectionId); + Transport.activeTransport.ServerDisconnect(connectionId); return; } // connectionId not in use yet? if (connections.ContainsKey(connectionId)) { - NetworkManager.singleton.transport.ServerDisconnect(connectionId); - if (LogFilter.Debug) { Debug.Log("Server connectionId " + connectionId + " already in use. kicked client:" + connectionId); } + Transport.activeTransport.ServerDisconnect(connectionId); + if (LogFilter.Debug) Debug.Log("Server connectionId " + connectionId + " already in use. kicked client:" + connectionId); return; } @@ -320,36 +363,38 @@ static void OnConnected(int connectionId) if (connections.Count < s_MaxConnections) { // get ip address from connection - string address = NetworkManager.singleton.transport.ServerGetClientAddress(connectionId); + string address = Transport.activeTransport.ServerGetClientAddress(connectionId); // add player info - NetworkConnection conn = new NetworkConnection(address, serverHostId, connectionId); - AddConnection(conn); + NetworkConnection conn = new NetworkConnection(address, connectionId); OnConnected(conn); } else { // kick - NetworkManager.singleton.transport.ServerDisconnect(connectionId); - if (LogFilter.Debug) { Debug.Log("Server full, kicked client:" + connectionId); } + Transport.activeTransport.ServerDisconnect(connectionId); + if (LogFilter.Debug) Debug.Log("Server full, kicked client:" + connectionId); } } static void OnConnected(NetworkConnection conn) { - if (LogFilter.Debug) { Debug.Log("Server accepted client:" + conn.connectionId); } - conn.InvokeHandlerNoData((short)MsgType.Connect); + if (LogFilter.Debug) Debug.Log("Server accepted client:" + conn.connectionId); + + // add connection and invoke connected event + AddConnection(conn); + conn.InvokeHandler(new ConnectMessage()); } static void OnDisconnected(int connectionId) { - if (LogFilter.Debug) { Debug.Log("Server disconnect client:" + connectionId); } + if (LogFilter.Debug) Debug.Log("Server disconnect client:" + connectionId); if (connections.TryGetValue(connectionId, out NetworkConnection conn)) { conn.Disconnect(); RemoveConnection(connectionId); - if (LogFilter.Debug) { Debug.Log("Server lost client:" + connectionId); } + if (LogFilter.Debug) Debug.Log("Server lost client:" + connectionId); OnDisconnected(conn); } @@ -357,7 +402,7 @@ static void OnDisconnected(int connectionId) static void OnDisconnected(NetworkConnection conn) { - conn.InvokeHandlerNoData((short)MsgType.Disconnect); + conn.InvokeHandler(new DisconnectMessage()); if (conn.playerController != null) { @@ -365,9 +410,7 @@ static void OnDisconnected(NetworkConnection conn) Debug.LogWarning("Player not destroyed when connection disconnected."); } - if (LogFilter.Debug) { Debug.Log("Server lost client:" + conn.connectionId); } - conn.RemoveObservers(); - conn.Dispose(); + if (LogFilter.Debug) Debug.Log("Server lost client:" + conn.connectionId); } static void OnDataReceived(int connectionId, byte[] data) @@ -395,7 +438,7 @@ static void OnData(NetworkConnection conn, byte[] data) static void GenerateConnectError(byte error) { - Debug.LogError("UNet Server Connect Error: " + error); + Debug.LogError("Mirror Server Connect Error: " + error); GenerateError(null, error); } @@ -403,21 +446,22 @@ static void GenerateConnectError(byte error) static void GenerateDataError(NetworkConnection conn, byte error) { NetworkError dataError = (NetworkError)error; - Debug.LogError("UNet Server Data Error: " + dataError); + Debug.LogError("Mirror Server Data Error: " + dataError); GenerateError(conn, error); } static void GenerateDisconnectError(NetworkConnection conn, byte error) { NetworkError disconnectError = (NetworkError)error; - Debug.LogError("UNet Server Disconnect Error: " + disconnectError + " conn:[" + conn + "]:" + conn.connectionId); + Debug.LogError("Mirror Server Disconnect Error: " + disconnectError + " conn:[" + conn + "]:" + conn.connectionId); GenerateError(conn, error); } */ static void GenerateError(NetworkConnection conn, byte error) { - if (handlers.ContainsKey((short)MsgType.Error)) + int msgId = MessagePacker.GetId(); + if (handlers.ContainsKey(msgId)) { ErrorMessage msg = new ErrorMessage { @@ -430,32 +474,56 @@ static void GenerateError(NetworkConnection conn, byte error) // pass a reader (attached to local buffer) to handler NetworkReader reader = new NetworkReader(writer.ToArray()); - conn.InvokeHandler((short)MsgType.Error, reader); + conn.InvokeHandler(msgId, reader); } } - public static void RegisterHandler(short msgType, NetworkMessageDelegate handler) + [Obsolete("Use RegisterHandler instead")] + public static void RegisterHandler(int msgType, NetworkMessageDelegate handler) { if (handlers.ContainsKey(msgType)) { - if (LogFilter.Debug) { Debug.Log("NetworkServer.RegisterHandler replacing " + msgType); } + if (LogFilter.Debug) Debug.Log("NetworkServer.RegisterHandler replacing " + msgType); } handlers[msgType] = handler; } + [Obsolete("Use RegisterHandler instead")] public static void RegisterHandler(MsgType msgType, NetworkMessageDelegate handler) { - RegisterHandler((short)msgType, handler); + RegisterHandler((int)msgType, handler); } - public static void UnregisterHandler(short msgType) + public static void RegisterHandler(Action handler) where T: MessageBase, new() + { + int msgType = MessagePacker.GetId(); + if (handlers.ContainsKey(msgType)) + { + if (LogFilter.Debug) Debug.Log("NetworkServer.RegisterHandler replacing " + msgType); + } + handlers[msgType] = networkMessage => + { + T message = networkMessage.ReadMessage(); + handler(networkMessage.conn, message); + }; + } + + [Obsolete("Use UnregisterHandler instead")] + public static void UnregisterHandler(int msgType) { handlers.Remove(msgType); } + [Obsolete("Use UnregisterHandler instead")] public static void UnregisterHandler(MsgType msgType) { - UnregisterHandler((short)msgType); + UnregisterHandler((int)msgType); + } + + public static void UnregisterHandler() where T:MessageBase + { + int msgType = MessagePacker.GetId(); + handlers.Remove(msgType); } public static void ClearHandlers() @@ -463,7 +531,8 @@ public static void ClearHandlers() handlers.Clear(); } - public static void SendToClient(int connectionId, short msgType, MessageBase msg) + [Obsolete("Use SendToClient instead")] + public static void SendToClient(int connectionId, int msgType, MessageBase msg) { if (connections.TryGetValue(connectionId, out NetworkConnection conn)) { @@ -473,8 +542,20 @@ public static void SendToClient(int connectionId, short msgType, MessageBase msg Debug.LogError("Failed to send message to connection ID '" + connectionId + ", not found in connection list"); } - // send this message to the player only - public static void SendToClientOfPlayer(NetworkIdentity identity, short msgType, MessageBase msg) + public static void SendToClient(int connectionId, T msg) where T : MessageBase + { + NetworkConnection conn; + if (connections.TryGetValue(connectionId, out conn)) + { + conn.Send(msg); + return; + } + Debug.LogError("Failed to send message to connection ID '" + connectionId + ", not found in connection list"); + } + + + [Obsolete("Use SendToClientOfPlayer instead")] + public static void SendToClientOfPlayer(NetworkIdentity identity, int msgType, MessageBase msg) { if (identity != null) { @@ -486,11 +567,24 @@ public static void SendToClientOfPlayer(NetworkIdentity identity, short msgType, } } + // send this message to the player only + public static void SendToClientOfPlayer(NetworkIdentity identity, T msg) where T: MessageBase + { + if (identity != null) + { + identity.connectionToClient.Send(msg); + } + else + { + Debug.LogError("SendToClientOfPlayer: player has no NetworkIdentity: " + identity.name); + } + } + public static bool ReplacePlayerForConnection(NetworkConnection conn, GameObject player, Guid assetId) { if (GetNetworkIdentity(player, out NetworkIdentity identity)) { - identity.SetDynamicAssetId(assetId); + identity.assetId = assetId; } return InternalReplacePlayerForConnection(conn, player); } @@ -504,7 +598,7 @@ public static bool AddPlayerForConnection(NetworkConnection conn, GameObject pla { if (GetNetworkIdentity(player, out NetworkIdentity identity)) { - identity.SetDynamicAssetId(assetId); + identity.assetId = assetId; } return InternalAddPlayerForConnection(conn, player); } @@ -534,7 +628,7 @@ internal static bool InternalAddPlayerForConnection(NetworkConnection conn, Game conn.SetPlayerController(identity); // Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients) - identity.SetConnectionToClient(conn); + identity.connectionToClient = conn; SetClientReady(conn); @@ -543,7 +637,7 @@ internal static bool InternalAddPlayerForConnection(NetworkConnection conn, Game return true; } - if (LogFilter.Debug) { Debug.Log("Adding new playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); } + if (LogFilter.Debug) Debug.Log("Adding new playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); FinishPlayerForConnection(conn, identity, playerGameObject); if (identity.localPlayerAuthority) @@ -555,11 +649,11 @@ internal static bool InternalAddPlayerForConnection(NetworkConnection conn, Game static bool SetupLocalPlayerForConnection(NetworkConnection conn, NetworkIdentity identity) { - if (LogFilter.Debug) { Debug.Log("NetworkServer SetupLocalPlayerForConnection netID:" + identity.netId); } + if (LogFilter.Debug) Debug.Log("NetworkServer SetupLocalPlayerForConnection netID:" + identity.netId); if (conn is ULocalConnectionToClient localConnection) { - if (LogFilter.Debug) { Debug.Log("NetworkServer AddPlayer handling ULocalConnectionToClient"); } + if (LogFilter.Debug) Debug.Log("NetworkServer AddPlayer handling ULocalConnectionToClient"); // Spawn this player for other players, instead of SpawnObject: if (identity.netId == 0) @@ -598,7 +692,7 @@ static void FinishPlayerForConnection(NetworkConnection conn, NetworkIdentity id { netId = identity.netId }; - conn.Send((short)MsgType.Owner, owner); + conn.Send(owner); } internal static bool InternalReplacePlayerForConnection(NetworkConnection conn, GameObject playerGameObject) @@ -611,30 +705,30 @@ internal static bool InternalReplacePlayerForConnection(NetworkConnection conn, } //NOTE: there can be an existing player - if (LogFilter.Debug) { Debug.Log("NetworkServer ReplacePlayer"); } + if (LogFilter.Debug) Debug.Log("NetworkServer ReplacePlayer"); // is there already an owner that is a different object?? if (conn.playerController != null) { conn.playerController.SetNotLocalPlayer(); - conn.playerController.ClearClientOwner(); + conn.playerController.clientAuthorityOwner = null; } conn.SetPlayerController(playerNetworkIdentity); // Set the connection on the NetworkIdentity on the server, NetworkIdentity.SetLocalPlayer is not called on the server (it is on clients) - playerNetworkIdentity.SetConnectionToClient(conn); + playerNetworkIdentity.connectionToClient = conn; //NOTE: DONT set connection ready. - if (LogFilter.Debug) { Debug.Log("NetworkServer ReplacePlayer setup local"); } + if (LogFilter.Debug) Debug.Log("NetworkServer ReplacePlayer setup local"); if (SetupLocalPlayerForConnection(conn, playerNetworkIdentity)) { return true; } - if (LogFilter.Debug) { Debug.Log("Replacing playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); } + if (LogFilter.Debug) Debug.Log("Replacing playerGameObject object netId: " + playerGameObject.GetComponent().netId + " asset ID " + playerGameObject.GetComponent().assetId); FinishPlayerForConnection(conn, playerNetworkIdentity, playerGameObject); if (playerNetworkIdentity.localPlayerAuthority) @@ -657,25 +751,25 @@ static bool GetNetworkIdentity(GameObject go, out NetworkIdentity identity) public static void SetClientReady(NetworkConnection conn) { - if (LogFilter.Debug) { Debug.Log("SetClientReadyInternal for conn:" + conn.connectionId); } + if (LogFilter.Debug) Debug.Log("SetClientReadyInternal for conn:" + conn.connectionId); if (conn.isReady) { - if (LogFilter.Debug) { Debug.Log("SetClientReady conn " + conn.connectionId + " already ready"); } + if (LogFilter.Debug) Debug.Log("SetClientReady conn " + conn.connectionId + " already ready"); return; } if (conn.playerController == null) { // this is now allowed - if (LogFilter.Debug) { Debug.LogWarning("Ready with no player object"); } + if (LogFilter.Debug) Debug.LogWarning("Ready with no player object"); } conn.isReady = true; if (conn is ULocalConnectionToClient localConnection) { - if (LogFilter.Debug) { Debug.Log("NetworkServer Ready handling ULocalConnectionToClient"); } + if (LogFilter.Debug) Debug.Log("NetworkServer Ready handling ULocalConnectionToClient"); // Setup spawned objects for local player // Only handle the local objects for the first player (no need to redo it when doing more local players) @@ -693,7 +787,7 @@ public static void SetClientReady(NetworkConnection conn) } if (!identity.isClient) { - if (LogFilter.Debug) { Debug.Log("LocalClient.SetSpawnObject calling OnStartClient"); } + if (LogFilter.Debug) Debug.Log("LocalClient.SetSpawnObject calling OnStartClient"); identity.OnStartClient(); } } @@ -702,9 +796,9 @@ public static void SetClientReady(NetworkConnection conn) } // Spawn/update all current server objects - if (LogFilter.Debug) { Debug.Log("Spawning " + NetworkIdentity.spawned.Count + " objects for conn " + conn.connectionId); } + if (LogFilter.Debug) Debug.Log("Spawning " + NetworkIdentity.spawned.Count + " objects for conn " + conn.connectionId); - conn.Send((short)MsgType.SpawnStarted, new ObjectSpawnStartedMessage()); + conn.Send(new ObjectSpawnStartedMessage()); foreach (NetworkIdentity identity in NetworkIdentity.spawned.Values) { @@ -718,7 +812,7 @@ public static void SetClientReady(NetworkConnection conn) continue; } - if (LogFilter.Debug) { Debug.Log("Sending spawn message for current server objects name='" + identity.name + "' netId=" + identity.netId); } + if (LogFilter.Debug) Debug.Log("Sending spawn message for current server objects name='" + identity.name + "' netId=" + identity.netId); bool visible = identity.OnCheckObserver(conn); if (visible) @@ -727,7 +821,7 @@ public static void SetClientReady(NetworkConnection conn) } } - conn.Send((short)MsgType.SpawnFinished, new ObjectSpawnFinishedMessage()); + conn.Send(new ObjectSpawnFinishedMessage()); } internal static void ShowForConnection(NetworkIdentity identity, NetworkConnection conn) @@ -738,11 +832,11 @@ internal static void ShowForConnection(NetworkIdentity identity, NetworkConnecti internal static void HideForConnection(NetworkIdentity identity, NetworkConnection conn) { - ObjectDestroyMessage msg = new ObjectDestroyMessage + ObjectHideMessage msg = new ObjectHideMessage { netId = identity.netId }; - conn.Send((short)MsgType.ObjectHide, msg); + conn.Send(msg); } // call this to make all the clients not ready, such as when changing levels. @@ -764,29 +858,28 @@ internal static void InternalSetClientNotReady(NetworkConnection conn) { if (conn.isReady) { - if (LogFilter.Debug) { Debug.Log("PlayerNotReady " + conn); } + if (LogFilter.Debug) Debug.Log("PlayerNotReady " + conn); conn.isReady = false; conn.RemoveObservers(); - NotReadyMessage msg = new NotReadyMessage(); - conn.Send((short)MsgType.NotReady, msg); + conn.Send(new NotReadyMessage()); } } // default ready handler. - static void OnClientReadyMessage(NetworkMessage netMsg) + static void OnClientReadyMessage(NetworkConnection conn, ReadyMessage msg) { - if (LogFilter.Debug) { Debug.Log("Default handler for ready message from " + netMsg.conn); } - SetClientReady(netMsg.conn); + if (LogFilter.Debug) Debug.Log("Default handler for ready message from " + conn); + SetClientReady(conn); } // default remove player handler - static void OnRemovePlayerMessage(NetworkMessage netMsg) + static void OnRemovePlayerMessage(NetworkConnection conn, RemovePlayerMessage msg) { - if (netMsg.conn.playerController != null) + if (conn.playerController != null) { - Destroy(netMsg.conn.playerController.gameObject); - netMsg.conn.RemovePlayerController(); + Destroy(conn.playerController.gameObject); + conn.RemovePlayerController(); } else { @@ -795,30 +888,28 @@ static void OnRemovePlayerMessage(NetworkMessage netMsg) } // Handle command from specific player, this could be one of multiple players on a single client - static void OnCommandMessage(NetworkMessage netMsg) + static void OnCommandMessage(NetworkConnection conn, CommandMessage msg) { - CommandMessage message = netMsg.ReadMessage(); - - if (!NetworkIdentity.spawned.TryGetValue(message.netId, out NetworkIdentity identity)) + if (!NetworkIdentity.spawned.TryGetValue(msg.netId, out NetworkIdentity identity)) { - Debug.LogWarning("Spawned object not found when handling Command message [netId=" + message.netId + "]"); + Debug.LogWarning("Spawned object not found when handling Command message [netId=" + msg.netId + "]"); return; } // Commands can be for player objects, OR other objects with client-authority // -> so if this connection's controller has a different netId then // only allow the command if clientAuthorityOwner - if (netMsg.conn.playerController != null && netMsg.conn.playerController.netId != identity.netId) + if (conn.playerController != null && conn.playerController.netId != identity.netId) { - if (identity.clientAuthorityOwner != netMsg.conn) + if (identity.clientAuthorityOwner != conn) { - Debug.LogWarning("Command for object without authority [netId=" + message.netId + "]"); + Debug.LogWarning("Command for object without authority [netId=" + msg.netId + "]"); return; } } - if (LogFilter.Debug) { Debug.Log("OnCommandMessage for netId=" + message.netId + " conn=" + netMsg.conn); } - identity.HandleCommand(message.componentIndex, message.functionHash, new NetworkReader(message.payload)); + if (LogFilter.Debug) Debug.Log("OnCommandMessage for netId=" + msg.netId + " conn=" + conn); + identity.HandleCommand(msg.componentIndex, msg.functionHash, new NetworkReader(msg.payload)); } internal static void SpawnObject(GameObject obj) @@ -839,7 +930,7 @@ internal static void SpawnObject(GameObject obj) identity.OnStartServer(false); - if (LogFilter.Debug) { Debug.Log("SpawnObject instance ID " + identity.netId + " asset ID " + identity.assetId); } + if (LogFilter.Debug) Debug.Log("SpawnObject instance ID " + identity.netId + " asset ID " + identity.assetId); identity.RebuildObservers(true); //SendSpawnMessage(objNetworkIdentity, null); @@ -850,7 +941,7 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio if (identity.serverOnly) return; - if (LogFilter.Debug) { Debug.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId + " netid=" + identity.netId); } // for easier debugging + if (LogFilter.Debug) Debug.Log("Server SendSpawnMessage: name=" + identity.name + " sceneId=" + identity.sceneId.ToString("X") + " netid=" + identity.netId); // for easier debugging // 'identity' is a prefab that should be spawned if (identity.sceneId == 0) @@ -861,6 +952,7 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio assetId = identity.assetId, position = identity.transform.position, rotation = identity.transform.rotation, + scale = identity.transform.localScale, // serialize all components with initialState = true payload = identity.OnSerializeAllSafely(true) @@ -869,12 +961,12 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio // conn is != null when spawning it for a client if (conn != null) { - conn.Send((short)MsgType.SpawnPrefab, msg); + conn.Send(msg); } // conn is == null when spawning it for the local player else { - SendToReady(identity, (short)MsgType.SpawnPrefab, msg); + SendToReady(identity, msg); } } // 'identity' is a scene object that should be spawned again @@ -886,6 +978,7 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio sceneId = identity.sceneId, position = identity.transform.position, rotation = identity.transform.rotation, + scale = identity.transform.localScale, // include synch data payload = identity.OnSerializeAllSafely(true) @@ -894,12 +987,12 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio // conn is != null when spawning it for a client if (conn != null) { - conn.Send((short)MsgType.SpawnSceneObject, msg); + conn.Send(msg); } // conn is == null when spawning it for the local player else { - SendToReady(identity, (short)MsgType.SpawnSceneObject, msg); + SendToReady(identity, msg); } } } @@ -1023,7 +1116,7 @@ public static void Spawn(GameObject obj, Guid assetId) { if (GetNetworkIdentity(obj, out NetworkIdentity identity)) { - identity.SetDynamicAssetId(assetId); + identity.assetId = assetId; } SpawnObject(obj); } @@ -1031,7 +1124,7 @@ public static void Spawn(GameObject obj, Guid assetId) static void DestroyObject(NetworkIdentity identity, bool destroyServerObject) { - if (LogFilter.Debug) { Debug.Log("DestroyObject instance:" + identity.netId); } + if (LogFilter.Debug) Debug.Log("DestroyObject instance:" + identity.netId); NetworkIdentity.spawned.Remove(identity.netId); identity.clientAuthorityOwner?.RemoveOwnedObject(identity); @@ -1040,7 +1133,7 @@ static void DestroyObject(NetworkIdentity identity, bool destroyServerObject) { netId = identity.netId }; - SendToObservers(identity, (short)MsgType.ObjectDestroy, msg); + SendToObservers(identity, msg); identity.ClearObservers(); if (NetworkClient.active && localClientActive) @@ -1060,7 +1153,7 @@ public static void Destroy(GameObject obj) { if (obj == null) { - if (LogFilter.Debug) { Debug.Log("NetworkServer DestroyObject is null"); } + if (LogFilter.Debug) Debug.Log("NetworkServer DestroyObject is null"); return; } @@ -1074,7 +1167,7 @@ public static void UnSpawn(GameObject obj) { if (obj == null) { - if (LogFilter.Debug) { Debug.Log("NetworkServer UnspawnObject is null"); } + if (LogFilter.Debug) Debug.Log("NetworkServer UnspawnObject is null"); return; } @@ -1084,21 +1177,6 @@ public static void UnSpawn(GameObject obj) } } - internal static bool InvokeBytes(ULocalConnectionToServer conn, byte[] buffer) - { - if (Protocol.UnpackMessage(buffer, out ushort msgType, out byte[] content)) - { - if (handlers.ContainsKey((short)msgType) && s_LocalConnection != null) - { - // this must be invoked with the connection to the client, not the client's connection to the server - s_LocalConnection.InvokeHandler((short)msgType, new NetworkReader(content)); - return true; - } - } - Debug.LogError("InvokeBytes: failed to unpack message:" + BitConverter.ToString(buffer)); - return false; - } - [Obsolete("Use NetworkIdentity.spawned[netId] instead.")] public static GameObject FindLocalObject(uint netId) { @@ -1133,7 +1211,7 @@ public static bool SpawnObjects() { if (ValidateSceneObject(identity)) { - if (LogFilter.Debug) { Debug.Log("SpawnObjects sceneId:" + identity.sceneId + " name:" + identity.gameObject.name); } + if (LogFilter.Debug) Debug.Log("SpawnObjects sceneId:" + identity.sceneId.ToString("X") + " name:" + identity.gameObject.name); identity.Reset(); identity.gameObject.SetActive(true); } diff --git a/Assets/Mirror/Runtime/NetworkTime.cs b/Assets/Mirror/Runtime/NetworkTime.cs index 02a235898..3f04e45fb 100644 --- a/Assets/Mirror/Runtime/NetworkTime.cs +++ b/Assets/Mirror/Runtime/NetworkTime.cs @@ -1,4 +1,4 @@ -using System; +using System; using UnityEngine; using Stopwatch = System.Diagnostics.Stopwatch; @@ -28,8 +28,8 @@ static NetworkTime() static ExponentialMovingAverage _offset = new ExponentialMovingAverage(10); // the true offset guaranteed to be in this range - private static double offsetMin = Double.MinValue; - private static double offsetMax = Double.MaxValue; + private static double offsetMin = double.MinValue; + private static double offsetMax = double.MaxValue; // returns the clock time _in this system_ static double LocalTime() @@ -41,8 +41,8 @@ public static void Reset() { _rtt = new ExponentialMovingAverage(PingWindowSize); _offset = new ExponentialMovingAverage(PingWindowSize); - offsetMin = Double.MinValue; - offsetMax = Double.MaxValue; + offsetMin = double.MinValue; + offsetMax = double.MaxValue; } internal static NetworkPingMessage GetPing() @@ -55,7 +55,7 @@ internal static void UpdateClient(NetworkClient networkClient) if (Time.time - lastPingTime >= PingFrequency) { NetworkPingMessage pingMessage = GetPing(); - networkClient.Send((short)MsgType.Ping, pingMessage); + networkClient.Send(pingMessage); lastPingTime = Time.time; } } @@ -63,40 +63,37 @@ internal static void UpdateClient(NetworkClient networkClient) // executed at the server when we receive a ping message // reply with a pong containing the time from the client // and time from the server - internal static void OnServerPing(NetworkMessage netMsg) + internal static void OnServerPing(NetworkConnection conn, NetworkPingMessage msg) { - NetworkPingMessage pingMsg = netMsg.ReadMessage(); - - if (LogFilter.Debug) { Debug.Log("OnPingServerMessage conn=" + netMsg.conn); } + if (LogFilter.Debug) Debug.Log("OnPingServerMessage conn=" + conn); NetworkPongMessage pongMsg = new NetworkPongMessage { - clientTime = pingMsg.value, + clientTime = msg.value, serverTime = LocalTime() }; - netMsg.conn.Send((short)MsgType.Pong, pongMsg); + conn.Send(pongMsg); } // Executed at the client when we receive a Pong message // find out how long it took since we sent the Ping // and update time offset - internal static void OnClientPong(NetworkMessage netMsg) + internal static void OnClientPong(NetworkConnection conn, NetworkPongMessage msg) { - NetworkPongMessage pongMsg = netMsg.ReadMessage(); double now = LocalTime(); // how long did this message take to come back - double rtt = now - pongMsg.clientTime; + double rtt = now - msg.clientTime; _rtt.Add(rtt); // the difference in time between the client and the server // but subtract half of the rtt to compensate for latency // half of rtt is the best approximation we have - double offset = now - rtt * 0.5f - pongMsg.serverTime; + double offset = now - rtt * 0.5f - msg.serverTime; - double newOffsetMin = now - rtt - pongMsg.serverTime; - double newOffsetMax = now - pongMsg.serverTime; + double newOffsetMin = now - rtt - msg.serverTime; + double newOffsetMax = now - msg.serverTime; offsetMin = Math.Max(offsetMin, newOffsetMin); offsetMax = Math.Min(offsetMax, newOffsetMax); diff --git a/Assets/Mirror/Runtime/NetworkWriter.cs b/Assets/Mirror/Runtime/NetworkWriter.cs index d03dd007d..e8a5b37b5 100644 --- a/Assets/Mirror/Runtime/NetworkWriter.cs +++ b/Assets/Mirror/Runtime/NetworkWriter.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Text; using UnityEngine; namespace Mirror @@ -7,41 +8,49 @@ namespace Mirror // Binary stream Writer. Supports simple types, buffers, arrays, structs, and nested types public class NetworkWriter { + // cache encoding instead of creating it with BinaryWriter each time + // 1000 readers before: 1MB GC, 30ms + // 1000 readers after: 0.8MB GC, 18ms + static readonly UTF8Encoding encoding = new UTF8Encoding(false, true); + // create writer immediately with it's own buffer so no one can mess with it and so that we can resize it. - readonly BinaryWriter writer = new BinaryWriter(new MemoryStream()); + readonly BinaryWriter writer = new BinaryWriter(new MemoryStream(), encoding); // 'int' is the best type for .Position. 'short' is too small if we send >32kb which would result in negative .Position // -> converting long to int is fine until 2GB of data (MAX_INT), so we don't have to worry about overflows here public int Position { get { return (int)writer.BaseStream.Position; } set { writer.BaseStream.Position = value; } } - // MemoryStream.ToArray() ignores .Position, but HLAPI's .ToArray() expects only the valid data until .Position. - // .ToArray() is often used for payloads or sends, we don't unnecessary old data in there (bandwidth etc.) - // Example: - // HLAPI writes 10 bytes, sends them - // HLAPI sets .Position = 0 - // HLAPI writes 5 bytes, sends them - // => .ToArray() would return 10 bytes because of the first write, which is exactly what we don't want. + // MemoryStream has 3 values: Position, Length and Capacity. + // Position is used to indicate where we are writing + // Length is how much data we have written + // capacity is how much memory we have allocated + // ToArray returns all the data we have written, regardless of the current position public byte[] ToArray() { writer.Flush(); - byte[] slice = new byte[Position]; - Array.Copy(((MemoryStream)writer.BaseStream).ToArray(), slice, Position); - return slice; + return ((MemoryStream)writer.BaseStream).ToArray(); } - public void Write(byte value) { writer.Write(value); } - public void Write(sbyte value) { writer.Write(value); } - public void Write(char value) { writer.Write(value); } - public void Write(bool value) { writer.Write(value); } - public void Write(short value) { writer.Write(value); } - public void Write(ushort value) { writer.Write(value); } - public void Write(int value) { writer.Write(value); } - public void Write(uint value) { writer.Write(value); } - public void Write(long value) { writer.Write(value); } - public void Write(ulong value) { writer.Write(value); } - public void Write(float value) { writer.Write(value); } - public void Write(double value) { writer.Write(value); } - public void Write(decimal value) { writer.Write(value); } + // reset both the position and length of the stream, but leaves the capacity the same + // so that we can reuse this writer without extra allocations + public void SetLength(long value) + { + ((MemoryStream)writer.BaseStream).SetLength(value); + } + + public void Write(byte value) => writer.Write(value); + public void Write(sbyte value) => writer.Write(value); + public void Write(char value) => writer.Write(value); + public void Write(bool value) => writer.Write(value); + public void Write(short value) => writer.Write(value); + public void Write(ushort value) => writer.Write(value); + public void Write(int value) => writer.Write(value); + public void Write(uint value) => writer.Write(value); + public void Write(long value) => writer.Write(value); + public void Write(ulong value) => writer.Write(value); + public void Write(float value) => writer.Write(value); + public void Write(double value) => writer.Write(value); + public void Write(decimal value) => writer.Write(value); public void Write(string value) { @@ -92,14 +101,14 @@ public void WriteBytesAndSize(byte[] buffer) } // http://sqlite.org/src4/doc/trunk/www/varint.wiki - public void WritePackedUInt32(UInt32 value) + public void WritePackedUInt32(uint value) { // for 32 bit values WritePackedUInt64 writes the // same exact thing bit by bit WritePackedUInt64(value); } - public void WritePackedUInt64(UInt64 value) + public void WritePackedUInt64(ulong value) { if (value <= 240) { diff --git a/Assets/Mirror/Runtime/SceneAttribute.cs b/Assets/Mirror/Runtime/SceneAttribute.cs deleted file mode 100644 index 07b455549..000000000 --- a/Assets/Mirror/Runtime/SceneAttribute.cs +++ /dev/null @@ -1,9 +0,0 @@ -using UnityEngine; - -namespace Mirror -{ - // For Scene property Drawer - public class SceneAttribute : PropertyAttribute - { - } -} \ No newline at end of file diff --git a/Assets/Mirror/Runtime/StringHash.cs b/Assets/Mirror/Runtime/StringHash.cs index 633684516..95e9b07ab 100644 --- a/Assets/Mirror/Runtime/StringHash.cs +++ b/Assets/Mirror/Runtime/StringHash.cs @@ -15,4 +15,4 @@ public static int GetStableHashCode(this string text) } } } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Runtime/SyncList.cs b/Assets/Mirror/Runtime/SyncList.cs index 8e99b329a..b13997b19 100644 --- a/Assets/Mirror/Runtime/SyncList.cs +++ b/Assets/Mirror/Runtime/SyncList.cs @@ -2,73 +2,37 @@ using System.Collections; using System.Collections.Generic; using System.ComponentModel; -using UnityEngine; namespace Mirror { public class SyncListString : SyncList { - protected override void SerializeItem(NetworkWriter writer, string item) - { - writer.Write(item); - } - - protected override string DeserializeItem(NetworkReader reader) - { - return reader.ReadString(); - } + protected override void SerializeItem(NetworkWriter writer, string item) => writer.Write(item); + protected override string DeserializeItem(NetworkReader reader) => reader.ReadString(); } public class SyncListFloat : SyncList { - protected override void SerializeItem(NetworkWriter writer, float item) - { - writer.Write(item); - } - - protected override float DeserializeItem(NetworkReader reader) - { - return reader.ReadSingle(); - } + protected override void SerializeItem(NetworkWriter writer, float item) => writer.Write(item); + protected override float DeserializeItem(NetworkReader reader) => reader.ReadSingle(); } public class SyncListInt : SyncList { - protected override void SerializeItem(NetworkWriter writer, int item) - { - writer.WritePackedUInt32((uint)item); - } - - protected override int DeserializeItem(NetworkReader reader) - { - return (int)reader.ReadPackedUInt32(); - } + protected override void SerializeItem(NetworkWriter writer, int item) => writer.WritePackedUInt32((uint)item); + protected override int DeserializeItem(NetworkReader reader) => (int)reader.ReadPackedUInt32(); } public class SyncListUInt : SyncList { - protected override void SerializeItem(NetworkWriter writer, uint item) - { - writer.WritePackedUInt32(item); - } - - protected override uint DeserializeItem(NetworkReader reader) - { - return reader.ReadPackedUInt32(); - } + protected override void SerializeItem(NetworkWriter writer, uint item) => writer.WritePackedUInt32(item); + protected override uint DeserializeItem(NetworkReader reader) => reader.ReadPackedUInt32(); } public class SyncListBool : SyncList { - protected override void SerializeItem(NetworkWriter writer, bool item) - { - writer.Write(item); - } - - protected override bool DeserializeItem(NetworkReader reader) - { - return reader.ReadBoolean(); - } + protected override void SerializeItem(NetworkWriter writer, bool item) => writer.Write(item); + protected override bool DeserializeItem(NetworkReader reader) => reader.ReadBoolean(); } // Original UNET name is SyncListStruct and original Weaver weavers anything @@ -81,19 +45,9 @@ protected override bool DeserializeItem(NetworkReader reader) // TODO rename back to SyncListStruct after 2019.1! public class SyncListSTRUCT : SyncList where T : struct { - protected override void SerializeItem(NetworkWriter writer, T item) - { - } - - protected override T DeserializeItem(NetworkReader reader) - { - return new T(); - } - - public T GetItem(int i) - { - return base[i]; - } + protected override void SerializeItem(NetworkWriter writer, T item) {} + protected override T DeserializeItem(NetworkReader reader) => new T(); + public T GetItem(int i) => base[i]; } [EditorBrowsable(EditorBrowsableState.Never)] @@ -127,7 +81,7 @@ struct Change readonly List Changes = new List(); // how many changes we need to ignore - // this is needed because when we initialize the list, + // this is needed because when we initialize the list, // we might later receive changes that have already been applied // so we need to skip them int changesAhead = 0; @@ -135,20 +89,11 @@ struct Change protected abstract void SerializeItem(NetworkWriter writer, T item); protected abstract T DeserializeItem(NetworkReader reader); - public bool IsDirty - { - get - { - return Changes.Count > 0; - } - } + public bool IsDirty => Changes.Count > 0; // throw away all the changes // this should be called after a successfull sync - public void Flush() - { - Changes.Clear(); - } + public void Flush() => Changes.Clear(); void AddOperation(Operation op, int itemIndex, T item) { @@ -169,10 +114,7 @@ void AddOperation(Operation op, int itemIndex, T item) Callback?.Invoke(op, itemIndex, item); } - void AddOperation(Operation op, int itemIndex) - { - AddOperation(op, itemIndex, default(T)); - } + void AddOperation(Operation op, int itemIndex) => AddOperation(op, itemIndex, default(T)); public void OnSerializeAll(NetworkWriter writer) { @@ -352,20 +294,11 @@ public void Clear() AddOperation(Operation.OP_CLEAR, 0); } - public bool Contains(T item) - { - return m_Objects.Contains(item); - } + public bool Contains(T item) => m_Objects.Contains(item); - public void CopyTo(T[] array, int index) - { - m_Objects.CopyTo(array, index); - } + public void CopyTo(T[] array, int index) => m_Objects.CopyTo(array, index); - public int IndexOf(T item) - { - return m_Objects.IndexOf(item); - } + public int IndexOf(T item) => m_Objects.IndexOf(item); public void Insert(int index, T item) { @@ -420,10 +353,7 @@ public T this[int i] } } - public IEnumerator GetEnumerator() - { - return m_Objects.GetEnumerator(); - } + public IEnumerator GetEnumerator() => m_Objects.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } diff --git a/Assets/Mirror/Runtime/SyncObject.cs b/Assets/Mirror/Runtime/SyncObject.cs index 0fceddff3..afaa508bf 100644 --- a/Assets/Mirror/Runtime/SyncObject.cs +++ b/Assets/Mirror/Runtime/SyncObject.cs @@ -1,13 +1,13 @@ -namespace Mirror +namespace Mirror { - // A sync object is an object that can synchronize it's state + // A sync object is an object that can synchronize it's state // between server and client, such as a SyncList public interface SyncObject { // true if there are changes since the last flush bool IsDirty { get; } - // Discard all the queued changes + // Discard all the queued changes // Consider the object fully synchronized with clients void Flush(); @@ -23,4 +23,4 @@ public interface SyncObject // deserialize changes since last sync void OnDeserializeDelta(NetworkReader reader); } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Runtime/Transport/LLAPITransport.cs b/Assets/Mirror/Runtime/Transport/LLAPITransport.cs index 253862907..9d8d31af5 100644 --- a/Assets/Mirror/Runtime/Transport/LLAPITransport.cs +++ b/Assets/Mirror/Runtime/Transport/LLAPITransport.cs @@ -1,4 +1,4 @@ -// wraps UNET's LLAPI for use as HLAPI TransportLayer +// wraps UNET's LLAPI for use as HLAPI TransportLayer using System; using UnityEngine; using UnityEngine.Networking; @@ -83,7 +83,7 @@ void Awake() Debug.Log("LLAPITransport initialized!"); } - // client ////////////////////////////////////////////////////////////// + #region client public override bool ClientConnected() { return clientConnectionId != -1; @@ -155,18 +155,6 @@ public bool ProcessClientMessage() return true; } - // IMPORTANT: set script execution order to >1000 to call Transport's - // LateUpdate after all others. Fixes race condition where - // e.g. in uSurvival Transport would apply Cmds before - // ShoulderRotation.LateUpdate, resulting in projectile - // spawns at the point before shoulder rotation. - public void LateUpdate() - { - // process all messages - while (ProcessClientMessage()) { } - while (ProcessServerMessage()) { } - } - public string ClientGetAddress() { NetworkTransport.GetConnectionInfo(serverHostId, clientId, out string address, out int port, out NetworkID networkId, out NodeID node, out error); @@ -181,14 +169,9 @@ public override void ClientDisconnect() clientId = -1; } } + #endregion - public override bool Available() - { - // websocket is available in all platforms (including webgl) - return useWebsockets || base.Available(); - } - - // server ////////////////////////////////////////////////////////////// + #region server public override bool ServerActive() { return serverHostId != -1; @@ -282,8 +265,27 @@ public override void ServerStop() serverHostId = -1; Debug.Log("LLAPITransport.ServerStop"); } + #endregion + + #region common + // IMPORTANT: set script execution order to >1000 to call Transport's + // LateUpdate after all others. Fixes race condition where + // e.g. in uSurvival Transport would apply Cmds before + // ShoulderRotation.LateUpdate, resulting in projectile + // spawns at the point before shoulder rotation. + public void LateUpdate() + { + // process all messages + while (ProcessClientMessage()) {} + while (ProcessServerMessage()) {} + } + + public override bool Available() + { + // websocket is available in all platforms (including webgl) + return useWebsockets || base.Available(); + } - // common ////////////////////////////////////////////////////////////// public override void Shutdown() { NetworkTransport.Shutdown(); @@ -310,5 +312,6 @@ public override string ToString() } return "LLAPI (inactive/disconnected)"; } + #endregion } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs index 625706c41..c59a6a124 100644 --- a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs +++ b/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Linq; using System.Text; using UnityEngine; diff --git a/Assets/Mirror/Runtime/Transport/TelepathyTransport.cs b/Assets/Mirror/Runtime/Transport/TelepathyTransport.cs index ddbf93792..a945430bd 100644 --- a/Assets/Mirror/Runtime/Transport/TelepathyTransport.cs +++ b/Assets/Mirror/Runtime/Transport/TelepathyTransport.cs @@ -1,5 +1,4 @@ -// wraps Telepathy for use as HLAPI TransportLayer -using System; +// wraps Telepathy for use as HLAPI TransportLayer using UnityEngine; namespace Mirror { @@ -34,9 +33,9 @@ void Awake() } // client - public override bool ClientConnected() { return client.Connected; } - public override void ClientConnect(string address) { client.Connect(address, port); } - public override bool ClientSend(int channelId, byte[] data) { return client.Send(data); } + public override bool ClientConnected() => client.Connected; + public override void ClientConnect(string address) => client.Connect(address, port); + public override bool ClientSend(int channelId, byte[] data) => client.Send(data); bool ProcessClientMessage() { @@ -63,7 +62,7 @@ bool ProcessClientMessage() } return false; } - public override void ClientDisconnect() { client.Disconnect(); } + public override void ClientDisconnect() => client.Disconnect(); // IMPORTANT: set script execution order to >1000 to call Transport's // LateUpdate after all others. Fixes race condition where @@ -75,14 +74,14 @@ public void LateUpdate() // note: we need to check enabled in case we set it to false // when LateUpdate already started. // (https://github.com/vis2k/Mirror/pull/379) - while (enabled && ProcessClientMessage()) { } - while (enabled && ProcessServerMessage()) { } + while (enabled && ProcessClientMessage()) {} + while (enabled && ProcessServerMessage()) {} } // server - public override bool ServerActive() { return server.Active; } - public override void ServerStart() { server.Start(port); } - public override bool ServerSend(int connectionId, int channelId, byte[] data) { return server.Send(connectionId, data); } + public override bool ServerActive() => server.Active; + public override void ServerStart() => server.Start(port); + public override bool ServerSend(int connectionId, int channelId, byte[] data) => server.Send(connectionId, data); public bool ProcessServerMessage() { if (server.GetNextMessage(out Telepathy.Message message)) @@ -107,9 +106,9 @@ public bool ProcessServerMessage() } return false; } - public override bool ServerDisconnect(int connectionId) { return server.Disconnect(connectionId); } - public override string ServerGetClientAddress(int connectionId) { return server.GetClientAddress(connectionId); } - public override void ServerStop() { server.Stop(); } + public override bool ServerDisconnect(int connectionId) => server.Disconnect(connectionId); + public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); + public override void ServerStop() => server.Stop(); // common public override void Shutdown() diff --git a/Assets/Mirror/Runtime/Transport/Transport.cs b/Assets/Mirror/Runtime/Transport/Transport.cs index 061967eda..284c470a4 100644 --- a/Assets/Mirror/Runtime/Transport/Transport.cs +++ b/Assets/Mirror/Runtime/Transport/Transport.cs @@ -1,4 +1,4 @@ -// abstract transport layer component +// abstract transport layer component // note: not all transports need a port, so add it to yours if needed. using System; using UnityEngine; @@ -15,6 +15,10 @@ namespace Mirror public abstract class Transport : MonoBehaviour { + // static Transport which receives all network events + // this is usually set by NetworkManager, but doesn't have to be. + public static Transport activeTransport; + // determines if the transport is available for this platform // by default a transport is available in all platforms except webgl public virtual bool Available() @@ -45,12 +49,20 @@ public virtual bool Available() public abstract void ServerStart(); public abstract bool ServerSend(int connectionId, int channelId, byte[] data); public abstract bool ServerDisconnect(int connectionId); + + [Obsolete("Use ServerGetClientAddress(int connectionId) instead")] + public virtual bool GetConnectionInfo(int connectionId, out string address) + { + address = ServerGetClientAddress(connectionId); + return true; + } + public abstract string ServerGetClientAddress(int connectionId); public abstract void ServerStop(); // common public abstract void Shutdown(); - public abstract int GetMaxPacketSize(int channelId=Channels.DefaultReliable); + public abstract int GetMaxPacketSize(int channelId = Channels.DefaultReliable); // block Update() to force Transports to use LateUpdate to avoid race // conditions. messages should be processed after all the game state @@ -68,4 +80,4 @@ public virtual bool Available() // spawns at the point before shoulder rotation. public void Update() {} } -} \ No newline at end of file +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket.meta b/Assets/Mirror/Runtime/Transport/Websocket.meta new file mode 100644 index 000000000..5797569d5 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 75b15785adf2d4b7c8d779f5ba6a6326 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Client.cs b/Assets/Mirror/Runtime/Transport/Websocket/Client.cs new file mode 100644 index 000000000..44ee8e129 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Client.cs @@ -0,0 +1,191 @@ +#if !UNITY_WEBGL || UNITY_EDITOR + +using System; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets; +using UnityEngine; + +namespace Mirror.Websocket +{ + + public class Client + { + public event Action Connected; + public event Action ReceivedData; + public event Action Disconnected; + public event Action ReceivedError; + + private const int MaxMessageSize = 1024 * 256; + WebSocket webSocket; + CancellationTokenSource cancellation; + + public bool NoDelay = true; + + public bool Connecting { get; set; } + public bool IsConnected { get; set; } + + private Uri uri; + + public async void Connect(Uri uri) + { + // not if already started + if (webSocket != null) + { + // paul: exceptions are better than silence + ReceivedError?.Invoke(new Exception("Client already connected")); + return; + } + this.uri = uri; + // We are connecting from now until Connect succeeds or fails + Connecting = true; + + WebSocketClientOptions options = new WebSocketClientOptions() + { + NoDelay = true, + KeepAliveInterval = TimeSpan.Zero, + SecWebSocketProtocol = "binary" + }; + + cancellation = new CancellationTokenSource(); + + var clientFactory = new WebSocketClientFactory(); + + try + { + using (webSocket = await clientFactory.ConnectAsync(uri, options, cancellation.Token)) + { + CancellationToken token = cancellation.Token; + IsConnected = true; + Connecting = false; + Connected?.Invoke(); + + await ReceiveLoop(webSocket, token); + } + } + catch (ObjectDisposedException) + { + // No error, the client got closed + } + catch (Exception ex) + { + ReceivedError?.Invoke(ex); + } + finally + { + Disconnect(); + Disconnected?.Invoke(); + } + } + + private async Task ReceiveLoop(WebSocket webSocket, CancellationToken token) + { + var buffer = new byte[MaxMessageSize]; + + while (true) + { + WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token); + + + if (result == null) + break; + if (result.MessageType == WebSocketMessageType.Close) + break; + + // we got a text or binary message, need the full message + byte[] data = await ReadFrames(result, webSocket, buffer); + + if (data == null) + break; + + try + { + ReceivedData?.Invoke(data); + } + catch (Exception exception) + { + ReceivedError?.Invoke(exception); + } + } + } + + // a message might come splitted in multiple frames + // collect all frames + private async Task ReadFrames(WebSocketReceiveResult result, WebSocket webSocket, byte[] buffer) + { + int count = result.Count; + + while (!result.EndOfMessage) + { + if (count >= MaxMessageSize) + { + string closeMessage = string.Format("Maximum message size: {0} bytes.", MaxMessageSize); + await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, CancellationToken.None); + ReceivedError?.Invoke(new WebSocketException(WebSocketError.HeaderError)); + return null; + } + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer, count, MaxMessageSize - count), CancellationToken.None); + count += result.Count; + + } + return new ArraySegment(buffer, 0, count).ToArray(); + } + + public void Disconnect() + { + cancellation?.Cancel(); + + // only if started + if (webSocket != null) + { + // close client + webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure,"", CancellationToken.None); + webSocket = null; + Connecting = false; + IsConnected = false; + } + } + + // send the data or throw exception + public async void Send(byte[] data) + { + if (webSocket == null) + { + ReceivedError?.Invoke(new SocketException((int)SocketError.NotConnected)); + return; + } + + try + { + await webSocket.SendAsync(new ArraySegment(data), WebSocketMessageType.Binary, true, cancellation.Token); + } + catch (Exception ex) + { + Disconnect(); + ReceivedError?.Invoke(ex); + } + } + + + public override string ToString() + { + if (IsConnected ) + { + return $"Websocket connected to {uri}"; + } + if (Connecting) + { + return $"Websocket connecting to {uri}"; + } + return ""; + } + } + +} + +#endif diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Client.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Client.cs.meta new file mode 100644 index 000000000..70e22d37a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Client.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f69ff0981a33445a89b5e37b245806f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs b/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs new file mode 100644 index 000000000..6fbd66866 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs @@ -0,0 +1,118 @@ +#if UNITY_WEBGL && !UNITY_EDITOR + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; +using AOT; +using Ninja.WebSockets; +using UnityEngine; + +namespace Mirror.Websocket +{ + // this is the client implementation used by browsers + public class Client + { + private static int idGenerator = 0; + private static readonly Dictionary clients = new Dictionary(); + + public bool NoDelay = true; + + public event Action Connected; + public event Action ReceivedData; + public event Action Disconnected; + public event Action ReceivedError; + + public bool Connecting { get; set; } + public bool IsConnected + { + get + { + return SocketState(m_NativeRef) != 0; + } + } + + int m_NativeRef = 0; + readonly int id; + + public Client() + { + id = Interlocked.Increment(ref idGenerator); + } + + public void Connect(Uri uri) + { + clients[id] = this; + + Connecting = true; + + m_NativeRef = SocketCreate(uri.ToString(), id, OnOpen, OnData, OnClose); + } + + public void Disconnect() + { + SocketClose(m_NativeRef); + } + + // send the data or throw exception + public void Send(byte[] data) + { + SocketSend(m_NativeRef, data, data.Length); + } + + + #region Javascript native functions + [DllImport("__Internal")] + private static extern int SocketCreate( + string url, + int id, + Action onpen, + Action ondata, + Action onclose); + + [DllImport("__Internal")] + private static extern int SocketState(int socketInstance); + + [DllImport("__Internal")] + private static extern void SocketSend(int socketInstance, byte[] ptr, int length); + + [DllImport("__Internal")] + private static extern void SocketClose(int socketInstance); + + #endregion + + #region Javascript callbacks + + [MonoPInvokeCallback(typeof(Action))] + public static void OnOpen(int id) + { + clients[id].Connecting = false; + clients[id].Connected?.Invoke(); + } + + [MonoPInvokeCallback(typeof(Action))] + public static void OnClose(int id) + { + clients[id].Connecting = false; + clients[id].Disconnected?.Invoke(); + clients.Remove(id); + } + + [MonoPInvokeCallback(typeof(Action))] + public static void OnData(int id, IntPtr ptr, int length) + { + byte[] data = new byte[length]; + Marshal.Copy(ptr, data, 0, length); + + clients[id].ReceivedData(data); + } + #endregion + } +} + +#endif diff --git a/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs.meta new file mode 100644 index 000000000..2fdca98f4 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/ClientJs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 278e08c90f2324e2e80a0fe8984b0590 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets.meta new file mode 100644 index 000000000..e15657884 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 100fd42034e0d46db8980db4cc0cd178 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs new file mode 100644 index 000000000..8e0f83d64 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets +{ + /// + /// This buffer pool is instance thread safe + /// Use GetBuffer to get a MemoryStream (with a publically accessible buffer) + /// Calling Close on this MemoryStream will clear its internal buffer and return the buffer to the pool for reuse + /// MemoryStreams can grow larger than the DEFAULT_BUFFER_SIZE (or whatever you passed in) + /// and the underlying buffers will be returned to the pool at their larger sizes + /// + public class BufferPool : IBufferPool + { + const int DEFAULT_BUFFER_SIZE = 16384; + private readonly ConcurrentStack _bufferPoolStack; + private readonly int _bufferSize; + + public BufferPool() : this(DEFAULT_BUFFER_SIZE) + { + } + + public BufferPool(int bufferSize) + { + _bufferSize = bufferSize; + _bufferPoolStack = new ConcurrentStack(); + } + + /// + /// This memory stream is not instance thread safe (not to be confused with the BufferPool which is instance thread safe) + /// + protected class PublicBufferMemoryStream : MemoryStream + { + private readonly BufferPool _bufferPoolInternal; + private byte[] _buffer; + private MemoryStream _ms; + + public PublicBufferMemoryStream(byte[] buffer, BufferPool bufferPool) : base(new byte[0]) + { + _bufferPoolInternal = bufferPool; + _buffer = buffer; + _ms = new MemoryStream(buffer, 0, buffer.Length, true, true); + } + + public override long Length => base.Length; + + public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _ms.BeginRead(buffer, offset, count, callback, state); + } + + public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback callback, object state) + { + return _ms.BeginWrite(buffer, offset, count, callback, state); + } + + public override bool CanRead => _ms.CanRead; + public override bool CanSeek => _ms.CanSeek; + public override bool CanTimeout => _ms.CanTimeout; + public override bool CanWrite => _ms.CanWrite; + public override int Capacity + { + get { return _ms.Capacity; } + set { _ms.Capacity = value; } + } + + public override void Close() + { + // clear the buffer - we only need to clear up to the number of bytes we have already written + Array.Clear(_buffer, 0, (int)_ms.Position); + + _ms.Close(); + + // return the buffer to the pool + _bufferPoolInternal.ReturnBuffer(_buffer); + } + + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return _ms.CopyToAsync(destination, bufferSize, cancellationToken); + } + + public override int EndRead(IAsyncResult asyncResult) + { + return _ms.EndRead(asyncResult); + } + + public override void EndWrite(IAsyncResult asyncResult) + { + _ms.EndWrite(asyncResult); + } + + public override void Flush() + { + _ms.Flush(); + } + + public override Task FlushAsync(CancellationToken cancellationToken) + { + return _ms.FlushAsync(cancellationToken); + } + + public override byte[] GetBuffer() + { + return _buffer; + } + + public override long Position + { + get { return _ms.Position; } + set { _ms.Position = value; } + } + + public override int Read(byte[] buffer, int offset, int count) + { + return _ms.Read(buffer, offset, count); + } + + private void EnlargeBufferIfRequired(int count) + { + // we cannot fit the data into the existing buffer, time for a new buffer + if (count > (_buffer.Length - _ms.Position)) + { + int position = (int)_ms.Position; + + // double the buffer size + int newSize = _buffer.Length * 2; + + // make sure the new size is big enough + int requiredSize = count + _buffer.Length - position; + if (requiredSize > newSize) + { + // compute the power of two larger than requiredSize. so 40000 => 65536 + newSize = (int)Math.Pow(2, Math.Ceiling(Math.Log(requiredSize) / Math.Log(2))); ; + } + + var newBuffer = new byte[newSize]; + Buffer.BlockCopy(_buffer, 0, newBuffer, 0, position); + _ms = new MemoryStream(newBuffer, 0, newBuffer.Length, true, true) + { + Position = position + }; + + _buffer = newBuffer; + } + } + + public override void WriteByte(byte value) + { + EnlargeBufferIfRequired(1); + _ms.WriteByte(value); + } + + public override void Write(byte[] buffer, int offset, int count) + { + EnlargeBufferIfRequired(count); + _ms.Write(buffer, offset, count); + } + + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + EnlargeBufferIfRequired(count); + return _ms.WriteAsync(buffer, offset, count); + } + + public override object InitializeLifetimeService() + { + return _ms.InitializeLifetimeService(); + } + + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _ms.ReadAsync(buffer, offset, count, cancellationToken); + } + + public override int ReadByte() + { + return _ms.ReadByte(); + } + + public override int ReadTimeout { + get { return _ms.ReadTimeout; } + set { _ms.ReadTimeout = value; } + } + + public override long Seek(long offset, SeekOrigin loc) + { + return _ms.Seek(offset, loc); + } + + /// + /// Note: This will not make the MemoryStream any smaller, only larger + /// + public override void SetLength(long value) + { + EnlargeBufferIfRequired((int)value); + } + + public override byte[] ToArray() + { + // you should never call this + return _ms.ToArray(); + } + + public override int WriteTimeout + { + get { return _ms.WriteTimeout; } + set { _ms.WriteTimeout = value; } + } + +#if !NET45 + public override bool TryGetBuffer(out ArraySegment buffer) + { + buffer = new ArraySegment(_buffer, 0, (int)_ms.Position); + return true; + } +#endif + + public override void WriteTo(Stream stream) + { + _ms.WriteTo(stream); + } + } + + /// + /// Gets a MemoryStream built from a buffer plucked from a thread safe pool + /// The pool grows automatically. + /// Closing the memory stream clears the buffer and returns it to the pool + /// + public MemoryStream GetBuffer() + { + byte[] buffer; + if (!_bufferPoolStack.TryPop(out buffer)) + { + buffer = new byte[_bufferSize]; + } + + return new PublicBufferMemoryStream(buffer, this); + } + + protected void ReturnBuffer(byte[] buffer) + { + _bufferPoolStack.Push(buffer); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs.meta new file mode 100644 index 000000000..2bfda082f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/BufferPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e362254f82b2a4627bfd83394d093715 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions.meta new file mode 100644 index 000000000..210aa6ba9 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ca217a8fd870444d0ae623d3905d603f +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs new file mode 100644 index 000000000..5a1724fab --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs @@ -0,0 +1,26 @@ +using System; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class EntityTooLargeException : Exception + { + public EntityTooLargeException() : base() + { + + } + + /// + /// Http header too large to fit in buffer + /// + public EntityTooLargeException(string message) : base(message) + { + + } + + public EntityTooLargeException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs.meta new file mode 100644 index 000000000..b54345b0d --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/EntityTooLargeException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ef87d4a0822c4d2da3e8daa392e61d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs new file mode 100644 index 000000000..73354ddb2 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class InvalidHttpResponseCodeException : Exception + { + public string ResponseCode { get; private set; } + + public string ResponseHeader { get; private set; } + + public string ResponseDetails { get; private set; } + + public InvalidHttpResponseCodeException() : base() + { + } + + public InvalidHttpResponseCodeException(string message) : base(message) + { + } + + public InvalidHttpResponseCodeException(string responseCode, string responseDetails, string responseHeader) : base(responseCode) + { + ResponseCode = responseCode; + ResponseDetails = responseDetails; + ResponseHeader = responseHeader; + } + + public InvalidHttpResponseCodeException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs.meta new file mode 100644 index 000000000..7e772d50c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/InvalidHttpResponseCodeException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c2d7303a1a324f0ebe15cb3faf9b0d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt new file mode 100644 index 000000000..a1d28e4bf --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt @@ -0,0 +1 @@ +Make sure that exceptions follow the microsoft standards \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt.meta new file mode 100644 index 000000000..2ac50596e --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/README.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 3488f8d8c73d64a12bc24930c0210a41 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs new file mode 100644 index 000000000..82476250a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class SecWebSocketKeyMissingException : Exception + { + public SecWebSocketKeyMissingException() : base() + { + + } + + public SecWebSocketKeyMissingException(string message) : base(message) + { + + } + + public SecWebSocketKeyMissingException(string message, Exception inner) : base(message, inner) + { + + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs.meta new file mode 100644 index 000000000..ebb2ec031 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/SecWebSocketKeyMissingException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66d99fe90f4cb4471bf01c6f391ffc29 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs new file mode 100644 index 000000000..69d536022 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Net.Sockets; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class ServerListenerSocketException : Exception + { + public ServerListenerSocketException() : base() + { + } + + public ServerListenerSocketException(string message) : base(message) + { + } + + public ServerListenerSocketException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs.meta new file mode 100644 index 000000000..10b338429 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/ServerListenerSocketException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9b188b3dddee84decadd5913a535746b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs new file mode 100644 index 000000000..682143f63 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class WebSocketBufferOverflowException : Exception + { + public WebSocketBufferOverflowException() : base() + { + } + + public WebSocketBufferOverflowException(string message) : base(message) + { + } + + public WebSocketBufferOverflowException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs.meta new file mode 100644 index 000000000..419f9d009 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketBufferOverflowException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c3188abcb6c441759011da01fa342f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs new file mode 100644 index 000000000..624dc7726 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class WebSocketHandshakeFailedException : Exception + { + public WebSocketHandshakeFailedException() : base() + { + } + + public WebSocketHandshakeFailedException(string message) : base(message) + { + } + + public WebSocketHandshakeFailedException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs.meta new file mode 100644 index 000000000..8bd42715c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketHandshakeFailedException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7dd72ca0a28054d258c1d1ad8254a16e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs new file mode 100644 index 000000000..f23a85a2f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Ninja.WebSockets.Exceptions +{ + [Serializable] + public class WebSocketVersionNotSupportedException : Exception + { + public WebSocketVersionNotSupportedException() : base() + { + } + + public WebSocketVersionNotSupportedException(string message) : base(message) + { + } + + public WebSocketVersionNotSupportedException(string message, Exception inner) : base(message, inner) + { + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs.meta new file mode 100644 index 000000000..6f1439494 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Exceptions/WebSocketVersionNotSupportedException.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dc12d4b9cfe854aeeaab3c9f2f3d165e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs new file mode 100644 index 000000000..cd0cf70e0 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs @@ -0,0 +1,202 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using System.Security.Cryptography; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets.Exceptions; +using System.Linq; + +namespace Ninja.WebSockets +{ + public class HttpHelper + { + private const string HTTP_GET_HEADER_REGEX = @"^GET(.*)HTTP\/1\.1"; + + /// + /// Calculates a random WebSocket key that can be used to initiate a WebSocket handshake + /// + /// A random websocket key + public static string CalculateWebSocketKey() + { + // this is not used for cryptography so doing something simple like he code below is op + Random rand = new Random((int)DateTime.Now.Ticks); + byte[] keyAsBytes = new byte[16]; + rand.NextBytes(keyAsBytes); + return Convert.ToBase64String(keyAsBytes); + } + + /// + /// Computes a WebSocket accept string from a given key + /// + /// The web socket key to base the accept string on + /// A web socket accept string + public static string ComputeSocketAcceptString(string secWebSocketKey) + { + // this is a guid as per the web socket spec + const string webSocketGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + string concatenated = secWebSocketKey + webSocketGuid; + byte[] concatenatedAsBytes = Encoding.UTF8.GetBytes(concatenated); + + // note an instance of SHA1 is not threadsafe so we have to create a new one every time here + byte[] sha1Hash = SHA1.Create().ComputeHash(concatenatedAsBytes); + string secWebSocketAccept = Convert.ToBase64String(sha1Hash); + return secWebSocketAccept; + } + + /// + /// Reads an http header as per the HTTP spec + /// + /// The stream to read UTF8 text from + /// The cancellation token + /// The HTTP header + public static async Task ReadHttpHeaderAsync(Stream stream, CancellationToken token) + { + int length = 1024*16; // 16KB buffer more than enough for http header + byte[] buffer = new byte[length]; + int offset = 0; + int bytesRead = 0; + + do + { + if (offset >= length) + { + throw new EntityTooLargeException("Http header message too large to fit in buffer (16KB)"); + } + + bytesRead = await stream.ReadAsync(buffer, offset, length - offset, token); + offset += bytesRead; + string header = Encoding.UTF8.GetString(buffer, 0, offset); + + // as per http specification, all headers should end this this + if (header.Contains("\r\n\r\n")) + { + return header; + } + + } while (bytesRead > 0); + + return string.Empty; + } + + /// + /// Decodes the header to detect is this is a web socket upgrade response + /// + /// The HTTP header + /// True if this is an http WebSocket upgrade response + public static bool IsWebSocketUpgradeRequest(String header) + { + Regex getRegex = new Regex(HTTP_GET_HEADER_REGEX, RegexOptions.IgnoreCase); + Match getRegexMatch = getRegex.Match(header); + + if (getRegexMatch.Success) + { + // check if this is a web socket upgrade request + Regex webSocketUpgradeRegex = new Regex("Upgrade: websocket", RegexOptions.IgnoreCase); + Match webSocketUpgradeRegexMatch = webSocketUpgradeRegex.Match(header); + return webSocketUpgradeRegexMatch.Success; + } + + return false; + } + + /// + /// Gets the path from the HTTP header + /// + /// The HTTP header to read + /// The path + public static string GetPathFromHeader(string httpHeader) + { + Regex getRegex = new Regex(HTTP_GET_HEADER_REGEX, RegexOptions.IgnoreCase); + Match getRegexMatch = getRegex.Match(httpHeader); + + if (getRegexMatch.Success) + { + // extract the path attribute from the first line of the header + return getRegexMatch.Groups[1].Value.Trim(); + } + + return null; + } + + public static IList GetSubProtocols(string httpHeader) + { + Regex regex = new Regex(@"Sec-WebSocket-Protocol:(?.+)", RegexOptions.IgnoreCase); + Match match = regex.Match(httpHeader); + + if (match.Success) + { + const int MAX_LEN = 2048; + if (match.Length > MAX_LEN) + { + throw new EntityTooLargeException($"Sec-WebSocket-Protocol exceeded the maximum of length of {MAX_LEN}"); + } + + // extract a csv list of sub protocols (in order of highest preference first) + string csv = match.Groups["protocols"].Value.Trim(); + return csv.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries) + .Select(x => x.Trim()) + .ToList(); + } + + return new List(); + } + + /// + /// Reads the HTTP response code from the http response string + /// + /// The response string + /// the response code + public static string ReadHttpResponseCode(string response) + { + Regex getRegex = new Regex(@"HTTP\/1\.1 (.*)", RegexOptions.IgnoreCase); + Match getRegexMatch = getRegex.Match(response); + + if (getRegexMatch.Success) + { + // extract the path attribute from the first line of the header + return getRegexMatch.Groups[1].Value.Trim(); + } + + return null; + } + + /// + /// Writes an HTTP response string to the stream + /// + /// The response (without the new line characters) + /// The stream to write to + /// The cancellation token + public static async Task WriteHttpHeaderAsync(string response, Stream stream, CancellationToken token) + { + response = response.Trim() + "\r\n\r\n"; + Byte[] bytes = Encoding.UTF8.GetBytes(response); + await stream.WriteAsync(bytes, 0, bytes.Length, token); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs.meta new file mode 100644 index 000000000..808420ce2 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/HttpHelper.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c90b5378af58402987e8a2938d763f2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs new file mode 100644 index 000000000..395a28172 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace Ninja.WebSockets +{ + public interface IBufferPool + { + /// + /// Gets a MemoryStream built from a buffer plucked from a thread safe pool + /// The pool grows automatically. + /// Closing the memory stream clears the buffer and returns it to the pool + /// + MemoryStream GetBuffer(); + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs.meta new file mode 100644 index 000000000..cb3da58ed --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IBufferPool.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 894bb86ff9cbe4179a6764904f9dd742 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs new file mode 100644 index 000000000..e87dbb666 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets +{ + /// + /// Ping Pong Manager used to facilitate ping pong WebSocket messages + /// + interface IPingPongManager + { + /// + /// Raised when a Pong frame is received + /// + event EventHandler Pong; + + /// + /// Sends a ping frame + /// + /// The payload (must be 125 bytes of less) + /// The cancellation token + Task SendPing(ArraySegment payload, CancellationToken cancellation); + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs.meta new file mode 100644 index 000000000..9ac6ac52f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IPingPongManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1432676d0074e4a7ca123228797fe0ac +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs new file mode 100644 index 000000000..7eb10718c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs @@ -0,0 +1,45 @@ +using System; +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets +{ + /// + /// Web socket client factory used to open web socket client connections + /// + public interface IWebSocketClientFactory + { + /// + /// Connect with default options + /// + /// The WebSocket uri to connect to (e.g. ws://example.com or wss://example.com for SSL) + /// The optional cancellation token + /// A connected web socket instance + Task ConnectAsync(Uri uri, CancellationToken token = default(CancellationToken)); + + /// + /// Connect with options specified + /// + /// The WebSocket uri to connect to (e.g. ws://example.com or wss://example.com for SSL) + /// The WebSocket client options + /// The optional cancellation token + /// A connected web socket instance + Task ConnectAsync(Uri uri, WebSocketClientOptions options, CancellationToken token = default(CancellationToken)); + + /// + /// Connect with a stream that has already been opened and HTTP websocket upgrade request sent + /// This function will check the handshake response from the server and proceed if successful + /// Use this function if you have specific requirements to open a conenction like using special http headers and cookies + /// You will have to build your own HTTP websocket upgrade request + /// You may not even choose to use TCP/IP and this function will allow you to do that + /// + /// The full duplex response stream from the server + /// The secWebSocketKey you used in the handshake request + /// The WebSocket client options + /// The optional cancellation token + /// + Task ConnectAsync(Stream responseStream, string secWebSocketKey, WebSocketClientOptions options, CancellationToken token = default(CancellationToken)); + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs.meta new file mode 100644 index 000000000..7039071ad --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketClientFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 290e337adc73544809ef6db4d09ec5bc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs new file mode 100644 index 000000000..6915463b5 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs @@ -0,0 +1,40 @@ +using System.IO; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets +{ + /// + /// Web socket server factory used to open web socket server connections + /// + public interface IWebSocketServerFactory + { + /// + /// Reads a http header information from a stream and decodes the parts relating to the WebSocket protocot upgrade + /// + /// The network stream + /// The optional cancellation token + /// Http data read from the stream + Task ReadHttpHeaderFromStreamAsync(Stream stream, CancellationToken token = default(CancellationToken)); + + /// + /// Accept web socket with default options + /// Call ReadHttpHeaderFromStreamAsync first to get WebSocketHttpContext + /// + /// The http context used to initiate this web socket request + /// The optional cancellation token + /// A connected web socket + Task AcceptWebSocketAsync(WebSocketHttpContext context, CancellationToken token = default(CancellationToken)); + + /// + /// Accept web socket with options specified + /// Call ReadHttpHeaderFromStreamAsync first to get WebSocketHttpContext + /// + /// The http context used to initiate this web socket request + /// The web socket options + /// The optional cancellation token + /// A connected web socket + Task AcceptWebSocketAsync(WebSocketHttpContext context, WebSocketServerOptions options, CancellationToken token = default(CancellationToken)); + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs.meta new file mode 100644 index 000000000..391cb7f59 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/IWebSocketServerFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 33e062311740342db82c2f6e53fbce73 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal.meta new file mode 100644 index 000000000..b6bf8b27a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 58fcf4fbb7c9b47eeaf267adb27810d3 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs new file mode 100644 index 000000000..e31c222d1 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs @@ -0,0 +1,149 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.IO; +using System.Net.Sockets; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets.Internal +{ + internal class BinaryReaderWriter + { + public static async Task ReadExactly(int length, Stream stream, ArraySegment buffer, CancellationToken cancellationToken) + { + if (buffer.Count < length) + { + // This will happen if the calling function supplied a buffer that was too small to fit the payload of the websocket frame. + // Note that this can happen on the close handshake where the message size can be larger than the regular payload + throw new InternalBufferOverflowException($"Unable to read {length} bytes into buffer (offset: {buffer.Offset} size: {buffer.Count}). Use a larger read buffer"); + } + + int offset = 0; + while (offset < length) + { + int bytesRead = 0; + + NetworkStream networkStream = stream as NetworkStream; + if (networkStream != null && networkStream.DataAvailable) + { + // paul: if data is available read it immediatelly. + // in my tests this performed a lot better, because ReadAsync always waited until + // the next frame. + bytesRead = stream.Read(buffer.Array, buffer.Offset + offset, length - offset); + } + else + { + bytesRead = await stream.ReadAsync(buffer.Array, buffer.Offset + offset, length - offset, cancellationToken); + } + + if (bytesRead == 0) + { + throw new EndOfStreamException(string.Format("Unexpected end of stream encountered whilst attempting to read {0:#,##0} bytes", length)); + } + + offset += bytesRead; + } + } + + public static async Task ReadUShortExactly(Stream stream, bool isLittleEndian, ArraySegment buffer, CancellationToken cancellationToken) + { + await ReadExactly(2, stream, buffer, cancellationToken); + + if (!isLittleEndian) + { + Array.Reverse(buffer.Array, buffer.Offset, 2); // big endian + } + + return BitConverter.ToUInt16(buffer.Array, buffer.Offset); + } + + public static async Task ReadULongExactly(Stream stream, bool isLittleEndian, ArraySegment buffer, CancellationToken cancellationToken) + { + await ReadExactly(8, stream, buffer, cancellationToken); + + if (!isLittleEndian) + { + Array.Reverse(buffer.Array, buffer.Offset, 8); // big endian + } + + return BitConverter.ToUInt64(buffer.Array, buffer.Offset); + } + + public static async Task ReadLongExactly(Stream stream, bool isLittleEndian, ArraySegment buffer, CancellationToken cancellationToken) + { + await ReadExactly(8, stream, buffer, cancellationToken); + + if (!isLittleEndian) + { + Array.Reverse(buffer.Array, buffer.Offset, 8); // big endian + } + + return BitConverter.ToInt64(buffer.Array, buffer.Offset); + } + + public static void WriteInt(int value, Stream stream, bool isLittleEndian) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian && !isLittleEndian) + { + Array.Reverse(buffer); + } + + stream.Write(buffer, 0, buffer.Length); + } + + public static void WriteULong(ulong value, Stream stream, bool isLittleEndian) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian && ! isLittleEndian) + { + Array.Reverse(buffer); + } + + stream.Write(buffer, 0, buffer.Length); + } + + public static void WriteLong(long value, Stream stream, bool isLittleEndian) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian && !isLittleEndian) + { + Array.Reverse(buffer); + } + + stream.Write(buffer, 0, buffer.Length); + } + + public static void WriteUShort(ushort value, Stream stream, bool isLittleEndian) + { + byte[] buffer = BitConverter.GetBytes(value); + if (BitConverter.IsLittleEndian && !isLittleEndian) + { + Array.Reverse(buffer); + } + + stream.Write(buffer, 0, buffer.Length); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs.meta new file mode 100644 index 000000000..8671b8180 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/BinaryReaderWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2379de83fde9446689e7fd94d8cefca1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs new file mode 100644 index 000000000..310092ebc --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs @@ -0,0 +1,393 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.Diagnostics.Tracing; +using System.Net.Security; +using System.Net.WebSockets; + +namespace Ninja.WebSockets.Internal +{ + /// + /// Use the Guid to locate this EventSource in PerfView using the Additional Providers box (without wildcard characters) + /// + [EventSource(Name = "Ninja-WebSockets", Guid = "7DE1A071-4F85-4DBD-8FB1-EE8D3845E087")] + internal sealed class Events : EventSource + { + public static Events Log = new Events(); + + [Event(1, Level = EventLevel.Informational)] + public void ClientConnectingToIpAddress(Guid guid, string ipAddress, int port) + { + if (this.IsEnabled()) + { + WriteEvent(1, guid, ipAddress, port); + } + } + + [Event(2, Level = EventLevel.Informational)] + public void ClientConnectingToHost(Guid guid, string host, int port) + { + if (this.IsEnabled()) + { + WriteEvent(2, guid, host, port); + } + } + + [Event(3, Level = EventLevel.Informational)] + public void AttemtingToSecureSslConnection(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(3, guid); + } + } + + [Event(4, Level = EventLevel.Informational)] + public void ConnectionSecured(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(4, guid); + } + } + + [Event(5, Level = EventLevel.Informational)] + public void ConnectionNotSecure(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(5, guid); + } + } + + [Event(6, Level = EventLevel.Error)] + public void SslCertificateError(SslPolicyErrors sslPolicyErrors) + { + if (this.IsEnabled()) + { + WriteEvent(6, sslPolicyErrors); + } + } + + [Event(7, Level = EventLevel.Informational)] + public void HandshakeSent(Guid guid, string httpHeader) + { + if (this.IsEnabled()) + { + WriteEvent(7, guid, httpHeader ?? string.Empty); + } + } + + [Event(8, Level = EventLevel.Informational)] + public void ReadingHttpResponse(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(8, guid); + } + } + + [Event(9, Level = EventLevel.Error)] + public void ReadHttpResponseError(Guid guid, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(9, guid, exception ?? string.Empty); + } + } + + [Event(10, Level = EventLevel.Warning)] + public void InvalidHttpResponseCode(Guid guid, string response) + { + if (this.IsEnabled()) + { + WriteEvent(10, guid, response ?? string.Empty); + } + } + + [Event(11, Level = EventLevel.Error)] + public void HandshakeFailure(Guid guid, string message) + { + if (this.IsEnabled()) + { + WriteEvent(11, guid, message ?? string.Empty); + } + } + + [Event(12, Level = EventLevel.Informational)] + public void ClientHandshakeSuccess(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(12, guid); + } + } + + [Event(13, Level = EventLevel.Informational)] + public void ServerHandshakeSuccess(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(13, guid); + } + } + + [Event(14, Level = EventLevel.Informational)] + public void AcceptWebSocketStarted(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(14, guid); + } + } + + [Event(15, Level = EventLevel.Informational)] + public void SendingHandshakeResponse(Guid guid, string response) + { + if (this.IsEnabled()) + { + WriteEvent(15, guid, response ?? string.Empty); + } + } + + [Event(16, Level = EventLevel.Error)] + public void WebSocketVersionNotSupported(Guid guid, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(16, guid, exception ?? string.Empty); + } + } + + [Event(17, Level = EventLevel.Error)] + public void BadRequest(Guid guid, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(17, guid, exception ?? string.Empty); + } + } + + [Event(18, Level = EventLevel.Informational)] + public void UsePerMessageDeflate(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(18, guid); + } + } + + [Event(19, Level = EventLevel.Informational)] + public void NoMessageCompression(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(19, guid); + } + } + + [Event(20, Level = EventLevel.Informational)] + public void KeepAliveIntervalZero(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(20, guid); + } + } + + [Event(21, Level = EventLevel.Informational)] + public void PingPongManagerStarted(Guid guid, int keepAliveIntervalSeconds) + { + if (this.IsEnabled()) + { + WriteEvent(21, guid, keepAliveIntervalSeconds); + } + } + + [Event(22, Level = EventLevel.Informational)] + public void PingPongManagerEnded(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(22, guid); + } + } + + [Event(23, Level = EventLevel.Warning)] + public void KeepAliveIntervalExpired(Guid guid, int keepAliveIntervalSeconds) + { + if (this.IsEnabled()) + { + WriteEvent(23, guid, keepAliveIntervalSeconds); + } + } + + [Event(24, Level = EventLevel.Warning)] + public void CloseOutputAutoTimeout(Guid guid, WebSocketCloseStatus closeStatus, string statusDescription, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(24, guid, closeStatus, statusDescription ?? string.Empty, exception ?? string.Empty); + } + } + + [Event(25, Level = EventLevel.Error)] + public void CloseOutputAutoTimeoutCancelled(Guid guid, int timeoutSeconds, WebSocketCloseStatus closeStatus, string statusDescription, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(25, guid, timeoutSeconds, closeStatus, statusDescription ?? string.Empty, exception ?? string.Empty); + } + } + + [Event(26, Level = EventLevel.Error)] + public void CloseOutputAutoTimeoutError(Guid guid, string closeException, WebSocketCloseStatus closeStatus, string statusDescription, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(26, guid, closeException ?? string.Empty, closeStatus, statusDescription ?? string.Empty, exception ?? string.Empty); + } + } + + [Event(27, Level = EventLevel.Warning)] + public void TryGetBufferNotSupported(Guid guid, string streamType) + { + if (this.IsEnabled()) + { + WriteEvent(27, guid, streamType ?? string.Empty); + } + } + + [Event(28, Level = EventLevel.Verbose)] + public void SendingFrame(Guid guid, WebSocketOpCode webSocketOpCode, bool isFinBitSet, int numBytes, bool isPayloadCompressed) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(28, guid, webSocketOpCode, isFinBitSet, numBytes, isPayloadCompressed); + } + } + + [Event(29, Level = EventLevel.Verbose)] + public void ReceivedFrame(Guid guid, WebSocketOpCode webSocketOpCode, bool isFinBitSet, int numBytes) + { + if (this.IsEnabled(EventLevel.Verbose, EventKeywords.None)) + { + WriteEvent(29, guid, webSocketOpCode, isFinBitSet, numBytes); + } + } + + [Event(30, Level = EventLevel.Informational)] + public void CloseOutputNoHandshake(Guid guid, WebSocketCloseStatus? closeStatus, string statusDescription) + { + if (this.IsEnabled()) + { + string closeStatusDesc = $"{closeStatus}"; + WriteEvent(30, guid, closeStatusDesc, statusDescription ?? string.Empty); + } + } + + [Event(31, Level = EventLevel.Informational)] + public void CloseHandshakeStarted(Guid guid, WebSocketCloseStatus? closeStatus, string statusDescription) + { + if (this.IsEnabled()) + { + string closeStatusDesc = $"{closeStatus}"; + WriteEvent(31, guid, closeStatusDesc, statusDescription ?? string.Empty); + } + } + + [Event(32, Level = EventLevel.Informational)] + public void CloseHandshakeRespond(Guid guid, WebSocketCloseStatus? closeStatus, string statusDescription) + { + if (this.IsEnabled()) + { + string closeStatusDesc = $"{closeStatus}"; + WriteEvent(32, guid, closeStatusDesc, statusDescription ?? string.Empty); + } + } + + [Event(33, Level = EventLevel.Informational)] + public void CloseHandshakeComplete(Guid guid) + { + if (this.IsEnabled()) + { + WriteEvent(33, guid); + } + } + + [Event(34, Level = EventLevel.Warning)] + public void CloseFrameReceivedInUnexpectedState(Guid guid, WebSocketState webSocketState, WebSocketCloseStatus? closeStatus, string statusDescription) + { + if (this.IsEnabled()) + { + string closeStatusDesc = $"{closeStatus}"; + WriteEvent(34, guid, webSocketState, closeStatusDesc, statusDescription ?? string.Empty); + } + } + + [Event(35, Level = EventLevel.Informational)] + public void WebSocketDispose(Guid guid, WebSocketState webSocketState) + { + if (this.IsEnabled()) + { + WriteEvent(35, guid, webSocketState); + } + } + + [Event(36, Level = EventLevel.Warning)] + public void WebSocketDisposeCloseTimeout(Guid guid, WebSocketState webSocketState) + { + if (this.IsEnabled()) + { + WriteEvent(36, guid, webSocketState); + } + } + + [Event(37, Level = EventLevel.Error)] + public void WebSocketDisposeError(Guid guid, WebSocketState webSocketState, string exception) + { + if (this.IsEnabled()) + { + WriteEvent(37, guid, webSocketState, exception ?? string.Empty); + } + } + + [Event(38, Level = EventLevel.Warning)] + public void InvalidStateBeforeClose(Guid guid, WebSocketState webSocketState) + { + if (this.IsEnabled()) + { + WriteEvent(38, guid, webSocketState); + } + } + + [Event(39, Level = EventLevel.Warning)] + public void InvalidStateBeforeCloseOutput(Guid guid, WebSocketState webSocketState) + { + if (this.IsEnabled()) + { + WriteEvent(39, guid, webSocketState); + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs.meta new file mode 100644 index 000000000..3e533becb --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/Events.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7713ae9d113e145389c0a175d58090be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs new file mode 100644 index 000000000..eb3731773 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs @@ -0,0 +1,31 @@ +using System.Net.WebSockets; + +namespace Ninja.WebSockets.Internal +{ + internal class WebSocketFrame + { + public bool IsFinBitSet { get; private set; } + + public WebSocketOpCode OpCode { get; private set; } + + public int Count { get; private set; } + + public WebSocketCloseStatus? CloseStatus { get; private set; } + + public string CloseStatusDescription { get; private set; } + + public WebSocketFrame(bool isFinBitSet, WebSocketOpCode webSocketOpCode, int count) + { + IsFinBitSet = isFinBitSet; + OpCode = webSocketOpCode; + Count = count; + } + + public WebSocketFrame(bool isFinBitSet, WebSocketOpCode webSocketOpCode, int count, WebSocketCloseStatus closeStatus, string closeStatusDescription) : this(isFinBitSet, webSocketOpCode, count) + { + CloseStatus = closeStatus; + CloseStatusDescription = closeStatusDescription; + } + + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs.meta new file mode 100644 index 000000000..f27e26d89 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrame.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a4e8a4f1bcb0c49d7a82e5f430c85e2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs new file mode 100644 index 000000000..48022bb4f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs @@ -0,0 +1,62 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; + +namespace Ninja.WebSockets.Internal +{ + internal static class WebSocketFrameCommon + { + public const int MaskKeyLength = 4; + + /// + /// Mutate payload with the mask key + /// This is a reversible process + /// If you apply this to masked data it will be unmasked and visa versa + /// + /// The 4 byte mask key + /// The payload to mutate + public static void ToggleMask(ArraySegment maskKey, ArraySegment payload) + { + if (maskKey.Count != MaskKeyLength) + { + throw new Exception($"MaskKey key must be {MaskKeyLength} bytes"); + } + + byte[] buffer = payload.Array; + byte[] maskKeyArray = maskKey.Array; + int payloadOffset = payload.Offset; + int payloadCount = payload.Count; + int maskKeyOffset = maskKey.Offset; + + // apply the mask key (this is a reversible process so no need to copy the payload) + // NOTE: this is a hot function + // TODO: make this faster + for (int i = payloadOffset; i < payloadCount; i++) + { + int payloadIndex = i - payloadOffset; // index should start at zero + int maskKeyIndex = maskKeyOffset + (payloadIndex % MaskKeyLength); + buffer[i] = (Byte)(buffer[i] ^ maskKeyArray[maskKeyIndex]); + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs.meta new file mode 100644 index 000000000..0011ea21e --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameCommon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: adbf6c11b66794c0494acf9c713a6759 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs new file mode 100644 index 000000000..c9dee08c4 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs @@ -0,0 +1,168 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.IO; +using System.Net.WebSockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace Ninja.WebSockets.Internal +{ + /// + /// Reads a WebSocket frame + /// see http://tools.ietf.org/html/rfc6455 for specification + /// + internal static class WebSocketFrameReader + { + /// + /// Read a WebSocket frame from the stream + /// + /// The stream to read from + /// The buffer to read into + /// the cancellation token + /// A websocket frame + public static async Task ReadAsync(Stream fromStream, ArraySegment intoBuffer, CancellationToken cancellationToken) + { + // allocate a small buffer to read small chunks of data from the stream + var smallBuffer = new ArraySegment(new byte[8]); + + await BinaryReaderWriter.ReadExactly(2, fromStream, smallBuffer, cancellationToken); + byte byte1 = smallBuffer.Array[0]; + byte byte2 = smallBuffer.Array[1]; + + // process first byte + byte finBitFlag = 0x80; + byte opCodeFlag = 0x0F; + bool isFinBitSet = (byte1 & finBitFlag) == finBitFlag; + WebSocketOpCode opCode = (WebSocketOpCode) (byte1 & opCodeFlag); + + // read and process second byte + byte maskFlag = 0x80; + bool isMaskBitSet = (byte2 & maskFlag) == maskFlag; + uint len = await ReadLength(byte2, smallBuffer, fromStream, cancellationToken); + int count = (int)len; + + try + { + // use the masking key to decode the data if needed + if (isMaskBitSet) + { + ArraySegment maskKey = new ArraySegment(smallBuffer.Array, 0, WebSocketFrameCommon.MaskKeyLength); + await BinaryReaderWriter.ReadExactly(maskKey.Count, fromStream, maskKey, cancellationToken); + await BinaryReaderWriter.ReadExactly(count, fromStream, intoBuffer, cancellationToken); + ArraySegment payloadToMask = new ArraySegment(intoBuffer.Array, intoBuffer.Offset, count); + WebSocketFrameCommon.ToggleMask(maskKey, payloadToMask); + } + else + { + await BinaryReaderWriter.ReadExactly(count, fromStream, intoBuffer, cancellationToken); + } + } + catch (InternalBufferOverflowException e) + { + throw new InternalBufferOverflowException($"Supplied buffer too small to read {0} bytes from {Enum.GetName(typeof(WebSocketOpCode), opCode)} frame", e); + } + + if (opCode == WebSocketOpCode.ConnectionClose) + { + return DecodeCloseFrame(isFinBitSet, opCode, count, intoBuffer); + } + else + { + // note that by this point the payload will be populated + return new WebSocketFrame(isFinBitSet, opCode, count); + } + } + + /// + /// Extracts close status and close description information from the web socket frame + /// + private static WebSocketFrame DecodeCloseFrame(bool isFinBitSet, WebSocketOpCode opCode, int count, ArraySegment buffer) + { + WebSocketCloseStatus closeStatus; + string closeStatusDescription; + + if (count >= 2) + { + Array.Reverse(buffer.Array, buffer.Offset, 2); // network byte order + int closeStatusCode = (int)BitConverter.ToUInt16(buffer.Array, buffer.Offset); + if (Enum.IsDefined(typeof(WebSocketCloseStatus), closeStatusCode)) + { + closeStatus = (WebSocketCloseStatus)closeStatusCode; + } + else + { + closeStatus = WebSocketCloseStatus.Empty; + } + + int offset = buffer.Offset + 2; + int descCount = count - 2; + + if (descCount > 0) + { + closeStatusDescription = Encoding.UTF8.GetString(buffer.Array, offset, descCount); + } + else + { + closeStatusDescription = null; + } + } + else + { + closeStatus = WebSocketCloseStatus.Empty; + closeStatusDescription = null; + } + + return new WebSocketFrame(isFinBitSet, opCode, count, closeStatus, closeStatusDescription); + } + + /// + /// Reads the length of the payload according to the contents of byte2 + /// + private static async Task ReadLength(byte byte2, ArraySegment smallBuffer, Stream fromStream, CancellationToken cancellationToken) + { + byte payloadLenFlag = 0x7F; + uint len = (uint) (byte2 & payloadLenFlag); + + // read a short length or a long length depending on the value of len + if (len == 126) + { + len = await BinaryReaderWriter.ReadUShortExactly(fromStream, false, smallBuffer, cancellationToken); + } + else if (len == 127) + { + len = (uint) await BinaryReaderWriter.ReadULongExactly(fromStream, false, smallBuffer, cancellationToken); + const uint maxLen = 2147483648; // 2GB - not part of the spec but just a precaution. Send large volumes of data in smaller frames. + + // protect ourselves against bad data + if (len > maxLen || len < 0) + { + throw new ArgumentOutOfRangeException($"Payload length out of range. Min 0 max 2GB. Actual {len:#,##0} bytes."); + } + } + + return len; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs.meta new file mode 100644 index 000000000..370baf452 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameReader.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 092a4b88aabbc4d3b91f6a82c40b47e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs new file mode 100644 index 000000000..53974f2c5 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs @@ -0,0 +1,100 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System.IO; +using System; +using System.Net.WebSockets; +using System.Text; + +namespace Ninja.WebSockets.Internal +{ + // see http://tools.ietf.org/html/rfc6455 for specification + // see fragmentation section for sending multi part messages + // EXAMPLE: For a text message sent as three fragments, + // the first fragment would have an opcode of TextFrame and isLastFrame false, + // the second fragment would have an opcode of ContinuationFrame and isLastFrame false, + // the third fragment would have an opcode of ContinuationFrame and isLastFrame true. + internal static class WebSocketFrameWriter + { + /// + /// This is used for data masking so that web proxies don't cache the data + /// Therefore, there are no cryptographic concerns + /// + private static readonly Random _random; + + static WebSocketFrameWriter() + { + _random = new Random((int)DateTime.Now.Ticks); + } + + /// + /// No async await stuff here because we are dealing with a memory stream + /// + /// The web socket opcode + /// Array segment to get payload data from + /// Stream to write to + /// True is this is the last frame in this message (usually true) + public static void Write(WebSocketOpCode opCode, ArraySegment fromPayload, MemoryStream toStream, bool isLastFrame, bool isClient) + { + MemoryStream memoryStream = toStream; + byte finBitSetAsByte = isLastFrame ? (byte)0x80 : (byte)0x00; + byte byte1 = (byte)(finBitSetAsByte | (byte)opCode); + memoryStream.WriteByte(byte1); + + // NB, set the mask flag if we are constructing a client frame + byte maskBitSetAsByte = isClient ? (byte)0x80 : (byte)0x00; + + // depending on the size of the length we want to write it as a byte, ushort or ulong + if (fromPayload.Count < 126) + { + byte byte2 = (byte)(maskBitSetAsByte | (byte)fromPayload.Count); + memoryStream.WriteByte(byte2); + } + else if (fromPayload.Count <= ushort.MaxValue) + { + byte byte2 = (byte)(maskBitSetAsByte | 126); + memoryStream.WriteByte(byte2); + BinaryReaderWriter.WriteUShort((ushort)fromPayload.Count, memoryStream, false); + } + else + { + byte byte2 = (byte)(maskBitSetAsByte | 127); + memoryStream.WriteByte(byte2); + BinaryReaderWriter.WriteULong((ulong)fromPayload.Count, memoryStream, false); + } + + // if we are creating a client frame then we MUST mack the payload as per the spec + if (isClient) + { + byte[] maskKey = new byte[WebSocketFrameCommon.MaskKeyLength]; + _random.NextBytes(maskKey); + memoryStream.Write(maskKey, 0, maskKey.Length); + + // mask the payload + ArraySegment maskKeyArraySegment = new ArraySegment(maskKey, 0, maskKey.Length); + WebSocketFrameCommon.ToggleMask(maskKeyArraySegment, fromPayload); + } + + memoryStream.Write(fromPayload.Array, fromPayload.Offset, fromPayload.Count); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs.meta new file mode 100644 index 000000000..acd29fab7 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketFrameWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df337f1f7ba5245f7acadbe2e44b86bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs new file mode 100644 index 000000000..e037c5652 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs @@ -0,0 +1,585 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.IO; +using System.IO.Compression; +using System.Net.WebSockets; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +#if RELEASESIGNED +[assembly: InternalsVisibleTo("Ninja.WebSockets.UnitTests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100b1707056f4761b7846ed503642fcde97fc350c939f78026211304a56ba51e094c9cefde77fadce5b83c0a621c17f032c37c520b6d9ab2da8291a21472175d9caad55bf67bab4bffb46a96f864ea441cf695edc854296e02a44062245a4e09ccd9a77ef6146ecf941ce1d9da078add54bc2d4008decdac2fa2b388e17794ee6a6")] +#else +[assembly: InternalsVisibleTo("Ninja.WebSockets.UnitTests")] +#endif + +namespace Ninja.WebSockets.Internal +{ + /// + /// Main implementation of the WebSocket abstract class + /// + internal class WebSocketImplementation : WebSocket + { + private readonly Guid _guid; + private readonly Func _recycledStreamFactory; + private readonly Stream _stream; + private readonly bool _includeExceptionInCloseResponse; + private readonly bool _isClient; + private readonly string _subProtocol; + private CancellationTokenSource _internalReadCts; + private WebSocketState _state; + private bool _isContinuationFrame; + private WebSocketMessageType _continuationFrameMessageType = WebSocketMessageType.Binary; + private readonly bool _usePerMessageDeflate = false; + private bool _tryGetBufferFailureLogged = false; + const int MAX_PING_PONG_PAYLOAD_LEN = 125; + private WebSocketCloseStatus? _closeStatus; + private string _closeStatusDescription; + + public event EventHandler Pong; + + internal WebSocketImplementation(Guid guid, Func recycledStreamFactory, Stream stream, TimeSpan keepAliveInterval, string secWebSocketExtensions, bool includeExceptionInCloseResponse, bool isClient, string subProtocol) + { + _guid = guid; + _recycledStreamFactory = recycledStreamFactory; + _stream = stream; + _isClient = isClient; + _subProtocol = subProtocol; + _internalReadCts = new CancellationTokenSource(); + _state = WebSocketState.Open; + + if (secWebSocketExtensions?.IndexOf("permessage-deflate") >= 0) + { + _usePerMessageDeflate = true; + Events.Log.UsePerMessageDeflate(guid); + } + else + { + Events.Log.NoMessageCompression(guid); + } + + KeepAliveInterval = keepAliveInterval; + _includeExceptionInCloseResponse = includeExceptionInCloseResponse; + if (keepAliveInterval.Ticks < 0) + { + throw new InvalidOperationException("KeepAliveInterval must be Zero or positive"); + } + + if (keepAliveInterval == TimeSpan.Zero) + { + Events.Log.KeepAliveIntervalZero(guid); + } + else + { + // the ping pong manager starts a task + // but we don't have to keep a reference to it +#pragma warning disable 0219 + var pingPongManager = new PingPongManager(guid, this, keepAliveInterval, _internalReadCts.Token); +#pragma warning restore 0219 + } + } + + public override WebSocketCloseStatus? CloseStatus => _closeStatus; + + public override string CloseStatusDescription => _closeStatusDescription; + + public override WebSocketState State { get { return _state; } } + + public override string SubProtocol => _subProtocol; + + public TimeSpan KeepAliveInterval { get; private set; } + + /// + /// Receive web socket result + /// + /// The buffer to copy data into + /// The cancellation token + /// The web socket result details + public override async Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) + { + try + { + // we may receive control frames so reading needs to happen in an infinite loop + while (true) + { + // allow this operation to be cancelled from iniside OR outside this instance + using (CancellationTokenSource linkedCts = CancellationTokenSource.CreateLinkedTokenSource(_internalReadCts.Token, cancellationToken)) + { + WebSocketFrame frame = null; + try + { + frame = await WebSocketFrameReader.ReadAsync(_stream, buffer, linkedCts.Token); + Events.Log.ReceivedFrame(_guid, frame.OpCode, frame.IsFinBitSet, frame.Count); + } + catch (InternalBufferOverflowException ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.MessageTooBig, "Frame too large to fit in buffer. Use message fragmentation", ex); + throw; + } + catch (ArgumentOutOfRangeException ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, "Payload length out of range", ex); + throw; + } + catch (EndOfStreamException ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InvalidPayloadData, "Unexpected end of stream encountered", ex); + throw; + } + catch (OperationCanceledException ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Operation cancelled", ex); + throw; + } + catch (Exception ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Error reading WebSocket frame", ex); + throw; + } + + switch (frame.OpCode) + { + case WebSocketOpCode.ConnectionClose: + return await RespondToCloseFrame(frame, buffer, linkedCts.Token); + case WebSocketOpCode.Ping: + ArraySegment pingPayload = new ArraySegment(buffer.Array, buffer.Offset, frame.Count); + await SendPongAsync(pingPayload, linkedCts.Token); + break; + case WebSocketOpCode.Pong: + ArraySegment pongBuffer = new ArraySegment(buffer.Array, frame.Count, buffer.Offset); + Pong?.Invoke(this, new PongEventArgs(pongBuffer)); + break; + case WebSocketOpCode.TextFrame: + if (!frame.IsFinBitSet) + { + // continuation frames will follow, record the message type Text + _continuationFrameMessageType = WebSocketMessageType.Text; + } + return new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Text, frame.IsFinBitSet); + case WebSocketOpCode.BinaryFrame: + if (!frame.IsFinBitSet) + { + // continuation frames will follow, record the message type Binary + _continuationFrameMessageType = WebSocketMessageType.Binary; + } + return new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Binary, frame.IsFinBitSet); + case WebSocketOpCode.ContinuationFrame: + return new WebSocketReceiveResult(frame.Count, _continuationFrameMessageType, frame.IsFinBitSet); + default: + Exception ex = new NotSupportedException($"Unknown WebSocket opcode {frame.OpCode}"); + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex); + throw ex; + } + } + } + } + catch (Exception catchAll) + { + // Most exceptions will be caught closer to their source to send an appropriate close message (and set the WebSocketState) + // However, if an unhandled exception is encountered and a close message not sent then send one here + if (_state == WebSocketState.Open) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.InternalServerError, "Unexpected error reading from WebSocket", catchAll); + } + + throw; + } + } + + /// + /// Send data to the web socket + /// + /// the buffer containing data to send + /// The message type. Can be Text or Binary + /// True if this message is a standalone message (this is the norm) + /// If it is a multi-part message then false (and true for the last message) + /// the cancellation token + public override async Task SendAsync(ArraySegment buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) + { + using (MemoryStream stream = _recycledStreamFactory()) + { + WebSocketOpCode opCode = GetOppCode(messageType); + + if (_usePerMessageDeflate) + { + // NOTE: Compression is currently work in progress and should NOT be used in this library. + // The code below is very inefficient for small messages. Ideally we would like to have some sort of moving window + // of data to get the best compression. And we don't want to create new buffers which is bad for GC. + using (MemoryStream temp = new MemoryStream()) + { + DeflateStream deflateStream = new DeflateStream(temp, CompressionMode.Compress); + deflateStream.Write(buffer.Array, buffer.Offset, buffer.Count); + deflateStream.Flush(); + var compressedBuffer = new ArraySegment(temp.ToArray()); + WebSocketFrameWriter.Write(opCode, compressedBuffer, stream, endOfMessage, _isClient); + Events.Log.SendingFrame(_guid, opCode, endOfMessage, compressedBuffer.Count, true); + } + } + else + { + WebSocketFrameWriter.Write(opCode, buffer, stream, endOfMessage, _isClient); + Events.Log.SendingFrame(_guid, opCode, endOfMessage, buffer.Count, false); + } + + await WriteStreamToNetwork(stream, cancellationToken); + _isContinuationFrame = !endOfMessage; // TODO: is this correct?? + } + } + + /// + /// Call this automatically from server side each keepAliveInterval period + /// NOTE: ping payload must be 125 bytes or less + /// + public async Task SendPingAsync(ArraySegment payload, CancellationToken cancellationToken) + { + if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) + { + throw new InvalidOperationException($"Cannot send Ping: Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); + } + + if (_state == WebSocketState.Open) + { + using (MemoryStream stream = _recycledStreamFactory()) + { + WebSocketFrameWriter.Write(WebSocketOpCode.Ping, payload, stream, true, _isClient); + Events.Log.SendingFrame(_guid, WebSocketOpCode.Ping, true, payload.Count, false); + await WriteStreamToNetwork(stream, cancellationToken); + } + } + } + + /// + /// Aborts the WebSocket without sending a Close frame + /// + public override void Abort() + { + _state = WebSocketState.Aborted; + _internalReadCts.Cancel(); + } + + /// + /// Polite close (use the close handshake) + /// + public override async Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + { + if (_state == WebSocketState.Open) + { + using (MemoryStream stream = _recycledStreamFactory()) + { + ArraySegment buffer = BuildClosePayload(closeStatus, statusDescription); + WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); + Events.Log.CloseHandshakeStarted(_guid, closeStatus, statusDescription); + Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true); + await WriteStreamToNetwork(stream, cancellationToken); + _state = WebSocketState.CloseSent; + } + } + else + { + Events.Log.InvalidStateBeforeClose(_guid, _state); + } + } + + /// + /// Fire and forget close + /// + public override async Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) + { + if (_state == WebSocketState.Open) + { + _state = WebSocketState.Closed; // set this before we write to the network because the write may fail + + using (MemoryStream stream = _recycledStreamFactory()) + { + ArraySegment buffer = BuildClosePayload(closeStatus, statusDescription); + WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, buffer, stream, true, _isClient); + Events.Log.CloseOutputNoHandshake(_guid, closeStatus, statusDescription); + Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, buffer.Count, true); + await WriteStreamToNetwork(stream, cancellationToken); + } + } + else + { + Events.Log.InvalidStateBeforeCloseOutput(_guid, _state); + } + + // cancel pending reads + _internalReadCts.Cancel(); + } + + /// + /// Dispose will send a close frame if the connection is still open + /// + public override void Dispose() + { + Events.Log.WebSocketDispose(_guid, _state); + + try + { + if (_state == WebSocketState.Open) + { + CancellationTokenSource cts = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + try + { + CloseOutputAsync(WebSocketCloseStatus.EndpointUnavailable, "Service is Disposed", cts.Token).Wait(); + } + catch (OperationCanceledException) + { + // log don't throw + Events.Log.WebSocketDisposeCloseTimeout(_guid, _state); + } + } + + // cancel pending reads - usually does nothing + _internalReadCts.Cancel(); + _stream.Close(); + } + catch (Exception ex) + { + // log dont throw + Events.Log.WebSocketDisposeError(_guid, _state, ex.ToString()); + } + } + + /// + /// Called when a Pong frame is received + /// + /// + protected virtual void OnPong(PongEventArgs e) + { + Pong?.Invoke(this, e); + } + + /// + /// As per the spec, write the close status followed by the close reason + /// + /// The close status + /// Optional extra close details + /// The payload to sent in the close frame + private ArraySegment BuildClosePayload(WebSocketCloseStatus closeStatus, string statusDescription) + { + byte[] statusBuffer = BitConverter.GetBytes((ushort)closeStatus); + Array.Reverse(statusBuffer); // network byte order (big endian) + + if (statusDescription == null) + { + return new ArraySegment(statusBuffer); + } + else + { + byte[] descBuffer = Encoding.UTF8.GetBytes(statusDescription); + byte[] payload = new byte[statusBuffer.Length + descBuffer.Length]; + Buffer.BlockCopy(statusBuffer, 0, payload, 0, statusBuffer.Length); + Buffer.BlockCopy(descBuffer, 0, payload, statusBuffer.Length, descBuffer.Length); + return new ArraySegment(payload); + } + } + + /// NOTE: pong payload must be 125 bytes or less + /// Pong should contain the same payload as the ping + private async Task SendPongAsync(ArraySegment payload, CancellationToken cancellationToken) + { + // as per websocket spec + if (payload.Count > MAX_PING_PONG_PAYLOAD_LEN) + { + Exception ex = new InvalidOperationException($"Max ping message size {MAX_PING_PONG_PAYLOAD_LEN} exceeded: {payload.Count}"); + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.ProtocolError, ex.Message, ex); + throw ex; + } + + try + { + if (_state == WebSocketState.Open) + { + using (MemoryStream stream = _recycledStreamFactory()) + { + WebSocketFrameWriter.Write(WebSocketOpCode.Pong, payload, stream, true, _isClient); + Events.Log.SendingFrame(_guid, WebSocketOpCode.Pong, true, payload.Count, false); + await WriteStreamToNetwork(stream, cancellationToken); + } + } + } + catch (Exception ex) + { + await CloseOutputAutoTimeoutAsync(WebSocketCloseStatus.EndpointUnavailable, "Unable to send Pong response", ex); + throw; + } + } + + /// + /// Called when a Close frame is received + /// Send a response close frame if applicable + /// + private async Task RespondToCloseFrame(WebSocketFrame frame, ArraySegment buffer, CancellationToken token) + { + _closeStatus = frame.CloseStatus; + _closeStatusDescription = frame.CloseStatusDescription; + + if (_state == WebSocketState.CloseSent) + { + // this is a response to close handshake initiated by this instance + _state = WebSocketState.Closed; + Events.Log.CloseHandshakeComplete(_guid); + } + else if (_state == WebSocketState.Open) + { + // do not echo the close payload back to the client, there is no requirement for it in the spec. + // However, the same CloseStatus as recieved should be sent back. + ArraySegment closePayload = new ArraySegment(new byte[0], 0, 0); + _state = WebSocketState.CloseReceived; + Events.Log.CloseHandshakeRespond(_guid, frame.CloseStatus, frame.CloseStatusDescription); + + using (MemoryStream stream = _recycledStreamFactory()) + { + WebSocketFrameWriter.Write(WebSocketOpCode.ConnectionClose, closePayload, stream, true, _isClient); + Events.Log.SendingFrame(_guid, WebSocketOpCode.ConnectionClose, true, closePayload.Count, false); + await WriteStreamToNetwork(stream, token); + } + } + else + { + Events.Log.CloseFrameReceivedInUnexpectedState(_guid, _state, frame.CloseStatus, frame.CloseStatusDescription); + } + + return new WebSocketReceiveResult(frame.Count, WebSocketMessageType.Close, frame.IsFinBitSet, frame.CloseStatus, frame.CloseStatusDescription); + } + + /// + /// Note that the way in which the stream buffer is accessed can lead to significant performance problems + /// You want to avoid a call to stream.ToArray to avoid extra memory allocation + /// MemoryStream can be configured to have its internal buffer accessible. + /// + private ArraySegment GetBuffer(MemoryStream stream) + { +#if NET45 + // NET45 does not have a TryGetBuffer function on Stream + if (_tryGetBufferFailureLogged) + { + return new ArraySegment(stream.ToArray(), 0, (int)stream.Position); + } + + // note that a MemoryStream will throw an UnuthorizedAccessException if the internal buffer is not public. Set publiclyVisible = true + try + { + return new ArraySegment(stream.GetBuffer(), 0, (int)stream.Position); + } + catch (UnauthorizedAccessException) + { + Events.Log.TryGetBufferNotSupported(_guid, stream?.GetType()?.ToString()); + _tryGetBufferFailureLogged = true; + return new ArraySegment(stream.ToArray(), 0, (int)stream.Position); + } +#else + // Avoid calling ToArray on the MemoryStream because it allocates a new byte array on tha heap + // We avaoid this by attempting to access the internal memory stream buffer + // This works with supported streams like the recyclable memory stream and writable memory streams + ArraySegment buffer; + if (!stream.TryGetBuffer(out buffer)) + { + if (!_tryGetBufferFailureLogged) + { + Events.Log.TryGetBufferNotSupported(_guid, stream?.GetType()?.ToString()); + _tryGetBufferFailureLogged = true; + } + + // internal buffer not suppoted, fall back to ToArray() + byte[] array = stream.ToArray(); + buffer = new ArraySegment(array, 0, array.Length); + } + + return new ArraySegment(buffer.Array, buffer.Offset, (int)stream.Position); +#endif + } + + /// + /// Puts data on the wire + /// + /// The stream to read data from + private async Task WriteStreamToNetwork(MemoryStream stream, CancellationToken cancellationToken) + { + ArraySegment buffer = GetBuffer(stream); + await _stream.WriteAsync(buffer.Array, buffer.Offset, buffer.Count, cancellationToken).ConfigureAwait(false); + } + + /// + /// Turns a spec websocket frame opcode into a WebSocketMessageType + /// + private WebSocketOpCode GetOppCode(WebSocketMessageType messageType) + { + if (_isContinuationFrame) + { + return WebSocketOpCode.ContinuationFrame; + } + else + { + switch (messageType) + { + case WebSocketMessageType.Binary: + return WebSocketOpCode.BinaryFrame; + case WebSocketMessageType.Text: + return WebSocketOpCode.TextFrame; + case WebSocketMessageType.Close: + throw new NotSupportedException("Cannot use Send function to send a close frame. Use Close function."); + default: + throw new NotSupportedException($"MessageType {messageType} not supported"); + } + } + } + + /// + /// Automatic WebSocket close in response to some invalid data from the remote websocket host + /// + /// The close status to use + /// A description of why we are closing + /// The exception (for logging) + private async Task CloseOutputAutoTimeoutAsync(WebSocketCloseStatus closeStatus, string statusDescription, Exception ex) + { + TimeSpan timeSpan = TimeSpan.FromSeconds(5); + Events.Log.CloseOutputAutoTimeout(_guid, closeStatus, statusDescription, ex.ToString()); + + try + { + // we may not want to send sensitive information to the client / server + if (_includeExceptionInCloseResponse) + { + statusDescription = statusDescription + "\r\n\r\n" + ex.ToString(); + } + + var autoCancel = new CancellationTokenSource(timeSpan); + await CloseOutputAsync(closeStatus, statusDescription, autoCancel.Token); + } + catch (OperationCanceledException) + { + // do not throw an exception because that will mask the original exception + Events.Log.CloseOutputAutoTimeoutCancelled(_guid, (int)timeSpan.TotalSeconds, closeStatus, statusDescription, ex.ToString()); + } + catch (Exception closeException) + { + // do not throw an exception because that will mask the original exception + Events.Log.CloseOutputAutoTimeoutError(_guid, closeException.ToString(), closeStatus, statusDescription, ex.ToString()); + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs.meta new file mode 100644 index 000000000..a573a6252 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketImplementation.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 087dfa54efe9345b390e0758d42e52cf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs new file mode 100644 index 000000000..6ef2ac0d4 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs @@ -0,0 +1,12 @@ +namespace Ninja.WebSockets.Internal +{ + internal enum WebSocketOpCode + { + ContinuationFrame = 0, + TextFrame = 1, + BinaryFrame = 2, + ConnectionClose = 8, + Ping = 9, + Pong = 10 + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs.meta new file mode 100644 index 000000000..f568d565c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Internal/WebSocketOpCode.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f2106e8022034b9180513e27b488b4c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md new file mode 100644 index 000000000..6fd6c111a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright 2018 David Haig + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md.meta new file mode 100644 index 000000000..9a7423ed2 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/LICENCE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 681505da4aa4b48b8b6251521a495d64 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs new file mode 100644 index 000000000..45e8e3b27 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs @@ -0,0 +1,139 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.Net.WebSockets; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets.Internal; + +namespace Ninja.WebSockets +{ + /// + /// Ping Pong Manager used to facilitate ping pong WebSocket messages + /// + public class PingPongManager : IPingPongManager + { + private readonly WebSocketImplementation _webSocket; + private readonly Guid _guid; + private readonly TimeSpan _keepAliveInterval; + private readonly Task _pingTask; + private readonly CancellationToken _cancellationToken; + private Stopwatch _stopwatch; + private long _pingSentTicks; + + /// + /// Raised when a Pong frame is received + /// + public event EventHandler Pong; + + /// + /// Initialises a new instance of the PingPongManager to facilitate ping pong WebSocket messages. + /// If you are manually creating an instance of this class then it is advisable to set keepAliveInterval to + /// TimeSpan.Zero when you create the WebSocket instance (using a factory) otherwise you may be automatically + /// be sending duplicate Ping messages (see keepAliveInterval below) + /// + /// The web socket used to listen to ping messages and send pong messages + /// The time between automatically sending ping messages. + /// Set this to TimeSpan.Zero if you with to manually control sending ping messages. + /// + /// The token used to cancel a pending ping send AND the automatic sending of ping messages + /// if keepAliveInterval is positive + public PingPongManager(Guid guid, WebSocket webSocket, TimeSpan keepAliveInterval, CancellationToken cancellationToken) + { + var webSocketImpl = webSocket as WebSocketImplementation; + _webSocket = webSocketImpl; + if (_webSocket == null) + throw new InvalidCastException("Cannot cast WebSocket to an instance of WebSocketImplementation. Please use the web socket factories to create a web socket"); + _guid = guid; + _keepAliveInterval = keepAliveInterval; + _cancellationToken = cancellationToken; + webSocketImpl.Pong += WebSocketImpl_Pong; + _stopwatch = Stopwatch.StartNew(); + + if (keepAliveInterval != TimeSpan.Zero) + { + Task.Run(PingForever, cancellationToken); + } + } + + /// + /// Sends a ping frame + /// + /// The payload (must be 125 bytes of less) + /// The cancellation token + public async Task SendPing(ArraySegment payload, CancellationToken cancellation) + { + await _webSocket.SendPingAsync(payload, cancellation); + } + + protected virtual void OnPong(PongEventArgs e) + { + Pong?.Invoke(this, e); + } + + private async Task PingForever() + { + Events.Log.PingPongManagerStarted(_guid, (int)_keepAliveInterval.TotalSeconds); + + try + { + while (!_cancellationToken.IsCancellationRequested) + { + await Task.Delay(_keepAliveInterval, _cancellationToken); + + if (_webSocket.State != WebSocketState.Open) + { + break; + } + + if (_pingSentTicks != 0) + { + Events.Log.KeepAliveIntervalExpired(_guid, (int)_keepAliveInterval.TotalSeconds); + await _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, $"No Pong message received in response to a Ping after KeepAliveInterval {_keepAliveInterval}", _cancellationToken); + break; + } + + if (!_cancellationToken.IsCancellationRequested) + { + _pingSentTicks = _stopwatch.Elapsed.Ticks; + ArraySegment buffer = new ArraySegment(BitConverter.GetBytes(_pingSentTicks)); + await SendPing(buffer, _cancellationToken); + } + } + } + catch (OperationCanceledException) + { + // normal, do nothing + } + + Events.Log.PingPongManagerEnded(_guid); + } + + private void WebSocketImpl_Pong(object sender, PongEventArgs e) + { + _pingSentTicks = 0; + OnPong(e); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs.meta new file mode 100644 index 000000000..80bb880b3 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PingPongManager.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e69d99db8e5024dda9afb8bb5d494260 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs new file mode 100644 index 000000000..027d06b0a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ninja.WebSockets +{ + /// + /// Pong EventArgs + /// + public class PongEventArgs : EventArgs + { + /// + /// The data extracted from a Pong WebSocket frame + /// + public ArraySegment Payload { get; private set; } + + /// + /// Initialises a new instance of the PongEventArgs class + /// + /// The pong payload must be 125 bytes or less (can be zero bytes) + public PongEventArgs(ArraySegment payload) + { + Payload = payload; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs.meta new file mode 100644 index 000000000..6b75b1650 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/PongEventArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57068e417e3ea4201a8de864e9a223a5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties.meta new file mode 100644 index 000000000..afecd370c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c1e6c099dd56f4b30b02f4258c38ff05 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles.meta new file mode 100644 index 000000000..5fc2e6604 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 52998228355394f00bf8c88cfad8e362 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 000000000..f873eee3c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,13 @@ + + + + + FileSystem + ReleaseSigned + netstandard2.0 + bin\ReleaseSigned\PublishOutput + Any CPU + + \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml.meta new file mode 100644 index 000000000..a829e200f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/Properties/PublishProfiles/FolderProfile.pubxml.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5e1a5037feecd49deb7892a8b0b755c6 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs new file mode 100644 index 000000000..1508c5169 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs @@ -0,0 +1,289 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Security; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets.Exceptions; +using Ninja.WebSockets.Internal; + +namespace Ninja.WebSockets +{ + /// + /// Web socket client factory used to open web socket client connections + /// + public class WebSocketClientFactory : IWebSocketClientFactory + { + private readonly Func _bufferFactory; + private readonly IBufferPool _bufferPool; + + /// + /// Initialises a new instance of the WebSocketClientFactory class without caring about internal buffers + /// + public WebSocketClientFactory() + { + _bufferPool = new BufferPool(); + _bufferFactory = _bufferPool.GetBuffer; + } + + /// + /// Initialises a new instance of the WebSocketClientFactory class with control over internal buffer creation + /// + /// Used to get a memory stream. Feel free to implement your own buffer pool. MemoryStreams will be disposed when no longer needed and can be returned to the pool. + public WebSocketClientFactory(Func bufferFactory) + { + _bufferFactory = bufferFactory; + } + + /// + /// Connect with default options + /// + /// The WebSocket uri to connect to (e.g. ws://example.com or wss://example.com for SSL) + /// The optional cancellation token + /// A connected web socket instance + public async Task ConnectAsync(Uri uri, CancellationToken token = default(CancellationToken)) + { + return await ConnectAsync(uri, new WebSocketClientOptions(), token); + } + + /// + /// Connect with options specified + /// + /// The WebSocket uri to connect to (e.g. ws://example.com or wss://example.com for SSL) + /// The WebSocket client options + /// The optional cancellation token + /// A connected web socket instance + public async Task ConnectAsync(Uri uri, WebSocketClientOptions options, CancellationToken token = default(CancellationToken)) + { + Guid guid = Guid.NewGuid(); + string host = uri.Host; + int port = uri.Port; + var tcpClient = new TcpClient(AddressFamily.InterNetworkV6); + tcpClient.NoDelay = options.NoDelay; + tcpClient.Client.DualMode = true; + string uriScheme = uri.Scheme.ToLower(); + bool useSsl = uriScheme == "wss" || uriScheme == "https"; + IPAddress ipAddress; + if (IPAddress.TryParse(host, out ipAddress)) + { + Events.Log.ClientConnectingToIpAddress(guid, ipAddress.ToString(), port); + await tcpClient.ConnectAsync(ipAddress, port); + } + else + { + Events.Log.ClientConnectingToHost(guid, host, port); + await tcpClient.ConnectAsync(host, port); + } + + token.ThrowIfCancellationRequested(); + Stream stream = GetStream(guid, tcpClient, useSsl, host); + return await PerformHandshake(guid, uri, stream, options, token); + } + + /// + /// Connect with a stream that has already been opened and HTTP websocket upgrade request sent + /// This function will check the handshake response from the server and proceed if successful + /// Use this function if you have specific requirements to open a conenction like using special http headers and cookies + /// You will have to build your own HTTP websocket upgrade request + /// You may not even choose to use TCP/IP and this function will allow you to do that + /// + /// The full duplex response stream from the server + /// The secWebSocketKey you used in the handshake request + /// The WebSocket client options + /// The optional cancellation token + /// + public async Task ConnectAsync(Stream responseStream, string secWebSocketKey, WebSocketClientOptions options, CancellationToken token = default(CancellationToken)) + { + Guid guid = Guid.NewGuid(); + return await ConnectAsync(guid, responseStream, secWebSocketKey, options.KeepAliveInterval, options.SecWebSocketExtensions, options.IncludeExceptionInCloseResponse, token); + } + + private async Task ConnectAsync(Guid guid, Stream responseStream, string secWebSocketKey, TimeSpan keepAliveInterval, string secWebSocketExtensions, bool includeExceptionInCloseResponse, CancellationToken token) + { + Events.Log.ReadingHttpResponse(guid); + string response = string.Empty; + + try + { + response = await HttpHelper.ReadHttpHeaderAsync(responseStream, token); + } + catch (Exception ex) + { + Events.Log.ReadHttpResponseError(guid, ex.ToString()); + throw new WebSocketHandshakeFailedException("Handshake unexpected failure", ex); + } + + ThrowIfInvalidResponseCode(response); + ThrowIfInvalidAcceptString(guid, response, secWebSocketKey); + string subProtocol = GetSubProtocolFromHeader(response); + return new WebSocketImplementation(guid, _bufferFactory, responseStream, keepAliveInterval, secWebSocketExtensions, includeExceptionInCloseResponse, true, subProtocol); + } + + private string GetSubProtocolFromHeader(string response) + { + // make sure we escape the accept string which could contain special regex characters + string regexPattern = "Sec-WebSocket-Protocol: (.*)"; + Regex regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + Match match = regex.Match(response); + if (match.Success) + { + return match.Groups[1].Value.Trim(); + } + + return null; + } + + private void ThrowIfInvalidAcceptString(Guid guid, string response, string secWebSocketKey) + { + // make sure we escape the accept string which could contain special regex characters + string regexPattern = "Sec-WebSocket-Accept: (.*)"; + Regex regex = new Regex(regexPattern, RegexOptions.IgnoreCase); + string actualAcceptString = regex.Match(response).Groups[1].Value.Trim(); + + // check the accept string + string expectedAcceptString = HttpHelper.ComputeSocketAcceptString(secWebSocketKey); + if (expectedAcceptString != actualAcceptString) + { + string warning = string.Format($"Handshake failed because the accept string from the server '{expectedAcceptString}' was not the expected string '{actualAcceptString}'"); + Events.Log.HandshakeFailure(guid, warning); + throw new WebSocketHandshakeFailedException(warning); + } + else + { + Events.Log.ClientHandshakeSuccess(guid); + } + } + + private void ThrowIfInvalidResponseCode(string responseHeader) + { + string responseCode = HttpHelper.ReadHttpResponseCode(responseHeader); + if (!string.Equals(responseCode, "101 Switching Protocols", StringComparison.InvariantCultureIgnoreCase)) + { + string[] lines = responseHeader.Split(new string[] { "\r\n" }, StringSplitOptions.None); + + for (int i = 0; i < lines.Length; i++) + { + // if there is more to the message than just the header + if (string.IsNullOrWhiteSpace(lines[i])) + { + StringBuilder builder = new StringBuilder(); + for (int j = i + 1; j < lines.Length - 1; j++) + { + builder.AppendLine(lines[j]); + } + + string responseDetails = builder.ToString(); + throw new InvalidHttpResponseCodeException(responseCode, responseDetails, responseHeader); + } + } + } + } + + private Stream GetStream(Guid guid, TcpClient tcpClient, bool isSecure, string host) + { + Stream stream = tcpClient.GetStream(); + + if (isSecure) + { + SslStream sslStream = new SslStream(stream, false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); + Events.Log.AttemtingToSecureSslConnection(guid); + + // This will throw an AuthenticationException if the certificate is not valid + sslStream.AuthenticateAsClient(host); + Events.Log.ConnectionSecured(guid); + return sslStream; + } + else + { + Events.Log.ConnectionNotSecure(guid); + return stream; + } + } + + /// + /// Invoked by the RemoteCertificateValidationDelegate + /// If you want to ignore certificate errors (for debugging) then return true + /// + private static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + if (sslPolicyErrors == SslPolicyErrors.None) + { + return true; + } + + Events.Log.SslCertificateError(sslPolicyErrors); + + // Do not allow this client to communicate with unauthenticated servers. + return false; + } + + private static string GetAdditionalHeaders(Dictionary additionalHeaders) + { + if (additionalHeaders == null || additionalHeaders.Count == 0) + { + return string.Empty; + } + else + { + StringBuilder builder = new StringBuilder(); + foreach (KeyValuePair pair in additionalHeaders) + { + builder.Append($"{pair.Key}: {pair.Value}\r\n"); + } + + return builder.ToString(); + } + } + + private async Task PerformHandshake(Guid guid, Uri uri, Stream stream, WebSocketClientOptions options, CancellationToken token) + { + Random rand = new Random(); + byte[] keyAsBytes = new byte[16]; + rand.NextBytes(keyAsBytes); + string secWebSocketKey = Convert.ToBase64String(keyAsBytes); + string additionalHeaders = GetAdditionalHeaders(options.AdditionalHttpHeaders); + string handshakeHttpRequest = $"GET {uri.PathAndQuery} HTTP/1.1\r\n" + + $"Host: {uri.Host}:{uri.Port}\r\n" + + "Upgrade: websocket\r\n" + + "Connection: Upgrade\r\n" + + $"Sec-WebSocket-Key: {secWebSocketKey}\r\n" + + $"Origin: http://{uri.Host}:{uri.Port}\r\n" + + $"Sec-WebSocket-Protocol: {options.SecWebSocketProtocol}\r\n" + + additionalHeaders + + "Sec-WebSocket-Version: 13\r\n\r\n"; + + byte[] httpRequest = Encoding.UTF8.GetBytes(handshakeHttpRequest); + stream.Write(httpRequest, 0, httpRequest.Length); + Events.Log.HandshakeSent(guid, handshakeHttpRequest); + return await ConnectAsync(stream, secWebSocketKey, options, token); + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs.meta new file mode 100644 index 000000000..a93bf3e9e --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 38af07c5cfa1940f2bd0b981bccea293 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs new file mode 100644 index 000000000..b6318a8a2 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ninja.WebSockets +{ + /// + /// Client WebSocket init options + /// + public class WebSocketClientOptions + { + /// + /// How often to send ping requests to the Server + /// This is done to prevent proxy servers from closing your connection + /// The default is TimeSpan.Zero meaning that it is disabled. + /// WebSocket servers usually send ping messages so it is not normally necessary for the client to send them (hence the TimeSpan.Zero default) + /// You can manually control ping pong messages using the PingPongManager class. + /// If you do that it is advisible to set this KeepAliveInterval to zero for the WebSocketClientFactory + /// + public TimeSpan KeepAliveInterval { get; set; } + + /// + /// Set to true to send a message immediately with the least amount of latency (typical usage for chat) + /// This will disable Nagle's algorithm which can cause high tcp latency for small packets sent infrequently + /// However, if you are streaming large packets or sending large numbers of small packets frequently it is advisable to set NoDelay to false + /// This way data will be bundled into larger packets for better throughput + /// + public bool NoDelay { get; set; } + + /// + /// Add any additional http headers to this dictionary + /// + public Dictionary AdditionalHttpHeaders { get; set; } + + /// + /// Include the full exception (with stack trace) in the close response + /// when an exception is encountered and the WebSocket connection is closed + /// The default is false + /// + public bool IncludeExceptionInCloseResponse { get; set; } + + /// + /// WebSocket Extensions as an HTTP header value + /// + public string SecWebSocketExtensions { get; set; } + + /// + /// A comma separated list of sub protocols in preference order (first one being the most preferred) + /// The server will return the first supported sub protocol (or none if none are supported) + /// Can be null + /// + public string SecWebSocketProtocol { get; set; } + + /// + /// Initialises a new instance of the WebSocketClientOptions class + /// + public WebSocketClientOptions() + { + KeepAliveInterval = TimeSpan.Zero; + NoDelay = true; + AdditionalHttpHeaders = new Dictionary(); + IncludeExceptionInCloseResponse = false; + SecWebSocketProtocol = null; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs.meta new file mode 100644 index 000000000..45c830509 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketClientOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f2911aa44c1154bf88781555542a9de3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs new file mode 100644 index 000000000..f1c85dc0e --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs @@ -0,0 +1,49 @@ +using System.Collections.Generic; +using System.IO; + +namespace Ninja.WebSockets +{ + /// + /// The WebSocket HTTP Context used to initiate a WebSocket handshake + /// + public class WebSocketHttpContext + { + /// + /// True if this is a valid WebSocket request + /// + public bool IsWebSocketRequest { get; private set; } + + public IList WebSocketRequestedProtocols { get; private set; } + + /// + /// The raw http header extracted from the stream + /// + public string HttpHeader { get; private set; } + + /// + /// The Path extracted from the http header + /// + public string Path { get; private set; } + + /// + /// The stream AFTER the header has already been read + /// + public Stream Stream { get; private set; } + + /// + /// Initialises a new instance of the WebSocketHttpContext class + /// + /// True if this is a valid WebSocket request + /// The raw http header extracted from the stream + /// The Path extracted from the http header + /// The stream AFTER the header has already been read + public WebSocketHttpContext(bool isWebSocketRequest, IList webSocketRequestedProtocols, string httpHeader, string path, Stream stream) + { + IsWebSocketRequest = isWebSocketRequest; + WebSocketRequestedProtocols = webSocketRequestedProtocols; + HttpHeader = httpHeader; + Path = path; + Stream = stream; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs.meta new file mode 100644 index 000000000..7e38e8a83 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketHttpContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: efbbd1ab9e45e4dd3b88faaca1048c7f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs new file mode 100644 index 000000000..8fb63f232 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs @@ -0,0 +1,169 @@ +// --------------------------------------------------------------------- +// Copyright 2018 David Haig +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// --------------------------------------------------------------------- + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net.WebSockets; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets.Exceptions; +using Ninja.WebSockets.Internal; + +namespace Ninja.WebSockets +{ + /// + /// Web socket server factory used to open web socket server connections + /// + public class WebSocketServerFactory : IWebSocketServerFactory + { + private readonly Func _bufferFactory; + private readonly IBufferPool _bufferPool; + + /// + /// Initialises a new instance of the WebSocketServerFactory class without caring about internal buffers + /// + public WebSocketServerFactory() + { + _bufferPool = new BufferPool(); + _bufferFactory = _bufferPool.GetBuffer; + } + + /// + /// Initialises a new instance of the WebSocketClientFactory class with control over internal buffer creation + /// + /// Used to get a memory stream. Feel free to implement your own buffer pool. MemoryStreams will be disposed when no longer needed and can be returned to the pool. + /// + public WebSocketServerFactory(Func bufferFactory) + { + _bufferFactory = bufferFactory; + } + + /// + /// Reads a http header information from a stream and decodes the parts relating to the WebSocket protocot upgrade + /// + /// The network stream + /// The optional cancellation token + /// Http data read from the stream + public async Task ReadHttpHeaderFromStreamAsync(Stream stream, CancellationToken token = default(CancellationToken)) + { + string header = await HttpHelper.ReadHttpHeaderAsync(stream, token); + string path = HttpHelper.GetPathFromHeader(header); + bool isWebSocketRequest = HttpHelper.IsWebSocketUpgradeRequest(header); + IList subProtocols = HttpHelper.GetSubProtocols(header); + return new WebSocketHttpContext(isWebSocketRequest, subProtocols, header, path, stream); + } + + /// + /// Accept web socket with default options + /// Call ReadHttpHeaderFromStreamAsync first to get WebSocketHttpContext + /// + /// The http context used to initiate this web socket request + /// The optional cancellation token + /// A connected web socket + public async Task AcceptWebSocketAsync(WebSocketHttpContext context, CancellationToken token = default(CancellationToken)) + { + return await AcceptWebSocketAsync(context, new WebSocketServerOptions(), token); + } + + /// + /// Accept web socket with options specified + /// Call ReadHttpHeaderFromStreamAsync first to get WebSocketHttpContext + /// + /// The http context used to initiate this web socket request + /// The web socket options + /// The optional cancellation token + /// A connected web socket + public async Task AcceptWebSocketAsync(WebSocketHttpContext context, WebSocketServerOptions options, CancellationToken token = default(CancellationToken)) + { + Guid guid = Guid.NewGuid(); + Events.Log.AcceptWebSocketStarted(guid); + await PerformHandshakeAsync(guid, context.HttpHeader, options.SubProtocol, context.Stream, token); + Events.Log.ServerHandshakeSuccess(guid); + string secWebSocketExtensions = null; + return new WebSocketImplementation(guid, _bufferFactory, context.Stream, options.KeepAliveInterval, secWebSocketExtensions, options.IncludeExceptionInCloseResponse, false, options.SubProtocol); + } + + private static void CheckWebSocketVersion(string httpHeader) + { + Regex webSocketVersionRegex = new Regex("Sec-WebSocket-Version: (.*)", RegexOptions.IgnoreCase); + + // check the version. Support version 13 and above + const int WebSocketVersion = 13; + Match match = webSocketVersionRegex.Match(httpHeader); + if (match.Success) + { + int secWebSocketVersion = Convert.ToInt32(match.Groups[1].Value.Trim()); + if (secWebSocketVersion < WebSocketVersion) + { + throw new WebSocketVersionNotSupportedException(string.Format("WebSocket Version {0} not suported. Must be {1} or above", secWebSocketVersion, WebSocketVersion)); + } + } + else + { + throw new WebSocketVersionNotSupportedException("Cannot find \"Sec-WebSocket-Version\" in http header"); + } + } + + private static async Task PerformHandshakeAsync(Guid guid, String httpHeader, string subProtocol, Stream stream, CancellationToken token) + { + try + { + Regex webSocketKeyRegex = new Regex("Sec-WebSocket-Key: (.*)", RegexOptions.IgnoreCase); + CheckWebSocketVersion(httpHeader); + + Match match = webSocketKeyRegex.Match(httpHeader); + if (match.Success) + { + string secWebSocketKey = match.Groups[1].Value.Trim(); + string setWebSocketAccept = HttpHelper.ComputeSocketAcceptString(secWebSocketKey); + string response = ("HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: websocket\r\n" + + (subProtocol != null ? $"Sec-WebSocket-Protocol: {subProtocol}\r\n" : "") + + $"Sec-WebSocket-Accept: {setWebSocketAccept}"); + + Events.Log.SendingHandshakeResponse(guid, response); + await HttpHelper.WriteHttpHeaderAsync(response, stream, token); + } + else + { + throw new SecWebSocketKeyMissingException("Unable to read \"Sec-WebSocket-Key\" from http header"); + } + } + catch (WebSocketVersionNotSupportedException ex) + { + Events.Log.WebSocketVersionNotSupported(guid, ex.ToString()); + string response = "HTTP/1.1 426 Upgrade Required\r\nSec-WebSocket-Version: 13" + ex.Message; + await HttpHelper.WriteHttpHeaderAsync(response, stream, token); + throw; + } + catch (Exception ex) + { + Events.Log.BadRequest(guid, ex.ToString()); + await HttpHelper.WriteHttpHeaderAsync("HTTP/1.1 400 Bad Request", stream, token); + throw; + } + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs.meta new file mode 100644 index 000000000..920e27a24 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerFactory.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c5fcfcd10538542edb4842d81798f4dd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs new file mode 100644 index 000000000..c347bd57c --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs @@ -0,0 +1,45 @@ +using System; + +namespace Ninja.WebSockets +{ + /// + /// Server WebSocket init options + /// + public class WebSocketServerOptions + { + /// + /// How often to send ping requests to the Client + /// The default is 60 seconds + /// This is done to prevent proxy servers from closing your connection + /// A timespan of zero will disable the automatic ping pong mechanism + /// You can manually control ping pong messages using the PingPongManager class. + /// If you do that it is advisible to set this KeepAliveInterval to zero in the WebSocketServerFactory + /// + public TimeSpan KeepAliveInterval { get; set; } + + /// + /// Include the full exception (with stack trace) in the close response + /// when an exception is encountered and the WebSocket connection is closed + /// The default is false + /// + public bool IncludeExceptionInCloseResponse { get; set; } + + /// + /// Specifies the sub protocol to send back to the client in the opening handshake + /// Can be null (the most common use case) + /// The client can specify multiple preferred protocols in the opening handshake header + /// The server should use the first supported one or set this to null if none of the requested sub protocols are supported + /// + public string SubProtocol { get; set; } + + /// + /// Initialises a new instance of the WebSocketServerOptions class + /// + public WebSocketServerOptions() + { + KeepAliveInterval = TimeSpan.FromSeconds(60); + IncludeExceptionInCloseResponse = false; + SubProtocol = null; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs.meta new file mode 100644 index 000000000..adfcf1dea --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Ninja.WebSockets/WebSocketServerOptions.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7f8d9d315d665461a94bc139e46cbfce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Plugins.meta b/Assets/Mirror/Runtime/Transport/Websocket/Plugins.meta new file mode 100644 index 000000000..6d6de23ad --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Plugins.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b64fa4674492a4cf1857ecee9f73fcb1 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib b/Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib new file mode 100644 index 000000000..ff1e6220f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib @@ -0,0 +1,108 @@ +var LibraryWebSockets = { + $webSocketInstances: [], + + SocketCreate: function(url, id, onopen, ondata, onclose) + { + var str = Pointer_stringify(url); + + var socket = new WebSocket(str, "binary"); + + socket.binaryType = 'arraybuffer'; + + socket.onopen = function(e) { + Runtime.dynCall('vi', onopen, [id]); + } + + socket.onerror = function(e) { + console.log("websocket error " + JSON.stringify(e)); + } + + socket.onmessage = function (e) { + // Todo: handle other data types? + if (e.data instanceof Blob) + { + var reader = new FileReader(); + reader.addEventListener("loadend", function() { + var array = new Uint8Array(reader.result); + }); + reader.readAsArrayBuffer(e.data); + } + else if (e.data instanceof ArrayBuffer) + { + var array = new Uint8Array(e.data); + var ptr = _malloc(array.length); + var dataHeap = new Uint8Array(HEAPU8.buffer, ptr, array.length); + dataHeap.set(array); + Runtime.dynCall('viii', ondata, [id, ptr, array.length]); + _free(ptr); + } + else if(typeof e.data === "string") { + var reader = new FileReader(); + reader.addEventListener("loadend", function() { + var array = new Uint8Array(reader.result); + }); + var blob = new Blob([e.data]); + reader.readAsArrayBuffer(blob); + } + }; + + socket.onclose = function (e) { + Runtime.dynCall('vi', onclose, [id]); + + if (e.code != 1000) + { + if (e.reason != null && e.reason.length > 0) + socket.error = e.reason; + else + { + switch (e.code) + { + case 1001: + socket.error = "Endpoint going away."; + break; + case 1002: + socket.error = "Protocol error."; + break; + case 1003: + socket.error = "Unsupported message."; + break; + case 1005: + socket.error = "No status."; + break; + case 1006: + socket.error = "Abnormal disconnection."; + break; + case 1009: + socket.error = "Data frame too large."; + break; + default: + socket.error = "Error "+e.code; + } + } + } + } + var instance = webSocketInstances.push(socket) - 1; + return instance; + }, + + SocketState: function (socketInstance) + { + var socket = webSocketInstances[socketInstance]; + return socket.readyState; + }, + + SocketSend: function (socketInstance, ptr, length) + { + var socket = webSocketInstances[socketInstance]; + socket.send (HEAPU8.buffer.slice(ptr, ptr+length)); + }, + + SocketClose: function (socketInstance) + { + var socket = webSocketInstances[socketInstance]; + socket.close(); + } +}; + +autoAddDeps(LibraryWebSockets, '$webSocketInstances'); +mergeInto(LibraryManager.library, LibraryWebSockets); \ No newline at end of file diff --git a/Assets/Mirror/Plugins/Mirror.Weaver.dll.meta b/Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib.meta similarity index 74% rename from Assets/Mirror/Plugins/Mirror.Weaver.dll.meta rename to Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib.meta index eca073933..67daa5e12 100644 --- a/Assets/Mirror/Plugins/Mirror.Weaver.dll.meta +++ b/Assets/Mirror/Runtime/Transport/Websocket/Plugins/WebSocket.jslib.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: e66877f32061045c699eba82eac729e4 +guid: 3fba16b22ae274c729f6e8f91c425355 PluginImporter: externalObjects: {} serializedVersion: 2 @@ -16,15 +16,19 @@ PluginImporter: - first: Editor: Editor second: - enabled: 1 + enabled: 0 settings: DefaultValueInitialized: true - first: - Windows Store Apps: WindowsStoreApps + Facebook: WebGL second: - enabled: 0 - settings: - CPU: AnyCPU + enabled: 1 + settings: {} + - first: + WebGL: WebGL + second: + enabled: 1 + settings: {} userData: assetBundleName: assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Server.cs b/Assets/Mirror/Runtime/Transport/Websocket/Server.cs new file mode 100644 index 000000000..3afd55dde --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Server.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Security; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using Ninja.WebSockets; +using UnityEngine; + +namespace Mirror.Websocket +{ + public class Server + { + public event Action Connected; + public event Action ReceivedData; + public event Action Disconnected; + public event Action ReceivedError; + + private const int MaxMessageSize = 256 * 1024; + + // listener + TcpListener listener; + private readonly IWebSocketServerFactory webSocketServerFactory = new WebSocketServerFactory(); + + CancellationTokenSource cancellation; + + // clients with + Dictionary clients = new Dictionary(); + + public bool NoDelay = true; + + // connectionId counter + // (right now we only use it from one listener thread, but we might have + // multiple threads later in case of WebSockets etc.) + // -> static so that another server instance doesn't start at 0 again. + static int counter = 0; + + // public next id function in case someone needs to reserve an id + // (e.g. if hostMode should always have 0 connection and external + // connections should start at 1, etc.) + public static int NextConnectionId() + { + int id = Interlocked.Increment(ref counter); + + // it's very unlikely that we reach the uint limit of 2 billion. + // even with 1 new connection per second, this would take 68 years. + // -> but if it happens, then we should throw an exception because + // the caller probably should stop accepting clients. + // -> it's hardly worth using 'bool Next(out id)' for that case + // because it's just so unlikely. + if (id == int.MaxValue) + { + throw new Exception("connection id limit reached: " + id); + } + + return id; + } + + // check if the server is running + public bool Active + { + get { return listener != null; } + } + + public WebSocket GetClient(int connectionId) + { + // paul: null is evil, throw exception if not found + return clients[connectionId]; + } + + public bool _secure = false; + + public SslConfiguration _sslConfig; + + public class SslConfiguration + { + public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate; + public bool ClientCertificateRequired; + public System.Security.Authentication.SslProtocols EnabledSslProtocols; + public bool CheckCertificateRevocation; + } + + public async void Listen(int port) + { + try + { + cancellation = new CancellationTokenSource(); + + listener = TcpListener.Create(port); + listener.Server.NoDelay = this.NoDelay; + listener.Start(); + Debug.Log($"Websocket server started listening on port {port}"); + while (true) + { + TcpClient tcpClient = await listener.AcceptTcpClientAsync(); + ProcessTcpClient(tcpClient, cancellation.Token); + } + } + catch (ObjectDisposedException) + { + // do nothing. This will be thrown if the Listener has been stopped + } + catch (Exception ex) + { + ReceivedError?.Invoke(0, ex); + } + } + + private async void ProcessTcpClient(TcpClient tcpClient, CancellationToken token) + { + + try + { + // this worker thread stays alive until either of the following happens: + // Client sends a close conection request OR + // An unhandled exception is thrown OR + // The server is disposed + + // get a secure or insecure stream + Stream stream = tcpClient.GetStream(); + if (_secure) + { + var sslStream = new SslStream(stream, false, CertVerificationCallback); + sslStream.AuthenticateAsServer(_sslConfig.Certificate, _sslConfig.ClientCertificateRequired, _sslConfig.EnabledSslProtocols, _sslConfig.CheckCertificateRevocation); + stream = sslStream; + } + WebSocketHttpContext context = await webSocketServerFactory.ReadHttpHeaderFromStreamAsync(stream, token); + if (context.IsWebSocketRequest) + { + var options = new WebSocketServerOptions() { KeepAliveInterval = TimeSpan.FromSeconds(30), SubProtocol = "binary" }; + + WebSocket webSocket = await webSocketServerFactory.AcceptWebSocketAsync(context, options); + + await ReceiveLoopAsync(webSocket, token); + } + else + { + Debug.Log("Http header contains no web socket upgrade request. Ignoring"); + } + + } + catch (ObjectDisposedException) + { + // do nothing. This will be thrown if the Listener has been stopped + } + catch (Exception ex) + { + ReceivedError?.Invoke(0, ex); + } + finally + { + try + { + tcpClient.Client.Close(); + tcpClient.Close(); + } + catch (Exception ex) + { + ReceivedError?.Invoke(0, ex); + } + } + } + + private bool CertVerificationCallback(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + { + // Much research has been done on this. When this is initiated from a HTTPS/WSS stream, + // the certificate is null and the SslPolicyErrors is RemoteCertificateNotAvailable. + // Meaning we CAN'T verify this and this is all we can do. + return true; + } + + private async Task ReceiveLoopAsync(WebSocket webSocket, CancellationToken token) + { + int connectionId = NextConnectionId(); + clients.Add(connectionId, webSocket); + + byte[] buffer = new byte[MaxMessageSize]; + + try + { + // someone connected, raise event + Connected?.Invoke(connectionId); + + + while (true) + { + WebSocketReceiveResult result = await webSocket.ReceiveAsync(new ArraySegment(buffer), token); + + if (result.MessageType == WebSocketMessageType.Close) + { + Debug.Log($"Client initiated close. Status: {result.CloseStatus} Description: {result.CloseStatusDescription}"); + break; + } + + byte[] data = await ReadFrames(connectionId, result, webSocket, buffer, token); + + if (data == null) + break; + + try + { + // we received some data, raise event + ReceivedData?.Invoke(connectionId, data); + } + catch (Exception exception) + { + ReceivedError?.Invoke(connectionId, exception); + } + } + + } + catch (Exception exception) + { + ReceivedError?.Invoke(connectionId, exception); + } + finally + { + clients.Remove(connectionId); + Disconnected?.Invoke(connectionId); + } + } + + // a message might come splitted in multiple frames + // collect all frames + private async Task ReadFrames(int connectionId, WebSocketReceiveResult result, WebSocket webSocket, byte[] buffer, CancellationToken token) + { + int count = result.Count; + + while (!result.EndOfMessage) + { + if (count >= MaxMessageSize) + { + string closeMessage = string.Format("Maximum message size: {0} bytes.", MaxMessageSize); + await webSocket.CloseAsync(WebSocketCloseStatus.MessageTooBig, closeMessage, CancellationToken.None); + ReceivedError?.Invoke(connectionId, new WebSocketException(WebSocketError.HeaderError)); + return null; + } + + result = await webSocket.ReceiveAsync(new ArraySegment(buffer, count, MaxMessageSize - count), CancellationToken.None); + count += result.Count; + + } + return new ArraySegment(buffer, 0, count).ToArray(); + } + + public void Stop() + { + // only if started + if (!Active) return; + + Debug.Log("Server: stopping..."); + cancellation.Cancel(); + + // stop listening to connections so that no one can connect while we + // close the client connections + listener.Stop(); + + // clear clients list + clients.Clear(); + listener = null; + } + + // send message to client using socket connection or throws exception + public async void Send(int connectionId, byte[] data) + { + // find the connection + WebSocket client; + if (clients.TryGetValue(connectionId, out client)) + { + try + { + await client.SendAsync(new ArraySegment(data), WebSocketMessageType.Binary, true, cancellation.Token); + } + catch (ObjectDisposedException) { + // connection has been closed, swallow exception + Disconnect(connectionId); + } + catch (Exception exception) + { + if (clients.ContainsKey(connectionId)) + { + // paul: If someone unplugs their internet + // we can potentially get hundreds of errors here all at once + // because all the WriteAsync wake up at once and throw exceptions + + // by hiding inside this if, I ensure that we only report the first error + // all other errors are swallowed. + // this prevents a log storm that freezes the server for several seconds + ReceivedError?.Invoke(connectionId, exception); + } + + Disconnect(connectionId); + } + } + else + { + ReceivedError?.Invoke(connectionId, new SocketException((int)SocketError.NotConnected)); + } + } + + // get connection info in case it's needed (IP etc.) + // (we should never pass the TcpClient to the outside) + public string GetClientAddress(int connectionId) + { + // find the connection + WebSocket client; + if (clients.TryGetValue(connectionId, out client)) + { + return ""; + } + return null; + } + + // disconnect (kick) a client + public bool Disconnect(int connectionId) + { + // find the connection + WebSocket client; + if (clients.TryGetValue(connectionId, out client)) + { + clients.Remove(connectionId); + // just close it. client thread will take care of the rest. + client.CloseAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None); + Debug.Log("Server.Disconnect connectionId:" + connectionId); + return true; + } + return false; + } + + public override string ToString() + { + if (Active) + { + return $"Websocket server {listener.LocalEndpoint}"; + } + return ""; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/Server.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/Server.cs.meta new file mode 100644 index 000000000..44d8af0d1 --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/Server.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b4bf9040513294fa4939bb9f2f0cda4e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs b/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs new file mode 100644 index 000000000..de007f82a --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs @@ -0,0 +1,137 @@ +using System; +using UnityEngine; + +namespace Mirror.Websocket +{ + public class WebsocketTransport : Transport + { + + protected Client client = new Client(); + protected Server server = new Server(); + + public int port = 7778; + + [Tooltip("Nagle Algorithm can be disabled by enabling NoDelay")] + public bool NoDelay = true; + + public bool Secure = false; + + public string CertificatePath; + + public string CertificatePassword; + + public WebsocketTransport() + { + // dispatch the events from the server + server.Connected += (connectionId) => OnServerConnected.Invoke(connectionId); + server.Disconnected += (connectionId) => OnServerDisconnected.Invoke(connectionId); + server.ReceivedData += (connectionId, data) => OnServerDataReceived.Invoke(connectionId, data); + server.ReceivedError += (connectionId, error) => OnServerError.Invoke(connectionId, error); + + // dispatch events from the client + client.Connected += () => OnClientConnected.Invoke(); + client.Disconnected += () => OnClientDisconnected.Invoke(); + client.ReceivedData += (data) => OnClientDataReceived.Invoke(data); + client.ReceivedError += (error) => OnClientError.Invoke(error); + + // configure + client.NoDelay = NoDelay; + server.NoDelay = NoDelay; + + // HLAPI's local connection uses hard coded connectionId '0', so we + // need to make sure that external connections always start at '1' + // by simple eating the first one before the server starts + Server.NextConnectionId(); + + Debug.Log("Websocket transport initialized!"); + } + + public override bool Available() + { + // WebSockets should be available on all platforms, including WebGL (automatically) using our included JSLIB code + return true; + } + + // client + public override bool ClientConnected() => client.IsConnected; + + public override void ClientConnect(string host) + { + if (Secure) + { + client.Connect(new Uri($"wss://{host}:{port}")); + } + else + { + client.Connect(new Uri($"ws://{host}:{port}")); + } + } + + public override bool ClientSend(int channelId, byte[] data) { client.Send(data); return true; } + + public override void ClientDisconnect() => client.Disconnect(); + + // server + public override bool ServerActive() => server.Active; + + public override void ServerStart() + { + + if (Secure) + { + server._secure = Secure; + server._sslConfig = new Server.SslConfiguration + { + Certificate = new System.Security.Cryptography.X509Certificates.X509Certificate2(Application.dataPath + CertificatePath, CertificatePassword), + ClientCertificateRequired = false, + CheckCertificateRevocation = false, + EnabledSslProtocols = System.Security.Authentication.SslProtocols.Default + }; + } + server.Listen(port); + } + + public override bool ServerSend(int connectionId, int channelId, byte[] data) + { + server.Send(connectionId, data); + return true; + } + + public override bool ServerDisconnect(int connectionId) + { + return server.Disconnect(connectionId); + } + + public override string ServerGetClientAddress(int connectionId) + { + return server.GetClientAddress(connectionId); + } + public override void ServerStop() => server.Stop(); + + // common + public override void Shutdown() + { + client.Disconnect(); + server.Stop(); + } + + public override int GetMaxPacketSize(int channelId) + { + // Telepathy's limit is Array.Length, which is int + return int.MaxValue; + } + + public override string ToString() + { + if (client.Connecting || client.IsConnected) + { + return client.ToString(); + } + if (server.Active) + { + return server.ToString(); + } + return ""; + } + } +} diff --git a/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs.meta b/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs.meta new file mode 100644 index 000000000..aab93802f --- /dev/null +++ b/Assets/Mirror/Runtime/Transport/Websocket/WebsocketTransport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5f039183eda8945448b822a77e2a9d0e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/UNetwork.cs b/Assets/Mirror/Runtime/UNetwork.cs index 96c9be161..a4b81414e 100644 --- a/Assets/Mirror/Runtime/UNetwork.cs +++ b/Assets/Mirror/Runtime/UNetwork.cs @@ -1,6 +1,5 @@ using System; using UnityEngine; -using UnityEngine.Rendering; namespace Mirror { @@ -14,7 +13,7 @@ namespace Mirror public delegate void UnSpawnDelegate(GameObject spawned); // invoke type for Cmd/Rpc/SyncEvents - public enum UNetInvokeType + public enum MirrorInvokeType { Command, ClientRpc, @@ -25,6 +24,7 @@ public enum UNetInvokeType // original HLAPI uses short, so let's keep short to not break packet header etc. // => use .ToString() to get the field name from the field value // => we specify the short values so it's easier to look up opcodes when debugging packets + [Obsolete("Use Send with no message id instead")] public enum MsgType : short { // internal system messages - cannot be replaced by user code @@ -58,25 +58,6 @@ public enum MsgType : short Highest = 47 } - public struct NetworkMessage - { - public short msgType; - public NetworkConnection conn; - public NetworkReader reader; - - public TMsg ReadMessage() where TMsg : MessageBase, new() - { - TMsg msg = new TMsg(); - msg.Deserialize(reader); - return msg; - } - - public void ReadMessage(TMsg msg) where TMsg : MessageBase - { - msg.Deserialize(reader); - } - } - public enum Version { Current = 1 @@ -87,112 +68,4 @@ public static class Channels public const int DefaultReliable = 0; public const int DefaultUnreliable = 1; } - - // network protocol 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 Protocol - { - // PackMessage is in hot path. caching the writer is really worth it to - // avoid large amounts of allocations. - static NetworkWriter packWriter = new NetworkWriter(); - - // pack message before sending - // -> pass writer instead of byte[] so we can reuse it - public static byte[] PackMessage(ushort msgType, MessageBase msg) - { - // reset cached writer's position - packWriter.Position = 0; - - // write message type - packWriter.WritePackedUInt32(msgType); - - // serialize message into writer - msg.Serialize(packWriter); - - // return byte[] - return packWriter.ToArray(); - } - - // unpack message after receiving - public static bool UnpackMessage(byte[] message, out ushort msgType, out byte[] content) - { - NetworkReader reader = new NetworkReader(message); - - // read message type (varint) - msgType = (UInt16)reader.ReadPackedUInt32(); - - // read content (remaining data in message) - content = reader.ReadBytes(reader.Length - reader.Position); - - return true; - } - } - - public static class Utils - { - // 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 * (float)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 + ((float)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 float[] 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 float[]{u, v, w}; - } - - // headless mode detection - public static bool IsHeadless() - { - return SystemInfo.graphicsDeviceType == GraphicsDeviceType.Null; - } - } } diff --git a/Assets/Mirror/Tests/ExponentialMovingAverageTest.cs b/Assets/Mirror/Tests/ExponentialMovingAverageTest.cs index 1302894b3..3b55264c4 100644 --- a/Assets/Mirror/Tests/ExponentialMovingAverageTest.cs +++ b/Assets/Mirror/Tests/ExponentialMovingAverageTest.cs @@ -1,5 +1,4 @@ -using NUnit.Framework; -using System; +using NUnit.Framework; namespace Mirror.Tests { [TestFixture] diff --git a/Assets/Mirror/Tests/FloatBytePackerTest.cs b/Assets/Mirror/Tests/FloatBytePackerTest.cs new file mode 100644 index 000000000..d57801fdf --- /dev/null +++ b/Assets/Mirror/Tests/FloatBytePackerTest.cs @@ -0,0 +1,25 @@ +using NUnit.Framework; +namespace Mirror +{ + [TestFixture] + public class FloatBytePackerTest + { + [Test] + public void TestScaleFloatToByte() + { + Assert.That(FloatBytePacker.ScaleFloatToByte( -1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(0)); + Assert.That(FloatBytePacker.ScaleFloatToByte( 0f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(127)); + Assert.That(FloatBytePacker.ScaleFloatToByte(0.5f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(191)); + Assert.That(FloatBytePacker.ScaleFloatToByte( 1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(255)); + } + + [Test] + public void ScaleByteToFloat() + { + Assert.That(FloatBytePacker.ScaleByteToFloat( 0, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-1).Within(0.0001f)); + Assert.That(FloatBytePacker.ScaleByteToFloat(127, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-0.003921569f).Within(0.0001f)); + Assert.That(FloatBytePacker.ScaleByteToFloat(191, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(0.4980392f).Within(0.0001f)); + Assert.That(FloatBytePacker.ScaleByteToFloat(255, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(1).Within(0.0001f)); + } + } +} diff --git a/Assets/Mirror/Tests/UtilsTest.cs.meta b/Assets/Mirror/Tests/FloatBytePackerTest.cs.meta similarity index 100% rename from Assets/Mirror/Tests/UtilsTest.cs.meta rename to Assets/Mirror/Tests/FloatBytePackerTest.cs.meta diff --git a/Assets/Mirror/Tests/MessagePackerTest.cs b/Assets/Mirror/Tests/MessagePackerTest.cs new file mode 100644 index 000000000..5023de746 --- /dev/null +++ b/Assets/Mirror/Tests/MessagePackerTest.cs @@ -0,0 +1,22 @@ +using NUnit.Framework; +namespace Mirror +{ + [TestFixture] + public class MessagePackerTest + { + [Test] + public void TestPacking() + { + SceneMessage message = new SceneMessage() + { + value = "Hello world" + }; + + byte[] data = MessagePacker.Pack(message); + + SceneMessage unpacked = MessagePacker.Unpack(data); + + Assert.That(unpacked.value, Is.EqualTo("Hello world")); + } + } +} diff --git a/Assets/Mirror/Tests/MessagePackerTest.cs.meta b/Assets/Mirror/Tests/MessagePackerTest.cs.meta new file mode 100644 index 000000000..70e62403b --- /dev/null +++ b/Assets/Mirror/Tests/MessagePackerTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d57c17d9ee7c49e6bacc54ddbeac751 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Mirror.Tests.asmdef b/Assets/Mirror/Tests/Mirror.Tests.asmdef index ea484b24b..1463222b6 100644 --- a/Assets/Mirror/Tests/Mirror.Tests.asmdef +++ b/Assets/Mirror/Tests/Mirror.Tests.asmdef @@ -1,7 +1,8 @@ { "name": "Mirror.Tests", "references": [ - "Mirror" + "Mirror", + "Mirror.Weaver" ], "optionalUnityReferences": [ "TestAssemblies" @@ -10,5 +11,9 @@ "Editor" ], "excludePlatforms": [], - "allowUnsafeCode": false + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] } \ No newline at end of file diff --git a/Assets/Mirror/Tests/NetworkWriterTest.cs b/Assets/Mirror/Tests/NetworkWriterTest.cs index 1d84421a3..7b7306e57 100644 --- a/Assets/Mirror/Tests/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/NetworkWriterTest.cs @@ -1,6 +1,5 @@ -using NUnit.Framework; using System; -using UnityEngine; +using NUnit.Framework; namespace Mirror.Tests { @@ -10,7 +9,7 @@ public class NetworkWriterTest [Test] public void TestWritingSmallMessage() { - // try serializing <32kb and see what happens + // try serializing less than 32kb and see what happens NetworkWriter writer = new NetworkWriter(); for (int i = 0; i < 30000 / 4; ++i) writer.Write(i); @@ -20,7 +19,7 @@ public void TestWritingSmallMessage() [Test] public void TestWritingLargeMessage() { - // try serializing <32kb and see what happens + // try serializing more than 32kb and see what happens NetworkWriter writer = new NetworkWriter(); for (int i = 0; i < 40000 / 4; ++i) writer.Write(i); @@ -30,7 +29,7 @@ public void TestWritingLargeMessage() [Test] public void TestWritingHugeArray() { - // try serializing array > 64KB and see what happens + // try serializing array more than 64KB large and see what happens NetworkWriter writer = new NetworkWriter(); writer.WriteBytesAndSize(new byte[100000]); byte[] data = writer.ToArray(); @@ -54,9 +53,8 @@ public void TestToArray() // set position back by one writer.Position = 1; - // .ToArray() length is 1, even though the internal array contains 2 bytes? - // (see .ToArray() function comments) - Assert.That(writer.ToArray().Length, Is.EqualTo(1)); + // Changing the position should not alter the size of the data + Assert.That(writer.ToArray().Length, Is.EqualTo(2)); } [Test] @@ -69,7 +67,7 @@ public void TestPackedUInt32() writer.WritePackedUInt32(67821); writer.WritePackedUInt32(16777210); writer.WritePackedUInt32(16777219); - writer.WritePackedUInt32(UInt32.MaxValue); + writer.WritePackedUInt32(uint.MaxValue); NetworkReader reader = new NetworkReader(writer.ToArray()); Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(0)); @@ -78,7 +76,7 @@ public void TestPackedUInt32() Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(67821)); Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(16777210)); Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(16777219)); - Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(UInt32.MaxValue)); + Assert.That(reader.ReadPackedUInt32(), Is.EqualTo(uint.MaxValue)); } [Test] @@ -95,7 +93,7 @@ public void TestPackedUInt64() writer.WritePackedUInt64(1099511627775); writer.WritePackedUInt64(281474976710655); writer.WritePackedUInt64(72057594037927935); - writer.WritePackedUInt64(UInt64.MaxValue); + writer.WritePackedUInt64(ulong.MaxValue); NetworkReader reader = new NetworkReader(writer.ToArray()); Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(0)); @@ -108,7 +106,7 @@ public void TestPackedUInt64() Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(1099511627775)); Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(281474976710655)); Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(72057594037927935)); - Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(UInt64.MaxValue)); + Assert.That(reader.ReadPackedUInt64(), Is.EqualTo(ulong.MaxValue)); } [Test] diff --git a/Assets/Mirror/Tests/SyncListTest.cs b/Assets/Mirror/Tests/SyncListTest.cs index 0a8915148..74057ae40 100644 --- a/Assets/Mirror/Tests/SyncListTest.cs +++ b/Assets/Mirror/Tests/SyncListTest.cs @@ -1,6 +1,5 @@ -using NUnit.Framework; using System; -using System.Linq; +using NUnit.Framework; namespace Mirror.Tests { @@ -59,7 +58,7 @@ public void TestClear() { serverSyncList.Clear(); SerializeDeltaTo(serverSyncList, clientSyncList); - Assert.That(clientSyncList, Is.EquivalentTo(new string[] { })); + Assert.That(clientSyncList, Is.EquivalentTo(new string[] {})); } [Test] diff --git a/Assets/Mirror/Tests/UtilsTest.cs b/Assets/Mirror/Tests/UtilsTest.cs deleted file mode 100644 index 37bdd40ee..000000000 --- a/Assets/Mirror/Tests/UtilsTest.cs +++ /dev/null @@ -1,25 +0,0 @@ -using NUnit.Framework; -namespace Mirror -{ - [TestFixture] - public class UtilsTest - { - [Test] - public void TestScaleFloatToByte() - { - Assert.That(Utils.ScaleFloatToByte( -1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(0)); - Assert.That(Utils.ScaleFloatToByte( 0f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(127)); - Assert.That(Utils.ScaleFloatToByte(0.5f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(191)); - Assert.That(Utils.ScaleFloatToByte( 1f, -1f, 1f, byte.MinValue, byte.MaxValue), Is.EqualTo(255)); - } - - [Test] - public void ScaleByteToFloat() - { - Assert.That(Utils.ScaleByteToFloat( 0, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-1).Within(0.0001f)); - Assert.That(Utils.ScaleByteToFloat(127, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(-0.003921569f).Within(0.0001f)); - Assert.That(Utils.ScaleByteToFloat(191, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(0.4980392f).Within(0.0001f)); - Assert.That(Utils.ScaleByteToFloat(255, byte.MinValue, byte.MaxValue, -1, 1), Is.EqualTo(1).Within(0.0001f)); - } - } -} \ No newline at end of file diff --git a/Assets/Mirror/Tests/WeaverAssembler.cs b/Assets/Mirror/Tests/WeaverAssembler.cs new file mode 100644 index 000000000..61b830efa --- /dev/null +++ b/Assets/Mirror/Tests/WeaverAssembler.cs @@ -0,0 +1,188 @@ +using System.IO; +using System.Linq; +using System.Collections.Generic; +using UnityEditor.Compilation; +using UnityEngine; + +public class WeaverAssembler : MonoBehaviour +{ + public const string OutputDirectory = "Assets/Mirror/Tests/WeaverTests~/"; + public static string OutputFile { get; set; } + public static HashSet SourceFiles { get; private set; } + public static HashSet ReferenceAssemblies { get; private set; } + public static bool AllowUnsafe { get; set; } + public static List CompilerMessages { get; private set; } + public static bool CompilerErrors { get; private set; } + public static bool DeleteOutputOnClear { get; set; } + + // static constructor to initialize static properties + static WeaverAssembler() + { + SourceFiles = new HashSet(); + ReferenceAssemblies = new HashSet(); + CompilerMessages = new List(); + } + + // Add a range of source files to compile + public static void AddSourceFiles(string[] sourceFiles) + { + foreach (string src in sourceFiles) + { + SourceFiles.Add(OutputDirectory + src); + } + } + + // Add a range of reference files by full path + public static void AddReferencesByFullPath(string[] refAsms) + { + foreach (string asm in refAsms) + { + ReferenceAssemblies.Add(asm); + } + } + + // Add a range of reference files by assembly name only + public static void AddReferencesByAssemblyName(string[] refAsms) + { + foreach (string asm in refAsms) + { + string asmFullPath; + if (FindReferenceAssemblyPath(asm, out asmFullPath)) + { + ReferenceAssemblies.Add(asmFullPath); + } + } + } + + // Find reference assembly specified by asmName and store its full path in asmFullPath + // do not pass in paths in asmName, just assembly names + public static bool FindReferenceAssemblyPath(string asmName, out string asmFullPath) + { + asmFullPath = ""; + + Assembly[] asms = CompilationPipeline.GetAssemblies(); + foreach (Assembly asm in asms) + { + foreach (string asmRef in asm.compiledAssemblyReferences) + { + if (asmRef.EndsWith(asmName)) + { + asmFullPath = asmRef; + return true; + } + } + } + + return false; + } + + // Add reference (not cleared during calls to Clear) + public static void ClearReferences() + { + ReferenceAssemblies.Clear(); + } + + // Delete output dll / pdb / mdb + public static void DeleteOutput() + { + if (OutputFile.Length < 5) return; // "x.dll" shortest possible dll name + + string projPathFile = OutputDirectory + OutputFile; + + try + { + File.Delete(projPathFile); + + } + catch {} + + try + { + File.Delete(Path.ChangeExtension(projPathFile, ".pdb")); + + } + catch {} + + try + { + File.Delete(Path.ChangeExtension(projPathFile, ".dll.mdb")); + + } + catch {} + } + + // clear all settings except for referenced assemblies (which are cleared with ClearReferences) + public static void Clear() + { + if (DeleteOutputOnClear) + { + DeleteOutput(); + } + + CompilerErrors = false; + OutputFile = ""; + SourceFiles.Clear(); + CompilerMessages.Clear(); + AllowUnsafe = false; + DeleteOutputOnClear = false; + } + + // build synchronously + public static void Build() + { + BuildAssembly(true); + } + + // build asynchronously - this isn't currently used + public static void BuildAsync() + { + BuildAssembly(false); + } + + private static void BuildAssembly(bool wait) + { + AssemblyBuilder assemblyBuilder = new AssemblyBuilder(OutputDirectory + OutputFile, SourceFiles.ToArray()); + assemblyBuilder.additionalReferences = ReferenceAssemblies.ToArray(); + if (AllowUnsafe) + { + assemblyBuilder.compilerOptions.AllowUnsafeCode = true; + } + + assemblyBuilder.buildStarted += delegate (string assemblyPath) + { + //Debug.LogFormat("Assembly build started for {0}", assemblyPath); + }; + + assemblyBuilder.buildFinished += delegate (string assemblyPath, CompilerMessage[] compilerMessages) + { + CompilerMessages.AddRange(compilerMessages); + foreach (CompilerMessage cm in compilerMessages) + { + if (cm.type == CompilerMessageType.Warning) + { + //Debug.LogWarningFormat("{0}:{1} -- {2}", cm.file, cm.line, cm.message); + } + else if (cm.type == CompilerMessageType.Error) + { + Debug.LogErrorFormat("{0}:{1} -- {2}", cm.file, cm.line, cm.message); + CompilerErrors = true; + } + } + }; + + // Start build of assembly + if (!assemblyBuilder.Build()) + { + Debug.LogErrorFormat("Failed to start build of assembly {0}", assemblyBuilder.assemblyPath); + return; + } + + if (wait) + { + while (assemblyBuilder.status != AssemblyBuilderStatus.Finished) + { + System.Threading.Thread.Sleep(10); + } + } + } +} diff --git a/Assets/Mirror/Tests/WeaverAssembler.cs.meta b/Assets/Mirror/Tests/WeaverAssembler.cs.meta new file mode 100644 index 000000000..d41b1b4f2 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverAssembler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 049ac5abfba3c0943a2694cd502fcc80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/WeaverTest.cs b/Assets/Mirror/Tests/WeaverTest.cs new file mode 100644 index 000000000..636878b5e --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTest.cs @@ -0,0 +1,806 @@ +//#define LOG_WEAVER_OUTPUTS + +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEditor.Compilation; + +using Mirror.Weaver; + +namespace Mirror +{ + [TestFixture] + public class WeaverTest + { + #region Private + List m_weaverErrors = new List(); + void HandleWeaverError(string msg) + { +#if LOG_WEAVER_OUTPUTS + Debug.LogError(msg); +#endif + m_weaverErrors.Add(msg); + } + + List m_weaverWarnings = new List(); + void HandleWeaverWarning(string msg) + { +#if LOG_WEAVER_OUTPUTS + Debug.LogWarning(msg); +#endif + m_weaverWarnings.Add(msg); + } + + private void BuildAndWeaveTestAssembly(string baseName) + { + WeaverAssembler.OutputFile = baseName + ".dll"; + WeaverAssembler.AddSourceFiles(new string[] { baseName + ".cs" }); + WeaverAssembler.Build(); + + Assert.That(WeaverAssembler.CompilerErrors, Is.False); + if (m_weaverErrors.Count > 0) + { + Assert.That(m_weaverErrors[0], Does.StartWith("Mirror.Weaver error: ")); + } + } + #endregion + + #region Setup and Teardown + [OneTimeSetUp] + public void FixtureSetup() + { + // TextRenderingModule is only referenced to use TextMesh type to throw errors about types from another module + WeaverAssembler.AddReferencesByAssemblyName(new string[] { "UnityEngine.dll", "UnityEngine.CoreModule.dll", "UnityEngine.TextRenderingModule.dll", "Mirror.dll" }); + + CompilationFinishedHook.UnityLogEnabled = false; + CompilationFinishedHook.OnWeaverError += HandleWeaverError; + CompilationFinishedHook.OnWeaverWarning += HandleWeaverWarning; + } + + [OneTimeTearDown] + public void FixtureCleanup() + { + CompilationFinishedHook.UnityLogEnabled = true; + } + + [SetUp] + public void TestSetup() + { + BuildAndWeaveTestAssembly(TestContext.CurrentContext.Test.Name); + } + + [TearDown] + public void TestCleanup() + { + WeaverAssembler.DeleteOutputOnClear = true; + WeaverAssembler.Clear(); + + m_weaverWarnings.Clear(); + m_weaverErrors.Clear(); + } + #endregion + + #region General tests + [Test] + public void InvalidType() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.AtLeast(1)); + Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type")); + } + + [Test] + public void RecursionCount() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.AtLeast(1)); + Assert.That(m_weaverErrors[0], Does.Match("Check for self-referencing member variables")); + } + + [Test] + public void ClientGuardWrongClass() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("\\[Client\\] guard on non-NetworkBehaviour script")); + } + + [Test] + public void ServerGuardWrongClass() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("\\[Server\\] guard on non-NetworkBehaviour script")); + } + + [Test] + public void GuardCmdWrongClass() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(4)); + Assert.That(m_weaverErrors[0], Does.Match("\\[Server\\] guard on non-NetworkBehaviour script")); + Assert.That(m_weaverErrors[1], Does.Match("\\[Server\\] guard on non-NetworkBehaviour script")); + Assert.That(m_weaverErrors[2], Does.Match("\\[Client\\] guard on non-NetworkBehaviour script")); + Assert.That(m_weaverErrors[3], Does.Match("\\[Client\\] guard on non-NetworkBehaviour script")); + } + + [Test] + public void JaggedArray() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.AtLeast(1)); + Assert.That(m_weaverErrors[0], Does.Match("Jagged and multidimensional arrays are not supported")); + } + #endregion + + #region SyncVar tests + [Test] + public void SyncVarsValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void SyncVarsNoHook() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar Hook function .* not found for")); + } + + [Test] + public void SyncVarsNoHookParams() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* must have one argument")); + } + + [Test] + public void SyncVarsTooManyHookParams() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* must have one argument")); + } + + [Test] + public void SyncVarsWrongHookType() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar Hook function .* has wrong type signature for")); + } + + [Test] + public void SyncVarsDerivedNetworkBehaviour() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be derived from NetworkBehaviour")); + } + + [Test] + public void SyncVarsDerivedScriptableObject() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be derived from ScriptableObject")); + } + + [Test] + public void SyncVarsStatic() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be static")); + } + + [Test] + public void SyncVarsGenericParam() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot have generic parameters")); + } + + [Test] + public void SyncVarsInterface() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be an interface")); + } + + [Test] + public void SyncVarsDifferentModule() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be a different module")); + } + + [Test] + public void SyncVarsCantBeArray() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("SyncVar .* cannot be an array")); + } + + [Test] + public void SyncVarsSyncList() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + Assert.That(m_weaverWarnings.Count, Is.EqualTo(2)); + Assert.That(m_weaverWarnings[0], Does.Match("SyncLists should not be marked with SyncVar")); + Assert.That(m_weaverWarnings[1], Does.Match("SyncLists should not be marked with SyncVar")); + } + + [Test] + public void SyncVarsMoreThan63() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script class .* has too many SyncVars")); + } + #endregion + + #region SyncList tests + [Test] + public void SyncListValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void SyncListMissingParamlessCtor() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Missing parameter-less constructor")); + } + #endregion + + #region SyncListStruct tests + [Test] + public void SyncListStructValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void SyncListStructGenericGeneric() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Struct passed into SyncListStruct can't have generic parameters")); + } + + [Test] + public void SyncListStructMemberGeneric() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("member cannot have generic parameters")); + } + + [Test] + public void SyncListStructMemberInterface() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("member cannot be an interface")); + } + + [Test] + public void SyncListStructMemberBasicType() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(2)); + Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type")); + Assert.That(m_weaverErrors[1], Does.Match("member variables must be basic types")); + } + #endregion + + #region NetworkBehaviour tests + [Test] + public void NetworkBehaviourValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void NetworkBehaviourAbstractBaseValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void NetworkBehaviourGeneric() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("NetworkBehaviour .* cannot have generic parameters")); + } + + [Test] + public void NetworkBehaviourCmdGenericParam() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command .* cannot have generic parameters")); + } + + [Test] + public void NetworkBehaviourCmdCoroutine() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command .* cannot be a coroutine")); + } + + [Test] + public void NetworkBehaviourCmdVoidReturn() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command .* must have a void return type")); + } + + [Test] + public void NetworkBehaviourTargetRpcGenericParam() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc .* cannot have generic parameters")); + } + + [Test] + public void NetworkBehaviourTargetRpcCoroutine() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc .* cannot be a coroutine")); + } + + [Test] + public void NetworkBehaviourTargetRpcVoidReturn() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc .* must have a void return type")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamOut() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc function .* cannot have out parameters")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamOptional() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpcfunction .* cannot have optional parameters")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamRef() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc function .* cannot have ref parameters")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamAbstract() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc function .* cannot have abstract parameters")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamComponent() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc function .* You cannot pass a Component to a remote call")); + } + + [Test] + public void NetworkBehaviourTargetRpcParamNetworkConnection() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void NetworkBehaviourTargetRpcDuplicateName() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Duplicate Target Rpc name")); + } + + [Test] + public void NetworkBehaviourClientRpcGenericParam() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc .* cannot have generic parameters")); + } + + [Test] + public void NetworkBehaviourClientRpcCoroutine() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc .* cannot be a coroutine")); + } + + [Test] + public void NetworkBehaviourClientRpcVoidReturn() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc .* must have a void return type")); + } + + [Test] + public void NetworkBehaviourClientRpcParamOut() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc function .* cannot have out parameters")); + } + + [Test] + public void NetworkBehaviourClientRpcParamOptional() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpcfunction .* cannot have optional parameters")); + } + + [Test] + public void NetworkBehaviourClientRpcParamRef() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc function .* cannot have ref parameters")); + } + + [Test] + public void NetworkBehaviourClientRpcParamAbstract() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc function .* cannot have abstract parameters")); + } + + [Test] + public void NetworkBehaviourClientRpcParamComponent() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc function .* You cannot pass a Component to a remote call")); + } + + [Test] + public void NetworkBehaviourClientRpcParamNetworkConnection() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(2)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc .* cannot use a NetworkConnection as a parameter")); + } + + [Test] + public void NetworkBehaviourClientRpcDuplicateName() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Duplicate ClientRpc name")); + } + + [Test] + public void NetworkBehaviourCmdParamOut() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* cannot have out parameters")); + } + + [Test] + public void NetworkBehaviourCmdParamOptional() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Commandfunction .* cannot have optional parameters")); + } + + [Test] + public void NetworkBehaviourCmdParamRef() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* cannot have ref parameters")); + } + + [Test] + public void NetworkBehaviourCmdParamAbstract() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* cannot have abstract parameters")); + } + + [Test] + public void NetworkBehaviourCmdParamComponent() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* You cannot pass a Component to a remote call")); + } + + [Test] + public void NetworkBehaviourCmdParamNetworkConnection() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(2)); + Assert.That(m_weaverErrors[0], Does.Match("Command .* cannot use a NetworkConnection as a parameter")); + } + + [Test] + public void NetworkBehaviourCmdDuplicateName() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Duplicate Command name")); + } + #endregion + + #region Command tests + [Test] + public void CommandValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void CommandStartsWithCmd() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* doesnt have 'Cmd' prefix")); + } + + [Test] + public void CommandCantBeStatic() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Command function .* cant be a static method")); + } + #endregion + + #region ClientRpc tests + [Test] + public void ClientRpcValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void ClientRpcStartsWithRpc() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Rpc function .* doesnt have 'Rpc' prefix")); + } + + [Test] + public void ClientRpcCantBeStatic() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("ClientRpc function .* cant be a static method")); + } + #endregion + + #region TargetRpc tests + [Test] + public void TargetRpcValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void TargetRpcStartsWithTarget() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Target Rpc function .* doesnt have 'Target' prefix")); + } + + [Test] + public void TargetRpcCantBeStatic() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("TargetRpc function .* cant be a static method")); + } + #endregion + + #region TargetRpc tests + [Test] + public void SyncEventValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void SyncEventStartsWithEvent() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Event .* doesnt have 'Event' prefix")); + } + + [Test] + public void SyncEventParamGeneric() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Event .* cannot have generic parameters")); + } + #endregion + + #region MonoBehaviour tests + [Test] + public void MonoBehaviourValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void MonoBehaviourSyncVar() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses \\[SyncVar\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourSyncList() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* defines field .* with type .*, but it's not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourCommand() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses \\[Command\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourClientRpc() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses \\[ClientRpc\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourTargetRpc() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses \\[TargetRpc\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourServer() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses the attribute \\[Server\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourServerCallback() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses the attribute \\[ServerCallback\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourClient() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses the attribute \\[Client\\] .* but is not a NetworkBehaviour")); + } + + [Test] + public void MonoBehaviourClientCallback() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("Script .* uses the attribute \\[ClientCallback\\] .* but is not a NetworkBehaviour")); + } + #endregion + + #region Message tests + [Test] + public void MessageValid() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.False); + Assert.That(m_weaverErrors.Count, Is.EqualTo(0)); + } + + [Test] + public void MessageSelfReferencing() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("GenerateSerialization for .* member cannot be self referencing")); + } + + [Test] + public void MessageInvalidSerializeFieldType() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(2)); + Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type")); + Assert.That(m_weaverErrors[1], Does.Match("GenerateSerialization for .* member variables must be basic types")); + } + + [Test] + public void MessageInvalidDeserializeFieldType() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(3)); + Assert.That(m_weaverErrors[0], Does.Match("please make sure to use a valid type")); + Assert.That(m_weaverErrors[1], Does.Match("GetReadFunc unable to generate function")); + Assert.That(m_weaverErrors[2], Does.Match("GenerateDeSerialization for .* member variables must be basic types")); + } + + [Test] + public void MessageMemberGeneric() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("GenerateSerialization for .* member cannot have generic parameters")); + } + + [Test] + public void MessageMemberInterface() + { + Assert.That(CompilationFinishedHook.WeaveFailed, Is.True); + Assert.That(m_weaverErrors.Count, Is.EqualTo(1)); + Assert.That(m_weaverErrors[0], Does.Match("GenerateSerialization for .* member cannot be an interface")); + } + #endregion + } +} diff --git a/Assets/Mirror/Tests/WeaverTest.cs.meta b/Assets/Mirror/Tests/WeaverTest.cs.meta new file mode 100644 index 000000000..a98d9222f --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3f52dab9c479dd4586d0aceeb2390f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/WeaverTests~/ClientGuardWrongClass.cs b/Assets/Mirror/Tests/WeaverTests~/ClientGuardWrongClass.cs new file mode 100644 index 000000000..5eee5ce38 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/ClientGuardWrongClass.cs @@ -0,0 +1,23 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer + { + // defining a SyncListStruct here will force Weaver to do work on this class + // which will then force it to check for Server / Client guards and fail + struct MyStruct + { + int potato; + float floatingpotato; + double givemetwopotatoes; + } + class MyStructClass : SyncListSTRUCT {}; + MyStructClass Foo; + + [Client] + public void CantClientGuardInThisClass() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/ClientRpcCantBeStatic.cs b/Assets/Mirror/Tests/WeaverTests~/ClientRpcCantBeStatic.cs new file mode 100644 index 000000000..31c608808 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/ClientRpcCantBeStatic.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + private static void RpcCantBeStatic() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/ClientRpcStartsWithRpc.cs b/Assets/Mirror/Tests/WeaverTests~/ClientRpcStartsWithRpc.cs new file mode 100644 index 000000000..ae9a624ed --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/ClientRpcStartsWithRpc.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + private void DoesntStartWithRpc() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/ClientRpcValid.cs b/Assets/Mirror/Tests/WeaverTests~/ClientRpcValid.cs new file mode 100644 index 000000000..4d82c665b --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/ClientRpcValid.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + private void RpcThatIsTotallyValid() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/CommandCantBeStatic.cs b/Assets/Mirror/Tests/WeaverTests~/CommandCantBeStatic.cs new file mode 100644 index 000000000..c008d74fe --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/CommandCantBeStatic.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + private static void CmdCantBeStatic() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/CommandStartsWithCmd.cs b/Assets/Mirror/Tests/WeaverTests~/CommandStartsWithCmd.cs new file mode 100644 index 000000000..cd87a9efa --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/CommandStartsWithCmd.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + private void DoesntStartWithCmd() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/CommandValid.cs b/Assets/Mirror/Tests/WeaverTests~/CommandValid.cs new file mode 100644 index 000000000..b7bc56b75 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/CommandValid.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + private void CmdThatIsTotallyValid() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/GuardCmdWrongClass.cs b/Assets/Mirror/Tests/WeaverTests~/GuardCmdWrongClass.cs new file mode 100644 index 000000000..31d5b014b --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/GuardCmdWrongClass.cs @@ -0,0 +1,32 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer + { + // defining a SyncListStruct here will force Weaver to do work on this class + // which will then force it to check for Server / Client guards and fail + struct MyStruct + { + int potato; + float floatingpotato; + double givemetwopotatoes; + } + class MyStructClass : SyncListSTRUCT {}; + MyStructClass Foo; + + [Server] + public void CantServerGuardInThisClass() {} + + [ServerCallback] + public void CantServerCallbackGuardInThisClass() {} + + [Client] + public void CantClientGuardInThisClass() {} + + [ClientCallback] + public void CantClientCallbackGuardInThisClass() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/InvalidType.cs b/Assets/Mirror/Tests/WeaverTests~/InvalidType.cs new file mode 100644 index 000000000..a2506edb9 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/InvalidType.cs @@ -0,0 +1,18 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + struct MyStruct + { + public AccessViolationException violatedPotato; + } + + class MyStructClass : SyncListSTRUCT {}; + + MyStructClass harpseals; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/JaggedArray.cs b/Assets/Mirror/Tests/WeaverTests~/JaggedArray.cs new file mode 100644 index 000000000..c702f8f77 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/JaggedArray.cs @@ -0,0 +1,20 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + // defining a SyncListStruct here will force Weaver to do work on this class + // which will then force it to check for Server / Client guards and fail + struct MyStruct + { + public int[][] jaggedArray; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyStructClass : SyncListSTRUCT {}; + MyStructClass Foo; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageInvalidDeserializeFieldType.cs b/Assets/Mirror/Tests/WeaverTests~/MessageInvalidDeserializeFieldType.cs new file mode 100644 index 000000000..420d613e4 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageInvalidDeserializeFieldType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public AccessViolationException invalidField; + public byte[] payload; + + // this will cause generate serialization to be skipped, testing generate deserialization + public override void Serialize(NetworkWriter writer) + { + writer.WritePackedUInt32(netId); + writer.Write(assetId); + writer.Write(position); + writer.Write(rotation); + writer.WriteBytesAndSize(payload); + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageInvalidSerializeFieldType.cs b/Assets/Mirror/Tests/WeaverTests~/MessageInvalidSerializeFieldType.cs new file mode 100644 index 000000000..d5f796de5 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageInvalidSerializeFieldType.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public AccessViolationException invalidField; + public byte[] payload; + + // this will guarantee only serialize will be generated (even though weaver does serialize first) + public override void Deserialize(NetworkReader reader) + { + netId = reader.ReadPackedUInt32(); + assetId = reader.ReadGuid(); + position = reader.ReadVector3(); + rotation = reader.ReadQuaternion(); + payload = reader.ReadBytesAndSize(); + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageMemberGeneric.cs b/Assets/Mirror/Tests/WeaverTests~/MessageMemberGeneric.cs new file mode 100644 index 000000000..78852f2b5 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageMemberGeneric.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class HasGeneric {} + + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public HasGeneric invalidField; + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageMemberInterface.cs b/Assets/Mirror/Tests/WeaverTests~/MessageMemberInterface.cs new file mode 100644 index 000000000..d1664c65d --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageMemberInterface.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + interface SuperCoolInterface {} + + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public SuperCoolInterface invalidField; + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageSelfReferencing.cs b/Assets/Mirror/Tests/WeaverTests~/MessageSelfReferencing.cs new file mode 100644 index 000000000..797a78c5d --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageSelfReferencing.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public PrefabClone selfReference = new PrefabClone(); + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MessageValid.cs b/Assets/Mirror/Tests/WeaverTests~/MessageValid.cs new file mode 100644 index 000000000..935fc9a69 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MessageValid.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class PrefabClone : MessageBase + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public byte[] payload; + + public override void Deserialize(NetworkReader reader) + { + netId = reader.ReadPackedUInt32(); + assetId = reader.ReadGuid(); + position = reader.ReadVector3(); + rotation = reader.ReadQuaternion(); + payload = reader.ReadBytesAndSize(); + } + + public override void Serialize(NetworkWriter writer) + { + writer.WritePackedUInt32(netId); + writer.Write(assetId); + writer.Write(position); + writer.Write(rotation); + writer.WriteBytesAndSize(payload); + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClient.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClient.cs new file mode 100644 index 000000000..8f6a29ebf --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClient.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [Client] + void ThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientCallback.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientCallback.cs new file mode 100644 index 000000000..7ab9769c3 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientCallback.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [ClientCallback] + void ThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientRpc.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientRpc.cs new file mode 100644 index 000000000..a8442fc9e --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourClientRpc.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [ClientRpc] + void RpcThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourCommand.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourCommand.cs new file mode 100644 index 000000000..3da90b54a --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourCommand.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [Command] + void CmdThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServer.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServer.cs new file mode 100644 index 000000000..4b0ba98b4 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServer.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [Server] + void ThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServerCallback.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServerCallback.cs new file mode 100644 index 000000000..6f34d0a10 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourServerCallback.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [ServerCallback] + void ThisCantBeOutsideNetworkBehaviour() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncList.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncList.cs new file mode 100644 index 000000000..cb1ecc070 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncList.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + SyncListInt potato; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncVar.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncVar.cs new file mode 100644 index 000000000..639d076f5 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourSyncVar.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [SyncVar] + int potato; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourTargetRpc.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourTargetRpc.cs new file mode 100644 index 000000000..380a47dab --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourTargetRpc.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + [TargetRpc] + void TargetThisCantBeOutsideNetworkBehaviour(NetworkConnection nc) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourValid.cs b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourValid.cs new file mode 100644 index 000000000..bf06ae2ef --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/MonoBehaviourValid.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : MonoBehaviour + { + int monkeys = 12; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourAbstractBaseValid.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourAbstractBaseValid.cs new file mode 100644 index 000000000..28a7ff79d --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourAbstractBaseValid.cs @@ -0,0 +1,19 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + public abstract class EntityBase : NetworkBehaviour {} + + public class EntityConcrete : EntityBase + { + [SyncVar] + public int abstractDerivedSync; + } + + public class EntityTest : EntityConcrete + { + [SyncVar] + public int concreteDerivedSync; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcCoroutine.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcCoroutine.cs new file mode 100644 index 000000000..8825a9673 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcCoroutine.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public IEnumerator RpcCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcDuplicateName.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcDuplicateName.cs new file mode 100644 index 000000000..c54ba2303 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcDuplicateName.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveSameName(int abc) {} + + [ClientRpc] + public void RpcCantHaveSameName(int abc, int def) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcGenericParam.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcGenericParam.cs new file mode 100644 index 000000000..4458395d3 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcGenericParam.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveGeneric() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamAbstract.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamAbstract.cs new file mode 100644 index 000000000..98fb91196 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamAbstract.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [ClientRpc] + public void RpcCantHaveParamAbstract(AbstractClass monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamComponent.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamComponent.cs new file mode 100644 index 000000000..2ea126960 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamComponent.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [ClientRpc] + public void RpcCantHaveParamComponent(ComponentClass monkeyComp) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs new file mode 100644 index 000000000..fad089a97 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOptional(NetworkConnection monkeyCon) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOptional.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOptional.cs new file mode 100644 index 000000000..165dff779 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOptional.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOptional(int monkeys = 12) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOut.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOut.cs new file mode 100644 index 000000000..a4a31f720 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamOut.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOut(out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamRef.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamRef.cs new file mode 100644 index 000000000..3206d0c31 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcParamRef.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamRef(ref int monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcVoidReturn.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcVoidReturn.cs new file mode 100644 index 000000000..a44e1c2de --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourClientRpcVoidReturn.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [ClientRpc] + public int RpcCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdCoroutine.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdCoroutine.cs new file mode 100644 index 000000000..3b862bd44 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdCoroutine.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public IEnumerator CmdCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdDuplicateName.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdDuplicateName.cs new file mode 100644 index 000000000..c6a25cfa2 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdDuplicateName.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveSameName(int abc) {} + + [Command] + public void CmdCantHaveSameName(int abc, int def) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdGenericParam.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdGenericParam.cs new file mode 100644 index 000000000..1a97e26dd --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdGenericParam.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveGeneric() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamAbstract.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamAbstract.cs new file mode 100644 index 000000000..c70c72334 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamAbstract.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [Command] + public void CmdCantHaveParamAbstract(AbstractClass monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamComponent.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamComponent.cs new file mode 100644 index 000000000..99821f14e --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamComponent.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [Command] + public void CmdCantHaveParamComponent(ComponentClass monkeyComp) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamNetworkConnection.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamNetworkConnection.cs new file mode 100644 index 000000000..2c266c256 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOptional(NetworkConnection monkeyCon) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOptional.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOptional.cs new file mode 100644 index 000000000..d5698aef4 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOptional.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOptional(int monkeys = 12) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOut.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOut.cs new file mode 100644 index 000000000..e933bc561 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamOut.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOut(out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamRef.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamRef.cs new file mode 100644 index 000000000..1af606d88 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdParamRef.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamRef(ref int monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdVoidReturn.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdVoidReturn.cs new file mode 100644 index 000000000..833fd7b90 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourCmdVoidReturn.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [Command] + public int CmdCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourGeneric.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourGeneric.cs new file mode 100644 index 000000000..d6b8e226a --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourGeneric.cs @@ -0,0 +1,10 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + T genericsNotAllowed; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcCoroutine.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcCoroutine.cs new file mode 100644 index 000000000..7d73fb95e --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcCoroutine.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public IEnumerator TargetRpcCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcDuplicateName.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcDuplicateName.cs new file mode 100644 index 000000000..a6543ddcf --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcDuplicateName.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveSameName(NetworkConnection monkeyCon, int abc) {} + + [TargetRpc] + public void TargetRpcCantHaveSameName(NetworkConnection monkeyCon, int abc, int def) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcGenericParam.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcGenericParam.cs new file mode 100644 index 000000000..80b1fbe5f --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcGenericParam.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveGeneric() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamAbstract.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamAbstract.cs new file mode 100644 index 000000000..1cdb89b72 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamAbstract.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [TargetRpc] + public void TargetRpcCantHaveParamAbstract(NetworkConnection monkeyCon, AbstractClass monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamComponent.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamComponent.cs new file mode 100644 index 000000000..aab2b7565 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamComponent.cs @@ -0,0 +1,17 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [TargetRpc] + public void TargetRpcCantHaveParamComponent(NetworkConnection monkeyCon, ComponentClass monkeyComp) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnection.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnection.cs new file mode 100644 index 000000000..4e141b4fc --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(NetworkConnection monkeyCon) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs new file mode 100644 index 000000000..e0db213e9 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(int abc, NetworkConnection monkeyCon) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOptional.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOptional.cs new file mode 100644 index 000000000..10e8bbf98 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOptional.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(NetworkConnection monkeyCon, int monkeys = 12) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOut.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOut.cs new file mode 100644 index 000000000..3664f43a7 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamOut.cs @@ -0,0 +1,15 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOut(NetworkConnection monkeyCon, out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamRef.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamRef.cs new file mode 100644 index 000000000..8b832f79a --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcParamRef.cs @@ -0,0 +1,12 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamRef(NetworkConnection monkeyCon, ref int monkeys) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcVoidReturn.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcVoidReturn.cs new file mode 100644 index 000000000..f59db6879 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourTargetRpcVoidReturn.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + public int TargetRpcCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourValid.cs b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourValid.cs new file mode 100644 index 000000000..02880aed0 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/NetworkBehaviourValid.cs @@ -0,0 +1,11 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar] + public int durpatron9000 = 12; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/RecursionCount.cs b/Assets/Mirror/Tests/WeaverTests~/RecursionCount.cs new file mode 100644 index 000000000..224c9e84f --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/RecursionCount.cs @@ -0,0 +1,24 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public class Potato0 + { + public int hamburgers = 17; + public Potato1 p1; + } + + public class Potato1 + { + public int hamburgers = 18; + public Potato0 p0; + } + + [SyncVar] + Potato0 recursionTime; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/ServerGuardWrongClass.cs b/Assets/Mirror/Tests/WeaverTests~/ServerGuardWrongClass.cs new file mode 100644 index 000000000..404df2716 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/ServerGuardWrongClass.cs @@ -0,0 +1,23 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer + { + // defining a SyncListStruct here will force Weaver to do work on this class + // which will then force it to check for Server / Client guards and fail + struct MyStruct + { + int potato; + float floatingpotato; + double givemetwopotatoes; + } + class MyStructClass : SyncListSTRUCT {}; + MyStructClass Foo; + + [Server] + public void CantServerGuardInThisClass() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncEventParamGeneric.cs b/Assets/Mirror/Tests/WeaverTests~/SyncEventParamGeneric.cs new file mode 100644 index 000000000..c81a675ee --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncEventParamGeneric.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public delegate void MySyncEventDelegate(T amount, float dir); + + [SyncEvent] + public event MySyncEventDelegate EventDoCoolThingsWithExcitingPeople; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncEventStartsWithEvent.cs b/Assets/Mirror/Tests/WeaverTests~/SyncEventStartsWithEvent.cs new file mode 100644 index 000000000..1f8e16ffd --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncEventStartsWithEvent.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public delegate void MySyncEventDelegate(int amount, float dir); + + [SyncEvent] + public event MySyncEventDelegate DoCoolThingsWithExcitingPeople; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncEventValid.cs b/Assets/Mirror/Tests/WeaverTests~/SyncEventValid.cs new file mode 100644 index 000000000..2c3d1cd9f --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncEventValid.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public delegate void MySyncEventDelegate(); + + [SyncEvent] + public event MySyncEventDelegate EventDoCoolThingsWithExcitingPeople; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListMissingParamlessCtor.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListMissingParamlessCtor.cs new file mode 100644 index 000000000..4847e5860 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListMissingParamlessCtor.cs @@ -0,0 +1,17 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public class SyncListString2 : SyncList + { + public SyncListString2(int phooey) {} + protected override void SerializeItem(NetworkWriter w, string item) {} + protected override string DeserializeItem(NetworkReader r) => ""; + } + + public SyncListString2 Foo; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListStructGenericGeneric.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListStructGenericGeneric.cs new file mode 100644 index 000000000..7c5dc988b --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListStructGenericGeneric.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + struct MyPODStruct + { + float floatingpotato; + } + + struct MyGenericStruct + { + T genericpotato; + } + + struct MyStruct + { + MyGenericStruct potato; + } + + class MyStructClass : SyncListSTRUCT> {}; + + MyStructClass harpseals; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberBasicType.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberBasicType.cs new file mode 100644 index 000000000..2fb4815ac --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberBasicType.cs @@ -0,0 +1,30 @@ +using System; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + class NonBasicPotato + { + int potato; + } + + struct MyStruct + { + public object nonbasicpotato; + } + + class MyStructClass : SyncListSTRUCT + { + int potatoCount; + public MyStructClass(int numberOfPotatoes) + { + potatoCount = numberOfPotatoes; + } + }; + + MyStructClass harpseals; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberGeneric.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberGeneric.cs new file mode 100644 index 000000000..37b555944 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberGeneric.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + struct MyPODStruct + { + float floatingpotato; + } + + struct MyGenericStruct + { + T genericpotato; + } + + struct MyStruct + { + public MyGenericStruct potato; + } + + class MyStructClass : SyncListSTRUCT {}; + + MyStructClass harpseals; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberInterface.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberInterface.cs new file mode 100644 index 000000000..34b7ee69a --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListStructMemberInterface.cs @@ -0,0 +1,22 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + interface IPotato + { + void Bake(); + } + + struct MyStruct + { + public IPotato potato; + } + + class MyStructClass : SyncListSTRUCT {}; + + MyStructClass harpseals; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListStructValid.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListStructValid.cs new file mode 100644 index 000000000..57af48eb6 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListStructValid.cs @@ -0,0 +1,17 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + struct MyStruct + { + int potato; + float floatingpotato; + double givemetwopotatoes; + } + class MyStructClass : SyncListSTRUCT {}; + MyStructClass Foo; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncListValid.cs b/Assets/Mirror/Tests/WeaverTests~/SyncListValid.cs new file mode 100644 index 000000000..7ce3b83f2 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncListValid.cs @@ -0,0 +1,10 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + public SyncListInt Foo; + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsCantBeArray.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsCantBeArray.cs new file mode 100644 index 000000000..675401d50 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsCantBeArray.cs @@ -0,0 +1,23 @@ +using UnityEngine; +using Mirror; +using System.Collections.Generic; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar] + int health; + + [SyncVar] + int[] thisShouldntWork = new int[100]; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedNetworkBehaviour.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedNetworkBehaviour.cs new file mode 100644 index 000000000..23da7feaf --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedNetworkBehaviour.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + class MySyncVar : NetworkBehaviour + { + int abc = 123; + } + [SyncVar] + MySyncVar invalidVar = new MySyncVar(); + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedScriptableObject.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedScriptableObject.cs new file mode 100644 index 000000000..2701c63ee --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDerivedScriptableObject.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + class MySyncVar : ScriptableObject + { + int abc = 123; + } + [SyncVar] + MySyncVar invalidVar = new MySyncVar(); + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsDifferentModule.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDifferentModule.cs new file mode 100644 index 000000000..757f44fca --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsDifferentModule.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + [SyncVar] + TextMesh invalidVar; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsGenericParam.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsGenericParam.cs new file mode 100644 index 000000000..a15d68458 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsGenericParam.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + class MySyncVar + { + T abc; + } + [SyncVar] + MySyncVar invalidVar = new MySyncVar(); + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsInterface.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsInterface.cs new file mode 100644 index 000000000..c48c7e27c --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsInterface.cs @@ -0,0 +1,31 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + interface MySyncVar + { + void interfaceMethod(); + } + [SyncVar] + MySyncVar invalidVar; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsMoreThan63.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsMoreThan63.cs new file mode 100644 index 000000000..4380264f3 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsMoreThan63.cs @@ -0,0 +1,88 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + [SyncVar] int var2; + [SyncVar] int var3; + [SyncVar] int var4; + [SyncVar] int var5; + [SyncVar] int var6; + [SyncVar] int var7; + [SyncVar] int var8; + [SyncVar] int var9; + [SyncVar] int var10; + [SyncVar] int var11; + [SyncVar] int var12; + [SyncVar] int var13; + [SyncVar] int var14; + [SyncVar] int var15; + [SyncVar] int var16; + [SyncVar] int var17; + [SyncVar] int var18; + [SyncVar] int var19; + [SyncVar] int var20; + [SyncVar] int var21; + [SyncVar] int var22; + [SyncVar] int var23; + [SyncVar] int var24; + [SyncVar] int var25; + [SyncVar] int var26; + [SyncVar] int var27; + [SyncVar] int var28; + [SyncVar] int var29; + [SyncVar] int var30; + [SyncVar] int var31; + [SyncVar] int var32; + [SyncVar] int var33; + [SyncVar] int var34; + [SyncVar] int var35; + [SyncVar] int var36; + [SyncVar] int var37; + [SyncVar] int var38; + [SyncVar] int var39; + [SyncVar] int var40; + [SyncVar] int var41; + [SyncVar] int var42; + [SyncVar] int var43; + [SyncVar] int var44; + [SyncVar] int var45; + [SyncVar] int var46; + [SyncVar] int var47; + [SyncVar] int var48; + [SyncVar] int var49; + [SyncVar] int var50; + [SyncVar] int var51; + [SyncVar] int var52; + [SyncVar] int var53; + [SyncVar] int var54; + [SyncVar] int var55; + [SyncVar] int var56; + [SyncVar] int var57; + [SyncVar] int var58; + [SyncVar] int var59; + [SyncVar] int var60; + [SyncVar] int var61; + [SyncVar] int var62; + [SyncVar] int var63; + [SyncVar] int var64; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHook.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHook.cs new file mode 100644 index 000000000..702838673 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHook.cs @@ -0,0 +1,19 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook = "OnChangeHealth")] + int health; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHookParams.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHookParams.cs new file mode 100644 index 000000000..2e26ec63b --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsNoHookParams.cs @@ -0,0 +1,24 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook = "OnChangeHealth")] + int health; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth() + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsStatic.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsStatic.cs new file mode 100644 index 000000000..5948ec26a --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsStatic.cs @@ -0,0 +1,27 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + [SyncVar] + static int invalidVar = 123; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsSyncList.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsSyncList.cs new file mode 100644 index 000000000..6bb309ae0 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsSyncList.cs @@ -0,0 +1,40 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + public class SyncObjImplementer : SyncObject + { + public bool IsDirty { get; } + public void Flush() {} + public void OnSerializeAll(NetworkWriter writer) {} + public void OnSerializeDelta(NetworkWriter writer) {} + public void OnDeserializeAll(NetworkReader reader) {} + public void OnDeserializeDelta(NetworkReader reader) {} + } + + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook = "OnChangeHealth")] + int health; + + [SyncVar] + SyncObjImplementer syncobj; + + [SyncVar] + SyncListInt syncints; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsTooManyHookParams.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsTooManyHookParams.cs new file mode 100644 index 000000000..f55ca48b7 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsTooManyHookParams.cs @@ -0,0 +1,24 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook = "OnChangeHealth")] + int health; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health, int extraFunParam) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsValid.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsValid.cs new file mode 100644 index 000000000..aec7595db --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsValid.cs @@ -0,0 +1,87 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook="OnChangeHealth")] + int health; + + [SyncVar] int var2; + [SyncVar] int var3; + [SyncVar] int var4; + [SyncVar] int var5; + [SyncVar] int var6; + [SyncVar] int var7; + [SyncVar] int var8; + [SyncVar] int var9; + [SyncVar] int var10; + [SyncVar] int var11; + [SyncVar] int var12; + [SyncVar] int var13; + [SyncVar] int var14; + [SyncVar] int var15; + [SyncVar] int var16; + [SyncVar] int var17; + [SyncVar] int var18; + [SyncVar] int var19; + [SyncVar] int var20; + [SyncVar] int var21; + [SyncVar] int var22; + [SyncVar] int var23; + [SyncVar] int var24; + [SyncVar] int var25; + [SyncVar] int var26; + [SyncVar] int var27; + [SyncVar] int var28; + [SyncVar] int var29; + [SyncVar] int var30; + [SyncVar] int var31; + [SyncVar] int var32; + [SyncVar] int var33; + [SyncVar] int var34; + [SyncVar] int var35; + [SyncVar] int var36; + [SyncVar] int var37; + [SyncVar] int var38; + [SyncVar] int var39; + [SyncVar] int var40; + [SyncVar] int var41; + [SyncVar] int var42; + [SyncVar] int var43; + [SyncVar] int var44; + [SyncVar] int var45; + [SyncVar] int var46; + [SyncVar] int var47; + [SyncVar] int var48; + [SyncVar] int var49; + [SyncVar] int var50; + [SyncVar] int var51; + [SyncVar] int var52; + [SyncVar] int var53; + [SyncVar] int var54; + [SyncVar] int var55; + [SyncVar] int var56; + [SyncVar] int var57; + [SyncVar] int var58; + [SyncVar] int var59; + [SyncVar] int var60; + [SyncVar] int var61; + [SyncVar] int var62; + [SyncVar] int var63; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/SyncVarsWrongHookType.cs b/Assets/Mirror/Tests/WeaverTests~/SyncVarsWrongHookType.cs new file mode 100644 index 000000000..d197c20a1 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/SyncVarsWrongHookType.cs @@ -0,0 +1,24 @@ +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar(hook = "OnChangeHealth")] + int health; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(bool health) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/TargetRpcCantBeStatic.cs b/Assets/Mirror/Tests/WeaverTests~/TargetRpcCantBeStatic.cs new file mode 100644 index 000000000..3520cba06 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/TargetRpcCantBeStatic.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + private static void TargetCantBeStatic(NetworkConnection nc) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionMissing.cs b/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionMissing.cs new file mode 100644 index 000000000..3fd679339 --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionMissing.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + private void TargetRpcMethod() {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionNotFirst.cs b/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionNotFirst.cs new file mode 100644 index 000000000..8c12090fb --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/TargetRpcNetworkConnectionNotFirst.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + private void TargetRpcMethod(int potatoesRcool, NetworkConnection nc) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/TargetRpcStartsWithTarget.cs b/Assets/Mirror/Tests/WeaverTests~/TargetRpcStartsWithTarget.cs new file mode 100644 index 000000000..b9b24118c --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/TargetRpcStartsWithTarget.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + private void DoesntStartWithTarget(NetworkConnection nc) {} + } +} diff --git a/Assets/Mirror/Tests/WeaverTests~/TargetRpcValid.cs b/Assets/Mirror/Tests/WeaverTests~/TargetRpcValid.cs new file mode 100644 index 000000000..3f80ece1d --- /dev/null +++ b/Assets/Mirror/Tests/WeaverTests~/TargetRpcValid.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections; +using UnityEngine; +using Mirror; + +namespace MirrorTest +{ + class MirrorTestPlayer : NetworkBehaviour + { + [TargetRpc] + private void TargetThatIsTotallyValid(NetworkConnection nc) {} + } +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f59f249e9..d7eaaf71c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -83,6 +83,7 @@ We can always use more test coverage. * **Provide a link to the related issue** if the pull request is a follow up of an existing bug report or enhancement suggestion. * **Comment why this pull request represents an enhancement** and give a rationale explaining why you did it that way and not another way. * **Use the same coding style as the one used in this project**. +* **Documentation:** If your PR adds or changes any public properties or methods, you must retain the old versions preceded with `[Obsolete("Describe what to do / use instead")` attribute wherever possbile, and you must update any relevant pages in the /docs folder. It's not done until it's documented! * **Welcome suggestions from the maintainers to improve your pull request**. Please follow our coding conventions (below) and make sure all of your commits are atomic (one feature per commit). Rebase your pull requests if necessary. @@ -119,9 +120,12 @@ Start reading our code and you'll get the hang of it. We optimize for readabilit Fields and methods in a class are private by default, no need to use the private keyword there. * This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. -**One Python Way** +**The Python Way** + Unlike Python, C# has different ways to do the same thing, which causes endless discussions and pull requests to change from one way to the other. Let's always use the most simple, most obvious way: * **type** vs. **var**: always use 'int x' instead of 'var x'. Less guess work. Less magic. If we **always** use the proper type then we have to waste no brain cycles on unnecessary decision making. * **if** vs. **switch**: any if statement could be converted to switch and back. Again, let's not have endless discussions and use if unless _switch_ makes overwhelmingly much sense. Python doesn't have switch either, they don't have those discussions and pull requests over there. - + * **int** vs. **Int32**: use int instead of Int32, double instead of Double, string instead of String and so on. We won't convert all ints to Int32, so it makes most sense to never use Int32 anywhere and avoid time wasting discussions. + * **Empty Class Bodies ({} vs. { })**: please use 'class MyMessage : EmptyMessage {}' instead of 'class MyMessage : EmptyMessage { }'. For the same reason that we use no white space inbetween parameterless function defintions like void Start() vs. void Start( ). + Thanks. diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index f8cd230af..ea445309b 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -63,6 +63,8 @@ PlayerSettings: use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 + androidStartInFullscreen: 1 + androidRenderOutsideSafeArea: 0 androidBlitType: 0 defaultIsNativeResolution: 1 macRetinaSupport: 1 @@ -96,9 +98,6 @@ PlayerSettings: xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 - n3dsDisableStereoscopicView: 0 - n3dsEnableSharedListOpt: 1 - n3dsEnableVSync: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 @@ -107,11 +106,12 @@ PlayerSettings: xboxOneDisableEsram: 0 xboxOnePresentImmediateThreshold: 0 switchQueueCommandMemory: 0 - videoMemoryForVertexBuffers: 0 - psp2PowerMode: 0 - psp2AcquireBGM: 1 + switchQueueControlMemory: 16384 + switchQueueComputeMemory: 262144 + switchNVNShaderPoolsGranularity: 33554432 + switchNVNDefaultPoolsGranularity: 16777216 + switchNVNOtherPoolsGranularity: 16777216 vulkanEnableSetSRGBWrite: 0 - vulkanUseSWCommandBuffers: 0 m_SupportedAspectRatios: 4:3: 1 5:4: 1 @@ -145,6 +145,7 @@ PlayerSettings: dashSupport: 0 enable360StereoCapture: 0 protectGraphicsMemory: 0 + enableFrameTimingStats: 0 useHDRDisplay: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 @@ -169,7 +170,7 @@ PlayerSettings: StripUnusedMeshComponents: 0 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 - iOSTargetOSVersionString: 8.0 + iOSTargetOSVersionString: 9.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 tvOSTargetOSVersionString: 9.0 @@ -235,7 +236,6 @@ PlayerSettings: appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 appleEnableProMotion: 0 - vulkanEditorSupport: 0 clonedFromGUID: 00000000000000000000000000000000 templatePackageId: templateDefaultScene: @@ -393,6 +393,7 @@ PlayerSettings: switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 switchUserAccountLockEnabled: 0 + switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 3 switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 @@ -453,6 +454,7 @@ PlayerSettings: ps4pnGameCustomData: 1 playerPrefsSupport: 0 enableApplicationExit: 0 + resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 @@ -476,53 +478,6 @@ PlayerSettings: ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] monoEnv: - psp2Splashimage: {fileID: 0} - psp2NPTrophyPackPath: - psp2NPSupportGBMorGJP: 0 - psp2NPAgeRating: 12 - psp2NPTitleDatPath: - psp2NPCommsID: - psp2NPCommunicationsID: - psp2NPCommsPassphrase: - psp2NPCommsSig: - psp2ParamSfxPath: - psp2ManualPath: - psp2LiveAreaGatePath: - psp2LiveAreaBackroundPath: - psp2LiveAreaPath: - psp2LiveAreaTrialPath: - psp2PatchChangeInfoPath: - psp2PatchOriginalPackage: - psp2PackagePassword: WRK5RhRXdCdG5nG5azdNMK66MuCV6GXi - psp2KeystoneFile: - psp2MemoryExpansionMode: 0 - psp2DRMType: 0 - psp2StorageType: 0 - psp2MediaCapacity: 0 - psp2DLCConfigPath: - psp2ThumbnailPath: - psp2BackgroundPath: - psp2SoundPath: - psp2TrophyCommId: - psp2TrophyPackagePath: - psp2PackagedResourcesPath: - psp2SaveDataQuota: 10240 - psp2ParentalLevel: 1 - psp2ShortTitle: Not Set - psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF - psp2Category: 0 - psp2MasterVersion: 01.00 - psp2AppVersion: 01.00 - psp2TVBootMode: 0 - psp2EnterButtonAssignment: 2 - psp2TVDisableEmu: 0 - psp2AllowTwitterDialog: 1 - psp2Upgradable: 0 - psp2HealthWarning: 0 - psp2UseLibLocation: 0 - psp2InfoBarOnStartup: 0 - psp2InfoBarColor: 0 - psp2ScriptOptimizationLevel: 0 splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} spritePackerPolicy: @@ -538,10 +493,13 @@ PlayerSettings: webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 webGLLinkerTarget: 1 - scriptingDefineSymbols: {} + webGLThreadsSupport: 0 + scriptingDefineSymbols: + 1: MIRROR;MIRROR_1726_OR_NEWER platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} + managedStrippingLevel: {} incrementalIl2cppBuild: {} allowUnsafeCode: 0 additionalIl2CppArgs: @@ -564,6 +522,8 @@ PlayerSettings: metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 + metroSupportStreamingInstall: 0 + metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 2 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} @@ -571,21 +531,11 @@ PlayerSettings: a: 1} metroSplashScreenUseBackgroundColor: 0 platformCapabilities: {} + metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: metroCompilationOverrides: 1 - n3dsUseExtSaveData: 0 - n3dsCompressStaticMem: 1 - n3dsExtSaveDataNumber: 0x12345 - n3dsStackSize: 131072 - n3dsTargetPlatform: 2 - n3dsRegion: 7 - n3dsMediaSize: 0 - n3dsLogoStyle: 3 - n3dsTitle: GameName - n3dsProductCode: - n3dsApplicationId: 0xFF3FF XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: @@ -611,16 +561,36 @@ PlayerSettings: XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 xboxOneScriptCompiler: 0 + XboxOneOverrideIdentityName: vrEditorSettings: daydream: daydreamIconForeground: {fileID: 0} daydreamIconBackground: {fileID: 0} cloudServicesEnabled: {} + luminIcon: + m_Name: + m_ModelFolderPath: + m_PortalFolderPath: + luminCert: + m_CertPath: + m_PrivateKeyPath: + luminIsChannelApp: 0 + luminVersion: + m_VersionCode: 1 + m_VersionName: facebookSdkVersion: 7.9.4 + facebookAppId: + facebookCookies: 1 + facebookLogging: 1 + facebookStatus: 1 + facebookXfbml: 0 + facebookFrictionlessRequests: 1 apiCompatibilityLevel: 3 cloudProjectId: + framebufferDepthMemorylessMode: 0 projectName: organizationId: cloudEnabled: 0 enableNativePlatformBackendsForNewInputSystem: 0 disableOldInputManagerSupport: 0 + legacyClampBlendShapeWeights: 1 diff --git a/ProjectSettings/UnityConnectSettings.asset b/ProjectSettings/UnityConnectSettings.asset index 1be460051..c3ae9a020 100644 --- a/ProjectSettings/UnityConnectSettings.asset +++ b/ProjectSettings/UnityConnectSettings.asset @@ -3,25 +3,25 @@ --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 + serializedVersion: 1 m_Enabled: 1 m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: + m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events + m_EventUrl: https://cdp.cloud.unity3d.com/v1/events + m_ConfigUrl: https://config.uca.cloud.unity3d.com m_TestInitMode: 0 CrashReportingSettings: - m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes - m_NativeEventUrl: https://perf-events.cloud.unity3d.com/symbolicate + m_EventUrl: https://perf-events.cloud.unity3d.com m_Enabled: 0 + m_LogBufferSize: 10 m_CaptureEditorExceptions: 1 UnityPurchasingSettings: m_Enabled: 0 m_TestMode: 0 UnityAnalyticsSettings: m_Enabled: 0 - m_InitializeOnStartup: 1 m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: + m_InitializeOnStartup: 1 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 diff --git a/README.md b/README.md index 45691df8b..7ab2701d5 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,16 @@ -![Mirror Logo](https://i.imgur.com/5dUNWxl.png) +![Mirror Logo](https://i.imgur.com/aZRKBeh.png) [![Download](https://img.shields.io/badge/asset_store-brightgreen.svg)](https://www.assetstore.unity3d.com/#!/content/129321) [![Documentation](https://img.shields.io/badge/documentation-brightgreen.svg)](https://vis2k.github.io/Mirror/) [![Forum](https://img.shields.io/badge/forum-brightgreen.svg)](https://forum.unity.com/threads/mirror-networking-for-unity-aka-hlapi-community-edition.425437/) -[![donate](https://img.shields.io/badge/donations-brightgreen.svg)](https://www.patreon.com/MirrorTelepathy) +[![donate](https://img.shields.io/badge/donations-brightgreen.svg)](https://www.patreon.com/MirrorNetworking) [![Build status](https://img.shields.io/appveyor/ci/vis2k73562/hlapi-community-edition/Mirror.svg)](https://ci.appveyor.com/project/vis2k73562/hlapi-community-edition/branch/mirror) -[![AppVeyor tests branch](https://img.shields.io/appveyor/tests/vis2k73562/hlapi-community-edition/Mirror.svg)](https://ci.appveyor.com/project/vis2k73562/hlapi-community-edition/branch/mirror/tests) [![Discord](https://img.shields.io/discord/343440455738064897.svg)](https://discordapp.com/invite/N9QVxbM) -[![Codecov](https://codecov.io/gh/vis2k/mirror/branch/mirror/graph/badge.svg)](https://codecov.io/gh/vis2k/mirror/branch/mirror) [![release](https://img.shields.io/github/release/vis2k/Mirror.svg)](https://github.com/vis2k/Mirror/releases/latest) Mirror is a **high level** Networking API for Unity, built on top of the **low level** [Telepathy](https://github.com/vis2k/Telepathy) library. -Mirror is built [and tested](https://docs.google.com/document/d/e/2PACX-1vQqf_iqOLlBRTUqqyor_OUx_rHlYx-SYvZWMvHGuLIuRuxJ-qX3s8JzrrBB5vxDdGfl-HhYZW3g5lLW/pub#h.h4wha2mpetsc) for **MMO Scale** Networking by the developers of [uMMORPG](https://www.assetstore.unity3d.com/#!/content/51212), [uSurvival](https://www.assetstore.unity3d.com/#!/content/95015) and [Cubica](https://cubica.net). +Mirror is built [and tested](https://www.youtube.com/watch?v=mDCNff1S9ZU) for **MMO Scale** Networking by the developers of [uMMORPG](https://www.assetstore.unity3d.com/#!/content/51212), [uSurvival](https://www.assetstore.unity3d.com/#!/content/95015) and [Cubica](https://cubica.net). Mirror is optimized for **ease of use** and **probability of success**. Projects that use Mirror are small, concise and maintainable. uMMORPG was possible with <6000 lines of code. We needed a networking library that allows us to [launch our games](https://ummorpg.net/showcase/), period. @@ -28,47 +26,45 @@ _Note: Mirror is based on Unity's abandoned UNET Networking system. We fixed it ## Documentation Check out our [Documentation](https://vis2k.github.io/Mirror/). -The main difference is that you have to use `using Mirror;` instead of `using UnityEngine.Networking;` at the top of your scripts. +If you are migrating from UNET, then please check out our [Migration Guide](https://vis2k.github.io/Mirror/General/Migration). Don't panic, it's very easy and won't take more than 5 minutes. -_Oh, and you won't have to worry about channels, low level networking, [packet loss](https://forum.unity.com/threads/unet-deprecation-thread.543501/page-3#post-3597869), [lack of support](https://forum.unity.com/threads/is-hlapi-dead.517436/) or [bugs](https://issuetracker.unity3d.com/issues/unet-networkwriter-dot-write-causing-readstring-slash-readbytes-out-of-range-errors-in-clients) ever again. Mirror just works._ +## Installation +We **recommend** to download the most **stable Mirror version** from the [Asset Store](https://www.assetstore.unity3d.com/#!/content/129321)! -## Usage Guide +For the latest improvements, [Download Mirror](https://github.com/vis2k/Mirror/releases) directly from this repository and extract it in your Assets folder. -**If you are coming from a current UNET implementation or are seeking the stable version:** +## Examples +We included several smaller example projects in Mirror. -Import mirror from the [Asset Store](https://www.assetstore.unity3d.com/#!/content/129321) into your project. +For a fully polished, complete project example, consider [uMMORPG](https://www.assetstore.unity3d.com/#!/content/51212) or [uSurvival](https://www.assetstore.unity3d.com/#!/content/95015). -**Alternatively, you can install new releases manually:** +## Transports +Mirror supports many different low level networking transports: -*Note: New releases are bleeding edge and may come with undiscovered bugs. Use at your own risk!* - -1. [Download Mirror](https://github.com/vis2k/Mirror/releases) (for Unity 2018.2.20 or 2018.3.x). -2. Decompress the zip file in Assets - -## Migration Guide -If you are still using UNET and want to switch to Mirror, you should check out our [Migration Guide](https://vis2k.github.io/Mirror/General/Migration). Don't panic, it's very easy and won't take more than 5 minutes. - -## Example Projects -Download Mirror from the [Asset Store](https://www.assetstore.unity3d.com/#!/content/129321), we have several small example projects included. - -For a fully polished complete project example, consider [uMMORPG](https://www.assetstore.unity3d.com/#!/content/51212) or [uSurvival](https://www.assetstore.unity3d.com/#!/content/95015). - -## Community Transports -If you don't want to use Telepathy or UNET's LLAPI as low level transport, then check out: -* https://github.com/FizzCube/SteamNetNetworkTransport (SteamNetwork) +* (built in) https://github.com/vis2k/Telepathy (Telepathy) +* (built in) Unity's LLAPI +* (built in) https://github.com/ninjasource/Ninja.WebSockets (Websockets) +* https://github.com/Raystorms/FizzySteamyMirror/ (SteamNetwork) * https://github.com/SoftwareGuy/Ignorance/ (ENet) +* https://github.com/MichalPetryka/LiteNetLib4Mirror (LiteNetLib) -## Donations -Mirror is developed by volunteers. If you like what we are doing, consider leaving [a small donation](https://www.patreon.com/MirrorTelepathy). +## Donations & Priority Support +Please support [Mirror on Patreon](https://www.patreon.com/MirrorNetworking). Priority support included! ## Benchmarks * Telepathy [1000 connections](https://github.com/vis2k/Telepathy) test * [uMMORPG 480 CCU worst case test](https://youtu.be/mDCNff1S9ZU) (everyone broadcasting to everyone else) * [uSurvival 122 CCU worst case test](https://docs.google.com/document/d/e/2PACX-1vT28FcGXYlbG8gwi8DhD914n7K-wCAE8qhfetPkSli96ikc1Td3zJO1IiwVhfPVtKUHF0l3N7ZkM5GU/pub#h.pwbvffnwcewe) -## Contributing -If you would like to contribute, feel free to [submit pull requests](https://vis2k.github.io/Mirror/General/Contributions) and visit our [Discord Server](https://discordapp.com/invite/N9QVxbM). +## The Mirror Mantra +> _“Would you tell me, please, which way I ought to go from here?”_
+> _“That depends a good deal on where you want to get to,"_ said the Cat.
+> _"I don’t much care where—“_ said Alice.
+> _“Then it doesn’t matter which way you go,”_ said the Cat.
+        - Alice in Wonderland _(Lewis Carroll)_ -We follow the [KISS](https://en.wikipedia.org/wiki/KISS_principle) principle, so make sure that your Pull Requests contain no magic. +As Indie Developers, **our #1 goal** for Mirror is **Ease of Use**. -We need Mirror to be MMO Scale. Bug fixes are always highly appreciated. New features will be considered very carefully. +If you want to contribute, always follow the [KISS](https://en.wikipedia.org/wiki/KISS_principle) principle. Everything needs to be **as simple as possible**. As result, the code will be stable, easy to understand, easy to modify and easy to maintain 5 years from now when our games are still running. + +Pull Requests for bug fixes are always highly appreciated. New features will be considered very carefully and will only be merged if they are the most simple solution to the given problem. diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.dll b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.dll deleted file mode 100644 index 9e77dcfe0..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.dll and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.pdb b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.pdb deleted file mode 100644 index 8d0fa22d6..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Mdb.pdb and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.dll b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.dll deleted file mode 100644 index 02d386dbe..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.dll and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.pdb b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.pdb deleted file mode 100644 index 9927515c0..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Pdb.pdb and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.dll b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.dll deleted file mode 100644 index 3003d9bcb..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.dll and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.pdb b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.pdb deleted file mode 100644 index 4ed07a9cb..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.Rocks.pdb and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.dll b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.dll deleted file mode 100644 index 5052f8dee..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.dll and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.pdb b/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.pdb deleted file mode 100644 index ed67846ef..000000000 Binary files a/Weaver/External/Unity.Cecil/lib/net35/Unity.Cecil.pdb and /dev/null differ diff --git a/Weaver/External/Unity.Cecil/version.txt b/Weaver/External/Unity.Cecil/version.txt deleted file mode 100644 index 718f5d612..000000000 --- a/Weaver/External/Unity.Cecil/version.txt +++ /dev/null @@ -1,3 +0,0 @@ -Repository: https://github.com/Unity-Technologies/cecil.git -Branch: * unity-master -Commit: b28d33c7da63de8162b41338f4d408b77a59f4c9 \ No newline at end of file diff --git a/Weaver/Networking.sln b/Weaver/Networking.sln deleted file mode 100644 index 355f3ab96..000000000 --- a/Weaver/Networking.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mirror.Weaver", "Weaver\Mirror.Weaver.csproj", "{709222FD-15C2-497D-8B31-366ADCC074CD}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - Testing|Any CPU = Testing|Any CPU - Debug-Editor|Any CPU = Debug-Editor|Any CPU - Release-Editor|Any CPU = Release-Editor|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {709222FD-15C2-497D-8B31-366ADCC074CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {709222FD-15C2-497D-8B31-366ADCC074CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {709222FD-15C2-497D-8B31-366ADCC074CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {709222FD-15C2-497D-8B31-366ADCC074CD}.Release|Any CPU.Build.0 = Release|Any CPU - {709222FD-15C2-497D-8B31-366ADCC074CD}.Testing|Any CPU.ActiveCfg = Release|Any CPU - {709222FD-15C2-497D-8B31-366ADCC074CD}.Testing|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/Weaver/Weaver/CompilationFinishedHook.cs b/Weaver/Weaver/CompilationFinishedHook.cs deleted file mode 100644 index 785d32910..000000000 --- a/Weaver/Weaver/CompilationFinishedHook.cs +++ /dev/null @@ -1,113 +0,0 @@ -// https://docs.unity3d.com/Manual/RunningEditorCodeOnLaunch.html -using System.IO; -using Mono.Cecil; -using UnityEngine; -using UnityEditor; -using UnityEditor.Compilation; -using System; -using System.Linq; - -namespace Mirror.Weaver -{ - // InitializeOnLoad is needed for Unity to call the static constructor on load - [InitializeOnLoad] - public class CompilationFinishedHook - { - static CompilationFinishedHook() - { - // assemblyPath: Library/ScriptAssemblies/Assembly-CSharp.dll/ - // assemblyPath: Library/ScriptAssemblies/Assembly-CSharp-Editor.dll - CompilationPipeline.assemblyCompilationFinished += (assemblyPath, messages) => - { - // if user scripts can't be compiled because of errors, - // assemblyCompilationFinished is still called but assemblyPath - // file won't exist. in that case, do nothing. - if (!File.Exists(assemblyPath)) - { - Console.WriteLine("Weaving skipped because assembly doesnt exist: " + assemblyPath); - return; - } - - string assemblyName = Path.GetFileName(assemblyPath); - - if (assemblyName == "Telepathy.dll" || assemblyName == "Mirror.dll" || assemblyName == "Mirror.Weaver.dll") - { - // don't weave mirror files - return; - } - - // UnityEngineCoreModule.DLL path: - string unityEngineCoreModuleDLL = UnityEditorInternal.InternalEditorUtility.GetEngineCoreModuleAssemblyPath(); - - // outputDirectory is the directory of assemblyPath - string outputDirectory = Path.GetDirectoryName(assemblyPath); - - string mirrorRuntimeDll = FindMirrorRuntime(); - 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; - } - - // unity calls it for Library/ScriptAssemblies/Assembly-CSharp-Editor.dll too, but we don't want to (and can't) weave this one - bool buildingForEditor = assemblyPath.EndsWith("Editor.dll"); - if (!buildingForEditor) - { - Console.WriteLine("Weaving: " + assemblyPath); - // assemblyResolver: unity uses this by default: - // ICompilationExtension compilationExtension = GetCompilationExtension(); - // IAssemblyResolver assemblyResolver = compilationExtension.GetAssemblyResolver(editor, file, null); - // but Weaver creates it's own if null, which is this one: - IAssemblyResolver assemblyResolver = new DefaultAssemblyResolver(); - if (Program.Process(unityEngineCoreModuleDLL, mirrorRuntimeDll, outputDirectory, new string[] { assemblyPath }, GetExtraAssemblyPaths(assemblyPath), assemblyResolver, Debug.LogWarning, Debug.LogError)) - { - Console.WriteLine("Weaving succeeded for: " + assemblyPath); - } - else - { - Debug.LogError("Weaving failed for: " + assemblyPath); - } - } - }; - } - - // Weaver needs the path for all the extra DLLs like UnityEngine.UI. - // otherwise if a script that is being weaved (like a NetworkBehaviour) - // uses UnityEngine.UI, then the Weaver won't be able to resolve it and - // throw an error. - // (the paths can be found by logging the extraAssemblyPaths in the - // original Weaver.Program.Process function.) - static string[] GetExtraAssemblyPaths(string assemblyPath) - { - Assembly[] assemblies = CompilationPipeline.GetAssemblies(); - - foreach (Assembly assembly in assemblies) - { - if (assembly.outputPath == assemblyPath) - { - return assembly.compiledAssemblyReferences.Select(Path.GetDirectoryName).ToArray(); - } - } - - Debug.LogWarning("Unable to find configuration for assembly " + assemblyPath); - return new string[] { }; - } - - static string FindMirrorRuntime() - { - Assembly[] assemblies = CompilationPipeline.GetAssemblies(); - - foreach (Assembly assembly in assemblies) - { - if (assembly.name == "Mirror") - { - return assembly.outputPath; - } - } - return ""; - } - } -} \ No newline at end of file diff --git a/Weaver/Weaver/Mirror.Weaver.csproj b/Weaver/Weaver/Mirror.Weaver.csproj deleted file mode 100644 index c48cf1cd2..000000000 --- a/Weaver/Weaver/Mirror.Weaver.csproj +++ /dev/null @@ -1,81 +0,0 @@ - - - - Debug - AnyCPU - {709222FD-15C2-497D-8B31-366ADCC074CD} - Library - Mirror.Weaver - Mirror.Weaver - v3.5 - - - True - full - False - bin\Debug - DEBUG; - prompt - 4 - true - 4 - - - none - True - bin\Release - prompt - 4 - true - - - - - - ..\External\Unity.Cecil\lib\net35\Unity.Cecil.dll - - - ..\External\Unity.Cecil\lib\net35\Unity.Cecil.Mdb.dll - - - ..\External\Unity.Cecil\lib\net35\Unity.Cecil.Pdb.dll - - - ..\lib\UnityEditor.dll - - - ..\lib\UnityEngine.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Weaver/lib/UnityEditor.dll b/Weaver/lib/UnityEditor.dll deleted file mode 100644 index 50e1c113f..000000000 Binary files a/Weaver/lib/UnityEditor.dll and /dev/null differ diff --git a/Weaver/lib/UnityEditor.dll.mdb b/Weaver/lib/UnityEditor.dll.mdb deleted file mode 100644 index 487ce6041..000000000 Binary files a/Weaver/lib/UnityEditor.dll.mdb and /dev/null differ diff --git a/Weaver/lib/UnityEngine.dll b/Weaver/lib/UnityEngine.dll deleted file mode 100644 index 53b97ee9d..000000000 Binary files a/Weaver/lib/UnityEngine.dll and /dev/null differ diff --git a/Weaver/lib/UnityEngine.dll.mdb b/Weaver/lib/UnityEngine.dll.mdb deleted file mode 100644 index 1408bf20e..000000000 Binary files a/Weaver/lib/UnityEngine.dll.mdb and /dev/null differ diff --git a/appveyor.yml b/appveyor.yml index 4ebb03a35..6185b3f8f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -33,14 +33,8 @@ artifacts: image: Visual Studio 2017 +before_deploy: + - npm install -g semantic-release@15 -deploy: - description: 'This is a bleeding edge release. BACKUP BEFORE USING THIS AUTOMATED BUILD\n$(APPVEYOR_REPO_COMMIT_MESSAGE)' - provider: GitHub - auth_token: - secure: G42snf8s5fEos7UfoRSW6eW+8rqGGMH3iw1ZCjRUjcUSKupHUfrKJMXQ11wbjRuW # your encrypted token from GitHub - artifact: Mirror.zip # upload all NuGet packages to release assets - draft: false - prerelease: false - on: - branch: master \ No newline at end of file +deploy_script: + - semantic-release diff --git a/docs/Components/NetworkAddressAndPortSettings.png b/docs/Components/NetworkAddressAndPortSettings.png index 918fed3c9..9900aca15 100644 Binary files a/docs/Components/NetworkAddressAndPortSettings.png and b/docs/Components/NetworkAddressAndPortSettings.png differ diff --git a/docs/Components/NetworkManager.md b/docs/Components/NetworkManager.md index 0bbac6b82..f617ab8c7 100644 --- a/docs/Components/NetworkManager.md +++ b/docs/Components/NetworkManager.md @@ -1,6 +1,6 @@ # NetworkManager -The Network Manager is a component for managing the networking aspects of a multiplayer game. +The Network Manager is a component for managing the networking aspects of a multiplayer game. The Network Manager features include: @@ -18,7 +18,9 @@ The Network Manager is the core controlling component of a multiplayer game. To The Inspector for the Network Manager in the Editor allows you to configure and control many things related to networking. -Note: You should only ever have one active Network Manager in each Scene. Do not place the Network Manager component on a networked GameObject (one which has a Network Identity component), because Mirror disables these when the Scene loads. +**Note**: You should only ever have one active Network Manager in each Scene. Do not place the Network Manager component on a networked GameObject (one which has a Network Identity component), because Mirror disables these when the Scene loads. + +**Another note**: Mirror`s Network Manager uses separate component (derived from the Transport class) to connect across the network. By default, it is TelepathyTransport. If you are already familiar with multiplayer game development, you might find it useful to know that the Network Manager component is implemented entirely using the API, so everything it does is also available to you through scripting. For advanced users, if you find that you need to expand on the Network Manager component’s features, you can use scripting to derive your own class from NetworkManager and customize its behaviour by overriding any of the virtual function hooks that it provides. However, the Network Manager component wraps up a lot of useful functionality into a single place, and makes creating, running and debugging multiplayer games as simple as possible. @@ -34,7 +36,7 @@ If you’re using the Network Manager HUD, it automatically tells the Network Ma ![The network address and port settings in the Network Manager component](NetworkAddressAndPortSettings.png) -Whichever mode the game starts in (client, server, or host), the Network Address and Network Port properties are used. In client mode, the game attempts to connect to the address and port specified. In server or host mode, the game listens for incoming connections on the port specified. +Whichever mode the game starts in (client, server, or host), the Network Address and TelepathyTransport`s Port properties are used. In client mode, the game attempts to connect to the address and port specified. In server or host mode, the game listens for incoming connections on the port specified. ## Spawn Management @@ -57,10 +59,10 @@ If you have only one Network Manager, you need to register to it all prefabs whi The Network Manager spawns player GameObjects using its implementation of NetworkManager.OnServerAddPlayer. If you want to customize the way player GameObjects are created, you can override that virtual function. This code shows an example of the default implementation: ```cs -public virtual void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) +public virtual void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) { var player = (GameObject)GameObject.Instantiate(playerPrefab, playerSpawnPos, Quaternion.identity); - NetworkServer.AddPlayerForConnection(conn, player, playerControllerId); + NetworkServer.AddPlayerForConnection(conn, player); } ``` @@ -99,7 +101,7 @@ You should normally make sure the Network Manager persists between Scenes, other There are virtual functions on the NetworkManager class that you can customize by creating your own derived class that inherits from NetworkManager. When implementing these functions, be sure to take care of the functionality that the default implementations provide. For example, in OnServerAddPlayer(), the function NetworkServer.AddPlayer must be called to activate the player GameObject for the connection. -These are all the callbacks that can happen for host/server and clients, in some cases it’s important to invoke the base class function to maintain default behaviour. To see the implementation itself you can view it in the source code. +These are all the callbacks that can happen for host/server and clients, in some cases it’s important to invoke the base class function to maintain default behavior. To see the implementation itself you can view it in the source code. ```cs using UnityEngine; @@ -132,11 +134,11 @@ public class CustomManager : NetworkManager { Debug.Log("Client is set to the ready state (ready to receive state updates): " + conn); } - public override void OnServerAddPlayer(NetworkConnection conn, short playerControllerId) + public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) { - var player = (GameObject)GameObject.Instantiate(playerPrefab, Vector3.zero, Quaternion.identity); + GameObject player = (GameObject)GameObject.Instantiate(playerPrefab, Vector3.zero, Quaternion.identity); - NetworkServer.AddPlayerForConnection(conn, player, playerControllerId); + NetworkServer.AddPlayerForConnection(conn, player); Debug.Log("Client has requested to get his player added to the game"); } @@ -243,42 +245,29 @@ public class CustomManager : NetworkManager { } ``` -The Network Manager component allows you to control the state of a networked game. It provides an interface in the Editor for you to configure the network, the Prefabs you use for spawning GameObjects, and the Scenesyou use for different game states. +## Public properties -For more details on implementing the Network Manager in your game, see documentation on Using the Network Manager. - -![The Network Manager component in the Inspector window](NetworkManagerUNetComponent.png) +![The Network Manager component in the Inspector window](NetworkManagerInspector.png) - **Dont Destroy On Load** Use this property to control whether or not Mirror should destroy the GameObject with the Network Manager when the Scene changes. Tick this checkbox to ensure Mirror does not destroy your Network Manager GameObject when the Scene changes in your game. Untick the checkbox if you want Mirror to destroy the GameObject when the Scene it exists in is no longer the active Scene. This is useful if you want to manage multiple, separate Network Manager GameObjects in each of your Scenes. This checkbox is ticked by default. - **Run In Background** Use this property to control whether the networked game runs when the window it is running in is not focused. Tick the checkbox if you want it to run; untick it if you want the game to stop running when the window is not focused. This checkbox is ticked by default. You need to enable this property if you want to run multiple instances of a program on the same machine, such as when testing using localhost. You should disable it when deploying to mobile platforms. When enabled, it sets Application.runInBackground to true when the Network Manager starts up. You can also set this property from the Unity menu: Edit \> Project Settings, then select the Player category, and navigate to the Resolution and Presentation panel. -- **Log Level** - Use this property to control the amount of information Mirror outputs to the console window. A low level results in more information; a high level results in less information. Each level includes message from all the levels higher than itself (for example, if you select “Warn”, the console also prints outputs all “Error” and “Fatal” log messages). The drop-down lists the levels from low to high. This property is set to Info by default. You can set Log Level to Set in Scripting to prevent the Network Manager from setting the log level at all. This means you can control the level from your own scripts instead. +- **Start On Headless** + If this box is checked (property is true) _and_ computer that runs the program has no graphic device, program will start in server mode. +- **Show Debug Messages** + Use this property to control the amount of information Mirror outputs to the console window. - **Offline Scene** If you assign a Scene to this field, the Network Manager automatically switches to the specified Scene when a network session stops - for example, when the client disconnects, or when the server shuts down. - **Online Scene** If you assign a Scene to this field, the Network Manager automatically switches to the specified Scene when a network session starts - for example, when the client connects to a server, or when the server starts listening for connections. - **Network Info** - You can expand this section of the inspector to access network-related settings, listed below - - **Use Web Sockets** - When running as a host, enable this setting to make the host listen for Web Socket connections instead of normal transport layer connections, so that WebGL clients can connect to it (if you build your game for the WebGL platform). These WebGL instances of your game cannot act as a host (in either peer-hosted or server-only mode). Therefore, for WebGL instances of your multiplayer game to be able to find each other and play together, you must host a server-only instance of your game running in LAN mode, with a publicly reachable IP address, and it must have this option enabled. This checkbox is unticked by default. + - **Transport** + A link to a Component derived from `Transport` class. TelepathyTransport is created and linked there by default. - **Network Address** 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. This is set to ‘localhost’ by default. - - **Network Port** - The network port currently in use. For clients, this is the port of the server connected to. For servers, this is the listen port. This is set to 7777 by default. - - **Server Bind To IP** - Allows you to tell the server whether to bind to a specific IP address. If this checkbox is not ticked, then there is no specific IP address bound to (IP_ANY). This checkbox is unticked by default. Use this if your server has multiple network addresses (eg, internal LAN, external internet, VPN) and you want to specific the IP address to serve your game on. - - **Server Bind Address** - This field is only visible when the Server Bind To IP checkbox is ticked. Use this to enter the specific IP address that the server should bind to. - - **Script CRC Check** - When this is enabled, Mirror checks that the clients and the server are using matching scripts. This is useful to make sure outdated versions of your client are not connecting to the latest (updated) version of your server. This checkbox is ticked by default. It does this by performing a ([CRC check](https://en.wikipedia.org/wiki/Cyclic_redundancy_check)) between the server and client that ensures the NetworkBehaviour scripts match. This may not be appropriate in some cases, such as when you are intentionally using different Unity projects for the client and server. In most other cases however, you should leave it enabled. - - **Max Delay** - The maximum time in seconds to delay buffered messages. The default of 0.01 seconds means packets are delayed at most by 10 milliseconds. Setting this to zero disables connection buffering. This is set to 0.01 by default. - - **Max Buffered Packets** - The maximum number of packets that a NetworkConnection can buffer for each channel. This corresponds to the ChannelOption.MaxPendingBuffers channel option. This is set to 16 by default. - - **Packet Fragmentation** - This allows the `NetworkConnection` instances to fragment packets that are larger than `maxPacketSize` to up a maximum of 64K. This can cause delays in sending large packets. This checkbox is ticked by default. + - **Max Connections** + Maximum quantity of clients connected to a server. Note that host is a server and one client. - **SpawnInfo** You can expand this section of the inspector to access spawn-related settings, listed below - **Player Prefab** @@ -293,38 +282,3 @@ For more details on implementing the Network Manager in your game, see documenta Choose Round Robin to cycle through startPositions in a set list. - **Registered Spawnable Prefabs** Use this list to add prefabs that you want the Network Manager to be aware of, so that it can spawn them. You can also add and remove them via scripting. -- **Advanced Configuration** - Tick this checkbox to reveal advanced configuration options in the Network Manager Inspector window. - - **Max Connections** - Define the maximum number of concurrent network connections to support. This is set to 4 by default. - - **Qos Channels** - A list containing the different communication channels the current Network Manager has, and the Quality Of Service (QoS) setting for each channel. Use this list to add or remove channels, and adjust their QoS setting. You can also configure the channels via scripting. For the descriptions of each QoS option, see QosType. -- **Timeouts** - - **Min Update Timeout** - Set the minimum time (in milliseconds) the network thread waits between sending network messages. The network thread doesn’t send multiplayer network messages immediately. Instead, it check each connection periodically at a fixed rate to see if it has something to send. This is set to 10ms by default. See API reference documentation on MinUpdateTimeout for more information. - - **Connect Timeout** - Define the amount of time (in milliseconds) Mirror should wait while trying to connect before attempting the connection again. This is set to 2000ms by default. See API reference documentation on ConnectTimeout for more information. - - **Disconnect Timeout** - The amount of time (in milliseconds) before Mirror considers a connection to be disconnected. This is set to 2000ms by default. See API reference documentation on DisconnectTimeout for more information. - - **Ping Timeout** - The amount of time (in milliseconds) between sending pings (also known as “keep-alive” packets). The ping timeout duration should be approximately one-third to one-quarter of the Disconnect Timeout duration, so that Mirror doesn’t assume that clients are disconnected until the server has failed to receive at least three pings from the client. This is set to 500ms by default. See API reference documentation on ConnectionConfig.PingTimeout for more information. -- **Global Config** - These settings relate to the Reactor. The Reactor is the part of the multiplayer system which receives network packets from the underlying operating system, and passes them into the multiplayer system for processing. - - **Thread Awake Timeout** - The timeout duration in milliseconds, used by the Reactor. How the Reactor uses this value depends on which Reactor Model you select (see below). This is set to 1ms by default. - - **Reactor Model** - Choose which type of reactor to use. The reactor model defines how Mirror reads incoming packets. For most games and applications, the default Select reactor is appropriate. If you want to trade a small delay in the processing of network messages for lower CPU usage and improved battery life, use the Fix Rate reactor. - - **Select Reactor** - This model uses the `select()` API which means that the network thread “awakens” (becomes active) as soon as a packet is available. Using this method means your game gets the data as fast as possible. This is the default Reactor Model setting. - - **Fix Rate Reactor** - This model lets the network thread sleep manually for a given amount of time (defined by the value in Thread Awake Timeout) before checking whether there are incoming packets waiting to be processed. - - **Reactor Max Recv Messages** - Set the maximum number of messages stored in the receive queue. This is set to 1024 messages by default. - - **Reactor Max Sent Messages** - Set the maximum number of messages stored in the send queue. This is set to 1024 messages by default. - - **Use Network Simulator** - Tick this checkbox to enable the usage of the network simulator. The network simulator introduces simulated latency and packet loss based on the following settings: - - **Simulated Average Latency** - The amount of delay in milliseconds to simulate. - - **Simulated Packet Loss** - The amount of packet loss to simulate in percent. diff --git a/docs/Components/NetworkManagerInspector.png b/docs/Components/NetworkManagerInspector.png index 7f670f0b7..dec8828fb 100644 Binary files a/docs/Components/NetworkManagerInspector.png and b/docs/Components/NetworkManagerInspector.png differ diff --git a/docs/Components/NetworkManagerSpawnInfo.png b/docs/Components/NetworkManagerSpawnInfo.png index b41fac1b4..fd5a94eee 100644 Binary files a/docs/Components/NetworkManagerSpawnInfo.png and b/docs/Components/NetworkManagerSpawnInfo.png differ diff --git a/docs/Components/NetworkTransform.md b/docs/Components/NetworkTransform.md index 8105dfbb0..ac7819648 100644 --- a/docs/Components/NetworkTransform.md +++ b/docs/Components/NetworkTransform.md @@ -4,42 +4,8 @@ The Network Transform component synchronizes the movement and rotation of GameOb ![The Network Transform component](NetworkTransform.png) -- **Network Send Rate (seconds)** - Set the number of network updates per second. You can set this slider to 0 for GameObjects that do not need to update after being created, like non-interactive effects generated by a player (for example, a dust cloud left behind that the player cannot interact with). -- **Transform Sync Mode** - Select what type of synchronization should occur on this GameObject. - - **Sync None** - Don’t synchronize. - - **Sync Transform** - Use the GameObject’s Transform for synchronization. Use this if the physics system does not control this GameObject (that is, if you are moving it via scripting or animation). This is the default option. - - **Sync Rigidbody 2D** - Use the Rigidbody2D component for synchronization. Use this if the 2D physics system controls this GameObject. - - **Sync Rigidbody 3D** - Use the Rigidbody component for synchronization. Use this if the 3D physics system controls this GameObject. - - **Sync Character Controller** - Use the Character Controller component for synchronization. Only select this if you’re using a Character Controller. -- **Movement:** - - **Movement Threshold** - Set the distance that a GameObject can move without sending a movement synchronization update. - - **Snap Threshold** - Set the threshold at which, if a movement update puts a GameObject further from its current position than this, the GameObject snaps to the position instead of moving smoothly. - - **Interpolate Movement Factor** - Use this to enable and control interpolation of the synchronized movement. The larger this number is, the faster the GameObject interpolates to the target position. If this is set to 0, the GameObject snaps to the new position. -- **Rotation:** - - **Rotation Axis** - Define which rotation axis or axes should synchronize. This is set to XYZ (full 3D) by default. - - **Interpolate Rotation Factor** - Use this to enable and control interpolation of the synchronized rotation. The larger this number is, the faster the GameObject interpolates to the target rotation. If this is set to 0, the GameObject snaps to the new rotation. - - **Compress Rotation** - If you compress rotation data, the amount of data sent is lower, and the accuracy of the rotation synchronization is lower. - - **None** - Choose this to apply no compression to the rotation synchronization. This is the default option. - - **Low** - Choose this to apply a low amount of compression to the rotation synchronization. This option lessens the amount of information sent for rotation data. - - **High** - Choose this to apply a high amount of compression to the rotation synchronization. This option sends the least amount of information possible for rotation data. - - **Sync Angular Velocity** - Tick this checkbox to synchronize the angular velocity of the attached Rigidbody component. +You can modify **Compress Rotation** to save some bandwidth when synchronizing the rotation. +You can use the **Network Sync Interval** to specify how often it syncs (in seconds). This component takes authority into account, so local player GameObjects (which have local authority) synchronize their position from the client to server, then out to other clients. Other GameObjects (with server authority) synchronize their position from the server to clients. diff --git a/docs/Components/NetworkTransform.png b/docs/Components/NetworkTransform.png index 9851ccf72..4df35b82b 100644 Binary files a/docs/Components/NetworkTransform.png and b/docs/Components/NetworkTransform.png differ diff --git a/docs/Concepts/Authentication.md b/docs/Concepts/Authentication.md new file mode 100644 index 000000000..5c9e8eeee --- /dev/null +++ b/docs/Concepts/Authentication.md @@ -0,0 +1,74 @@ +# Authentication + +When you have a multiplayer game, often you need to store information about your player for later games, keep game stats or communicate with your friends. For all these use cases, you often need a way to uniquely identify a user. Being able to tell users appart is called authentication. There are several methods available, some examples include: + +* Ask the user for username and password +* Use a third party oath or openid identity provider, such as facebook, twitter, google +* Use a third party service such as playfab, gamelift or steam +* Use the device id, very popular method in mobile +* Use Google Play in android +* Use Game Center in ios +* Use a web service in your website + +Trying to write a comprehensive authentication framework that cover all these is very complex. There is no one size fit all, and we would quickly end up with bloated code. + +Instead, **Mirror does not perform authentication**, but we provide hooks you can use to implement any of these. + +Here is an example of how to implement simple username/password authentication: + +1) Select your NetworkManager gameobject in the unity editor. +2) In the inspector, under `Spawn Info`, disable `Auto Create Player` +3) Call `AddPlayer` in your client to pass the credentials. +4) Override the `OnServerAddPlayer` method and validate the user's credential. + + +For example this would be part of your NetworkManager class: + +```cs +class MyGameNetworkManager : NetworkManager { + + class CredentialsMessage : MessageBase + { + // use whatever credentials make sense for your game + // for example, you might want to pass the accessToken if using oauth + public string username; + public string password; + } + + // this gets called in your client after + // it has connected to the server, + public override void OnClientConnect(NetworkConnection conn) + { + base.OnClientConnect(conn); + + var msg = new CredentialsMessage() + { + // perhaps get the username and password + // from textboxes instead + username = "Joe", + password = "Gaba Gaba" + }; + + ClientScene.AddPlayer(connection, MessagePacker.Pack(msg)); + } + + // this gets called in your server when the + // client requests to add a player. + public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) + { + var msg = MessagePacker.Unpack(extraMessage.value); + + // check the credentials by calling your web server, database table, playfab api, or any method appropriate. + if (msg.username == "Joe" && msg.password == "Gaba Gaba") + { + // proceed to regular add player + base.OnServerAddPlayer(conn, extraMessage); + } + else + { + conn.Disconnect(); + } + } +} + +``` \ No newline at end of file diff --git a/docs/Concepts/Communications/NetworkMessages.md b/docs/Concepts/Communications/NetworkMessages.md index a79ae5411..ac7b817a6 100644 --- a/docs/Concepts/Communications/NetworkMessages.md +++ b/docs/Concepts/Communications/NetworkMessages.md @@ -1,14 +1,15 @@ # Network Messages -In addition to “high-level” Commands and RPC calls, you can also send raw network messages. +For the most part we recommend the high level [Commands and RPC](RemoteActions.md) calls and [SyncVar](../StateSync.md), but you can also send low level network messages. This can be useful if you want clients to send messages that are not tied to gameobjects, such as logging, analytics or profiling information. -There is a class called MessageBase that you can extend to make serializable network message classes. This class has Serialize and Deserialize functions that take writer and reader objects. You can implement these functions yourself, or you can rely on code-generated implementations that are automatically created by the networking -system. The base class looks like this: +There is a class called MessageBase that you can extend to make serializable network message classes. This class has Serialize and Deserialize functions that take writer and reader objects. You can implement these functions yourself, but we recommend you let Mirror generate them for you. -``` +The base class looks like this: + +```cs public abstract class MessageBase { - // De-serialize the contents of the reader into this message + // Deserialize the contents of the reader into this message public virtual void Deserialize(NetworkReader reader) {} // Serialize the contents of this message into the writer @@ -16,49 +17,13 @@ public abstract class MessageBase } ``` -Message classes can contain members that are basic types, structs, and arrays, including most of the common Unity Engine types (such as Vector3). They cannot contain members that are complex classes or generic containers. Remember that if you want to rely on the code-generated implementations, you must make sure your types are publicly visible. +The auto generated Serialize/Deserialize can efficiently deal with basic types, structs, arrays and common Unity value types such as Color, Vector3, Quaternion. Make your members public. If you need class members or complex containers such as List and Dictionary, you must implement the Serialize and Deserialize methods yourself. -There are built-in message classes for common types of network messages: - -- EmptyMessage -- StringMessage -- IntegerMessage - -To send a message, use the `Send()` method on the NetworkClient, NetworkServer, and NetworkConnection classes which work the same way. It takes a message ID, and a message object that is derived from MessageBase. The code below demonstrates how to send and handle a message using one of the built-in message classes: - -``` -using UnityEngine; -using Mirror; - -public class Begin : NetworkBehaviour -{ - const short MyBeginMsg = 1002; - - NetworkClient m_client; - - public void SendReadyToBeginMessage(int myId) - { - var msg = new IntegerMessage(myId); - m_client.Send(MyBeginMsg, msg); - } - - public void Init(NetworkClient client) - { - m_client = client; - NetworkServer.RegisterHandler(MyBeginMsg, OnServerReadyToBeginMessage); - } - - void OnServerReadyToBeginMessage(NetworkMessage netMsg) - { - var beginMessage = netMsg.ReadMessage(); - Debug.Log("received OnServerReadyToBeginMessage " + beginMessage.value); - } -} -``` +To send a message, use the `Send()` method on the NetworkClient, NetworkServer, and NetworkConnection classes which work the same way. It takes a message object that is derived from MessageBase. The code below demonstrates how to send and handle a message: To declare a custom network message class and use it: -``` +```cs using UnityEngine; using Mirror; @@ -66,11 +31,6 @@ public class Scores : MonoBehaviour { NetworkClient myClient; - public class MyMsgType - { - public static short Score = MsgType.Highest + 1; - }; - public class ScoreMessage : MessageBase { public int score; @@ -80,33 +40,28 @@ public class Scores : MonoBehaviour public void SendScore(int score, Vector3 scorePos, int lives) { - ScoreMessage msg = new ScoreMessage(); - msg.score = score; - msg.scorePos = scorePos; - msg.lives = lives; - - NetworkServer.SendToAll(MyMsgType.Score, msg); + ScoreMessage msg = new ScoreMessage() + { + score = score, + scorePos = scorePos, + lives = lives + }; + + NetworkServer.SendToAll(msg); } // Create a client and connect to the server port public void SetupClient() { myClient = new NetworkClient(); - myClient.RegisterHandler(MsgType.Connect, OnConnected); - myClient.RegisterHandler(MyMsgType.Score, OnScore); + myClient.RegisterHandler(OnScore); myClient.Connect("127.0.0.1", 4444); } - public void OnScore(NetworkMessage netMsg) + public void OnScore(ScoreMessage msg) { - ScoreMessage msg = netMsg.ReadMessage(); Debug.Log("OnScoreMessage " + msg.score); } - - public void OnConnected(NetworkMessage netMsg) - { - Debug.Log("Connected to server"); - } } ``` @@ -118,7 +73,7 @@ There is also an ErrorMessage class that is derived from `MessageBase`. This cla The errorCode in the ErrorMessage class corresponds to the Networking.NetworkError enumeration. -``` +```cs class MyClient { NetworkClient client; @@ -126,12 +81,11 @@ class MyClient void Start() { client = new NetworkClient(); - client.RegisterHandler(MsgType.Error, OnError); + client.RegisterHandler(OnError); } - void OnError(NetworkMessage netMsg) + void OnError(ErrorMessage errorMsg) { - var errorMsg = netMsg.ReadMessage(); Debug.Log("Error:" + errorMsg.errorCode); } } diff --git a/docs/Concepts/GameObjects/SpawnObjectCustom.md b/docs/Concepts/GameObjects/SpawnObjectCustom.md index 8d206fad8..816b9cd5d 100644 --- a/docs/Concepts/GameObjects/SpawnObjectCustom.md +++ b/docs/Concepts/GameObjects/SpawnObjectCustom.md @@ -18,7 +18,7 @@ The asset ID passed to the spawn function can be found on NetworkIdentity.assetI ```cs // generate a new unique assetId -System.Guid creatureAssetId = System.Guid.NewGuid; +System.Guid creatureAssetId = System.Guid.NewGuid(); // register handlers for the new assetId ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature); diff --git a/docs/Concepts/GameObjects/SpawnPlayerCustom.md b/docs/Concepts/GameObjects/SpawnPlayerCustom.md index e4d4944c3..709748ccf 100644 --- a/docs/Concepts/GameObjects/SpawnPlayerCustom.md +++ b/docs/Concepts/GameObjects/SpawnPlayerCustom.md @@ -26,11 +26,11 @@ using Mirror; public class MyNetworkManager : NetworkManager { - public override void OnServerAddPlayer(NetworkConnection conn) + public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) { GameObject player = (GameObject)Instantiate(playerPrefab, Vector3.zero, Quaternion.identity); player.GetComponent().color = Color.red; - NetworkServer.AddPlayerForConnection(conn, player, conn.playerControllerId); + NetworkServer.AddPlayerForConnection(conn, player); } } ``` diff --git a/docs/General/ChangeLog.md b/docs/General/ChangeLog.md new file mode 100644 index 000000000..7f923e7f5 --- /dev/null +++ b/docs/General/ChangeLog.md @@ -0,0 +1,75 @@ +# Change Log + +## Version 1.7 -- In Progress + +- Added: Semantic Versioning +- Added: SyncDictionary...SyncHashSet coming soon™ +- Fixed: Host should not call Disconnect on transports +- Fixed: Offline scene switching now works via `StopClient()` +- Fixed: Pong example updated +- Changed: Movement example replaced with Tank example +- Changed: SyncList now supports structs directly, making SyncListSTRUCT obsolete. +- Removed: SyncListSTRUCT - Use SyncList instead. + +## Version 1.6 -- 2019-Mar-14 + +- Fixed: Websockets transport moved to Mirror.Websocket namespace +- Fixed: NetworkAnimator bandwidth abuse +- Fixed: NetworkAnimator float sync bug +- Fixed: Persistent SceneID's for Networked objects +- Changed: Documentation for [Transports](../Transports) +- Changed: Weaver is now full source...FINALLY! +- Changed: ClientScene.AddPlayer 2nd parameter is now `byte[] extraData` instead of `MessageBase extraMessage` +- Changed: NetworkManager -- Headless Auto-Start moved to `Start()` from `Awake()` +- Changed: Removed Message ID's for all messages - See [Network Messages](../Concepts/Communications/NetworkMessages) for details + - Message IDs are now generated automatically based on the message name. + - Previously you would call Send(MyMessage.MsgId, message), now you call Send(message) +- Removed: Documentation for Groove Transport - use Websockets Transport instead + +## Version 1.5 -- 2019-Mar-01 + +- Added: **Migration Tool** to (mostly) automate conversion from UNet +- Added: Full support for WebSockets and WebSocketsSecure to replace UNet LLAPI +- Added: Transport Multiplexer - allows the use of multiple concurrent transports +- Added: NetworkLobbyManager and NetworkLobbyPlayer with example game +- Added: Configurable Server Tickrate in NetworkManager +- Added: New virtual OnClientChangeScene fires right before SceneManager.LoadSceneAsync is executed +- Added: Unit tests for Weaver +- Fixed: Garbage allocations removed from a lot of things (more work to do, we know) +- Fixed: NetworkProximityChecker now uses OverlapSphereNonAlloc and OverlapCircleNonAlloc +- Fixed: SyncVar hook not firing when clients joined +- Fixed: NetworkManager no longer assumes it's on Scene(0) in Build Settings +- Fixed: NetworkAnimator no longer lmited to 6 variables +- Fixed: TelepathyTransport delivering messages when disabled +- Changed: Minimum Unity version: **2018.3.6** +- Removed: SceneAttribute.cs (merged to CustomAttributes.cs) +- Removed: NetworkClient.allClients (Use NetworkClient.singleton instead) +- Removed: NetworkServer.hostId and NetworkConnection.hostId (holdovers from LLAPI) +- Removed: NetworkConnection.isConnected (NetworkConnection is always connected) +- Removed: Transport.GetConnectionInfo (Use ServerGetClientAddress instead) + + +## Version 1.4 -- 2019-Feb-01 + +- Added: HelpURL attirbutes to components +- Added: Automatic targetFramerate for headless builds +- Added: ByteMessage to Messages class +- Fixed: Connectiing state can be cancelled properly +- Fixed: NetworkTransformBase interpolation applied to client's own object +- Fixed: Objects are spawned with correct rotation +- Fixed: SceneId assignment +- Fixed: Changed syncInterval wasn't saved...it is now +- Fixed: Additive Scene loading +- Changed: **Mirror is now full source** -- no more DLL's +- Changed: **Transports are now components** -- TCP, UDP, WebGL, Steam +- Changed: Transport class now dispatches Unity Events +- Changed: NetworkServer.SendToClientOfPlayer uses NetworkIdentity now +- Changed: NetworkServer.SendToObservers uses NetworkIdentity parameter now +- Changed: NetworkServer.SendToReady uses NetworkIdentity now +- Changed: NetworkServer.DestroyPlayerForConnection uses NetworkIdentity.spawned now +- Changed: NetworkConnection.Dispose uses NetworkIdentity.spawned now +- Changed: NetworkReader.ReadTransform uses NetworkIdentity.spawned now +- Changed: NetworkTransform reimplemented -- physics removed, code simplified +- Removed: NetworkClient.hostPort (port is handled at Transport level) +- Removed: NetworkServer.FindLocalObject (Use NetworkIdentity.spawned\[netId\] instead) +- Removed: ClientScene.FindLocalObject (Use NetworkIdentity.spawned\[netId\] instead) diff --git a/docs/General/Cubica.jpg b/docs/General/Cubica.jpg new file mode 100644 index 000000000..50fdc98f5 Binary files /dev/null and b/docs/General/Cubica.jpg differ diff --git a/docs/General/Migration.md b/docs/General/Migration.md index 636defaee..68e88d80c 100644 --- a/docs/General/Migration.md +++ b/docs/General/Migration.md @@ -187,17 +187,17 @@ public override void OnServerAddPlayer(NetworkConnection conn, short playerContr } ``` -In your newly Mirror-capable NetworkManager, if you are using the OnServerAddPlayer override, remove the "playerControllerId" parameter from your override and the base call: +In your newly Mirror-capable NetworkManager, if you are using the `OnServerAddPlayer` override, remove the "playerControllerId" parameter from your override and the base call, and change the `NetworkReader` into `AddPlayerMessage`: ```cs -public override void OnServerAddPlayer(NetworkConnection conn, NetworkReader extraMessageReader) +public override void OnServerAddPlayer(NetworkConnection conn, AddPlayerMessage extraMessage) { - base.OnServerAddPlayer(conn, extraMessageReader); + base.OnServerAddPlayer(conn, extraMessage); // your code } ``` -Note that in both UNet and Mirror the parameter "extraMessageReader" is optional. +Note that in UNet the parameter "extraMessageReader" is optional, but in Mirror it is required. ### 10. Pick your transport diff --git a/docs/General/PickupHockey.png b/docs/General/PickupHockey.png new file mode 100644 index 000000000..23f77026e Binary files /dev/null and b/docs/General/PickupHockey.png differ diff --git a/docs/General/ProjectRetrograde.jpg b/docs/General/ProjectRetrograde.jpg new file mode 100644 index 000000000..733321935 Binary files /dev/null and b/docs/General/ProjectRetrograde.jpg differ diff --git a/docs/General/Showcase.md b/docs/General/Showcase.md new file mode 100644 index 000000000..1e7f4b84d --- /dev/null +++ b/docs/General/Showcase.md @@ -0,0 +1,41 @@ +# Games made with Mirror + +## [uMMORPG](https://assetstore.unity.com/packages/templates/systems/ummorpg-51212) + +![uMMORPG](uMMORPG.jpg) + + +## [uSurvival](https://assetstore.unity.com/packages/templates/systems/usurvival-95015) + +![uSurvival](uSurvival.jpg) + + +## [Cubica](https://www.cubica.net/) + +![Cubica](Cubica.jpg) + + +## [The Wall](https://store.steampowered.com/app/719200/The_Wall/) + +![The Wall](TheWall.jpg) + + +## [Pickup Hockey](http://www.electricfalcon.net/pickuphockey/) + +![Pickup Hockey](PickupHockey.png) + + +## [The Last IO](http://thelast.io/) + +![The Last IO](TheLastIO.png) + + +## [Project Retrograde](https://freemergency.weebly.com/) + +![Project Retrograde](ProjectRetrograde.jpg) + + +## [SamuTale](https://www.samutale.com/) + +![SamuTale](samutale-banner.png) + diff --git a/docs/General/TheLastIO.png b/docs/General/TheLastIO.png new file mode 100644 index 000000000..569614d78 Binary files /dev/null and b/docs/General/TheLastIO.png differ diff --git a/docs/General/TheWall.jpg b/docs/General/TheWall.jpg new file mode 100644 index 000000000..47b1a765a Binary files /dev/null and b/docs/General/TheWall.jpg differ diff --git a/docs/General/WhyTCP.md b/docs/General/WhyTCP.md index 1b551aa5c..9c45998c4 100644 --- a/docs/General/WhyTCP.md +++ b/docs/General/WhyTCP.md @@ -1,62 +1,45 @@ # Why TCP by default and not UDP? -## The same old Discussion - -It's the year 2018 and every game developer swears by UDP. Yet we chose TCP as default for Mirror. Why is that? - -UDP vs. TCP, the technical aspects +It's the year 2019 and every game developer swears by UDP. Yet we chose TCP as default for Mirror. Why is that? First of all, a quick word about the major differences between UDP and TCP. -- UDP has lower latency and is unreliable by default and hard to use correctly -- TCP has higher latency and is reliable by default and easy to use +- UDP has lower latency, unreliable and hard to use correctly +- TCP has higher latency, reliable and easy to use -Now instead of having yet another technical UDP vs. TCP discussion, let's take a look at a real world example to see why we chose TCP over UDP. +## TCP (Transmision Control Protocol) -## That Game again +[Tcp](https://en.wikipedia.org/wiki/Transmission_Control_Protocol#Congestion_control) is a protocol built on top of IP. It is by far the most popular protocol on the internet. Everything you are seeing in this page was sent to your browser via TCP. It is designed to be simple to use and scalable. -Back in 2011, some guy named Markus Persson (aka Notch) created arguably the biggest multiplayer game of all time. The game is called Minecraft. +Servers open a TCP port and wait for connections. Clients send an initial message (handshake) to establish the connection then send data. Data flows one byte after another, always in the right order, without missing anything. Some of the key features include: -Minecraft uses TCP, but why is that? Nobody knows, except Markus Persson. +* Reliable: if a packet gets lost, TCP will resend it. All data is either transmitted successfully or you get an error and the connection is closed. Applications don't have to worry about missing packets. +* Fragmented: network cards cannot just send 1 MB of data. They can only send small packets of 1.5Kb or less. If a lot of data is sent by the application, TCP will split it into small packets and reassemble the data on the receiving end. +* Sequenced: If you send data "a" and "b" you will not receive "b" and "a". TCP guarantees that every byte will arrive in the same order it was sent. +* Connection oriented: TCP has the concept of a connection. A client sends an initial handshake message. A connection is considered established until either the client and server decides to disconnect. Both the client and server get notified when the connection ends and can react accordingly, for example saving and destroying player object. +* Congestion control: If a server is being overwhelmed, TCP will throttle the data to avoid congestion collapse. -But we can make an educated guess. +These are great features that make it very easy for programmers to work with TCP, but they come at a cost: Latency. -## On Java vs. C++ +Suppose an object is moving from point a to b to c. The server sends 3 messages: move to a, b, c. Suppose b gets lost (wifi drops a lot of packets for example) and c arrives fine. We could skip b and move towards c instead, but we can't because the operating system won't give us c until b is retransmited. -But wait, let's go back a bit further. Minecraft was written in Java, which is outrageous given that back in 2011 every game developer used to swear by C++. +For this reason, AAA studios consistently prefer UDP for fast paced action games. -Here are the major differences between C++ and Java: +## UDP (User Datagram Protocol) -- C++ has a lower footprint, it's faster and it's hard to use correctly -- Java is slow, uses ungodly amounts of memory and is easy to use +[UDP](https://en.wikipedia.org/wiki/User_Datagram_Protocol) is also a protocol based on IP. It is used for real time applications such as fast paced action games or voice over ip, where low latency is more important than reliability. -That discussion sounds oddly familiar. Speed vs. ease of use, just like UDP vs. TCP. +A server opens a port and waits for messages. Clients send messages to the port, and the server may send messages back. Data flows in both ways as individual messages. -## Why? +There is no concept of connection, so there is no built in way to determine if a client disconnects. Messages are delivered as soon as possible, there is no guarantee that the order will be preserved or that they will be delivered at all. Messages must be small, typically 1.5Kb or less. -Okay so, why did Notch chose Java instead of C++ and TCP instead of UDP, given that they are both so much slower than their counter parts? +Mirror does need reliability, fragmentation, sequenced, connections for many things, so we would not use raw UDP. We would use a library that implements those features on top of UDP such as [ENet](http://enet.bespin.org/), [LiteNetLib](https://github.com/RevenantX/LiteNetLib) or LLAPI, typically refered to as RUDP (Reliable UDP) -Well, obviously because Notch is an idiot and knows nothing about game development. +The obvious question is: do RUDP libraries just reinventing TCP? yes, to some degree they do. But the point is that those features are optional and we can send messages without the extra features for low latency data such as movement or voice. -But wait, something doesn't add up. Every kid on the planet plays Minecraft and right now there are thousands of people who are having a blast building shelters, surviving zombies at night, making friends and occasionally falling in love. +## Dark ages -Oh, and Notch is a billionaire now. - -All the evidence points us to the fact that back in 2011, Notch knew something that others didn't. - -## The Riddle - -The answer to the riddle is the question about optimization. - -Notch never optimized for performance. That's why he didn't care about C++ or UDP. - -Notch optimized for probability of success. That's why he chose Java and TCP. - -And it worked. What good would it be if Minecraft ran at twice the framerate and half the latency without ever seeing the light of day? - -## Summary - -Back in 2015 when we started uMMORPG and Cubica, we originally used Unity's built in Networking system aka UNET. UNET used UDP and avoided garbage collection at all costs. +Back in 2015 when we started uMMORPG and Cubica, we originally used Unity's built in Networking system aka UNET. UNET used LLAPI, an RUDP library that avoided garbage collection at all costs. What sounds good in theory, was terrible in practice. We spent about half our work hours from 2015 to 2018 dealing with UNET bugs. There was packet loss, highly complex code due to GC avoidance, synchronization issues, memory leaks and random errors. Most importantly, no decent way to debug any of it. @@ -69,8 +52,13 @@ If a monster didn't spawn on a client, we wouldn't know what caused it. - Did we use the right networking config for the host that we tested it on? - Or was it a bug in our own project? -After 3 years in UDP hell, we realized what Notch had realized a long time ago: if we ever wanted to finish our games, we would need a networking layer that just works. +After 3 years in UDP/LLAPI hell, we realized if we ever wanted to finish our games, we would need a networking layer that just works. We could have tried other RUDP transports, but we would end up debugging them instead. That's why we made Telepathy and Mirror. **Life is short. We just need the damn thing to work.** -We acknowledge not everyone will agree with our reasoning. Rather than push our views on users, we made Mirror transport independent. You can easily swap out the transport for one of the several RUDP implementations simply by dragging it into your NetworkManager gameobject. Pick whatever works best for you. We recommend you profile your game and collect real world numbers before you make a final decision. +## The choice is yours + +We acknowledge not everyone will agree with our reasoning. Rather than push our views on users, we made Mirror transport independent. A few months later, Unity did the same thing. You can easily swap out the transport for one of the several RUDP implementations simply by dragging it into your NetworkManager gameobject. Pick whatever works best for you. We recommend you profile your game and collect real world numbers before you make a final decision. + +After we made Mirror transport independent, the community stepped up integrated several RUDP transports with Mirror. While the default is Telepathy (simple "just works" TCP transport) you can choose among several [transports](../Transports) or even write your own. + diff --git a/docs/General/samutale-banner.png b/docs/General/samutale-banner.png new file mode 100644 index 000000000..fc0476d7b Binary files /dev/null and b/docs/General/samutale-banner.png differ diff --git a/docs/General/uMMORPG.jpg b/docs/General/uMMORPG.jpg new file mode 100644 index 000000000..6b07b2011 Binary files /dev/null and b/docs/General/uMMORPG.jpg differ diff --git a/docs/General/uSurvival.jpg b/docs/General/uSurvival.jpg new file mode 100644 index 000000000..3a2c2bd50 Binary files /dev/null and b/docs/General/uSurvival.jpg differ diff --git a/docs/Test.md b/docs/Test.md deleted file mode 100644 index 6e1777a9c..000000000 --- a/docs/Test.md +++ /dev/null @@ -1,55 +0,0 @@ -# Heading 1 - -## Heading 2 - -### Heading 3 - -#### Heading 4 - -##### Heading 5 - -Lorem ipsum dolor sit amet, consectetur *adipiscing elit, sed do eiusmod tempor* incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. **Duis aute irure** dolor in [^1] in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. - -[^1]: reprehenderit: Footnote - -![Pong](Samples/Pong1.jpg) - -- Bullet -- Bullet - -1. One -2. Two -3. Three - -| Name | Color | -|-------|--------| -| Apple | Red | -| Peach | Orange | - -```cs -using System; -using UnityEngine; - -namespace Mirror -{ - // sending messages on this connection causes the client's handler function to be invoked directly - class ULocalConnectionToClient : NetworkConnection - { - LocalClient m_LocalClient; - - public LocalClient localClient { get { return m_LocalClient; } } - - public ULocalConnectionToClient(LocalClient localClient) - { - address = "localClient"; - m_LocalClient = localClient; - } - - protected override bool SendBytes(byte[] bytes, int channelId = Channels.DefaultReliable) - { - m_LocalClient.InvokeBytesOnClient(bytes); - return true; - } - } -} -``` diff --git a/docs/Transports/Fizzy.md b/docs/Transports/Fizzy.md index 683090fda..35aacc04b 100644 --- a/docs/Transports/Fizzy.md +++ b/docs/Transports/Fizzy.md @@ -1,3 +1,20 @@ -# Fizzy +# FizzySteam -General description of Fizzy +Fizzy Steam is a Steam P2P transport for Mirror, it utilizes Steam's P2P service to directly connect or relay your connection to another player. Fizzy Steam is based of [Steamworks.Net](https://github.com/rlabrecque/Steamworks.NET) wrapper. + +You can get the release **[Here](https://github.com/Raystorms/FizzySteamyMirror/releases)** with the latest version of Steamworks.Net included or you can clone the repo **[Here](https://github.com/Raystorms/FizzySteamyMirror)**. + +## Features + +* Multiple Customizable Channels : You can customize the channels in the transport, whether you want just 1 or 5 channels that are unrealiable or realiable (best to leave channel 0 as reliable). +* Steam Nat Punching & Relay : The transport will use Steam to do Nat Punching to your destination, and if that doesn't work, steam's relay Server will be used to ensure you can alway connect (latency may vary). +* No Code Changes Needed : If you Already use Mirror, you just need to slap this transport in (maybe add your steam App ID in your build), and everything should work the same like any other Mirror Transport. "It Just Works" -Todd Howard + +![The Fizzy Transport component in the Inspector window](SteamTransport.PNG) + +## Credits +* [Fizz Cube](https://github.com/FizzCube) : Original author for this Transport. +* [Raystorms](https://github.com/Raystorms) : The current maintainer for this Tranport. +* [rlabrecque](https://github.com/rlabrecque) : Creator of Steamworks.Net. +* [vis2k](https://github.com/vis2k) : Creator of Mirror. +* Valve : Steam diff --git a/docs/Transports/Ignorance.md b/docs/Transports/Ignorance.md index 6c163c1e3..33c961298 100644 --- a/docs/Transports/Ignorance.md +++ b/docs/Transports/Ignorance.md @@ -7,6 +7,8 @@ ENET is a solid reliable UDP C++ network library that is mature and stable. Ther Ignorance at the time of this article is at version 1.0.9.1 and fully supports Unity 2017.4 LTS. A version for Unity 2018 LTS with async support is planned and should arrive in 2019. *Ignorance was also designed to address that ugly TCP vs UDP war that many developers fought in.* +![The Ignorance Transport component in the Inspector window](IgnoranceTransport.PNG) + ## Why would I want to use reliable UDP over TCP? - if you have realtime communications that you need speed over reliability (VoIP...) - if you need a data hose for your game (a first person shooter, racing game, etc) diff --git a/docs/Transports/IgnoranceTransport.PNG b/docs/Transports/IgnoranceTransport.PNG new file mode 100644 index 000000000..3f6767e8a Binary files /dev/null and b/docs/Transports/IgnoranceTransport.PNG differ diff --git a/docs/Transports/LiteNetLib4Mirror.md b/docs/Transports/LiteNetLib4Mirror.md new file mode 100644 index 000000000..f6a3e357d --- /dev/null +++ b/docs/Transports/LiteNetLib4Mirror.md @@ -0,0 +1,5 @@ +# LiteNetLib4Mirror + +General description of LiteNetLib4Mirror + +![The LiteNetLib4Mirror Transport component in the Inspector window](LiteNetLibTransport.PNG) diff --git a/docs/Transports/LiteNetLibTransport.PNG b/docs/Transports/LiteNetLibTransport.PNG new file mode 100644 index 000000000..a65ab03e0 Binary files /dev/null and b/docs/Transports/LiteNetLibTransport.PNG differ diff --git a/docs/Transports/MultiplexSample.png b/docs/Transports/MultiplexSample.png new file mode 100644 index 000000000..900e12fa1 Binary files /dev/null and b/docs/Transports/MultiplexSample.png differ diff --git a/docs/Transports/Multiplexer.md b/docs/Transports/Multiplexer.md new file mode 100644 index 000000000..64cb62555 --- /dev/null +++ b/docs/Transports/Multiplexer.md @@ -0,0 +1,23 @@ +# MultiplexTransport + +The MultiplexTransport is not a transport itself, but it allows you to combine other transports so that your clients can connect to your servers via either one of them. + +A common use case for the MultiplexTransport is a server listening to both websockets and TCP. Your webgl clients can connect to the server using websockets and your mobile or desktop clients can connect to the same server via TCP. In HLAPI, you had to chose between websockets and UDP, but you cannot use both at the same time. You can configure any number of transports in the MultiplexTransport. + +To use the MultiplexTransport follow these steps: + +1. Add a gameobject with a NetworkManager to your scene if you have not done so +2. By default, Unity will add TelepathyTransport to your NetworkManager GameObject +3. Add a MultiplexTransport component to the gameobject +4. Assign the MultiplexTransport component in your NetworkManager's transport +5. Add a WebsocketTransport component to the gameobject +6. Add the TelepathyTransport component to the MultiplexTransport as the first transport +7. Add the WebsocketTransport component to the MultiplexTransport as the second transport + +Please note that Telepathy and WebsocketTransport cannot listen to the same port. By default Telepathy listens to 7777 and the websocket transport listens to 7778. + +If you build your game as a webgl game, the TelepathyTransport will be skipped and your client will use the websocket transport. If you build your game as a mobile or desktop app, it will choose TelepathyTransport. The server will happily accept connections from both. + +Here is what the configuration should look like: + +![Multiplex Example](MultiplexSample.png) diff --git a/docs/Transports/SteamTransport.PNG b/docs/Transports/SteamTransport.PNG new file mode 100644 index 000000000..f925aafea Binary files /dev/null and b/docs/Transports/SteamTransport.PNG differ diff --git a/docs/Transports/WebSockets.md b/docs/Transports/WebSockets.md new file mode 100644 index 000000000..e089df80a --- /dev/null +++ b/docs/Transports/WebSockets.md @@ -0,0 +1,5 @@ +# WebSockets + +General description of WebSockets + +![The WebSockets Transport component in the Inspector window](WebsocketTransport.PNG) diff --git a/docs/Transports/WebsocketTransport.PNG b/docs/Transports/WebsocketTransport.PNG new file mode 100644 index 000000000..8faca4264 Binary files /dev/null and b/docs/Transports/WebsocketTransport.PNG differ diff --git a/docs/Transports/index.md b/docs/Transports/index.md index 7c2805563..c1319f3fa 100644 --- a/docs/Transports/index.md +++ b/docs/Transports/index.md @@ -1,12 +1,20 @@ +# Transports +Mirror is a high level Networking Library that can use several different low level transports. +To use a transport, simply add it as component to the NetworkManager and drag it into the NetworkManager's Transport field. + # Transports Overview - [TCP - Telepathy](Telepathy) Simple, message based, MMO Scale TCP networking in C#. And no magic. - [UDP - Ignorance](Ignorance) - Ignorance implements a reliable and unreliable sequenced UDP transport. -- [WebGL - Groove](Groove) + Ignorance implements a reliable and unreliable sequenced UDP transport based on ENet. +- [UDP - LiteNetLib4Mirror](LiteNetLib4Mirror) + LiteNetLib4Mirror implements a UDP transport based on LiteNetLib with Network Discovery and uPnP included. +- [WebGL - WebSockets](WebSockets) WebSockets transport layer for Mirror that target WebGL clients, without relying on Unity's stodgy old LLAPI. - [Steam - Fizzy](Fizzy) A complete rebuild utilising Async (Previously SteamNetNetworkTransport) of a Steam P2P network transport layer. +- [Multiplexer - Multiplexer](Multiplexer) + Multiplexer is a bridging transport to allow a server to handle clients on different transports concurrnently, for example desktop clients using Telepathy together with WebGL clients using Websockets. - [Socket Server - Insight](Insight) Insight is a simple Socket Server for Unity and Mirror. diff --git a/docs/_config.yml b/docs/_config.yml index ffff83c47..f9b896ebd 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -29,9 +29,6 @@ plugins: - jekyll-feed - jekyll-sitemap -links: - - github: vis2k/Mirror - # A list of the pages to display in the navigation bar header_pages: diff --git a/docs/_data/navigation.yml b/docs/_data/navigation.yml index 578890441..01fa0083d 100644 --- a/docs/_data/navigation.yml +++ b/docs/_data/navigation.yml @@ -2,14 +2,18 @@ children: - title: "Overview" url: "/General" - - title: "Why TCP?" - url: "/General/WhyTCP" - title: "Getting Started" url: "/General/Start" - title: "Migration Guide" url: "/General/Migration" - title: "Deprecations" url: "/General/Deprecations" + - title: "ChangeLog" + url: "/General/ChangeLog" + - title: "Showcase" + url: "/General/Showcase" + - title: "Why TCP?" + url: "/General/WhyTCP" - title: "Support" url: "/General/Support" - title: "Concepts" @@ -22,6 +26,8 @@ url: "/Concepts/IDs" - title: "Clients and Servers" url: "/Concepts/ClientsServers" + - title: "Authentication" + url: "/Concepts/Authentication" - title: "Conversion" url: "/Concepts/Conversion" - title: "Debugging" @@ -136,10 +142,14 @@ url: "/Transports/Telepathy" - title: "UDP - Ignorance" url: "/Transports/Ignorance" - - title: "WebGL - Groove" - url: "/Transports/Groove" + - title: "UDP - LiteNetLib4Mirror" + url: "/Transports/LiteNetLib4Mirror" + - title: "WebGL - WebSockets" + url: "/Transports/WebSockets" - title: "Steam - Fizzy" url: "/Transports/Fizzy" + - title: "Multiplexer - Multiplexer" + url: "/Transports/Multiplexer" - title: "Socket - Insight" url: "/Transports/Insight" - title: "Sample Projects" diff --git a/docs/_includes/header.html b/docs/_includes/header.html index 5f62b1bce..11ee222d1 100644 --- a/docs/_includes/header.html +++ b/docs/_includes/header.html @@ -15,14 +15,9 @@ Home (current) - \ No newline at end of file