From d67dc74bbd26532f6bfce66bd27ada2e346a1e70 Mon Sep 17 00:00:00 2001 From: Robin Rolf Date: Wed, 23 Feb 2022 05:50:15 +0100 Subject: [PATCH 001/824] feat: Allow generic NetworkBehaviour subclasses (#3073) * feat: Allow generic NetworkBehaviour subclasses It's only generic SyncVars (via attribute) and rpcs/cmds we don't want to deal with and that aren't supported. Even generic SyncVar works * Generate IL2CPP compatible base calls see https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45 * Make SyncVar field/hook references generic too Fixes bad IL * Update Extensions.cs * Update Assets/Mirror/Editor/Weaver/Extensions.cs Co-authored-by: vis2k --- Assets/Mirror/Editor/Weaver/Extensions.cs | 82 ++++++++++++++++ .../Processors/NetworkBehaviourProcessor.cs | 41 +++++--- .../Weaver/Processors/SyncObjectProcessor.cs | 6 ++ .../Processors/SyncVarAttributeProcessor.cs | 95 ++++++++++++++++--- Assets/Mirror/Editor/Weaver/Resolvers.cs | 11 ++- .../Weaver/WeaverNetworkBehaviourTests.cs | 13 ++- .../NetworkBehaviourGeneric.cs | 16 ++++ .../NetworkBehaviourGeneric.cs.meta | 3 + ...neric.cs => NetworkBehaviourGenericRpc.cs} | 3 +- .../NetworkBehaviourGenericSyncVar.cs | 12 +++ 10 files changed, 249 insertions(+), 33 deletions(-) create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourGeneric.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourGeneric.cs.meta rename Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/{NetworkBehaviourGeneric.cs => NetworkBehaviourGenericRpc.cs} (72%) create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericSyncVar.cs diff --git a/Assets/Mirror/Editor/Weaver/Extensions.cs b/Assets/Mirror/Editor/Weaver/Extensions.cs index a14e2b7e030..e5ddb1fc5b3 100644 --- a/Assets/Mirror/Editor/Weaver/Extensions.cs +++ b/Assets/Mirror/Editor/Weaver/Extensions.cs @@ -140,6 +140,18 @@ public static MethodReference MakeHostInstanceGeneric(this MethodReference self, return module.ImportReference(reference); } + // needed for NetworkBehaviour support + // https://github.com/vis2k/Mirror/pull/3073/ + public static FieldReference MakeHostInstanceGeneric(this FieldReference self) + { + var declaringType = new GenericInstanceType(self.DeclaringType); + foreach (var parameter in self.DeclaringType.GenericParameters) + { + declaringType.GenericArguments.Add(parameter); + } + return new FieldReference(self.Name, self.FieldType, declaringType); + } + // Given a field of a generic class such as Writer.write, // and a generic instance such as ArraySegment`int // Creates a reference to the specialized method ArraySegment`int`.get_Count @@ -251,5 +263,75 @@ public static AssemblyNameReference FindReference(this ModuleDefinition module, } return null; } + + // Takes generic arguments from child class and applies them to parent reference, if possible + // eg makes `Base` in Child : Base have `int` instead of `T` + // Originally by James-Frowen under MIT + // https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45 + public static TypeReference ApplyGenericParameters(this TypeReference parentReference, + TypeReference childReference) + { + // If the parent is not generic, we got nothing to apply + if (!parentReference.IsGenericInstance) + return parentReference; + + GenericInstanceType parentGeneric = (GenericInstanceType)parentReference; + // make new type so we can replace the args on it + // resolve it so we have non-generic instance (eg just instance with instead of ) + // if we don't cecil will make it double generic (eg INVALID IL) + GenericInstanceType generic = new GenericInstanceType(parentReference.Resolve()); + foreach (TypeReference arg in parentGeneric.GenericArguments) + generic.GenericArguments.Add(arg); + + for (int i = 0; i < generic.GenericArguments.Count; i++) + { + // if arg is not generic + // eg List would be int so not generic. + // But List would be T so is generic + if (!generic.GenericArguments[i].IsGenericParameter) + continue; + + // get the generic name, eg T + string name = generic.GenericArguments[i].Name; + // find what type T is, eg turn it into `int` if `List` + TypeReference arg = FindMatchingGenericArgument(childReference, name); + + // import just to be safe + TypeReference imported = parentReference.Module.ImportReference(arg); + // set arg on generic, parent ref will be Base instead of just Base + generic.GenericArguments[i] = imported; + } + + return generic; + } + + // Finds the type reference for a generic parameter with the provided name in the child reference + // Originally by James-Frowen under MIT + // https://github.com/MirageNet/Mirage/commit/cf91e1d54796866d2cf87f8e919bb5c681977e45 + static TypeReference FindMatchingGenericArgument(TypeReference childReference, string paramName) + { + TypeDefinition def = childReference.Resolve(); + // child class must be generic if we are in this part of the code + // eg Child : Base <--- child must have generic if Base has T + // vs Child : Base <--- wont be here if Base has int (we check if T exists before calling this) + if (!def.HasGenericParameters) + throw new InvalidOperationException( + "Base class had generic parameters, but could not find them in child class"); + + // go through parameters in child class, and find the generic that matches the name + for (int i = 0; i < def.GenericParameters.Count; i++) + { + GenericParameter param = def.GenericParameters[i]; + if (param.Name == paramName) + { + GenericInstanceType generic = (GenericInstanceType)childReference; + // return generic arg with same index + return generic.GenericArguments[i]; + } + } + + // this should never happen, if it does it means that this code is bugged + throw new InvalidOperationException("Did not find matching generic"); + } } } diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index 626353b0687..06792d603ac 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -68,14 +68,6 @@ public bool Process(ref bool WeavingFailed) return false; } - if (netBehaviourSubclass.HasGenericParameters) - { - Log.Error($"{netBehaviourSubclass.Name} cannot have generic parameters", netBehaviourSubclass); - WeavingFailed = true; - // originally Process returned true in every case, except if already processed. - // maybe return false here in the future. - return true; - } MarkAsProcessed(netBehaviourSubclass); // deconstruct tuple and set fields @@ -437,8 +429,13 @@ void GenerateSerialization(ref bool WeavingFailed) worker.Emit(OpCodes.Ldarg_2); worker.Emit(OpCodes.Brfalse, initialStateLabel); - foreach (FieldDefinition syncVar in syncVars) + foreach (FieldDefinition syncVarDef in syncVars) { + FieldReference syncVar = syncVarDef; + if (netBehaviourSubclass.HasGenericParameters) + { + syncVar = syncVarDef.MakeHostInstanceGeneric(); + } // Generates a writer call for each sync variable // writer worker.Emit(OpCodes.Ldarg_1); @@ -481,8 +478,14 @@ void GenerateSerialization(ref bool WeavingFailed) // start at number of syncvars in parent int dirtyBit = syncVarAccessLists.GetSyncVarStart(netBehaviourSubclass.BaseType.FullName); - foreach (FieldDefinition syncVar in syncVars) + foreach (FieldDefinition syncVarDef in syncVars) { + + FieldReference syncVar = syncVarDef; + if (netBehaviourSubclass.HasGenericParameters) + { + syncVar = syncVarDef.MakeHostInstanceGeneric(); + } Instruction varLabel = worker.Create(OpCodes.Nop); // Generates: if ((base.get_syncVarDirtyBits() & 1uL) != 0uL) @@ -539,7 +542,15 @@ void DeserializeField(FieldDefinition syncVar, ILProcessor worker, ref bool Weav // push 'ref T this.field' worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, syncVar); + // if the netbehaviour class is generic, we need to make the field reference generic as well for correct IL + if (netBehaviourSubclass.HasGenericParameters) + { + worker.Emit(OpCodes.Ldflda, syncVar.MakeHostInstanceGeneric()); + } + else + { + worker.Emit(OpCodes.Ldflda, syncVar); + } // hook? then push 'new Action(Hook)' onto stack MethodDefinition hookMethod = syncVarAttributeProcessor.GetHookMethod(netBehaviourSubclass, syncVar, ref WeavingFailed); @@ -821,6 +832,14 @@ bool ValidateParameters(MethodReference method, RemoteCallType callType, ref boo // validate parameters for a remote function call like Rpc/Cmd bool ValidateParameter(MethodReference method, ParameterDefinition param, RemoteCallType callType, bool firstParam, ref bool WeavingFailed) { + // need to check this before any type lookups since those will fail since generic types don't resolve + if (param.ParameterType.IsGenericParameter) + { + Log.Error($"{method.Name} cannot have generic parameters", method); + WeavingFailed = true; + return false; + } + bool isNetworkConnection = param.ParameterType.Is(); bool isSenderConnection = IsSenderConnection(param, callType); diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs index d07345b4bc1..2cec8a4861c 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncObjectProcessor.cs @@ -16,6 +16,12 @@ public static List FindSyncObjectsFields(Writers writers, Reade foreach (FieldDefinition fd in td.Fields) { + if (fd.FieldType.IsGenericParameter) + { + // can't call .Resolve on generic ones + continue; + } + if (fd.FieldType.Resolve().IsDerivedFrom()) { if (fd.IsStatic) diff --git a/Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeProcessor.cs index 63c1e599c59..a5f95cd3088 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/SyncVarAttributeProcessor.cs @@ -69,17 +69,28 @@ public void GenerateNewActionFromHookMethod(FieldDefinition syncVar, ILProcessor worker.Emit(OpCodes.Ldarg_0); } + MethodReference hookMethodReference; + // if the network behaviour class is generic, we need to make the method reference generic for correct IL + if (hookMethod.DeclaringType.HasGenericParameters) + { + hookMethodReference = hookMethod.MakeHostInstanceGeneric(hookMethod.Module, hookMethod.DeclaringType.MakeGenericInstanceType(hookMethod.DeclaringType.GenericParameters.ToArray())); + } + else + { + hookMethodReference = hookMethod; + } + // we support regular and virtual hook functions. if (hookMethod.IsVirtual) { // for virtual / overwritten hooks, we need different IL. // this is from simply testing Action = VirtualHook; in C#. worker.Emit(OpCodes.Dup); - worker.Emit(OpCodes.Ldvirtftn, hookMethod); + worker.Emit(OpCodes.Ldvirtftn, hookMethodReference); } else { - worker.Emit(OpCodes.Ldftn, hookMethod); + worker.Emit(OpCodes.Ldftn, hookMethodReference); } // call 'new Action()' constructor to convert the function to an action @@ -143,6 +154,29 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina ILProcessor worker = get.Body.GetILProcessor(); + FieldReference fr; + if (fd.DeclaringType.HasGenericParameters) + { + fr = fd.MakeHostInstanceGeneric(); + } + else + { + fr = fd; + } + + FieldReference netIdFieldReference = null; + if (netFieldId != null) + { + if (netFieldId.DeclaringType.HasGenericParameters) + { + netIdFieldReference = netFieldId.MakeHostInstanceGeneric(); + } + else + { + netIdFieldReference = netFieldId; + } + } + // [SyncVar] GameObject? if (fd.FieldType.Is()) { @@ -150,9 +184,9 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina // this. worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldfld, netFieldId); + worker.Emit(OpCodes.Ldfld, netIdFieldReference); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, fd); + worker.Emit(OpCodes.Ldflda, fr); worker.Emit(OpCodes.Call, weaverTypes.getSyncVarGameObjectReference); worker.Emit(OpCodes.Ret); } @@ -163,9 +197,9 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina // this. worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldfld, netFieldId); + worker.Emit(OpCodes.Ldfld, netIdFieldReference); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, fd); + worker.Emit(OpCodes.Ldflda, fr); worker.Emit(OpCodes.Call, weaverTypes.getSyncVarNetworkIdentityReference); worker.Emit(OpCodes.Ret); } @@ -175,9 +209,9 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina // this. worker.Emit(OpCodes.Ldarg_0); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldfld, netFieldId); + worker.Emit(OpCodes.Ldfld, netIdFieldReference); worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, fd); + worker.Emit(OpCodes.Ldflda, fr); MethodReference getFunc = weaverTypes.getSyncVarNetworkBehaviourReference.MakeGeneric(assembly.MainModule, fd.FieldType); worker.Emit(OpCodes.Call, getFunc); worker.Emit(OpCodes.Ret); @@ -186,7 +220,7 @@ public MethodDefinition GenerateSyncVarGetter(FieldDefinition fd, string origina else { worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldfld, fd); + worker.Emit(OpCodes.Ldfld, fr); worker.Emit(OpCodes.Ret); } @@ -215,6 +249,28 @@ public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition weaverTypes.Import(typeof(void))); ILProcessor worker = set.Body.GetILProcessor(); + FieldReference fr; + if (fd.DeclaringType.HasGenericParameters) + { + fr = fd.MakeHostInstanceGeneric(); + } + else + { + fr = fd; + } + + FieldReference netIdFieldReference = null; + if (netFieldId != null) + { + if (netFieldId.DeclaringType.HasGenericParameters) + { + netIdFieldReference = netFieldId.MakeHostInstanceGeneric(); + } + else + { + netIdFieldReference = netFieldId; + } + } // if (!SyncVarEqual(value, ref playerData)) Instruction endOfMethod = worker.Create(OpCodes.Nop); @@ -241,7 +297,7 @@ public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition // push 'ref T this.field' worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, fd); + worker.Emit(OpCodes.Ldflda, fr); // push the dirty bit for this SyncVar worker.Emit(OpCodes.Ldc_I8, dirtyBit); @@ -265,14 +321,14 @@ public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition { // GameObject setter needs one more parameter: netId field ref worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, netFieldId); + worker.Emit(OpCodes.Ldflda, netIdFieldReference); worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_GameObject); } else if (fd.FieldType.Is()) { // NetworkIdentity setter needs one more parameter: netId field ref worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, netFieldId); + worker.Emit(OpCodes.Ldflda, netIdFieldReference); worker.Emit(OpCodes.Call, weaverTypes.generatedSyncVarSetter_NetworkIdentity); } // TODO this only uses the persistent netId for types DERIVED FROM NB. @@ -283,7 +339,7 @@ public MethodDefinition GenerateSyncVarSetter(TypeDefinition td, FieldDefinition // NetworkIdentity setter needs one more parameter: netId field ref // (actually its a NetworkBehaviourSyncVar type) worker.Emit(OpCodes.Ldarg_0); - worker.Emit(OpCodes.Ldflda, netFieldId); + worker.Emit(OpCodes.Ldflda, netIdFieldReference); // make generic version of GeneratedSyncVarSetter_NetworkBehaviour MethodReference getFunc = weaverTypes.generatedSyncVarSetter_NetworkBehaviour_T.MakeGeneric(assembly.MainModule, fd.FieldType); worker.Emit(OpCodes.Call, getFunc); @@ -315,16 +371,18 @@ public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary()) { netIdField = new FieldDefinition($"___{fd.Name}NetId", - FieldAttributes.Private, + FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed weaverTypes.Import()); + netIdField.DeclaringType = td; syncVarNetIds[fd] = netIdField; } else if (fd.FieldType.IsNetworkIdentityField()) { netIdField = new FieldDefinition($"___{fd.Name}NetId", - FieldAttributes.Private, + FieldAttributes.Family, // needs to be protected for generic classes, otherwise access isn't allowed weaverTypes.Import()); + netIdField.DeclaringType = td; syncVarNetIds[fd] = netIdField; } @@ -377,6 +435,13 @@ public void ProcessSyncVar(TypeDefinition td, FieldDefinition fd, Dictionary : NetworkBehaviour + { + public T param; + public readonly SyncVar syncVar = new SyncVar(default); + public readonly SyncList syncList = new SyncList(); + } + + class GenericImplInt : NetworkBehaviourGeneric + { + + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourGeneric.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourGeneric.cs.meta new file mode 100644 index 00000000000..15cb3c69ef9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourGeneric.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b279c2f37eaa4de09c1f15a5b80029b4 +timeCreated: 1643560588 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericRpc.cs similarity index 72% rename from Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs rename to Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericRpc.cs index c6deba0bbea..cee6c6357b9 100644 --- a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericRpc.cs @@ -4,6 +4,7 @@ namespace WeaverNetworkBehaviourTests.NetworkBehaviourGeneric { class NetworkBehaviourGeneric : NetworkBehaviour { - T genericsNotAllowed; + [ClientRpc] + void RpcGeneric(T param) {} } } diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericSyncVar.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericSyncVar.cs new file mode 100644 index 00000000000..a2ce78df1b0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGenericSyncVar.cs @@ -0,0 +1,12 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourGeneric +{ + class NetworkBehaviourGeneric : NetworkBehaviour + { + [SyncVar] + T genericSyncVarNotAllowed; + + T genericTypeIsFine; + } +} From a88b03739f0d5716f060bf5c97451d274f96b659 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 23 Feb 2022 14:57:25 -0500 Subject: [PATCH 002/824] fix: NetworkConnectionToClient in Authenticators --- Assets/Mirror/Authenticators/BasicAuthenticator.cs | 4 ++-- Assets/Mirror/Authenticators/DeviceAuthenticator.cs | 2 +- Assets/Mirror/Authenticators/TimeoutAuthenticator.cs | 2 +- Assets/Mirror/Runtime/NetworkAuthenticator.cs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Assets/Mirror/Authenticators/BasicAuthenticator.cs b/Assets/Mirror/Authenticators/BasicAuthenticator.cs index dea92cadb56..f66b7b89ece 100644 --- a/Assets/Mirror/Authenticators/BasicAuthenticator.cs +++ b/Assets/Mirror/Authenticators/BasicAuthenticator.cs @@ -63,7 +63,7 @@ public override void OnStopServer() /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate /// /// Connection to client. - public override void OnServerAuthenticate(NetworkConnection conn) + public override void OnServerAuthenticate(NetworkConnectionToClient conn) { // do nothing...wait for AuthRequestMessage from client } @@ -115,7 +115,7 @@ public void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMess } } - IEnumerator DelayedDisconnect(NetworkConnection conn, float waitTime) + IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime) { yield return new WaitForSeconds(waitTime); diff --git a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs index 8f8b0490ebc..a6fe5da8ba1 100644 --- a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs +++ b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs @@ -50,7 +50,7 @@ public override void OnStopServer() /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate /// /// Connection to client. - public override void OnServerAuthenticate(NetworkConnection conn) + public override void OnServerAuthenticate(NetworkConnectionToClient conn) { // do nothing, wait for client to send his id } diff --git a/Assets/Mirror/Authenticators/TimeoutAuthenticator.cs b/Assets/Mirror/Authenticators/TimeoutAuthenticator.cs index 2680f079ecf..711ee6e3759 100644 --- a/Assets/Mirror/Authenticators/TimeoutAuthenticator.cs +++ b/Assets/Mirror/Authenticators/TimeoutAuthenticator.cs @@ -41,7 +41,7 @@ public override void OnStopClient() authenticator.OnStopClient(); } - public override void OnServerAuthenticate(NetworkConnection conn) + public override void OnServerAuthenticate(NetworkConnectionToClient conn) { authenticator.OnServerAuthenticate(conn); if (timeout > 0) diff --git a/Assets/Mirror/Runtime/NetworkAuthenticator.cs b/Assets/Mirror/Runtime/NetworkAuthenticator.cs index 92d32c0fd34..77c2a8019a4 100644 --- a/Assets/Mirror/Runtime/NetworkAuthenticator.cs +++ b/Assets/Mirror/Runtime/NetworkAuthenticator.cs @@ -26,14 +26,14 @@ public virtual void OnStartServer() {} public virtual void OnStopServer() {} /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate - public abstract void OnServerAuthenticate(NetworkConnection conn); + public abstract void OnServerAuthenticate(NetworkConnectionToClient conn); protected void ServerAccept(NetworkConnectionToClient conn) { OnServerAuthenticated.Invoke(conn); } - protected void ServerReject(NetworkConnection conn) + protected void ServerReject(NetworkConnectionToClient conn) { conn.Disconnect(); } From 44dfce94b19782666981f0388d274babc0c2a2e8 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Thu, 24 Feb 2022 08:22:33 -0500 Subject: [PATCH 003/824] fix: Chat Example Authenticator --- Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs index 1b75a532564..b9fdf5bc4b5 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs @@ -61,7 +61,7 @@ public override void OnStopServer() /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate /// /// Connection to client. - public override void OnServerAuthenticate(NetworkConnection conn) + public override void OnServerAuthenticate(NetworkConnectionToClient conn) { // do nothing...wait for AuthRequestMessage from client } @@ -121,7 +121,7 @@ public void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMess } } - IEnumerator DelayedDisconnect(NetworkConnection conn, float waitTime) + IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime) { yield return new WaitForSeconds(waitTime); From c1e05d9df40fa84d60d4c40074d60d91868a6090 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 25 Feb 2022 09:46:23 -0500 Subject: [PATCH 004/824] fix: NetworkAuthenticator methods virtual - abstract throws an error for derived classes that wrap server methods in UNITY_SERVER or similar as missing required override. --- Assets/Mirror/Runtime/NetworkAuthenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkAuthenticator.cs b/Assets/Mirror/Runtime/NetworkAuthenticator.cs index 77c2a8019a4..9f99b507046 100644 --- a/Assets/Mirror/Runtime/NetworkAuthenticator.cs +++ b/Assets/Mirror/Runtime/NetworkAuthenticator.cs @@ -26,7 +26,7 @@ public virtual void OnStartServer() {} public virtual void OnStopServer() {} /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate - public abstract void OnServerAuthenticate(NetworkConnectionToClient conn); + public virtual void OnServerAuthenticate(NetworkConnectionToClient conn) {} protected void ServerAccept(NetworkConnectionToClient conn) { @@ -45,7 +45,7 @@ public virtual void OnStartClient() {} public virtual void OnStopClient() {} /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate - public abstract void OnClientAuthenticate(); + public virtual void OnClientAuthenticate() {} protected void ClientAccept() { From 66eff281c7a6299da548352778b97082847a2fcb Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 26 Feb 2022 15:26:04 +0800 Subject: [PATCH 005/824] breaking: removed unnecessary 'initialize' parameter from InterestManagement.OnRebuildObservers --- .../Distance/DistanceInterestManagement.cs | 2 +- .../InterestManagement/Match/MatchInterestManagement.cs | 2 +- .../InterestManagement/Scene/SceneInterestManagement.cs | 2 +- .../SpatialHashing/SpatialHashingInterestManagement.cs | 2 +- .../InterestManagement/Team/TeamInterestManagement.cs | 5 ++--- Assets/Mirror/Runtime/InterestManagement.cs | 2 +- Assets/Mirror/Runtime/NetworkServer.cs | 2 +- 7 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs index 89bc9845f43..116051b8a8b 100644 --- a/Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/Distance/DistanceInterestManagement.cs @@ -32,7 +32,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection return Vector3.Distance(identity.transform.position, newObserver.identity.transform.position) < range; } - public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers) { // cache range and .transform because both call GetComponent. int range = GetVisRange(identity); diff --git a/Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs index 65ae6d4b121..ff2eadc137b 100644 --- a/Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/Match/MatchInterestManagement.cs @@ -137,7 +137,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection return identityNetworkMatch.matchId == newObserverNetworkMatch.matchId; } - public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers) { if (!identity.TryGetComponent(out NetworkMatch networkMatch)) return; diff --git a/Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs index 94638b22c5c..8cbfa3bb868 100644 --- a/Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/Scene/SceneInterestManagement.cs @@ -94,7 +94,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection return identity.gameObject.scene == newObserver.identity.gameObject.scene; } - public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers) { if (!sceneObjects.TryGetValue(identity.gameObject.scene, out HashSet objects)) return; diff --git a/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs index b02c9e75c5b..eb4c2c562ac 100644 --- a/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs @@ -52,7 +52,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection return (projected - observerProjected).sqrMagnitude <= 2; } - public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers) { // add everyone in 9 neighbour grid // -> pass observers to GetWithNeighbours directly to avoid allocations diff --git a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs index 3fe284bf4b1..22a8eb01751 100644 --- a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using UnityEngine; namespace Mirror @@ -145,7 +144,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection return identityNetworkTeam.teamId == newObserverNetworkTeam.teamId; } - public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize) + public override void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers) { // If this object doesn't have a NetworkTeam then it's visible to all clients if (!identity.TryGetComponent(out NetworkTeam networkTeam)) diff --git a/Assets/Mirror/Runtime/InterestManagement.cs b/Assets/Mirror/Runtime/InterestManagement.cs index ae53c6379ee..ab149c3242d 100644 --- a/Assets/Mirror/Runtime/InterestManagement.cs +++ b/Assets/Mirror/Runtime/InterestManagement.cs @@ -54,7 +54,7 @@ public virtual void Reset() {} // // Mirror maintains .observing automatically in the background. best of // both worlds without any worrying now! - public abstract void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers, bool initialize); + public abstract void OnRebuildObservers(NetworkIdentity identity, HashSet newObservers); // helper function to trigger a full rebuild. // most implementations should call this in a certain interval. diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 95c5644a537..c8dfbc1298b 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -1442,7 +1442,7 @@ static void RebuildObserversCustom(NetworkIdentity identity, bool initialize) // not force hidden? if (identity.visible != Visibility.ForceHidden) { - aoi.OnRebuildObservers(identity, newObservers, initialize); + aoi.OnRebuildObservers(identity, newObservers); } // IMPORTANT: AFTER rebuilding add own player connection in any case From 3a1d9822bdb0922ddff70d6763b5f3249be091c9 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 26 Feb 2022 14:52:54 -0500 Subject: [PATCH 006/824] Updated Authenticators --- Assets/Mirror/Authenticators/BasicAuthenticator.cs | 2 +- Assets/Mirror/Authenticators/DeviceAuthenticator.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Assets/Mirror/Authenticators/BasicAuthenticator.cs b/Assets/Mirror/Authenticators/BasicAuthenticator.cs index f66b7b89ece..0fdc7e2e0b5 100644 --- a/Assets/Mirror/Authenticators/BasicAuthenticator.cs +++ b/Assets/Mirror/Authenticators/BasicAuthenticator.cs @@ -139,7 +139,7 @@ IEnumerator DelayedDisconnect(NetworkConnectionToClient conn, float waitTime) public override void OnStartClient() { // register a handler for the authentication response we expect from server - NetworkClient.RegisterHandler((Action)OnAuthResponseMessage, false); + NetworkClient.RegisterHandler(OnAuthResponseMessage, false); } /// diff --git a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs index a6fe5da8ba1..6723cc98d16 100644 --- a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs +++ b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs @@ -1,4 +1,4 @@ -using System; +using System; using UnityEngine; namespace Mirror.Authenticators @@ -80,7 +80,7 @@ void OnAuthRequestMessage(NetworkConnectionToClient conn, AuthRequestMessage msg public override void OnStartClient() { // register a handler for the authentication response we expect from server - NetworkClient.RegisterHandler((Action)OnAuthResponseMessage, false); + NetworkClient.RegisterHandler(OnAuthResponseMessage, false); } /// From a51bc743a83faf6c0938fe032875a13fe44216b3 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 26 Feb 2022 15:02:16 -0500 Subject: [PATCH 007/824] syntax --- Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs index b9fdf5bc4b5..bb37d7f9025 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; using UnityEngine; @@ -153,7 +153,7 @@ public void SetPlayername(string username) public override void OnStartClient() { // register a handler for the authentication response we expect from server - NetworkClient.RegisterHandler((Action)OnAuthResponseMessage, false); + NetworkClient.RegisterHandler(OnAuthResponseMessage, false); } /// From d40b2f6be218b1ee12107a8ec184da8bf45e365a Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 7 Mar 2022 19:54:58 +0800 Subject: [PATCH 008/824] sponsor button --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000000..c9515890278 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +github: [vis2k] \ No newline at end of file From 7e198816306642c341dba9d3fd14886e7438c179 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 7 Mar 2022 20:14:17 +0800 Subject: [PATCH 009/824] MIRROR_65_0_OR_NEWER --- Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs | 3 ++- ProjectSettings/ProjectSettings.asset | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs b/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs index 02f158a0e9f..2e973531aab 100644 --- a/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs +++ b/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs @@ -41,7 +41,8 @@ public static void AddDefineSymbols() "MIRROR_53_0_OR_NEWER", "MIRROR_55_0_OR_NEWER", "MIRROR_57_0_OR_NEWER", - "MIRROR_58_0_OR_NEWER" + "MIRROR_58_0_OR_NEWER", + "MIRROR_65_0_OR_NEWER" }; // only touch PlayerSettings if we actually modified it. diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 2a0a23bab58..b72d55b51b7 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -576,7 +576,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLWasmStreaming: 0 scriptingDefineSymbols: - 1: MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER + 1: MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} From 376494e7f5db7631a32975a0c10c6ad7cf461c86 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:46:50 +0800 Subject: [PATCH 010/824] syntax --- Assets/Mirror/Runtime/NetworkClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 730e2901dc7..3a246c4ef61 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1284,7 +1284,9 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me identity.isLocalPlayer = message.isLocalPlayer; if (identity.isLocalPlayer) + { localPlayer = identity; + } else if (localPlayer == identity) { // localPlayer may already be assigned to something else From 401074fc67a3be3ac7af3a838ec4723ec756b125 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:47:43 +0800 Subject: [PATCH 011/824] syntax --- Assets/Mirror/Runtime/NetworkServer.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index c8dfbc1298b..9d51b75a76c 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -814,12 +814,16 @@ public static bool ReplacePlayerForConnection(NetworkConnectionToClient conn, Ga Respawn(identity); if (keepAuthority) + { // This needs to be sent to clear isLocalPlayer on // client while keeping hasAuthority true SendChangeOwnerMessage(previousPlayer, conn); + } else + { // This clears both isLocalPlayer and hasAuthority on client previousPlayer.RemoveClientAuthority(); + } return true; } From a34cb00eb28760773720ff8adaf21000c3bdc61b Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:49:53 +0800 Subject: [PATCH 012/824] comment --- Assets/Mirror/Runtime/NetworkClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 3a246c4ef61..cd047cdb867 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1277,6 +1277,8 @@ internal static void OnChangeOwner(ChangeOwnerMessage message) Debug.LogError($"OnChangeOwner: Could not find object with netId {message.netId}"); } + // ChangeOwnerMessage contains new 'owned' and new 'localPlayer' + // that we need to apply to the identity. internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage message) { identity.hasAuthority = message.isOwner; From c14566c731d8a33aa394e6ee3d414e82c8576158 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:51:12 +0800 Subject: [PATCH 013/824] comments --- Assets/Mirror/Runtime/NetworkClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index cd047cdb867..8f094970e42 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1281,9 +1281,11 @@ internal static void OnChangeOwner(ChangeOwnerMessage message) // that we need to apply to the identity. internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage message) { + // set ownership flag (aka authority) identity.hasAuthority = message.isOwner; identity.NotifyAuthority(); + // set localPlayer flag identity.isLocalPlayer = message.isLocalPlayer; if (identity.isLocalPlayer) { @@ -1296,7 +1298,6 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me localPlayer = null; identity.OnStopLocalPlayer(); } - CheckForLocalPlayer(identity); } From 8bef2bf8641f7b3e999722451802347d5d01dce1 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:51:54 +0800 Subject: [PATCH 014/824] TODO --- Assets/Mirror/Runtime/NetworkClient.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 8f094970e42..6a5de0e02e7 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1283,6 +1283,7 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me { // set ownership flag (aka authority) identity.hasAuthority = message.isOwner; + // TODO call this AFTER OnStopLocalPlayer? clear everything first, then set new values? identity.NotifyAuthority(); // set localPlayer flag From 303b66247e206177bb6502b00fca2b861b7bced0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:54:27 +0800 Subject: [PATCH 015/824] ChangeOwner comments --- Assets/Mirror/Runtime/NetworkClient.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 6a5de0e02e7..926b3394f2e 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1288,14 +1288,15 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me // set localPlayer flag identity.isLocalPlayer = message.isLocalPlayer; + + // identity is now local player. set our static helper field to it. if (identity.isLocalPlayer) { localPlayer = identity; } + // identity's isLocalPlayer was set false. was it local player before? else if (localPlayer == identity) { - // localPlayer may already be assigned to something else - // so only make it null if it's this identity. localPlayer = null; identity.OnStopLocalPlayer(); } From 5846045122a0eb90df0d54bb4ca4545e011c1dab Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 15:55:05 +0800 Subject: [PATCH 016/824] comments --- Assets/Mirror/Runtime/NetworkClient.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 926b3394f2e..8b77475e6b6 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1300,6 +1300,8 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me localPlayer = null; identity.OnStopLocalPlayer(); } + + // call OnStartLocalPlayer if it's the local player now. CheckForLocalPlayer(identity); } From 35ec47d21350c2a6493207e5c877ee60f4af98e0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:02:24 +0800 Subject: [PATCH 017/824] Tests: guarantee OnStopLocalPlayer is called when using RemovePlayerForConnection. for #3106 --- .../Mirror/Tests/Editor/NetworkServerTest.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index 2dea47fcc96..599898f72ea 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1315,5 +1315,26 @@ public void SyncObjectChanges_DontGrowWithoutObservers() // changes should be empty since we have no observers Assert.That(comp.list.GetChangeCount(), Is.EqualTo(0)); } + + [Test] + public void RemovePlayerForConnection_CallsOnStopLocalPlayer() + { + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + + // spawn owned object + CreateNetworkedAndSpawnPlayer( + out _, out NetworkIdentity serverIdentity, out StopLocalPlayerCalledNetworkBehaviour serverComp, + out _, out NetworkIdentity clientIdentity, out StopLocalPlayerCalledNetworkBehaviour clientComp, + connectionToClient); + Assert.That(clientComp.called, Is.EqualTo(0)); + + // set it to not be owned by this connection anymore + NetworkServer.RemovePlayerForConnection(connectionToClient, false); + ProcessMessages(); + + // should call OnStopLocalPlayer on client + Assert.That(clientComp.called, Is.EqualTo(1)); + } } } From 1423bee9d297b6171a427e2111340da39b7b6da7 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:03:46 +0800 Subject: [PATCH 018/824] comment --- Assets/Mirror/Tests/Editor/NetworkServerTest.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index 599898f72ea..f6cc6b337c6 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1316,6 +1316,7 @@ public void SyncObjectChanges_DontGrowWithoutObservers() Assert.That(comp.list.GetChangeCount(), Is.EqualTo(0)); } + // test for https://github.com/vis2k/Mirror/issues/3106 [Test] public void RemovePlayerForConnection_CallsOnStopLocalPlayer() { From fedb6002b60a6e33b74728bb326ec940f9346e67 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:04:30 +0800 Subject: [PATCH 019/824] not necessary --- Assets/Mirror/Tests/Editor/NetworkServerTest.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index f6cc6b337c6..e3ada5a7cc7 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1328,7 +1328,6 @@ public void RemovePlayerForConnection_CallsOnStopLocalPlayer() out _, out NetworkIdentity serverIdentity, out StopLocalPlayerCalledNetworkBehaviour serverComp, out _, out NetworkIdentity clientIdentity, out StopLocalPlayerCalledNetworkBehaviour clientComp, connectionToClient); - Assert.That(clientComp.called, Is.EqualTo(0)); // set it to not be owned by this connection anymore NetworkServer.RemovePlayerForConnection(connectionToClient, false); From e2d1ad0b5e63ef3fb308c0a17197f55992e46625 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:18:40 +0800 Subject: [PATCH 020/824] better comment --- Assets/Mirror/Runtime/NetworkClient.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 8b77475e6b6..ec3e4cc8e23 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1294,7 +1294,8 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me { localPlayer = identity; } - // identity's isLocalPlayer was set false. was it local player before? + // identity's isLocalPlayer was set to false. + // clear our static localPlayer IF (and only IF) it was that one before. else if (localPlayer == identity) { localPlayer = null; From f6df4edaae9bb77990cd17e50e0ff2ba1acf2cdd Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:21:43 +0800 Subject: [PATCH 021/824] fix: #3106 NetworkClient.ChangeOwner now uses .isLocalPlayer flag to check if OnStopLocalPlayer should be called. previously it used localPlayer static identity, but that's error prone because in this case it was set by another function before already. added test to guarantee it never fails again. --- Assets/Mirror/Runtime/NetworkClient.cs | 9 ++++-- .../Mirror/Tests/Editor/NetworkServerTest.cs | 32 +++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index ec3e4cc8e23..204695fa7ba 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1281,9 +1281,15 @@ internal static void OnChangeOwner(ChangeOwnerMessage message) // that we need to apply to the identity. internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage message) { + // local player before, but not anymore? + // call OnStopLocalPlayer before setting new values. + if (identity.isLocalPlayer && !message.isLocalPlayer) + { + identity.OnStopLocalPlayer(); + } + // set ownership flag (aka authority) identity.hasAuthority = message.isOwner; - // TODO call this AFTER OnStopLocalPlayer? clear everything first, then set new values? identity.NotifyAuthority(); // set localPlayer flag @@ -1299,7 +1305,6 @@ internal static void ChangeOwner(NetworkIdentity identity, ChangeOwnerMessage me else if (localPlayer == identity) { localPlayer = null; - identity.OnStopLocalPlayer(); } // call OnStartLocalPlayer if it's the local player now. diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index e3ada5a7cc7..d1d228eed69 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1336,5 +1336,37 @@ public void RemovePlayerForConnection_CallsOnStopLocalPlayer() // should call OnStopLocalPlayer on client Assert.That(clientComp.called, Is.EqualTo(1)); } + + // test for https://github.com/vis2k/Mirror/issues/3106 + [Test] + public void ReplacePlayerForConnection_CallsOnStopLocalPlayer() + { + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + + // spawn owned object + CreateNetworkedAndSpawnPlayer( + out _, out NetworkIdentity serverPreviousIdentity, out StopLocalPlayerCalledNetworkBehaviour serverPreviousComp, + out _, out NetworkIdentity clientPreviousIdentity, out StopLocalPlayerCalledNetworkBehaviour clientPreviousComp, + connectionToClient); + serverPreviousIdentity.name = nameof(serverPreviousIdentity); + clientPreviousIdentity.name = nameof(clientPreviousIdentity); + + // spawn not owned object + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverNextIdentity, out StopLocalPlayerCalledNetworkBehaviour serverNextComp, + out _, out NetworkIdentity clientNextIdentity, out StopLocalPlayerCalledNetworkBehaviour clientNextComp); + serverNextIdentity.name = nameof(serverNextIdentity); + clientNextIdentity.name = nameof(clientNextIdentity); + + // replace connection's player from 'previous' to 'next' + NetworkServer.ReplacePlayerForConnection(connectionToClient, serverNextIdentity.gameObject); + ProcessMessages(); + + // should call OnStopLocalPlayer on 'previous' since it's not owned anymore now. + // should NOT call OnStopLocalPlayer on 'next' since it just became the local player. + Assert.That(clientPreviousComp.called, Is.EqualTo(1)); + Assert.That(clientNextComp.called, Is.EqualTo(0)); + } } } From 70e40b88b5fd7bcd7385d0836685a9a2c37176f9 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:25:28 +0800 Subject: [PATCH 022/824] test for #3106 OnStartLocalPlayer being called twice from ReplacePlayerForConnection --- .../Mirror/Tests/Editor/NetworkServerTest.cs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index d1d228eed69..f42942b9400 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1337,6 +1337,39 @@ public void RemovePlayerForConnection_CallsOnStopLocalPlayer() Assert.That(clientComp.called, Is.EqualTo(1)); } + // test for https://github.com/vis2k/Mirror/issues/3106 + // need to make sure OnStartLocalPlayer is only called ONCE, not TWICE. + [Test] + public void ReplacePlayerForConnection_CallsOnStartLocalPlayer() + { + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + + // spawn owned object + CreateNetworkedAndSpawnPlayer( + out _, out NetworkIdentity serverPreviousIdentity, out StartLocalPlayerCalledNetworkBehaviour serverPreviousComp, + out _, out NetworkIdentity clientPreviousIdentity, out StartLocalPlayerCalledNetworkBehaviour clientPreviousComp, + connectionToClient); + serverPreviousIdentity.name = nameof(serverPreviousIdentity); + clientPreviousIdentity.name = nameof(clientPreviousIdentity); + + // spawn not owned object + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverNextIdentity, out StartLocalPlayerCalledNetworkBehaviour serverNextComp, + out _, out NetworkIdentity clientNextIdentity, out StartLocalPlayerCalledNetworkBehaviour clientNextComp); + serverNextIdentity.name = nameof(serverNextIdentity); + clientNextIdentity.name = nameof(clientNextIdentity); + + // replace connection's player from 'previous' to 'next' + NetworkServer.ReplacePlayerForConnection(connectionToClient, serverNextIdentity.gameObject); + ProcessMessages(); + + // should NOT call OnStartLocalPlayer on 'previous' since it just stopped being the local player + // should call OnStartLocalPlayer on 'next' since it's became the new local player. + Assert.That(clientPreviousComp.called, Is.EqualTo(0)); + Assert.That(clientNextComp.called, Is.EqualTo(1)); + } + // test for https://github.com/vis2k/Mirror/issues/3106 [Test] public void ReplacePlayerForConnection_CallsOnStopLocalPlayer() From 926761c452dcca75c58668489b56802c81252abe Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:33:34 +0800 Subject: [PATCH 023/824] fix test --- Assets/Mirror/Tests/Editor/NetworkServerTest.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index f42942b9400..5c970db7754 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1360,6 +1360,10 @@ public void ReplacePlayerForConnection_CallsOnStartLocalPlayer() serverNextIdentity.name = nameof(serverNextIdentity); clientNextIdentity.name = nameof(clientNextIdentity); + // OnStartLocalPlayer was called when spawning the owned one above. + // reset our counter first. + clientPreviousComp.called = 0; + // replace connection's player from 'previous' to 'next' NetworkServer.ReplacePlayerForConnection(connectionToClient, serverNextIdentity.gameObject); ProcessMessages(); From 006efa090e21bdf1fcdc2af7451a98bf3ec1ea86 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 9 Mar 2022 16:40:31 +0800 Subject: [PATCH 024/824] fix test --- Assets/Mirror/Tests/Editor/NetworkServerTest.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index 5c970db7754..7ad5401a3c9 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1360,17 +1360,11 @@ public void ReplacePlayerForConnection_CallsOnStartLocalPlayer() serverNextIdentity.name = nameof(serverNextIdentity); clientNextIdentity.name = nameof(clientNextIdentity); - // OnStartLocalPlayer was called when spawning the owned one above. - // reset our counter first. - clientPreviousComp.called = 0; - // replace connection's player from 'previous' to 'next' NetworkServer.ReplacePlayerForConnection(connectionToClient, serverNextIdentity.gameObject); ProcessMessages(); - // should NOT call OnStartLocalPlayer on 'previous' since it just stopped being the local player - // should call OnStartLocalPlayer on 'next' since it's became the new local player. - Assert.That(clientPreviousComp.called, Is.EqualTo(0)); + // should call OnStartLocalPlayer on 'next' since it became the new local player. Assert.That(clientNextComp.called, Is.EqualTo(1)); } From f0c9a81c100141d5ec09c7ba8ce5ade75213b7c2 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:37:21 +0800 Subject: [PATCH 025/824] breaking: NetworkReader/WriterPool API simplified: GetReader/Writer => Get; Recycle => Return; (#3112) * breaking: NetworkReader/WriterPool API simplified: GetReader/Writer => Take; Recycle => Return; * rename Take() to Get() * Pool Take() => Get() --- .../Discovery/NetworkDiscoveryBase.cs | 8 +++---- Assets/Mirror/Components/NetworkAnimator.cs | 12 +++++----- .../Weaver/Processors/CommandProcessor.cs | 4 ++-- .../Processors/NetworkBehaviourProcessor.cs | 8 +++---- .../Editor/Weaver/Processors/RpcProcessor.cs | 4 ++-- .../Weaver/Processors/TargetRpcProcessor.cs | 4 ++-- Assets/Mirror/Editor/Weaver/WeaverTypes.cs | 8 +++---- Assets/Mirror/Runtime/Batching/Batcher.cs | 4 ++-- Assets/Mirror/Runtime/Batching/Unbatcher.cs | 4 ++-- .../Mirror/Runtime/LocalConnectionToClient.cs | 2 +- .../Mirror/Runtime/LocalConnectionToServer.cs | 6 ++--- Assets/Mirror/Runtime/NetworkClient.cs | 6 ++--- Assets/Mirror/Runtime/NetworkConnection.cs | 4 ++-- Assets/Mirror/Runtime/NetworkReaderPool.cs | 24 ++++++++++++++----- Assets/Mirror/Runtime/NetworkServer.cs | 10 ++++---- Assets/Mirror/Runtime/NetworkWriterPool.cs | 16 +++++++++---- Assets/Mirror/Runtime/Pool.cs | 6 ++++- .../Mirror/Tests/Editor/MessagePackingTest.cs | 4 ++-- .../Editor/NetworkBehaviourSerializeTest.cs | 4 ++-- .../Mirror/Tests/Editor/NetworkReaderTest.cs | 2 +- Assets/Mirror/Tests/Editor/PoolTests.cs | 4 ++-- Assets/Mirror/Tests/Editor/SyncListTest.cs | 4 ++-- 22 files changed, 86 insertions(+), 62 deletions(-) diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs index 6b64fffc732..8945a7e8c2e 100644 --- a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs @@ -175,7 +175,7 @@ async Task ReceiveRequestAsync(UdpClient udpClient) UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) { long handshake = networkReader.ReadLong(); if (handshake != secretHandshake) @@ -206,7 +206,7 @@ protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint if (info == null) return; - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { try { @@ -369,7 +369,7 @@ public void BroadcastDiscoveryRequest() IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort); - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { writer.WriteLong(secretHandshake); @@ -406,7 +406,7 @@ async Task ReceiveGameBroadcastAsync(UdpClient udpClient) UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(udpReceiveResult.Buffer)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) { if (networkReader.ReadLong() != secretHandshake) return; diff --git a/Assets/Mirror/Components/NetworkAnimator.cs b/Assets/Mirror/Components/NetworkAnimator.cs index 23fc4ecd74a..815be8a52fc 100644 --- a/Assets/Mirror/Components/NetworkAnimator.cs +++ b/Assets/Mirror/Components/NetworkAnimator.cs @@ -106,7 +106,7 @@ void FixedUpdate() continue; } - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { WriteParameters(writer); SendAnimationMessage(stateHash, normalizedTime, i, layerWeight[i], writer.ToArray()); @@ -193,7 +193,7 @@ void CheckSendRate() { nextSendTime = now + syncInterval; - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { if (WriteParameters(writer)) SendAnimationParametersMessage(writer.ToArray()); @@ -527,7 +527,7 @@ void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, int layerI //Debug.Log($"OnAnimationMessage for netId {netId}"); // handle and broadcast - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(parameters)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) { HandleAnimMsg(stateHash, normalizedTime, layerId, weight, networkReader); RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, weight, parameters); @@ -542,7 +542,7 @@ void CmdOnAnimationParametersServerMessage(byte[] parameters) return; // handle and broadcast - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(parameters)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) { HandleAnimParamsMsg(networkReader); RpcOnAnimationParametersClientMessage(parameters); @@ -600,14 +600,14 @@ void CmdSetAnimatorSpeed(float newSpeed) [ClientRpc] void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, int layerId, float weight, byte[] parameters) { - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(parameters)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) HandleAnimMsg(stateHash, normalizedTime, layerId, weight, networkReader); } [ClientRpc] void RpcOnAnimationParametersClientMessage(byte[] parameters) { - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(parameters)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) HandleAnimParamsMsg(networkReader); } diff --git a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs index 35ba2533f74..55893f75677 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/CommandProcessor.cs @@ -38,7 +38,7 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); // NetworkWriter writer = new NetworkWriter(); - NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes); // write all the arguments that the user passed to the Cmd call if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.Command, ref WeavingFailed)) @@ -59,7 +59,7 @@ public static MethodDefinition ProcessCommandCall(WeaverTypes weaverTypes, Write worker.Emit(requiresAuthority ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Call, weaverTypes.sendCommandInternal); - NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); return cmd; diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index 06792d603ac..84e696f22e8 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -140,18 +140,18 @@ public static void WriteSetupLocals(ILProcessor worker, WeaverTypes weaverTypes) worker.Body.Variables.Add(new VariableDefinition(weaverTypes.Import())); } - public static void WriteCreateWriter(ILProcessor worker, WeaverTypes weaverTypes) + public static void WriteGetWriter(ILProcessor worker, WeaverTypes weaverTypes) { // create writer - worker.Emit(OpCodes.Call, weaverTypes.GetPooledWriterReference); + worker.Emit(OpCodes.Call, weaverTypes.GetWriterReference); worker.Emit(OpCodes.Stloc_0); } - public static void WriteRecycleWriter(ILProcessor worker, WeaverTypes weaverTypes) + public static void WriteReturnWriter(ILProcessor worker, WeaverTypes weaverTypes) { // NetworkWriterPool.Recycle(writer); worker.Emit(OpCodes.Ldloc_0); - worker.Emit(OpCodes.Call, weaverTypes.RecycleWriterReference); + worker.Emit(OpCodes.Call, weaverTypes.ReturnWriterReference); } public static bool WriteArguments(ILProcessor worker, Writers writers, Logger Log, MethodDefinition method, RemoteCallType callType, ref bool WeavingFailed) diff --git a/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs index 7413ce5b09b..df44f205c21 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/RpcProcessor.cs @@ -68,7 +68,7 @@ public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers w //worker.Emit(OpCodes.Ldstr, $"Call ClientRpc function {md.Name}"); //worker.Emit(OpCodes.Call, WeaverTypes.logErrorReference); - NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes); // write all the arguments that the user passed to the Rpc call if (!NetworkBehaviourProcessor.WriteArguments(worker, writers, Log, md, RemoteCallType.ClientRpc, ref WeavingFailed)) @@ -89,7 +89,7 @@ public static MethodDefinition ProcessRpcCall(WeaverTypes weaverTypes, Writers w worker.Emit(includeOwner ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); worker.Emit(OpCodes.Callvirt, weaverTypes.sendRpcInternal); - NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); diff --git a/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs index fa2e2a28b23..8afba94ec39 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/TargetRpcProcessor.cs @@ -100,7 +100,7 @@ public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Wri NetworkBehaviourProcessor.WriteSetupLocals(worker, weaverTypes); - NetworkBehaviourProcessor.WriteCreateWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteGetWriter(worker, weaverTypes); // write all the arguments that the user passed to the TargetRpc call // (skip first one if first one is NetworkConnection) @@ -127,7 +127,7 @@ public static MethodDefinition ProcessTargetRpcCall(WeaverTypes weaverTypes, Wri worker.Emit(OpCodes.Ldc_I4, targetRpcAttr.GetField("channel", 0)); worker.Emit(OpCodes.Callvirt, weaverTypes.sendTargetRpcInternal); - NetworkBehaviourProcessor.WriteRecycleWriter(worker, weaverTypes); + NetworkBehaviourProcessor.WriteReturnWriter(worker, weaverTypes); worker.Emit(OpCodes.Ret); diff --git a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs index 935bd36c393..173af58a858 100644 --- a/Assets/Mirror/Editor/Weaver/WeaverTypes.cs +++ b/Assets/Mirror/Editor/Weaver/WeaverTypes.cs @@ -11,8 +11,8 @@ public class WeaverTypes public MethodReference ScriptableObjectCreateInstanceMethod; public MethodReference NetworkBehaviourDirtyBitsReference; - public MethodReference GetPooledWriterReference; - public MethodReference RecycleWriterReference; + public MethodReference GetWriterReference; + public MethodReference ReturnWriterReference; public MethodReference NetworkClientConnectionReference; @@ -95,8 +95,8 @@ public WeaverTypes(AssemblyDefinition assembly, Logger Log, ref bool WeavingFail NetworkBehaviourDirtyBitsReference = Resolvers.ResolveProperty(NetworkBehaviourType, assembly, "syncVarDirtyBits"); TypeReference NetworkWriterPoolType = Import(typeof(NetworkWriterPool)); - GetPooledWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "GetWriter", ref WeavingFailed); - RecycleWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Recycle", ref WeavingFailed); + GetWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Get", ref WeavingFailed); + ReturnWriterReference = Resolvers.ResolveMethod(NetworkWriterPoolType, assembly, Log, "Return", ref WeavingFailed); NetworkClientConnectionReference = Resolvers.ResolveMethod(NetworkClientType, assembly, Log, "get_connection", ref WeavingFailed); diff --git a/Assets/Mirror/Runtime/Batching/Batcher.cs b/Assets/Mirror/Runtime/Batching/Batcher.cs index b77e3125008..3d52405506d 100644 --- a/Assets/Mirror/Runtime/Batching/Batcher.cs +++ b/Assets/Mirror/Runtime/Batching/Batcher.cs @@ -52,7 +52,7 @@ public void AddMessage(ArraySegment message) // would add a size header. we want to write directly. // -> will be returned to pool when making the batch! // IMPORTANT: NOT adding a size header / msg saves LOTS of bandwidth - PooledNetworkWriter writer = NetworkWriterPool.GetWriter(); + PooledNetworkWriter writer = NetworkWriterPool.Get(); writer.WriteBytes(message.Array, message.Offset, message.Count); messages.Enqueue(writer); } @@ -83,7 +83,7 @@ public bool MakeNextBatch(NetworkWriter writer, double timeStamp) writer.WriteBytes(segment.Array, segment.Offset, segment.Count); // return the writer to pool - NetworkWriterPool.Recycle(message); + NetworkWriterPool.Return(message); } // keep going as long as we have more messages, // AND the next one would fit into threshold. diff --git a/Assets/Mirror/Runtime/Batching/Unbatcher.cs b/Assets/Mirror/Runtime/Batching/Unbatcher.cs index be3c77b07a0..dde7df006bd 100644 --- a/Assets/Mirror/Runtime/Batching/Unbatcher.cs +++ b/Assets/Mirror/Runtime/Batching/Unbatcher.cs @@ -55,7 +55,7 @@ public bool AddBatch(ArraySegment batch) // -> WriteBytes instead of WriteSegment because the latter // would add a size header. we want to write directly. // -> will be returned to pool when sending! - PooledNetworkWriter writer = NetworkWriterPool.GetWriter(); + PooledNetworkWriter writer = NetworkWriterPool.Get(); writer.WriteBytes(batch.Array, batch.Offset, batch.Count); // first batch? then point reader there @@ -112,7 +112,7 @@ public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp { // retire the batch PooledNetworkWriter writer = batches.Dequeue(); - NetworkWriterPool.Recycle(writer); + NetworkWriterPool.Return(writer); // do we have another batch? if (batches.Count > 0) diff --git a/Assets/Mirror/Runtime/LocalConnectionToClient.cs b/Assets/Mirror/Runtime/LocalConnectionToClient.cs index 0d816466a29..627edc2dac7 100644 --- a/Assets/Mirror/Runtime/LocalConnectionToClient.cs +++ b/Assets/Mirror/Runtime/LocalConnectionToClient.cs @@ -21,7 +21,7 @@ internal override void Send(ArraySegment segment, int channelId = Channels // => WriteBytes instead of WriteArraySegment because the latter // includes a 4 bytes header. we just want to write raw. //Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}"); - PooledNetworkWriter writer = NetworkWriterPool.GetWriter(); + PooledNetworkWriter writer = NetworkWriterPool.Get(); writer.WriteBytes(segment.Array, segment.Offset, segment.Count); connectionToServer.queue.Enqueue(writer); } diff --git a/Assets/Mirror/Runtime/LocalConnectionToServer.cs b/Assets/Mirror/Runtime/LocalConnectionToServer.cs index 86e53926267..6bbbcd48591 100644 --- a/Assets/Mirror/Runtime/LocalConnectionToServer.cs +++ b/Assets/Mirror/Runtime/LocalConnectionToServer.cs @@ -37,7 +37,7 @@ internal override void Send(ArraySegment segment, int channelId = Channels // flush it to the server's OnTransportData immediately. // local connection to server always invokes immediately. - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) if (batcher.MakeNextBatch(writer, NetworkTime.localTime)) @@ -71,7 +71,7 @@ internal override void Update() Batcher batcher = GetBatchForChannelId(Channels.Reliable); batcher.AddMessage(message); - using (PooledNetworkWriter batchWriter = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter batchWriter = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) if (batcher.MakeNextBatch(batchWriter, NetworkTime.localTime)) @@ -80,7 +80,7 @@ internal override void Update() } } - NetworkWriterPool.Recycle(writer); + NetworkWriterPool.Return(writer); } // should we still process a disconnected event? diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 204695fa7ba..1e3859b6429 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1005,7 +1005,7 @@ internal static void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage me // (Count is 0 if there were no components) if (message.payload.Count > 0) { - using (PooledNetworkReader payloadReader = NetworkReaderPool.GetReader(message.payload)) + using (PooledNetworkReader payloadReader = NetworkReaderPool.Get(message.payload)) { identity.OnDeserializeAllSafely(payloadReader, true); } @@ -1238,7 +1238,7 @@ static void OnEntityStateMessage(EntityStateMessage message) // Debug.Log($"NetworkClient.OnUpdateVarsMessage {msg.netId}"); if (spawned.TryGetValue(message.netId, out NetworkIdentity localObject) && localObject != null) { - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(message.payload)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(message.payload)) localObject.OnDeserializeAllSafely(networkReader, false); } else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); @@ -1249,7 +1249,7 @@ static void OnRPCMessage(RpcMessage message) // Debug.Log($"NetworkClient.OnRPCMessage hash:{msg.functionHash} netId:{msg.netId}"); if (spawned.TryGetValue(message.netId, out NetworkIdentity identity)) { - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(message.payload)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(message.payload)) identity.HandleRemoteCall(message.componentIndex, message.functionHash, RemoteCallType.ClientRpc, networkReader); } } diff --git a/Assets/Mirror/Runtime/NetworkConnection.cs b/Assets/Mirror/Runtime/NetworkConnection.cs index 73ecbfe0ccb..29820604630 100644 --- a/Assets/Mirror/Runtime/NetworkConnection.cs +++ b/Assets/Mirror/Runtime/NetworkConnection.cs @@ -129,7 +129,7 @@ protected static bool ValidatePacketSize(ArraySegment segment, int channel public void Send(T message, int channelId = Channels.Reliable) where T : struct, NetworkMessage { - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // pack message and send allocation free MessagePacking.Pack(message, writer); @@ -176,7 +176,7 @@ internal virtual void Update() // make and send as many batches as necessary from the stored // messages. Batcher batcher = kvp.Value; - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) while (batcher.MakeNextBatch(writer, NetworkTime.localTime)) diff --git a/Assets/Mirror/Runtime/NetworkReaderPool.cs b/Assets/Mirror/Runtime/NetworkReaderPool.cs index beb867fff42..1a4334039fd 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPool.cs +++ b/Assets/Mirror/Runtime/NetworkReaderPool.cs @@ -8,7 +8,7 @@ public sealed class PooledNetworkReader : NetworkReader, IDisposable { internal PooledNetworkReader(byte[] bytes) : base(bytes) {} internal PooledNetworkReader(ArraySegment segment) : base(segment) {} - public void Dispose() => NetworkReaderPool.Recycle(this); + public void Dispose() => NetworkReaderPool.Return(this); } /// Pool of NetworkReaders to avoid allocations. @@ -24,29 +24,41 @@ public static class NetworkReaderPool 1000 ); + // DEPRECATED 2022-03-10 + [Obsolete("GetReader() was renamed to Get()")] + public static PooledNetworkReader GetReader(byte[] bytes) => Get(bytes); + /// Get the next reader in the pool. If pool is empty, creates a new Reader [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkReader GetReader(byte[] bytes) + public static PooledNetworkReader Get(byte[] bytes) { // grab from pool & set buffer - PooledNetworkReader reader = Pool.Take(); + PooledNetworkReader reader = Pool.Get(); reader.SetBuffer(bytes); return reader; } + // DEPRECATED 2022-03-10 + [Obsolete("GetReader() was renamed to Get()")] + public static PooledNetworkReader GetReader(ArraySegment segment) => Get(segment); + /// Get the next reader in the pool. If pool is empty, creates a new Reader [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkReader GetReader(ArraySegment segment) + public static PooledNetworkReader Get(ArraySegment segment) { // grab from pool & set buffer - PooledNetworkReader reader = Pool.Take(); + PooledNetworkReader reader = Pool.Get(); reader.SetBuffer(segment); return reader; } + // DEPRECATED 2022-03-10 + [Obsolete("Recycle() was renamed to Return()")] + public static void Recycle(PooledNetworkReader reader) => Return(reader); + /// Returns a reader to the pool. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Recycle(PooledNetworkReader reader) + public static void Return(PooledNetworkReader reader) { Pool.Return(reader); } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 9d51b75a76c..a5715fb4bdd 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -282,7 +282,7 @@ public static void SendToAll(T message, int channelId = Channels.Reliable, bo } // Debug.Log($"Server.SendToAll {typeof(T)}"); - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // pack message only once MessagePacking.Pack(message, writer); @@ -328,7 +328,7 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI if (identity == null || identity.observers == null || identity.observers.Count == 0) return; - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // pack message into byte[] once MessagePacking.Pack(message, writer); @@ -352,7 +352,7 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message, if (identity == null || identity.observers == null || identity.observers.Count == 0) return; - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { // pack message only once MessagePacking.Pack(message, writer); @@ -960,7 +960,7 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}"); - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(msg.payload)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(msg.payload)) identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); } @@ -996,7 +996,7 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio //Debug.Log($"Server SendSpawnMessage: name:{identity.name} sceneId:{identity.sceneId:X} netid:{identity.netId}"); // one writer for owner, one for observers - using (PooledNetworkWriter ownerWriter = NetworkWriterPool.GetWriter(), observersWriter = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter ownerWriter = NetworkWriterPool.Get(), observersWriter = NetworkWriterPool.Get()) { bool isOwner = identity.connectionToClient == conn; ArraySegment payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter); diff --git a/Assets/Mirror/Runtime/NetworkWriterPool.cs b/Assets/Mirror/Runtime/NetworkWriterPool.cs index 608162d63c4..a7535b86bc7 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPool.cs +++ b/Assets/Mirror/Runtime/NetworkWriterPool.cs @@ -6,7 +6,7 @@ namespace Mirror /// Pooled NetworkWriter, automatically returned to pool when using 'using' public sealed class PooledNetworkWriter : NetworkWriter, IDisposable { - public void Dispose() => NetworkWriterPool.Recycle(this); + public void Dispose() => NetworkWriterPool.Return(this); } /// Pool of NetworkWriters to avoid allocations. @@ -24,19 +24,27 @@ public static class NetworkWriterPool 1000 ); + // DEPRECATED 2022-03-10 + [Obsolete("GetWriter() was renamed to Get()")] + public static PooledNetworkWriter GetWriter() => Get(); + /// Get a writer from the pool. Creates new one if pool is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkWriter GetWriter() + public static PooledNetworkWriter Get() { // grab from pool & reset position - PooledNetworkWriter writer = Pool.Take(); + PooledNetworkWriter writer = Pool.Get(); writer.Reset(); return writer; } + // DEPRECATED 2022-03-10 + [Obsolete("Recycle() was renamed to Return()")] + public static void Recycle(PooledNetworkWriter writer) => Return(writer); + /// Return a writer to the pool. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Recycle(PooledNetworkWriter writer) + public static void Return(PooledNetworkWriter writer) { Pool.Return(writer); } diff --git a/Assets/Mirror/Runtime/Pool.cs b/Assets/Mirror/Runtime/Pool.cs index 230c1b78a70..e3de19be4f6 100644 --- a/Assets/Mirror/Runtime/Pool.cs +++ b/Assets/Mirror/Runtime/Pool.cs @@ -24,9 +24,13 @@ public Pool(Func objectGenerator, int initialCapacity) objects.Push(objectGenerator()); } + // DEPRECATED 2022-03-10 + [Obsolete("Take() was renamed to Get()")] + public T Take() => Get(); + // take an element from the pool, or create a new one if empty [MethodImpl(MethodImplOptions.AggressiveInlining)] - public T Take() => objects.Count > 0 ? objects.Pop() : objectGenerator(); + public T Get() => objects.Count > 0 ? objects.Pop() : objectGenerator(); // return an element to the pool [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs index c473765e50d..c435012e17f 100644 --- a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs +++ b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs @@ -14,7 +14,7 @@ public struct EmptyMessage : NetworkMessage {} public static byte[] PackToByteArray(T message) where T : struct, NetworkMessage { - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { MessagePacking.Pack(message, writer); return writer.ToArray(); @@ -25,7 +25,7 @@ public static byte[] PackToByteArray(T message) public static T UnpackFromByteArray(byte[] data) where T : struct, NetworkMessage { - using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(data)) + using (PooledNetworkReader networkReader = NetworkReaderPool.Get(data)) { int msgType = MessagePacking.GetId(); diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs index e8cb4c3c886..626efe70ac0 100644 --- a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs @@ -125,11 +125,11 @@ public class NetworkBehaviourSerializeTest : MirrorEditModeTest { static void SyncNetworkBehaviour(NetworkBehaviour source, NetworkBehaviour target, bool initialState) { - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { source.OnSerialize(writer, initialState); - using (PooledNetworkReader reader = NetworkReaderPool.GetReader(writer.ToArraySegment())) + using (PooledNetworkReader reader = NetworkReaderPool.Get(writer.ToArraySegment())) { target.OnDeserialize(reader, initialState); } diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs index 49bf1bd48f6..964ba81ff7f 100644 --- a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs @@ -66,7 +66,7 @@ public void ReadBytesCountTooBigTest() // should throw an exception byte[] bytes = { 0x00, 0x01 }; - using (PooledNetworkReader reader = NetworkReaderPool.GetReader(bytes)) + using (PooledNetworkReader reader = NetworkReaderPool.Get(bytes)) { try { diff --git a/Assets/Mirror/Tests/Editor/PoolTests.cs b/Assets/Mirror/Tests/Editor/PoolTests.cs index 2002c8de577..2f692856160 100644 --- a/Assets/Mirror/Tests/Editor/PoolTests.cs +++ b/Assets/Mirror/Tests/Editor/PoolTests.cs @@ -22,7 +22,7 @@ public void TearDown() public void TakeFromEmpty() { // taking from an empty pool should give us a completely new string - Assert.That(pool.Take(), Is.EqualTo("new string")); + Assert.That(pool.Get(), Is.EqualTo("new string")); } [Test] @@ -31,7 +31,7 @@ public void ReturnAndTake() // returning and then taking should get the returned one, not a // newly generated one. pool.Return("returned"); - Assert.That(pool.Take(), Is.EqualTo("returned")); + Assert.That(pool.Get(), Is.EqualTo("returned")); } [Test] diff --git a/Assets/Mirror/Tests/Editor/SyncListTest.cs b/Assets/Mirror/Tests/Editor/SyncListTest.cs index 9e33ccec705..1c211822647 100644 --- a/Assets/Mirror/Tests/Editor/SyncListTest.cs +++ b/Assets/Mirror/Tests/Editor/SyncListTest.cs @@ -420,11 +420,11 @@ public static class SyncObjectTestMethods { public static uint GetChangeCount(this SyncObject syncObject) { - using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + using (PooledNetworkWriter writer = NetworkWriterPool.Get()) { syncObject.OnSerializeDelta(writer); - using (PooledNetworkReader reader = NetworkReaderPool.GetReader(writer.ToArraySegment())) + using (PooledNetworkReader reader = NetworkReaderPool.Get(writer.ToArraySegment())) { return reader.ReadUInt(); } From 0402d31eb22628ded06f59edbae1c2bb510dd184 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:44:13 +0800 Subject: [PATCH 026/824] breaking: PooledNetworkReader/Writer renamed to NetworkReader/WriterPooled --- .../Discovery/NetworkDiscoveryBase.cs | 8 ++--- Assets/Mirror/Components/NetworkAnimator.cs | 12 +++---- .../Processors/NetworkBehaviourProcessor.cs | 2 +- Assets/Mirror/Runtime/Batching/Batcher.cs | 6 ++-- Assets/Mirror/Runtime/Batching/Unbatcher.cs | 10 +++--- .../Mirror/Runtime/LocalConnectionToClient.cs | 2 +- .../Mirror/Runtime/LocalConnectionToServer.cs | 8 ++--- Assets/Mirror/Runtime/NetworkClient.cs | 6 ++-- Assets/Mirror/Runtime/NetworkConnection.cs | 4 +-- Assets/Mirror/Runtime/NetworkReaderPool.cs | 32 ++++++++++++------- Assets/Mirror/Runtime/NetworkServer.cs | 12 +++---- Assets/Mirror/Runtime/NetworkWriterPool.cs | 21 +++++++----- .../Mirror/Tests/Editor/MessagePackingTest.cs | 4 +-- .../Editor/NetworkBehaviourSerializeTest.cs | 4 +-- .../Mirror/Tests/Editor/NetworkReaderTest.cs | 2 +- Assets/Mirror/Tests/Editor/SyncListTest.cs | 4 +-- 16 files changed, 75 insertions(+), 62 deletions(-) diff --git a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs index 8945a7e8c2e..ac57b75070a 100644 --- a/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs +++ b/Assets/Mirror/Components/Discovery/NetworkDiscoveryBase.cs @@ -175,7 +175,7 @@ async Task ReceiveRequestAsync(UdpClient udpClient) UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) { long handshake = networkReader.ReadLong(); if (handshake != secretHandshake) @@ -206,7 +206,7 @@ protected virtual void ProcessClientRequest(Request request, IPEndPoint endpoint if (info == null) return; - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { try { @@ -369,7 +369,7 @@ public void BroadcastDiscoveryRequest() IPEndPoint endPoint = new IPEndPoint(IPAddress.Broadcast, serverBroadcastListenPort); - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { writer.WriteLong(secretHandshake); @@ -406,7 +406,7 @@ async Task ReceiveGameBroadcastAsync(UdpClient udpClient) UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync(); - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(udpReceiveResult.Buffer)) { if (networkReader.ReadLong() != secretHandshake) return; diff --git a/Assets/Mirror/Components/NetworkAnimator.cs b/Assets/Mirror/Components/NetworkAnimator.cs index 815be8a52fc..2360fe39b6c 100644 --- a/Assets/Mirror/Components/NetworkAnimator.cs +++ b/Assets/Mirror/Components/NetworkAnimator.cs @@ -106,7 +106,7 @@ void FixedUpdate() continue; } - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { WriteParameters(writer); SendAnimationMessage(stateHash, normalizedTime, i, layerWeight[i], writer.ToArray()); @@ -193,7 +193,7 @@ void CheckSendRate() { nextSendTime = now + syncInterval; - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { if (WriteParameters(writer)) SendAnimationParametersMessage(writer.ToArray()); @@ -527,7 +527,7 @@ void CmdOnAnimationServerMessage(int stateHash, float normalizedTime, int layerI //Debug.Log($"OnAnimationMessage for netId {netId}"); // handle and broadcast - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(parameters)) { HandleAnimMsg(stateHash, normalizedTime, layerId, weight, networkReader); RpcOnAnimationClientMessage(stateHash, normalizedTime, layerId, weight, parameters); @@ -542,7 +542,7 @@ void CmdOnAnimationParametersServerMessage(byte[] parameters) return; // handle and broadcast - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(parameters)) { HandleAnimParamsMsg(networkReader); RpcOnAnimationParametersClientMessage(parameters); @@ -600,14 +600,14 @@ void CmdSetAnimatorSpeed(float newSpeed) [ClientRpc] void RpcOnAnimationClientMessage(int stateHash, float normalizedTime, int layerId, float weight, byte[] parameters) { - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(parameters)) HandleAnimMsg(stateHash, normalizedTime, layerId, weight, networkReader); } [ClientRpc] void RpcOnAnimationParametersClientMessage(byte[] parameters) { - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(parameters)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(parameters)) HandleAnimParamsMsg(networkReader); } diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index 84e696f22e8..ac00f65b3a3 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -137,7 +137,7 @@ public static void WriteServerActiveCheck(ILProcessor worker, WeaverTypes weaver public static void WriteSetupLocals(ILProcessor worker, WeaverTypes weaverTypes) { worker.Body.InitLocals = true; - worker.Body.Variables.Add(new VariableDefinition(weaverTypes.Import())); + worker.Body.Variables.Add(new VariableDefinition(weaverTypes.Import())); } public static void WriteGetWriter(ILProcessor worker, WeaverTypes weaverTypes) diff --git a/Assets/Mirror/Runtime/Batching/Batcher.cs b/Assets/Mirror/Runtime/Batching/Batcher.cs index 3d52405506d..2d52b7a824c 100644 --- a/Assets/Mirror/Runtime/Batching/Batcher.cs +++ b/Assets/Mirror/Runtime/Batching/Batcher.cs @@ -35,7 +35,7 @@ public class Batcher // batched messages // IMPORTANT: we queue the serialized messages! // queueing NetworkMessage would box and allocate! - Queue messages = new Queue(); + Queue messages = new Queue(); public Batcher(int threshold) { @@ -52,7 +52,7 @@ public void AddMessage(ArraySegment message) // would add a size header. we want to write directly. // -> will be returned to pool when making the batch! // IMPORTANT: NOT adding a size header / msg saves LOTS of bandwidth - PooledNetworkWriter writer = NetworkWriterPool.Get(); + NetworkWriterPooled writer = NetworkWriterPool.Get(); writer.WriteBytes(message.Array, message.Offset, message.Count); messages.Enqueue(writer); } @@ -78,7 +78,7 @@ public bool MakeNextBatch(NetworkWriter writer, double timeStamp) { // add next message no matter what. even if > threshold. // (we do allow > threshold sized messages as single batch) - PooledNetworkWriter message = messages.Dequeue(); + NetworkWriterPooled message = messages.Dequeue(); ArraySegment segment = message.ToArraySegment(); writer.WriteBytes(segment.Array, segment.Offset, segment.Count); diff --git a/Assets/Mirror/Runtime/Batching/Unbatcher.cs b/Assets/Mirror/Runtime/Batching/Unbatcher.cs index dde7df006bd..495ada90ea6 100644 --- a/Assets/Mirror/Runtime/Batching/Unbatcher.cs +++ b/Assets/Mirror/Runtime/Batching/Unbatcher.cs @@ -14,7 +14,7 @@ public class Unbatcher { // supporting adding multiple batches before GetNextMessage is called. // just in case. - Queue batches = new Queue(); + Queue batches = new Queue(); public int BatchesCount => batches.Count; @@ -27,7 +27,7 @@ public class Unbatcher double readerRemoteTimeStamp; // helper function to start reading a batch. - void StartReadingBatch(PooledNetworkWriter batch) + void StartReadingBatch(NetworkWriterPooled batch) { // point reader to it reader.SetBuffer(batch.ToArraySegment()); @@ -55,7 +55,7 @@ public bool AddBatch(ArraySegment batch) // -> WriteBytes instead of WriteSegment because the latter // would add a size header. we want to write directly. // -> will be returned to pool when sending! - PooledNetworkWriter writer = NetworkWriterPool.Get(); + NetworkWriterPooled writer = NetworkWriterPool.Get(); writer.WriteBytes(batch.Array, batch.Offset, batch.Count); // first batch? then point reader there @@ -111,7 +111,7 @@ public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp if (reader.Remaining == 0) { // retire the batch - PooledNetworkWriter writer = batches.Dequeue(); + NetworkWriterPooled writer = batches.Dequeue(); NetworkWriterPool.Return(writer); // do we have another batch? @@ -119,7 +119,7 @@ public bool GetNextMessage(out NetworkReader message, out double remoteTimeStamp { // point reader to the next batch. // we'll return the reader below. - PooledNetworkWriter next = batches.Peek(); + NetworkWriterPooled next = batches.Peek(); StartReadingBatch(next); } // otherwise there's nothing more to read diff --git a/Assets/Mirror/Runtime/LocalConnectionToClient.cs b/Assets/Mirror/Runtime/LocalConnectionToClient.cs index 627edc2dac7..67c964974b2 100644 --- a/Assets/Mirror/Runtime/LocalConnectionToClient.cs +++ b/Assets/Mirror/Runtime/LocalConnectionToClient.cs @@ -21,7 +21,7 @@ internal override void Send(ArraySegment segment, int channelId = Channels // => WriteBytes instead of WriteArraySegment because the latter // includes a 4 bytes header. we just want to write raw. //Debug.Log($"Enqueue {BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}"); - PooledNetworkWriter writer = NetworkWriterPool.Get(); + NetworkWriterPooled writer = NetworkWriterPool.Get(); writer.WriteBytes(segment.Array, segment.Offset, segment.Count); connectionToServer.queue.Enqueue(writer); } diff --git a/Assets/Mirror/Runtime/LocalConnectionToServer.cs b/Assets/Mirror/Runtime/LocalConnectionToServer.cs index 6bbbcd48591..f1428baa254 100644 --- a/Assets/Mirror/Runtime/LocalConnectionToServer.cs +++ b/Assets/Mirror/Runtime/LocalConnectionToServer.cs @@ -11,7 +11,7 @@ public class LocalConnectionToServer : NetworkConnectionToServer internal LocalConnectionToClient connectionToClient; // packet queue - internal readonly Queue queue = new Queue(); + internal readonly Queue queue = new Queue(); public override string address => "localhost"; @@ -37,7 +37,7 @@ internal override void Send(ArraySegment segment, int channelId = Channels // flush it to the server's OnTransportData immediately. // local connection to server always invokes immediately. - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) if (batcher.MakeNextBatch(writer, NetworkTime.localTime)) @@ -63,7 +63,7 @@ internal override void Update() while (queue.Count > 0) { // call receive on queued writer's content, return to pool - PooledNetworkWriter writer = queue.Dequeue(); + NetworkWriterPooled writer = queue.Dequeue(); ArraySegment message = writer.ToArraySegment(); // OnTransportData assumes a proper batch with timestamp etc. @@ -71,7 +71,7 @@ internal override void Update() Batcher batcher = GetBatchForChannelId(Channels.Reliable); batcher.AddMessage(message); - using (PooledNetworkWriter batchWriter = NetworkWriterPool.Get()) + using (NetworkWriterPooled batchWriter = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) if (batcher.MakeNextBatch(batchWriter, NetworkTime.localTime)) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 1e3859b6429..2e1308b10af 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1005,7 +1005,7 @@ internal static void ApplySpawnPayload(NetworkIdentity identity, SpawnMessage me // (Count is 0 if there were no components) if (message.payload.Count > 0) { - using (PooledNetworkReader payloadReader = NetworkReaderPool.Get(message.payload)) + using (NetworkReaderPooled payloadReader = NetworkReaderPool.Get(message.payload)) { identity.OnDeserializeAllSafely(payloadReader, true); } @@ -1238,7 +1238,7 @@ static void OnEntityStateMessage(EntityStateMessage message) // Debug.Log($"NetworkClient.OnUpdateVarsMessage {msg.netId}"); if (spawned.TryGetValue(message.netId, out NetworkIdentity localObject) && localObject != null) { - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(message.payload)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload)) localObject.OnDeserializeAllSafely(networkReader, false); } else Debug.LogWarning($"Did not find target for sync message for {message.netId} . Note: this can be completely normal because UDP messages may arrive out of order, so this message might have arrived after a Destroy message."); @@ -1249,7 +1249,7 @@ static void OnRPCMessage(RpcMessage message) // Debug.Log($"NetworkClient.OnRPCMessage hash:{msg.functionHash} netId:{msg.netId}"); if (spawned.TryGetValue(message.netId, out NetworkIdentity identity)) { - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(message.payload)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload)) identity.HandleRemoteCall(message.componentIndex, message.functionHash, RemoteCallType.ClientRpc, networkReader); } } diff --git a/Assets/Mirror/Runtime/NetworkConnection.cs b/Assets/Mirror/Runtime/NetworkConnection.cs index 29820604630..7e75e843708 100644 --- a/Assets/Mirror/Runtime/NetworkConnection.cs +++ b/Assets/Mirror/Runtime/NetworkConnection.cs @@ -129,7 +129,7 @@ protected static bool ValidatePacketSize(ArraySegment segment, int channel public void Send(T message, int channelId = Channels.Reliable) where T : struct, NetworkMessage { - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // pack message and send allocation free MessagePacking.Pack(message, writer); @@ -176,7 +176,7 @@ internal virtual void Update() // make and send as many batches as necessary from the stored // messages. Batcher batcher = kvp.Value; - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) while (batcher.MakeNextBatch(writer, NetworkTime.localTime)) diff --git a/Assets/Mirror/Runtime/NetworkReaderPool.cs b/Assets/Mirror/Runtime/NetworkReaderPool.cs index 1a4334039fd..ea7823ad4de 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPool.cs +++ b/Assets/Mirror/Runtime/NetworkReaderPool.cs @@ -3,11 +3,19 @@ namespace Mirror { - /// Pooled NetworkReader, automatically returned to pool when using 'using' - public sealed class PooledNetworkReader : NetworkReader, IDisposable + [Obsolete("PooledNetworkReader was renamed to NetworkReaderPooled. It's cleaner & slightly easier to use.")] + public sealed class PooledNetworkReader : NetworkReaderPooled { internal PooledNetworkReader(byte[] bytes) : base(bytes) {} internal PooledNetworkReader(ArraySegment segment) : base(segment) {} + } + + /// Pooled NetworkReader, automatically returned to pool when using 'using' + // TODO make sealed again after removing obsolete NetworkWriterPooled! + public class NetworkReaderPooled : NetworkReader, IDisposable + { + internal NetworkReaderPooled(byte[] bytes) : base(bytes) {} + internal NetworkReaderPooled(ArraySegment segment) : base(segment) {} public void Dispose() => NetworkReaderPool.Return(this); } @@ -17,48 +25,48 @@ public static class NetworkReaderPool // reuse Pool // we still wrap it in NetworkReaderPool.Get/Recyle so we can reset the // position and array before reusing. - static readonly Pool Pool = new Pool( + static readonly Pool Pool = new Pool( // byte[] will be assigned in GetReader - () => new PooledNetworkReader(new byte[]{}), + () => new NetworkReaderPooled(new byte[]{}), // initial capacity to avoid allocations in the first few frames 1000 ); // DEPRECATED 2022-03-10 [Obsolete("GetReader() was renamed to Get()")] - public static PooledNetworkReader GetReader(byte[] bytes) => Get(bytes); + public static NetworkReaderPooled GetReader(byte[] bytes) => Get(bytes); /// Get the next reader in the pool. If pool is empty, creates a new Reader [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkReader Get(byte[] bytes) + public static NetworkReaderPooled Get(byte[] bytes) { // grab from pool & set buffer - PooledNetworkReader reader = Pool.Get(); + NetworkReaderPooled reader = Pool.Get(); reader.SetBuffer(bytes); return reader; } // DEPRECATED 2022-03-10 [Obsolete("GetReader() was renamed to Get()")] - public static PooledNetworkReader GetReader(ArraySegment segment) => Get(segment); + public static NetworkReaderPooled GetReader(ArraySegment segment) => Get(segment); /// Get the next reader in the pool. If pool is empty, creates a new Reader [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkReader Get(ArraySegment segment) + public static NetworkReaderPooled Get(ArraySegment segment) { // grab from pool & set buffer - PooledNetworkReader reader = Pool.Get(); + NetworkReaderPooled reader = Pool.Get(); reader.SetBuffer(segment); return reader; } // DEPRECATED 2022-03-10 [Obsolete("Recycle() was renamed to Return()")] - public static void Recycle(PooledNetworkReader reader) => Return(reader); + public static void Recycle(NetworkReaderPooled reader) => Return(reader); /// Returns a reader to the pool. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(PooledNetworkReader reader) + public static void Return(NetworkReaderPooled reader) { Pool.Return(reader); } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index a5715fb4bdd..04e380dd5d7 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -282,7 +282,7 @@ public static void SendToAll(T message, int channelId = Channels.Reliable, bo } // Debug.Log($"Server.SendToAll {typeof(T)}"); - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // pack message only once MessagePacking.Pack(message, writer); @@ -328,7 +328,7 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI if (identity == null || identity.observers == null || identity.observers.Count == 0) return; - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // pack message into byte[] once MessagePacking.Pack(message, writer); @@ -352,7 +352,7 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message, if (identity == null || identity.observers == null || identity.observers.Count == 0) return; - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // pack message only once MessagePacking.Pack(message, writer); @@ -960,12 +960,12 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}"); - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(msg.payload)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload)) identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); } // spawning //////////////////////////////////////////////////////////// - static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, PooledNetworkWriter ownerWriter, PooledNetworkWriter observersWriter) + static ArraySegment CreateSpawnMessagePayload(bool isOwner, NetworkIdentity identity, NetworkWriterPooled ownerWriter, NetworkWriterPooled observersWriter) { // Only call OnSerializeAllSafely if there are NetworkBehaviours if (identity.NetworkBehaviours.Length == 0) @@ -996,7 +996,7 @@ internal static void SendSpawnMessage(NetworkIdentity identity, NetworkConnectio //Debug.Log($"Server SendSpawnMessage: name:{identity.name} sceneId:{identity.sceneId:X} netid:{identity.netId}"); // one writer for owner, one for observers - using (PooledNetworkWriter ownerWriter = NetworkWriterPool.Get(), observersWriter = NetworkWriterPool.Get()) + using (NetworkWriterPooled ownerWriter = NetworkWriterPool.Get(), observersWriter = NetworkWriterPool.Get()) { bool isOwner = identity.connectionToClient == conn; ArraySegment payload = CreateSpawnMessagePayload(isOwner, identity, ownerWriter, observersWriter); diff --git a/Assets/Mirror/Runtime/NetworkWriterPool.cs b/Assets/Mirror/Runtime/NetworkWriterPool.cs index a7535b86bc7..a68a8246113 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPool.cs +++ b/Assets/Mirror/Runtime/NetworkWriterPool.cs @@ -3,8 +3,13 @@ namespace Mirror { + // DEPRECATED 2022-03-10 + [Obsolete("PooledNetworkWriter was renamed to NetworkWriterPooled. It's cleaner & slightly easier to use.")] + public sealed class PooledNetworkWriter : NetworkWriterPooled {} + /// Pooled NetworkWriter, automatically returned to pool when using 'using' - public sealed class PooledNetworkWriter : NetworkWriter, IDisposable + // TODO make sealed again after removing obsolete NetworkWriterPooled! + public class NetworkWriterPooled : NetworkWriter, IDisposable { public void Dispose() => NetworkWriterPool.Return(this); } @@ -17,8 +22,8 @@ public static class NetworkWriterPool // position before reusing. // this is also more consistent with NetworkReaderPool where we need to // assign the internal buffer before reusing. - static readonly Pool Pool = new Pool( - () => new PooledNetworkWriter(), + static readonly Pool Pool = new Pool( + () => new NetworkWriterPooled(), // initial capacity to avoid allocations in the first few frames // 1000 * 1200 bytes = around 1 MB. 1000 @@ -26,25 +31,25 @@ public static class NetworkWriterPool // DEPRECATED 2022-03-10 [Obsolete("GetWriter() was renamed to Get()")] - public static PooledNetworkWriter GetWriter() => Get(); + public static NetworkWriterPooled GetWriter() => Get(); /// Get a writer from the pool. Creates new one if pool is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static PooledNetworkWriter Get() + public static NetworkWriterPooled Get() { // grab from pool & reset position - PooledNetworkWriter writer = Pool.Get(); + NetworkWriterPooled writer = Pool.Get(); writer.Reset(); return writer; } // DEPRECATED 2022-03-10 [Obsolete("Recycle() was renamed to Return()")] - public static void Recycle(PooledNetworkWriter writer) => Return(writer); + public static void Recycle(NetworkWriterPooled writer) => Return(writer); /// Return a writer to the pool. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(PooledNetworkWriter writer) + public static void Return(NetworkWriterPooled writer) { Pool.Return(writer); } diff --git a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs index c435012e17f..52ad7d73854 100644 --- a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs +++ b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs @@ -14,7 +14,7 @@ public struct EmptyMessage : NetworkMessage {} public static byte[] PackToByteArray(T message) where T : struct, NetworkMessage { - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { MessagePacking.Pack(message, writer); return writer.ToArray(); @@ -25,7 +25,7 @@ public static byte[] PackToByteArray(T message) public static T UnpackFromByteArray(byte[] data) where T : struct, NetworkMessage { - using (PooledNetworkReader networkReader = NetworkReaderPool.Get(data)) + using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(data)) { int msgType = MessagePacking.GetId(); diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs index 626efe70ac0..48065313dcf 100644 --- a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs @@ -125,11 +125,11 @@ public class NetworkBehaviourSerializeTest : MirrorEditModeTest { static void SyncNetworkBehaviour(NetworkBehaviour source, NetworkBehaviour target, bool initialState) { - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { source.OnSerialize(writer, initialState); - using (PooledNetworkReader reader = NetworkReaderPool.Get(writer.ToArraySegment())) + using (NetworkReaderPooled reader = NetworkReaderPool.Get(writer.ToArraySegment())) { target.OnDeserialize(reader, initialState); } diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs index 964ba81ff7f..44caf79a88a 100644 --- a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs @@ -66,7 +66,7 @@ public void ReadBytesCountTooBigTest() // should throw an exception byte[] bytes = { 0x00, 0x01 }; - using (PooledNetworkReader reader = NetworkReaderPool.Get(bytes)) + using (NetworkReaderPooled reader = NetworkReaderPool.Get(bytes)) { try { diff --git a/Assets/Mirror/Tests/Editor/SyncListTest.cs b/Assets/Mirror/Tests/Editor/SyncListTest.cs index 1c211822647..ec21dab12e6 100644 --- a/Assets/Mirror/Tests/Editor/SyncListTest.cs +++ b/Assets/Mirror/Tests/Editor/SyncListTest.cs @@ -420,11 +420,11 @@ public static class SyncObjectTestMethods { public static uint GetChangeCount(this SyncObject syncObject) { - using (PooledNetworkWriter writer = NetworkWriterPool.Get()) + using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { syncObject.OnSerializeDelta(writer); - using (PooledNetworkReader reader = NetworkReaderPool.Get(writer.ToArraySegment())) + using (NetworkReaderPooled reader = NetworkReaderPool.Get(writer.ToArraySegment())) { return reader.ReadUInt(); } From d92dbe1572812d69ca0016c3b1115100ea401e1b Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:45:03 +0800 Subject: [PATCH 027/824] NetworkReaderPooled moved into separate file --- Assets/Mirror/Runtime/NetworkReaderPool.cs | 16 --------------- Assets/Mirror/Runtime/NetworkReaderPooled.cs | 20 +++++++++++++++++++ .../Runtime/NetworkReaderPooled.cs.meta | 3 +++ 3 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 Assets/Mirror/Runtime/NetworkReaderPooled.cs create mode 100644 Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta diff --git a/Assets/Mirror/Runtime/NetworkReaderPool.cs b/Assets/Mirror/Runtime/NetworkReaderPool.cs index ea7823ad4de..745a7f327bf 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPool.cs +++ b/Assets/Mirror/Runtime/NetworkReaderPool.cs @@ -3,22 +3,6 @@ namespace Mirror { - [Obsolete("PooledNetworkReader was renamed to NetworkReaderPooled. It's cleaner & slightly easier to use.")] - public sealed class PooledNetworkReader : NetworkReaderPooled - { - internal PooledNetworkReader(byte[] bytes) : base(bytes) {} - internal PooledNetworkReader(ArraySegment segment) : base(segment) {} - } - - /// Pooled NetworkReader, automatically returned to pool when using 'using' - // TODO make sealed again after removing obsolete NetworkWriterPooled! - public class NetworkReaderPooled : NetworkReader, IDisposable - { - internal NetworkReaderPooled(byte[] bytes) : base(bytes) {} - internal NetworkReaderPooled(ArraySegment segment) : base(segment) {} - public void Dispose() => NetworkReaderPool.Return(this); - } - /// Pool of NetworkReaders to avoid allocations. public static class NetworkReaderPool { diff --git a/Assets/Mirror/Runtime/NetworkReaderPooled.cs b/Assets/Mirror/Runtime/NetworkReaderPooled.cs new file mode 100644 index 00000000000..283c57f1ad3 --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkReaderPooled.cs @@ -0,0 +1,20 @@ +using System; + +namespace Mirror +{ + [Obsolete("PooledNetworkReader was renamed to NetworkReaderPooled. It's cleaner & slightly easier to use.")] + public sealed class PooledNetworkReader : NetworkReaderPooled + { + internal PooledNetworkReader(byte[] bytes) : base(bytes) {} + internal PooledNetworkReader(ArraySegment segment) : base(segment) {} + } + + /// Pooled NetworkReader, automatically returned to pool when using 'using' + // TODO make sealed again after removing obsolete NetworkWriterPooled! + public class NetworkReaderPooled : NetworkReader, IDisposable + { + internal NetworkReaderPooled(byte[] bytes) : base(bytes) {} + internal NetworkReaderPooled(ArraySegment segment) : base(segment) {} + public void Dispose() => NetworkReaderPool.Return(this); + } +} diff --git a/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta b/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta new file mode 100644 index 00000000000..e84fd1493fa --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: faafa97c32e44adf8e8888de817a370a +timeCreated: 1646912662 \ No newline at end of file From 70bd831bd8bf14982e80ae3e923590dd29b0b5d8 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:45:37 +0800 Subject: [PATCH 028/824] NetworkWriterPooled moved into separate file --- Assets/Mirror/Runtime/NetworkWriterPool.cs | 11 ----------- Assets/Mirror/Runtime/NetworkWriterPooled.cs | 15 +++++++++++++++ Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta | 3 +++ 3 files changed, 18 insertions(+), 11 deletions(-) create mode 100644 Assets/Mirror/Runtime/NetworkWriterPooled.cs create mode 100644 Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta diff --git a/Assets/Mirror/Runtime/NetworkWriterPool.cs b/Assets/Mirror/Runtime/NetworkWriterPool.cs index a68a8246113..19e276f10d6 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPool.cs +++ b/Assets/Mirror/Runtime/NetworkWriterPool.cs @@ -3,17 +3,6 @@ namespace Mirror { - // DEPRECATED 2022-03-10 - [Obsolete("PooledNetworkWriter was renamed to NetworkWriterPooled. It's cleaner & slightly easier to use.")] - public sealed class PooledNetworkWriter : NetworkWriterPooled {} - - /// Pooled NetworkWriter, automatically returned to pool when using 'using' - // TODO make sealed again after removing obsolete NetworkWriterPooled! - public class NetworkWriterPooled : NetworkWriter, IDisposable - { - public void Dispose() => NetworkWriterPool.Return(this); - } - /// Pool of NetworkWriters to avoid allocations. public static class NetworkWriterPool { diff --git a/Assets/Mirror/Runtime/NetworkWriterPooled.cs b/Assets/Mirror/Runtime/NetworkWriterPooled.cs new file mode 100644 index 00000000000..99f1019c80e --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkWriterPooled.cs @@ -0,0 +1,15 @@ +using System; + +namespace Mirror +{ + // DEPRECATED 2022-03-10 + [Obsolete("PooledNetworkWriter was renamed to NetworkWriterPooled. It's cleaner & slightly easier to use.")] + public sealed class PooledNetworkWriter : NetworkWriterPooled {} + + /// Pooled NetworkWriter, automatically returned to pool when using 'using' + // TODO make sealed again after removing obsolete NetworkWriterPooled! + public class NetworkWriterPooled : NetworkWriter, IDisposable + { + public void Dispose() => NetworkWriterPool.Return(this); + } +} diff --git a/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta b/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta new file mode 100644 index 00000000000..81b3f71fba3 --- /dev/null +++ b/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: a9fab936bf3c4716a452d94ad5ecbebe +timeCreated: 1646912712 \ No newline at end of file From 3643679ec5cb9de304391c95f43558876e3f9c5b Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:46:42 +0800 Subject: [PATCH 029/824] comments --- Assets/Mirror/Runtime/NetworkReaderPooled.cs | 4 +++- Assets/Mirror/Runtime/NetworkWriterPooled.cs | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderPooled.cs b/Assets/Mirror/Runtime/NetworkReaderPooled.cs index 283c57f1ad3..fcfa792481f 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPooled.cs +++ b/Assets/Mirror/Runtime/NetworkReaderPooled.cs @@ -1,3 +1,5 @@ +// "NetworkReaderPooled" instead of "PooledNetworkReader" to group files, for +// easier IDE workflow and more elegant code. using System; namespace Mirror @@ -10,7 +12,7 @@ internal PooledNetworkReader(ArraySegment segment) : base(segment) {} } /// Pooled NetworkReader, automatically returned to pool when using 'using' - // TODO make sealed again after removing obsolete NetworkWriterPooled! + // TODO make sealed again after removing obsolete NetworkReaderPooled! public class NetworkReaderPooled : NetworkReader, IDisposable { internal NetworkReaderPooled(byte[] bytes) : base(bytes) {} diff --git a/Assets/Mirror/Runtime/NetworkWriterPooled.cs b/Assets/Mirror/Runtime/NetworkWriterPooled.cs index 99f1019c80e..ce113bcfaa1 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPooled.cs +++ b/Assets/Mirror/Runtime/NetworkWriterPooled.cs @@ -1,3 +1,5 @@ +// "NetworkWriterPooled" instead of "PooledNetworkWriter" to group files, for +// easier IDE workflow and more elegant code. using System; namespace Mirror From 24142b085b08158c1eb426056510576f1a208297 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:50:08 +0800 Subject: [PATCH 030/824] comments --- Assets/Mirror/Runtime/NetworkReaderPool.cs | 1 + Assets/Mirror/Runtime/NetworkWriterPool.cs | 1 + Assets/Mirror/Runtime/Pool.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkReaderPool.cs b/Assets/Mirror/Runtime/NetworkReaderPool.cs index 745a7f327bf..ebbfac5eb7e 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPool.cs +++ b/Assets/Mirror/Runtime/NetworkReaderPool.cs @@ -1,3 +1,4 @@ +// API consistent with Microsoft's ObjectPool. using System; using System.Runtime.CompilerServices; diff --git a/Assets/Mirror/Runtime/NetworkWriterPool.cs b/Assets/Mirror/Runtime/NetworkWriterPool.cs index 19e276f10d6..c63323da41c 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPool.cs +++ b/Assets/Mirror/Runtime/NetworkWriterPool.cs @@ -1,3 +1,4 @@ +// API consistent with Microsoft's ObjectPool. using System; using System.Runtime.CompilerServices; diff --git a/Assets/Mirror/Runtime/Pool.cs b/Assets/Mirror/Runtime/Pool.cs index e3de19be4f6..e5261391f95 100644 --- a/Assets/Mirror/Runtime/Pool.cs +++ b/Assets/Mirror/Runtime/Pool.cs @@ -1,4 +1,5 @@ // Pool to avoid allocations (from libuv2k) +// API consistent with Microsoft's ObjectPool. using System; using System.Collections.Generic; using System.Runtime.CompilerServices; From 99c3c0ded686a9fa7982bc19ef867a468f6d0c2a Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 19:57:35 +0800 Subject: [PATCH 031/824] perf: NetworkConnection.Send() inlining --- Assets/Mirror/Runtime/NetworkConnection.cs | 4 ++++ Assets/Mirror/Runtime/NetworkConnectionToClient.cs | 2 ++ Assets/Mirror/Runtime/NetworkConnectionToServer.cs | 2 ++ 3 files changed, 8 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkConnection.cs b/Assets/Mirror/Runtime/NetworkConnection.cs index 7e75e843708..3aefeee4032 100644 --- a/Assets/Mirror/Runtime/NetworkConnection.cs +++ b/Assets/Mirror/Runtime/NetworkConnection.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace Mirror @@ -126,6 +127,7 @@ protected static bool ValidatePacketSize(ArraySegment segment, int channel // Send stage one: NetworkMessage /// Send a NetworkMessage to this connection over the given channel. + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Send(T message, int channelId = Channels.Reliable) where T : struct, NetworkMessage { @@ -141,6 +143,7 @@ public void Send(T message, int channelId = Channels.Reliable) // Send stage two: serialized NetworkMessage as ArraySegment // internal because no one except Mirror should send bytes directly to // the client. they would be detected as a message. send messages instead. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal virtual void Send(ArraySegment segment, int channelId = Channels.Reliable) { //Debug.Log($"ConnectionSend {this} bytes:{BitConverter.ToString(segment.Array, segment.Offset, segment.Count)}"); @@ -165,6 +168,7 @@ internal virtual void Send(ArraySegment segment, int channelId = Channels. } // Send stage three: hand off to transport + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected abstract void SendToTransport(ArraySegment segment, int channelId = Channels.Reliable); // flush batched messages at the end of every Update. diff --git a/Assets/Mirror/Runtime/NetworkConnectionToClient.cs b/Assets/Mirror/Runtime/NetworkConnectionToClient.cs index 077ca8ee7a1..4cb56f5b556 100644 --- a/Assets/Mirror/Runtime/NetworkConnectionToClient.cs +++ b/Assets/Mirror/Runtime/NetworkConnectionToClient.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; namespace Mirror { @@ -26,6 +27,7 @@ public NetworkConnectionToClient(int networkConnectionId) : base(networkConnectionId) {} // Send stage three: hand off to transport + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SendToTransport(ArraySegment segment, int channelId = Channels.Reliable) => Transport.activeTransport.ServerSend(connectionId, segment, channelId); diff --git a/Assets/Mirror/Runtime/NetworkConnectionToServer.cs b/Assets/Mirror/Runtime/NetworkConnectionToServer.cs index 022891e0101..a1ebc5fa205 100644 --- a/Assets/Mirror/Runtime/NetworkConnectionToServer.cs +++ b/Assets/Mirror/Runtime/NetworkConnectionToServer.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; namespace Mirror { @@ -7,6 +8,7 @@ public class NetworkConnectionToServer : NetworkConnection public override string address => ""; // Send stage three: hand off to transport + [MethodImpl(MethodImplOptions.AggressiveInlining)] protected override void SendToTransport(ArraySegment segment, int channelId = Channels.Reliable) => Transport.activeTransport.ClientSend(segment, channelId); From 9686c68bb7bb5403d8babcf4b9f806b748795784 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 20:25:49 +0800 Subject: [PATCH 032/824] base Transport moved to Runtime folder; Transport folder renamed to Transports --- Assets/Mirror/Runtime/{Transport => }/Transport.cs | 0 Assets/Mirror/Runtime/{Transport => }/Transport.cs.meta | 0 Assets/Mirror/Runtime/{Transport.meta => Transports.meta} | 0 Assets/Mirror/Runtime/{Transport => Transports}/KCP.meta | 0 .../Runtime/{Transport => Transports}/KCP/MirrorTransport.meta | 0 .../{Transport => Transports}/KCP/MirrorTransport/KcpTransport.cs | 0 .../KCP/MirrorTransport/KcpTransport.cs.meta | 0 Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k.meta | 0 Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/LICENSE | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/LICENSE.meta | 0 Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/VERSION | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/VERSION.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/highlevel.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/Extensions.cs | 0 .../KCP/kcp2k/highlevel/Extensions.cs.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/KcpChannel.cs | 0 .../KCP/kcp2k/highlevel/KcpChannel.cs.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/KcpClient.cs | 0 .../KCP/kcp2k/highlevel/KcpClient.cs.meta | 0 .../KCP/kcp2k/highlevel/KcpClientConnection.cs | 0 .../KCP/kcp2k/highlevel/KcpClientConnection.cs.meta | 0 .../KCP/kcp2k/highlevel/KcpConnection.cs | 0 .../KCP/kcp2k/highlevel/KcpConnection.cs.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/KcpHeader.cs | 0 .../KCP/kcp2k/highlevel/KcpHeader.cs.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/KcpServer.cs | 0 .../KCP/kcp2k/highlevel/KcpServer.cs.meta | 0 .../KCP/kcp2k/highlevel/KcpServerConnection.cs | 0 .../KCP/kcp2k/highlevel/KcpServerConnection.cs.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/Log.cs | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/Log.cs.meta | 0 .../{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc.meta | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs | 0 .../kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs | 0 .../kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs | 0 .../KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta | 0 .../Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp.meta | 0 .../{Transport => Transports}/KCP/kcp2k/kcp/AssemblyInfo.cs | 0 .../{Transport => Transports}/KCP/kcp2k/kcp/AssemblyInfo.cs.meta | 0 .../Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Kcp.cs | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Kcp.cs.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Pool.cs | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Pool.cs.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Segment.cs | 0 .../{Transport => Transports}/KCP/kcp2k/kcp/Segment.cs.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Utils.cs | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Utils.cs.meta | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp2k.asmdef | 0 .../Runtime/{Transport => Transports}/KCP/kcp2k/kcp2k.asmdef.meta | 0 .../{Transport => Transports}/KCP/kcp2k/where-allocation.meta | 0 .../{Transport => Transports}/KCP/kcp2k/where-allocation/LICENSE | 0 .../KCP/kcp2k/where-allocation/LICENSE.meta | 0 .../KCP/kcp2k/where-allocation/Scripts.meta | 0 .../KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs | 0 .../KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta | 0 .../KCP/kcp2k/where-allocation/Scripts/Extensions.cs | 0 .../KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta | 0 .../KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs | 0 .../KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta | 0 .../KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef | 0 .../kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta | 0 .../{Transport => Transports}/KCP/kcp2k/where-allocation/VERSION | 0 .../KCP/kcp2k/where-allocation/VERSION.meta | 0 .../Mirror/Runtime/{Transport => Transports}/LatencySimulation.cs | 0 .../Runtime/{Transport => Transports}/LatencySimulation.cs.meta | 0 .../Runtime/{Transport => Transports}/MiddlewareTransport.cs | 0 .../Runtime/{Transport => Transports}/MiddlewareTransport.cs.meta | 0 .../Runtime/{Transport => Transports}/MultiplexTransport.cs | 0 .../Runtime/{Transport => Transports}/MultiplexTransport.cs.meta | 0 .../Runtime/{Transport => Transports}/SimpleWebTransport.meta | 0 .../SimpleWebTransport/.cert.example.Json | 0 .../{Transport => Transports}/SimpleWebTransport/AssemblyInfo.cs | 0 .../SimpleWebTransport/AssemblyInfo.cs.meta | 0 .../{Transport => Transports}/SimpleWebTransport/CHANGELOG.md | 0 .../SimpleWebTransport/CHANGELOG.md.meta | 0 .../{Transport => Transports}/SimpleWebTransport/Client.meta | 0 .../SimpleWebTransport/Client/SimpleWebClient.cs | 0 .../SimpleWebTransport/Client/SimpleWebClient.cs.meta | 0 .../SimpleWebTransport/Client/StandAlone.meta | 0 .../SimpleWebTransport/Client/StandAlone/ClientHandshake.cs | 0 .../SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta | 0 .../SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs | 0 .../SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta | 0 .../Client/StandAlone/WebSocketClientStandAlone.cs | 0 .../Client/StandAlone/WebSocketClientStandAlone.cs.meta | 0 .../SimpleWebTransport/Client/Webgl.meta | 0 .../SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs | 0 .../SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta | 0 .../SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs | 0 .../SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta | 0 .../SimpleWebTransport/Client/Webgl/plugin.meta | 0 .../SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib | 0 .../SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta | 0 .../{Transport => Transports}/SimpleWebTransport/Common.meta | 0 .../SimpleWebTransport/Common/BufferPool.cs | 0 .../SimpleWebTransport/Common/BufferPool.cs.meta | 0 .../SimpleWebTransport/Common/Connection.cs | 0 .../SimpleWebTransport/Common/Connection.cs.meta | 0 .../SimpleWebTransport/Common/Constants.cs | 0 .../SimpleWebTransport/Common/Constants.cs.meta | 0 .../SimpleWebTransport/Common/EventType.cs | 0 .../SimpleWebTransport/Common/EventType.cs.meta | 0 .../{Transport => Transports}/SimpleWebTransport/Common/Log.cs | 0 .../SimpleWebTransport/Common/Log.cs.meta | 0 .../SimpleWebTransport/Common/Message.cs | 0 .../SimpleWebTransport/Common/Message.cs.meta | 0 .../SimpleWebTransport/Common/MessageProcessor.cs | 0 .../SimpleWebTransport/Common/MessageProcessor.cs.meta | 0 .../SimpleWebTransport/Common/ReadHelper.cs | 0 .../SimpleWebTransport/Common/ReadHelper.cs.meta | 0 .../SimpleWebTransport/Common/ReceiveLoop.cs | 0 .../SimpleWebTransport/Common/ReceiveLoop.cs.meta | 0 .../SimpleWebTransport/Common/SendLoop.cs | 0 .../SimpleWebTransport/Common/SendLoop.cs.meta | 0 .../SimpleWebTransport/Common/TcpConfig.cs | 0 .../SimpleWebTransport/Common/TcpConfig.cs.meta | 0 .../{Transport => Transports}/SimpleWebTransport/Common/Utils.cs | 0 .../SimpleWebTransport/Common/Utils.cs.meta | 0 .../Runtime/{Transport => Transports}/SimpleWebTransport/LICENSE | 0 .../{Transport => Transports}/SimpleWebTransport/LICENSE.meta | 0 .../{Transport => Transports}/SimpleWebTransport/README.txt | 0 .../{Transport => Transports}/SimpleWebTransport/README.txt.meta | 0 .../{Transport => Transports}/SimpleWebTransport/Server.meta | 0 .../SimpleWebTransport/Server/ServerHandshake.cs | 0 .../SimpleWebTransport/Server/ServerHandshake.cs.meta | 0 .../SimpleWebTransport/Server/ServerSslHelper.cs | 0 .../SimpleWebTransport/Server/ServerSslHelper.cs.meta | 0 .../SimpleWebTransport/Server/SimpleWebServer.cs | 0 .../SimpleWebTransport/Server/SimpleWebServer.cs.meta | 0 .../SimpleWebTransport/Server/WebSocketServer.cs | 0 .../SimpleWebTransport/Server/WebSocketServer.cs.meta | 0 .../SimpleWebTransport/SimpleWebTransport.asmdef | 0 .../SimpleWebTransport/SimpleWebTransport.asmdef.meta | 0 .../SimpleWebTransport/SimpleWebTransport.cs | 0 .../SimpleWebTransport/SimpleWebTransport.cs.meta | 0 .../SimpleWebTransport/SslConfigLoader.cs | 0 .../SimpleWebTransport/SslConfigLoader.cs.meta | 0 Assets/Mirror/Runtime/{Transport => Transports}/Telepathy.meta | 0 .../Runtime/{Transport => Transports}/Telepathy/Telepathy.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Client.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Client.cs.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Common.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Common.cs.meta | 0 .../Telepathy/Telepathy/ConnectionState.cs | 0 .../Telepathy/Telepathy/ConnectionState.cs.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Empty.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Empty/Logger.cs | 0 .../Telepathy/Telepathy/Empty/Logger.cs.meta | 0 .../Telepathy/Telepathy/Empty/Message.cs | 0 .../Telepathy/Telepathy/Empty/Message.cs.meta | 0 .../Telepathy/Telepathy/Empty/SafeQueue.cs | 0 .../Telepathy/Telepathy/Empty/SafeQueue.cs.meta | 0 .../Telepathy/Telepathy/Empty/ThreadExtensions.cs | 0 .../Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/EventType.cs | 0 .../Telepathy/Telepathy/EventType.cs.meta | 0 .../Runtime/{Transport => Transports}/Telepathy/Telepathy/LICENSE | 0 .../{Transport => Transports}/Telepathy/Telepathy/LICENSE.meta | 0 .../Runtime/{Transport => Transports}/Telepathy/Telepathy/Log.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Log.cs.meta | 0 .../Telepathy/Telepathy/MagnificentReceivePipe.cs | 0 .../Telepathy/Telepathy/MagnificentReceivePipe.cs.meta | 0 .../Telepathy/Telepathy/MagnificentSendPipe.cs | 0 .../Telepathy/Telepathy/MagnificentSendPipe.cs.meta | 0 .../Telepathy/Telepathy/NetworkStreamExtensions.cs | 0 .../Telepathy/Telepathy/NetworkStreamExtensions.cs.meta | 0 .../Runtime/{Transport => Transports}/Telepathy/Telepathy/Pool.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Pool.cs.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Server.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Server.cs.meta | 0 .../Telepathy/Telepathy/Telepathy.asmdef | 0 .../Telepathy/Telepathy/Telepathy.asmdef.meta | 0 .../Telepathy/Telepathy/ThreadFunctions.cs | 0 .../Telepathy/Telepathy/ThreadFunctions.cs.meta | 0 .../{Transport => Transports}/Telepathy/Telepathy/Utils.cs | 0 .../{Transport => Transports}/Telepathy/Telepathy/Utils.cs.meta | 0 .../Runtime/{Transport => Transports}/Telepathy/Telepathy/VERSION | 0 .../{Transport => Transports}/Telepathy/Telepathy/VERSION.meta | 0 .../{Transport => Transports}/Telepathy/TelepathyTransport.cs | 0 .../Telepathy/TelepathyTransport.cs.meta | 0 184 files changed, 0 insertions(+), 0 deletions(-) rename Assets/Mirror/Runtime/{Transport => }/Transport.cs (100%) rename Assets/Mirror/Runtime/{Transport => }/Transport.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport.meta => Transports.meta} (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/MirrorTransport.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/MirrorTransport/KcpTransport.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/MirrorTransport/KcpTransport.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/LICENSE (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/LICENSE.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/VERSION (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/VERSION.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/Extensions.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/Extensions.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpChannel.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpChannel.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpClient.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpClient.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpClientConnection.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpConnection.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpConnection.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpHeader.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpHeader.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpServer.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpServer.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpServerConnection.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/Log.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/Log.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/AssemblyInfo.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/AssemblyInfo.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Kcp.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Kcp.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Pool.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Pool.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Segment.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Segment.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Utils.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp/Utils.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp2k.asmdef (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/kcp2k.asmdef.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/LICENSE (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/LICENSE.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/Extensions.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/VERSION (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/KCP/kcp2k/where-allocation/VERSION.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/LatencySimulation.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/LatencySimulation.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/MiddlewareTransport.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/MiddlewareTransport.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/MultiplexTransport.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/MultiplexTransport.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/.cert.example.Json (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/AssemblyInfo.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/AssemblyInfo.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/CHANGELOG.md (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/CHANGELOG.md.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/SimpleWebClient.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/SimpleWebClient.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/plugin.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/BufferPool.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/BufferPool.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Connection.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Connection.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Constants.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Constants.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/EventType.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/EventType.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Log.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Log.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Message.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Message.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/MessageProcessor.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/MessageProcessor.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/ReadHelper.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/ReadHelper.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/ReceiveLoop.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/ReceiveLoop.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/SendLoop.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/SendLoop.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/TcpConfig.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/TcpConfig.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Utils.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Common/Utils.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/LICENSE (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/LICENSE.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/README.txt (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/README.txt.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/ServerHandshake.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/ServerHandshake.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/ServerSslHelper.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/ServerSslHelper.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/SimpleWebServer.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/SimpleWebServer.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/WebSocketServer.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/Server/WebSocketServer.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SimpleWebTransport.asmdef (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SimpleWebTransport.asmdef.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SimpleWebTransport.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SimpleWebTransport.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SslConfigLoader.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/SimpleWebTransport/SslConfigLoader.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Client.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Client.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Common.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Common.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/ConnectionState.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/ConnectionState.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/Logger.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/Logger.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/Message.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/Message.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/SafeQueue.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/SafeQueue.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/ThreadExtensions.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/EventType.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/EventType.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/LICENSE (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/LICENSE.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Log.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Log.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/MagnificentReceivePipe.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/MagnificentSendPipe.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/MagnificentSendPipe.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/NetworkStreamExtensions.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Pool.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Pool.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Server.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Server.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Telepathy.asmdef (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Telepathy.asmdef.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/ThreadFunctions.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/ThreadFunctions.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Utils.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/Utils.cs.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/VERSION (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/Telepathy/VERSION.meta (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/TelepathyTransport.cs (100%) rename Assets/Mirror/Runtime/{Transport => Transports}/Telepathy/TelepathyTransport.cs.meta (100%) diff --git a/Assets/Mirror/Runtime/Transport/Transport.cs b/Assets/Mirror/Runtime/Transport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Transport.cs rename to Assets/Mirror/Runtime/Transport.cs diff --git a/Assets/Mirror/Runtime/Transport/Transport.cs.meta b/Assets/Mirror/Runtime/Transport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Transport.cs.meta rename to Assets/Mirror/Runtime/Transport.cs.meta diff --git a/Assets/Mirror/Runtime/Transport.meta b/Assets/Mirror/Runtime/Transports.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport.meta rename to Assets/Mirror/Runtime/Transports.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP.meta b/Assets/Mirror/Runtime/Transports/KCP.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP.meta rename to Assets/Mirror/Runtime/Transports/KCP.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/MirrorTransport.meta rename to Assets/Mirror/Runtime/Transports/KCP/MirrorTransport.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs rename to Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/MirrorTransport/KcpTransport.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/LICENSE similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/LICENSE diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/LICENSE.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/LICENSE.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/LICENSE.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/VERSION.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Extensions.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Extensions.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Extensions.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Extensions.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Extensions.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Extensions.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Extensions.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Extensions.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpChannel.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpChannel.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpChannel.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpChannel.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpChannel.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClient.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpConnection.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpHeader.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpHeader.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpHeader.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpHeader.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpHeader.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServer.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServerConnection.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServerConnection.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServerConnection.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Log.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Log.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Log.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/Log.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/Log.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientConnectionNonAlloc.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerConnectionNonAlloc.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/AssemblyInfo.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/AssemblyInfo.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/AssemblyInfo.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/AssemblyInfo.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/AssemblyInfo.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Kcp.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Kcp.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Kcp.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Kcp.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Kcp.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Pool.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Pool.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Pool.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Pool.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Pool.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Segment.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Segment.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Segment.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Segment.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Segment.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Utils.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Utils.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Utils.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp/Utils.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp/Utils.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/kcp2k.asmdef.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/LICENSE similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/LICENSE diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/LICENSE.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/LICENSE.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/LICENSE.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/AssemblyInfo.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/Extensions.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/Extensions.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/Extensions.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/IPEndPointNonAlloc.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/Scripts/where-allocations.asmdef.meta diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/VERSION similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/VERSION diff --git a/Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/VERSION.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/KCP/kcp2k/where-allocation/VERSION.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/where-allocation/VERSION.meta diff --git a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs b/Assets/Mirror/Runtime/Transports/LatencySimulation.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/LatencySimulation.cs rename to Assets/Mirror/Runtime/Transports/LatencySimulation.cs diff --git a/Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta b/Assets/Mirror/Runtime/Transports/LatencySimulation.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/LatencySimulation.cs.meta rename to Assets/Mirror/Runtime/Transports/LatencySimulation.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs b/Assets/Mirror/Runtime/Transports/MiddlewareTransport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs rename to Assets/Mirror/Runtime/Transports/MiddlewareTransport.cs diff --git a/Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta b/Assets/Mirror/Runtime/Transports/MiddlewareTransport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/MiddlewareTransport.cs.meta rename to Assets/Mirror/Runtime/Transports/MiddlewareTransport.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs b/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/MultiplexTransport.cs rename to Assets/Mirror/Runtime/Transports/MultiplexTransport.cs diff --git a/Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta b/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/MultiplexTransport.cs.meta rename to Assets/Mirror/Runtime/Transports/MultiplexTransport.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/.cert.example.Json similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/.cert.example.Json rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/.cert.example.Json diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/AssemblyInfo.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/AssemblyInfo.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/AssemblyInfo.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/AssemblyInfo.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/AssemblyInfo.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/CHANGELOG.md b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/CHANGELOG.md similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/CHANGELOG.md rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/CHANGELOG.md diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/CHANGELOG.md.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/CHANGELOG.md.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/CHANGELOG.md.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/CHANGELOG.md.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/SimpleWebClient.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/SimpleWebClient.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/SimpleWebClient.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/SimpleWebClient.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/SimpleWebClient.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientHandshake.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/ClientSslHelper.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/StandAlone/WebSocketClientStandAlone.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/SimpleWebJSLib.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/WebSocketClientWebGl.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Client/Webgl/plugin/SimpleWeb.jslib.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/BufferPool.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/BufferPool.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/BufferPool.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/BufferPool.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/BufferPool.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Connection.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Connection.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Connection.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Connection.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Connection.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Constants.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Constants.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Constants.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Constants.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Constants.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/EventType.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/EventType.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/EventType.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/EventType.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/EventType.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Log.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Log.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Log.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Log.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Log.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Message.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Message.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Message.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Message.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Message.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/MessageProcessor.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/MessageProcessor.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/MessageProcessor.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/MessageProcessor.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/MessageProcessor.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReadHelper.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReadHelper.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReadHelper.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReadHelper.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReadHelper.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReceiveLoop.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReceiveLoop.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReceiveLoop.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/ReceiveLoop.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/ReceiveLoop.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/SendLoop.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/SendLoop.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/SendLoop.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/SendLoop.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/SendLoop.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/TcpConfig.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/TcpConfig.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/TcpConfig.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/TcpConfig.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/TcpConfig.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Utils.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Utils.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Utils.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Common/Utils.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Common/Utils.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/LICENSE b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/LICENSE similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/LICENSE rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/LICENSE diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/LICENSE.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/LICENSE.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/LICENSE.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/LICENSE.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/README.txt similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/README.txt diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/README.txt.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/README.txt.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/README.txt.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerHandshake.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerSslHelper.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerSslHelper.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerSslHelper.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/ServerSslHelper.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerSslHelper.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/SimpleWebServer.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/SimpleWebServer.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/SimpleWebServer.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/SimpleWebServer.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/SimpleWebServer.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/WebSocketServer.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/WebSocketServer.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/WebSocketServer.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/Server/WebSocketServer.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/WebSocketServer.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.asmdef similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.asmdef diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.asmdef.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.asmdef.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.asmdef.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SimpleWebTransport.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SslConfigLoader.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SslConfigLoader.cs diff --git a/Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SslConfigLoader.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/SimpleWebTransport/SslConfigLoader.cs.meta rename to Assets/Mirror/Runtime/Transports/SimpleWebTransport/SslConfigLoader.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy.meta b/Assets/Mirror/Runtime/Transports/Telepathy.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy.meta rename to Assets/Mirror/Runtime/Transports/Telepathy.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Client.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Client.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Client.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Client.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Client.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Common.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Common.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Common.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Common.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Common.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ConnectionState.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ConnectionState.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ConnectionState.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ConnectionState.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ConnectionState.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Logger.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Logger.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Logger.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Logger.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Logger.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Message.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Message.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Message.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/Message.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/Message.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/SafeQueue.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/SafeQueue.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/SafeQueue.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/SafeQueue.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/SafeQueue.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/ThreadExtensions.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/ThreadExtensions.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Empty/ThreadExtensions.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/EventType.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/EventType.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/EventType.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/EventType.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/EventType.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/LICENSE similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/LICENSE diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/LICENSE.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/LICENSE.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/LICENSE.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Log.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Log.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Log.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Log.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Log.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentReceivePipe.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentReceivePipe.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentReceivePipe.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentSendPipe.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentSendPipe.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentSendPipe.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/MagnificentSendPipe.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/MagnificentSendPipe.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/NetworkStreamExtensions.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/NetworkStreamExtensions.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/NetworkStreamExtensions.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Pool.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Pool.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Pool.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Pool.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Pool.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Server.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Server.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Server.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Server.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Server.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Telepathy.asmdef similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Telepathy.asmdef diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Telepathy.asmdef.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Telepathy.asmdef.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Telepathy.asmdef.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ThreadFunctions.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ThreadFunctions.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ThreadFunctions.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/ThreadFunctions.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/ThreadFunctions.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Utils.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Utils.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Utils.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/Utils.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/Utils.cs.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/VERSION similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/VERSION diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta b/Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/VERSION.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/Telepathy/VERSION.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/Telepathy/VERSION.meta diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs b/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs rename to Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs diff --git a/Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta b/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs.meta similarity index 100% rename from Assets/Mirror/Runtime/Transport/Telepathy/TelepathyTransport.cs.meta rename to Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs.meta From e71f0e1cc4e79e1407d1ed3047a1678e382b9ee1 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 20:29:41 +0800 Subject: [PATCH 033/824] script icons .meta --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs.meta | 10 +++++++++- Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta | 10 +++++++++- Assets/Mirror/Runtime/NetworkWriterExtensions.cs.meta | 10 +++++++++- Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta | 10 +++++++++- 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs.meta b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs.meta index ffc2c05e30a..66536c96e7f 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs.meta +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 364a9f7ccd5541e19aa2ae0b81f0b3cf -timeCreated: 1644998339 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta b/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta index e84fd1493fa..4eb6e9d6fe0 100644 --- a/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta +++ b/Assets/Mirror/Runtime/NetworkReaderPooled.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: faafa97c32e44adf8e8888de817a370a -timeCreated: 1646912662 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs.meta b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs.meta index f7c367976a9..9bbdaf051de 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs.meta +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: 94259792df2a404892c3e2377f58d0cb -timeCreated: 1644998104 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta b/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta index 81b3f71fba3..5571d6fa168 100644 --- a/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta +++ b/Assets/Mirror/Runtime/NetworkWriterPooled.cs.meta @@ -1,3 +1,11 @@ fileFormatVersion: 2 guid: a9fab936bf3c4716a452d94ad5ecbebe -timeCreated: 1646912712 \ No newline at end of file +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: From 0499dc0503caa8af508e43632ff5f6767f27946e Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 10 Mar 2022 22:19:21 +0800 Subject: [PATCH 034/824] Tests: ShutdownCleanup test cleanup to prepare for Transport events += -= hooking --- Assets/Mirror/Tests/Editor/NetworkServerTest.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs index 7ad5401a3c9..aa90931c1d3 100644 --- a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -1163,8 +1163,7 @@ public void ShutdownCleanup() NetworkServer.SetLocalConnection(new LocalConnectionToClient()); // connect a client - transport.ClientConnect("localhost"); - UpdateTransport(); + base.ConnectClientBlockingAuthenticatedAndReady(out _); Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); // shutdown From dc5162099ef02c135a2ffbb0ce9dfd2800eb6b58 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 11 Mar 2022 13:10:12 +0800 Subject: [PATCH 035/824] feature: allow hooking into Transport events via += -= (prepares for NetworkStatistics) (#3116) --- Assets/Mirror/Runtime/NetworkClient.cs | 22 ++++++++++++++---- Assets/Mirror/Runtime/NetworkServer.cs | 23 +++++++++++++++---- Assets/Mirror/Runtime/Transport.cs | 16 ++++++------- Assets/Mirror/Tests/Common/MemoryTransport.cs | 18 ++++++++++----- 4 files changed, 57 insertions(+), 22 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 2e1308b10af..4357b53e2d5 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -103,10 +103,20 @@ public static class NetworkClient // initialization ////////////////////////////////////////////////////// static void AddTransportHandlers() { - Transport.activeTransport.OnClientConnected = OnTransportConnected; - Transport.activeTransport.OnClientDataReceived = OnTransportData; - Transport.activeTransport.OnClientDisconnected = OnTransportDisconnected; - Transport.activeTransport.OnClientError = OnError; + // += so that other systems can also hook into it (i.e. statistics) + Transport.activeTransport.OnClientConnected += OnTransportConnected; + Transport.activeTransport.OnClientDataReceived += OnTransportData; + Transport.activeTransport.OnClientDisconnected += OnTransportDisconnected; + Transport.activeTransport.OnClientError += OnError; + } + + static void RemoveTransportHandlers() + { + // -= so that other systems can also hook into it (i.e. statistics) + Transport.activeTransport.OnClientConnected -= OnTransportConnected; + Transport.activeTransport.OnClientDataReceived -= OnTransportData; + Transport.activeTransport.OnClientDisconnected -= OnTransportDisconnected; + Transport.activeTransport.OnClientError -= OnError; } internal static void RegisterSystemHandlers(bool hostMode) @@ -414,6 +424,10 @@ internal static void OnTransportDisconnected() // previously this was done in Disconnect() already, but we still // need it for the above OnDisconnectedEvent. connection = null; + + // transport handlers are only added when connecting. + // so only remove when actually disconnecting. + RemoveTransportHandlers(); } static void OnError(Exception exception) diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 04e380dd5d7..45d00c491fb 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -80,10 +80,20 @@ static void Initialize() static void AddTransportHandlers() { - Transport.activeTransport.OnServerConnected = OnTransportConnected; - Transport.activeTransport.OnServerDataReceived = OnTransportData; - Transport.activeTransport.OnServerDisconnected = OnTransportDisconnected; - Transport.activeTransport.OnServerError = OnError; + // += so that other systems can also hook into it (i.e. statistics) + Transport.activeTransport.OnServerConnected += OnTransportConnected; + Transport.activeTransport.OnServerDataReceived += OnTransportData; + Transport.activeTransport.OnServerDisconnected += OnTransportDisconnected; + Transport.activeTransport.OnServerError += OnError; + } + + static void RemoveTransportHandlers() + { + // -= so that other systems can also hook into it (i.e. statistics) + Transport.activeTransport.OnServerConnected -= OnTransportConnected; + Transport.activeTransport.OnServerDataReceived -= OnTransportData; + Transport.activeTransport.OnServerDisconnected -= OnTransportDisconnected; + Transport.activeTransport.OnServerError -= OnError; } // calls OnStartClient for all SERVER objects in host mode once. @@ -174,6 +184,11 @@ public static void Shutdown() // but we still need to stop the server. // fixes https://github.com/vis2k/Mirror/issues/2536 Transport.activeTransport.ServerStop(); + + // transport handlers are hooked into when initializing. + // so only remove them when shutting down. + RemoveTransportHandlers(); + initialized = false; } diff --git a/Assets/Mirror/Runtime/Transport.cs b/Assets/Mirror/Runtime/Transport.cs index fd402c5d914..4d8bc21abfe 100644 --- a/Assets/Mirror/Runtime/Transport.cs +++ b/Assets/Mirror/Runtime/Transport.cs @@ -37,16 +37,16 @@ public abstract class Transport : MonoBehaviour public abstract bool Available(); /// Called by Transport when the client connected to the server. - public Action OnClientConnected = () => Debug.LogWarning("OnClientConnected called with no handler"); + public Action OnClientConnected; /// Called by Transport when the client received a message from the server. - public Action, int> OnClientDataReceived = (data, channel) => Debug.LogWarning("OnClientDataReceived called with no handler"); + public Action, int> OnClientDataReceived; /// Called by Transport when the client encountered an error. - public Action OnClientError = (error) => Debug.LogWarning("OnClientError called with no handler"); + public Action OnClientError; /// Called by Transport when the client disconnected from the server. - public Action OnClientDisconnected = () => Debug.LogWarning("OnClientDisconnected called with no handler"); + public Action OnClientDisconnected; /// True if the client is currently connected to the server. public abstract bool ClientConnected(); @@ -74,17 +74,17 @@ public virtual void ClientConnect(Uri uri) public abstract Uri ServerUri(); /// Called by Transport when a new client connected to the server. - public Action OnServerConnected = (connId) => Debug.LogWarning("OnServerConnected called with no handler"); + public Action OnServerConnected; /// Called by Transport when the server received a message from a client. - public Action, int> OnServerDataReceived = (connId, data, channel) => Debug.LogWarning("OnServerDataReceived called with no handler"); + public Action, int> OnServerDataReceived; /// Called by Transport when a server's connection encountered a problem. /// If a Disconnect will also be raised, raise the Error first. - public Action OnServerError = (connId, error) => Debug.LogWarning("OnServerError called with no handler"); + public Action OnServerError; /// Called by Transport when a client disconnected from the server. - public Action OnServerDisconnected = (connId) => Debug.LogWarning("OnServerDisconnected called with no handler"); + public Action OnServerDisconnected; /// True if the server is currently listening for connections. public abstract bool ServerActive(); diff --git a/Assets/Mirror/Tests/Common/MemoryTransport.cs b/Assets/Mirror/Tests/Common/MemoryTransport.cs index 6f0ace3bcce..0cef1bde5d7 100644 --- a/Assets/Mirror/Tests/Common/MemoryTransport.cs +++ b/Assets/Mirror/Tests/Common/MemoryTransport.cs @@ -107,15 +107,18 @@ public override void ClientEarlyUpdate() { case EventType.Connected: Debug.Log("MemoryTransport Client Message: Connected"); - OnClientConnected.Invoke(); + // event might be null in tests if no NetworkClient is used. + OnClientConnected?.Invoke(); break; case EventType.Data: Debug.Log($"MemoryTransport Client Message: Data: {BitConverter.ToString(message.data)}"); - OnClientDataReceived.Invoke(new ArraySegment(message.data), 0); + // event might be null in tests if no NetworkClient is used. + OnClientDataReceived?.Invoke(new ArraySegment(message.data), 0); break; case EventType.Disconnected: Debug.Log("MemoryTransport Client Message: Disconnected"); - OnClientDisconnected.Invoke(); + // event might be null in tests if no NetworkClient is used. + OnClientDisconnected?.Invoke(); break; } } @@ -191,15 +194,18 @@ public override void ServerEarlyUpdate() { case EventType.Connected: Debug.Log("MemoryTransport Server Message: Connected"); - OnServerConnected.Invoke(message.connectionId); + // event might be null in tests if no NetworkClient is used. + OnServerConnected?.Invoke(message.connectionId); break; case EventType.Data: Debug.Log($"MemoryTransport Server Message: Data: {BitConverter.ToString(message.data)}"); - OnServerDataReceived.Invoke(message.connectionId, new ArraySegment(message.data), 0); + // event might be null in tests if no NetworkClient is used. + OnServerDataReceived?.Invoke(message.connectionId, new ArraySegment(message.data), 0); break; case EventType.Disconnected: Debug.Log("MemoryTransport Server Message: Disconnected"); - OnServerDisconnected.Invoke(message.connectionId); + // event might be null in tests if no NetworkClient is used. + OnServerDisconnected?.Invoke(message.connectionId); break; } } From b100307929dadd5eb4b5a236ab370be4c0b67ab0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 11 Mar 2022 13:14:06 +0800 Subject: [PATCH 036/824] feature: Transport.OnClient/ServerDataSent events += (prepares for NetworkStatistics) (#3117) * feature: Transport.OnClient/ServerDataSent events += (prepares for NetworkStatistics) * comments --- Assets/Mirror/Runtime/Transport.cs | 14 +++++++++ .../KCP/MirrorTransport/KcpTransport.cs | 6 ++++ .../SimpleWebTransport/SimpleWebTransport.cs | 6 ++++ .../Telepathy/TelepathyTransport.cs | 30 +++++++++++++------ Assets/Mirror/Tests/Common/MemoryTransport.cs | 6 ++++ 5 files changed, 53 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Runtime/Transport.cs b/Assets/Mirror/Runtime/Transport.cs index 4d8bc21abfe..13f2b9f0b68 100644 --- a/Assets/Mirror/Runtime/Transport.cs +++ b/Assets/Mirror/Runtime/Transport.cs @@ -42,6 +42,13 @@ public abstract class Transport : MonoBehaviour /// Called by Transport when the client received a message from the server. public Action, int> OnClientDataReceived; + /// Called by Transport when the client sent a message to the server. + // Transports are responsible for calling it because: + // - groups it together with OnReceived responsibility + // - allows transports to decide if anything was sent or not + // - allows transports to decide the actual used channel (i.e. tcp always sending reliable) + public Action, int> OnClientDataSent; + /// Called by Transport when the client encountered an error. public Action OnClientError; @@ -79,6 +86,13 @@ public virtual void ClientConnect(Uri uri) /// Called by Transport when the server received a message from a client. public Action, int> OnServerDataReceived; + /// Called by Transport when the server sent a message to a client. + // Transports are responsible for calling it because: + // - groups it together with OnReceived responsibility + // - allows transports to decide if anything was sent or not + // - allows transports to decide the actual used channel (i.e. tcp always sending reliable) + public Action, int> OnServerDataSent; + /// Called by Transport when a server's connection encountered a problem. /// If a Disconnect will also be raised, raise the Error first. public Action OnServerError; diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index ac10a54a013..cf0f13da190 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -161,6 +161,9 @@ public override void ClientConnect(Uri uri) public override void ClientSend(ArraySegment segment, int channelId) { client.Send(segment, ToKcpChannel(channelId)); + + // call event. might be null if no statistics are listening etc. + OnClientDataSent?.Invoke(segment, channelId); } public override void ClientDisconnect() => client.Disconnect(); // process incoming in early update @@ -188,6 +191,9 @@ public override Uri ServerUri() public override void ServerSend(int connectionId, ArraySegment segment, int channelId) { server.Send(connectionId, segment, ToKcpChannel(channelId)); + + // call event. might be null if no statistics are listening etc. + OnServerDataSent?.Invoke(connectionId, segment, channelId); } public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId); public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); diff --git a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs index 166323c039b..66badc39af1 100644 --- a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs +++ b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs @@ -181,6 +181,9 @@ public override void ClientSend(ArraySegment segment, int channelId) } client.Send(segment); + + // call event. might be null if no statistics are listening etc. + OnClientDataSent?.Invoke(segment, Channels.Reliable); } // messages should always be processed in early update @@ -259,6 +262,9 @@ public override void ServerSend(int connectionId, ArraySegment segment, in } server.SendOne(connectionId, segment); + + // call event. might be null if no statistics are listening etc. + OnServerDataSent?.Invoke(connectionId, segment, Channels.Reliable); } public override string ServerGetClientAddress(int connectionId) diff --git a/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs b/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs index 84034200890..5e0bc0597ef 100644 --- a/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs +++ b/Assets/Mirror/Runtime/Transports/Telepathy/TelepathyTransport.cs @@ -81,7 +81,7 @@ public override bool Available() } // client - private void CreateClient() + private void CreateClient() { // create client client = new Telepathy.Client(clientMaxMessageSize); @@ -104,7 +104,7 @@ private void CreateClient() client.ReceiveQueueLimit = clientReceiveQueueLimit; } public override bool ClientConnected() => client != null && client.Connected; - public override void ClientConnect(string address) + public override void ClientConnect(string address) { CreateClient(); client.Connect(address, port); @@ -119,8 +119,14 @@ public override void ClientConnect(Uri uri) int serverPort = uri.IsDefaultPort ? port : uri.Port; client.Connect(uri.Host, serverPort); } - public override void ClientSend(ArraySegment segment, int channelId) => client?.Send(segment); - public override void ClientDisconnect() + public override void ClientSend(ArraySegment segment, int channelId) + { + client?.Send(segment); + + // call event. might be null if no statistics are listening etc. + OnClientDataSent?.Invoke(segment, Channels.Reliable); + } + public override void ClientDisconnect() { client?.Disconnect(); client = null; @@ -150,11 +156,11 @@ public override Uri ServerUri() return builder.Uri; } public override bool ServerActive() => server != null && server.Active; - public override void ServerStart() + public override void ServerStart() { // create server server = new Telepathy.Server(serverMaxMessageSize); - + // server hooks // other systems hook into transport events in OnCreate or // OnStartRunning in no particular order. the only way to avoid @@ -172,11 +178,17 @@ public override void ServerStart() server.ReceiveTimeout = ReceiveTimeout; server.SendQueueLimit = serverSendQueueLimitPerConnection; server.ReceiveQueueLimit = serverReceiveQueueLimitPerConnection; - + server.Start(port); } - public override void ServerSend(int connectionId, ArraySegment segment, int channelId) => server?.Send(connectionId, segment); + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + server?.Send(connectionId, segment); + + // call event. might be null if no statistics are listening etc. + OnServerDataSent?.Invoke(connectionId, segment, Channels.Reliable); + } public override void ServerDisconnect(int connectionId) => server?.Disconnect(connectionId); public override string ServerGetClientAddress(int connectionId) { @@ -197,7 +209,7 @@ public override string ServerGetClientAddress(int connectionId) return "unknown"; } } - public override void ServerStop() + public override void ServerStop() { server?.Stop(); server = null; diff --git a/Assets/Mirror/Tests/Common/MemoryTransport.cs b/Assets/Mirror/Tests/Common/MemoryTransport.cs index 0cef1bde5d7..82760a1615f 100644 --- a/Assets/Mirror/Tests/Common/MemoryTransport.cs +++ b/Assets/Mirror/Tests/Common/MemoryTransport.cs @@ -72,6 +72,9 @@ public override void ClientSend(ArraySegment segment, int channelId) // add server data message with connId=1 because 0 is reserved serverIncoming.Enqueue(new Message(1, EventType.Data, data)); + + // call event. might be null if no statistics are listening etc. + OnClientDataSent?.Invoke(segment, channelId); } } public override void ClientDisconnect() @@ -147,6 +150,9 @@ public override void ServerSend(int connectionId, ArraySegment segment, in // add client data message clientIncoming.Enqueue(new Message(0, EventType.Data, data)); + + // call event. might be null if no statistics are listening etc. + OnServerDataSent?.Invoke(connectionId, segment, channelId); } } From 3d03146699815a4eea05b93336160dea84816fb5 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 11 Mar 2022 13:29:39 +0800 Subject: [PATCH 037/824] feature: NetworkStatistics (#3118) * feature: NetworkStatistics * better UI --- Assets/Mirror/Components/NetworkStatistics.cs | 185 ++++++++++++++++++ .../Components/NetworkStatistics.cs.meta | 11 ++ Assets/Mirror/Runtime/Utils.cs | 18 ++ Assets/Mirror/Tests/Editor/UtilsTests.cs | 24 +++ 4 files changed, 238 insertions(+) create mode 100644 Assets/Mirror/Components/NetworkStatistics.cs create mode 100644 Assets/Mirror/Components/NetworkStatistics.cs.meta diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs new file mode 100644 index 00000000000..917a7812b88 --- /dev/null +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -0,0 +1,185 @@ +using System; +using UnityEngine; + +namespace Mirror +{ + public class NetworkStatistics : MonoBehaviour + { + // update interval + double intervalStartTime; + + // --------------------------------------------------------------------- + + // CLIENT + // long bytes to support >2GB + int clientIntervalReceivedPackets; + long clientIntervalReceivedBytes; + int clientIntervalSentPackets; + long clientIntervalSentBytes; + + // results from last interval + // long bytes to support >2GB + int clientReceivedPacketsPerSecond; + long clientReceivedBytesPerSecond; + int clientSentPacketsPerSecond; + long clientSentBytesPerSecond; + + // --------------------------------------------------------------------- + + // SERVER + // capture interval + // long bytes to support >2GB + int serverIntervalReceivedPackets; + long serverIntervalReceivedBytes; + int serverIntervalSentPackets; + long serverIntervalSentBytes; + + // results from last interval + // long bytes to support >2GB + int serverReceivedPacketsPerSecond; + long serverReceivedBytesPerSecond; + int serverSentPacketsPerSecond; + long serverSentBytesPerSecond; + + // NetworkManager sets Transport.activeTransport in Awake(). + // so let's hook into it in Start(). + void Start() + { + // find available transport + Transport transport = Transport.activeTransport; + if (transport != null) + { + transport.OnClientDataReceived += OnClientReceive; + transport.OnClientDataSent += OnClientSend; + transport.OnServerDataReceived += OnServerReceive; + transport.OnServerDataSent += OnServerSend; + } + else Debug.LogError($"NetworkStatistics: no available or active Transport found on this platform: {Application.platform}"); + } + + void OnDestroy() + { + // remove transport hooks + Transport transport = Transport.activeTransport; + if (transport != null) + { + transport.OnClientDataReceived -= OnClientReceive; + transport.OnClientDataSent -= OnClientSend; + transport.OnServerDataReceived -= OnServerReceive; + transport.OnServerDataSent -= OnServerSend; + } + } + + void OnClientReceive(ArraySegment data, int channelId) + { + ++clientIntervalReceivedPackets; + clientIntervalReceivedBytes += data.Count; + } + + void OnClientSend(ArraySegment data, int channelId) + { + ++clientIntervalSentPackets; + clientIntervalSentBytes += data.Count; + } + + void OnServerReceive(int connectionId, ArraySegment data, int channelId) + { + ++serverIntervalReceivedPackets; + serverIntervalReceivedBytes += data.Count; + } + + void OnServerSend(int connectionId, ArraySegment data, int channelId) + { + ++serverIntervalSentPackets; + serverIntervalSentBytes += data.Count; + } + + void UpdateClient() + { + clientReceivedPacketsPerSecond = clientIntervalReceivedPackets; + clientReceivedBytesPerSecond = clientIntervalReceivedBytes; + clientSentPacketsPerSecond = clientIntervalSentPackets; + clientSentBytesPerSecond = clientIntervalSentBytes; + + clientIntervalReceivedPackets = 0; + clientIntervalReceivedBytes = 0; + clientIntervalSentPackets = 0; + clientIntervalSentBytes = 0; + } + + void UpdateServer() + { + serverReceivedPacketsPerSecond = serverIntervalReceivedPackets; + serverReceivedBytesPerSecond = serverIntervalReceivedBytes; + serverSentPacketsPerSecond = serverIntervalSentPackets; + serverSentBytesPerSecond = serverIntervalSentBytes; + + serverIntervalReceivedPackets = 0; + serverIntervalReceivedBytes = 0; + serverIntervalSentPackets = 0; + serverIntervalSentBytes = 0; + } + + void Update() + { + // calculate results every second + if (Time.timeAsDouble >= intervalStartTime + 1) + { + if (NetworkClient.active) UpdateClient(); + if (NetworkServer.active) UpdateServer(); + + intervalStartTime = Time.timeAsDouble; + } + } + + void OnClientGUI() + { + // background + GUILayout.BeginVertical("Box"); + GUILayout.Label("Client Statistics"); + + // sending ("msgs" instead of "packets" to fit larger numbers) + GUILayout.Label($"Send: {clientSentPacketsPerSecond} msgs @ {Utils.PrettyBytes(clientSentBytesPerSecond)}/s"); + + // receiving ("msgs" instead of "packets" to fit larger numbers) + GUILayout.Label($"Recv: {clientReceivedPacketsPerSecond} msgs @ {Utils.PrettyBytes(clientReceivedBytesPerSecond)}/s"); + + // end background + GUILayout.EndVertical(); + } + + void OnServerGUI() + { + // background + GUILayout.BeginVertical("Box"); + GUILayout.Label("Server Statistics"); + + // sending ("msgs" instead of "packets" to fit larger numbers) + GUILayout.Label($"Send: {serverSentPacketsPerSecond} msgs @ {Utils.PrettyBytes(serverSentBytesPerSecond)}/s"); + + // receiving ("msgs" instead of "packets" to fit larger numbers) + GUILayout.Label($"Recv: {serverReceivedPacketsPerSecond} msgs @ {Utils.PrettyBytes(serverReceivedBytesPerSecond)}/s"); + + // end background + GUILayout.EndVertical(); + } + + void OnGUI() + { + // only show if either server or client active + if (NetworkClient.active || NetworkServer.active) + { + // create main GUI area + // 105 is below NetworkManager HUD in all cases. + GUILayout.BeginArea(new Rect(15, 105, 210, 300)); + + // show client / server stats if active + if (NetworkClient.active) OnClientGUI(); + if (NetworkServer.active) OnServerGUI(); + + // end of GUI area + GUILayout.EndArea(); + } + } + } +} diff --git a/Assets/Mirror/Components/NetworkStatistics.cs.meta b/Assets/Mirror/Components/NetworkStatistics.cs.meta new file mode 100644 index 00000000000..0bf4251fcd7 --- /dev/null +++ b/Assets/Mirror/Components/NetworkStatistics.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6d7da4e566d24ea7b0e12178d934b648 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Runtime/Utils.cs b/Assets/Mirror/Runtime/Utils.cs index 27e29f934b5..d39ed98fce3 100644 --- a/Assets/Mirror/Runtime/Utils.cs +++ b/Assets/Mirror/Runtime/Utils.cs @@ -78,6 +78,24 @@ public static bool IsPointInScreen(Vector2 point) => 0 <= point.x && point.x < Screen.width && 0 <= point.y && point.y < Screen.height; + // pretty print bytes as KB/MB/GB/etc. from DOTSNET + // long to support > 2GB + // divides by floats to return "2.5MB" etc. + public static string PrettyBytes(long bytes) + { + // bytes + if (bytes < 1024) + return $"{bytes} B"; + // kilobytes + else if (bytes < 1024L * 1024L) + return $"{(bytes / 1024f):F2} KB"; + // megabytes + else if (bytes < 1024 * 1024L * 1024L) + return $"{(bytes / (1024f * 1024f)):F2} MB"; + // gigabytes + return $"{(bytes / (1024f * 1024f * 1024f)):F2} GB"; + } + // universal .spawned function [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NetworkIdentity GetSpawnedInServerOrClient(uint netId) diff --git a/Assets/Mirror/Tests/Editor/UtilsTests.cs b/Assets/Mirror/Tests/Editor/UtilsTests.cs index cda272b2f62..f964d74d59b 100644 --- a/Assets/Mirror/Tests/Editor/UtilsTests.cs +++ b/Assets/Mirror/Tests/Editor/UtilsTests.cs @@ -27,5 +27,29 @@ public void IsPointInScreen() Assert.That(Utils.IsPointInScreen(new Vector2(width / 2, height)), Is.False); Assert.That(Utils.IsPointInScreen(new Vector2(width + 1, height + 1)), Is.False); } + + [Test] + public void PrettyBytes() + { + // bytes + Assert.That(Utils.PrettyBytes(0), Is.EqualTo("0 B")); + Assert.That(Utils.PrettyBytes(512), Is.EqualTo("512 B")); + Assert.That(Utils.PrettyBytes(1023), Is.EqualTo("1023 B")); + + // kilobytes + Assert.That(Utils.PrettyBytes(1024), Is.EqualTo("1.00 KB")); + Assert.That(Utils.PrettyBytes(1024 + 512), Is.EqualTo("1.50 KB")); + Assert.That(Utils.PrettyBytes(2048), Is.EqualTo("2.00 KB")); + + // megabytes + Assert.That(Utils.PrettyBytes(1024 * 1024), Is.EqualTo("1.00 MB")); + Assert.That(Utils.PrettyBytes(1024 * (1024 + 512)), Is.EqualTo("1.50 MB")); + Assert.That(Utils.PrettyBytes(1024 * 1024 * 2), Is.EqualTo("2.00 MB")); + + // gigabytes + Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L), Is.EqualTo("1.00 GB")); + Assert.That(Utils.PrettyBytes(1024L * 1024L * (1024L + 512L)), Is.EqualTo("1.50 GB")); + Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L * 2L), Is.EqualTo("2.00 GB")); + } } } From b6a9a955726877462c206804f6f3948c696913e1 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 11 Mar 2022 13:33:36 +0800 Subject: [PATCH 038/824] align NetworkStatistics exactly below NetworkManager HUD --- Assets/Mirror/Components/NetworkStatistics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index 917a7812b88..3f8d2aa603a 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -171,7 +171,7 @@ void OnGUI() { // create main GUI area // 105 is below NetworkManager HUD in all cases. - GUILayout.BeginArea(new Rect(15, 105, 210, 300)); + GUILayout.BeginArea(new Rect(10, 105, 215, 300)); // show client / server stats if active if (NetworkClient.active) OnClientGUI(); From 5515eae4e89d4b605b5b3b7feb24ff00cab3fdfb Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 11 Mar 2022 23:55:35 +0800 Subject: [PATCH 039/824] perf: HandleRemoteCall passes componentIndex as byte to avoid extra < 0 check --- Assets/Mirror/Runtime/NetworkIdentity.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 3a5b8d7ff8a..6e5fbaf2d53 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -1061,7 +1061,7 @@ internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState) } // Helper function to handle Command/Rpc - internal void HandleRemoteCall(int componentIndex, int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) + internal void HandleRemoteCall(byte componentIndex, int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) { // check if unity object has been destroyed if (this == null) @@ -1071,7 +1071,7 @@ internal void HandleRemoteCall(int componentIndex, int functionHash, RemoteCallT } // find the right component to invoke the function on - if (componentIndex < 0 || componentIndex >= NetworkBehaviours.Length) + if (componentIndex >= NetworkBehaviours.Length) { Debug.LogWarning($"Component [{componentIndex}] not found for [netId={netId}]"); return; From a868368ecac7574410bed15476ef03db3b2559df Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 18:26:00 +0800 Subject: [PATCH 040/824] perf: Rpcs/Cmds functionHash bandwidth reduced from 4 => 2 bytes (with the potential for 1 byte VarInt) (#3119) * perf: Rpcs/Cmds functionHash bandwidth reduced from 4 => 2 bytes (with the potential for 1 byte VarInt) * tests --- Assets/Mirror/Runtime/Messages.cs | 8 ++- Assets/Mirror/Runtime/NetworkBehaviour.cs | 10 ++- Assets/Mirror/Runtime/NetworkClient.cs | 2 +- Assets/Mirror/Runtime/NetworkIdentity.cs | 8 +-- Assets/Mirror/Runtime/NetworkServer.cs | 4 +- Assets/Mirror/Runtime/RemoteCalls.cs | 81 +++++++++++++++++++---- Assets/Mirror/Tests/Common/MirrorTest.cs | 5 ++ 7 files changed, 90 insertions(+), 28 deletions(-) diff --git a/Assets/Mirror/Runtime/Messages.cs b/Assets/Mirror/Runtime/Messages.cs index d3816f8ac53..2ec729d127e 100644 --- a/Assets/Mirror/Runtime/Messages.cs +++ b/Assets/Mirror/Runtime/Messages.cs @@ -28,7 +28,9 @@ public struct CommandMessage : NetworkMessage { public uint netId; public byte componentIndex; - public int functionHash; + // NOTE: this could be 1 byte most of the time via VarInt! + // but requires custom serialization for Command/RpcMessages. + public ushort functionIndex; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; @@ -38,7 +40,9 @@ public struct RpcMessage : NetworkMessage { public uint netId; public byte componentIndex; - public int functionHash; + // NOTE: this could be 1 byte most of the time via VarInt! + // but requires custom serialization for Command/RpcMessages. + public ushort functionIndex; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index adb0367f5b7..25d39063792 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; +using Mirror.RemoteCalls; namespace Mirror { @@ -235,8 +236,7 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer { netId = netId, componentIndex = (byte)ComponentIndex, - // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -271,8 +271,7 @@ protected void SendRPCInternal(string functionFullName, NetworkWriter writer, in { netId = netId, componentIndex = (byte)ComponentIndex, - // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -319,8 +318,7 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull { netId = netId, componentIndex = (byte)ComponentIndex, - // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), // segment to avoid reader allocations payload = writer.ToArraySegment() }; diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 4357b53e2d5..4ca4d143c2a 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1264,7 +1264,7 @@ static void OnRPCMessage(RpcMessage message) if (spawned.TryGetValue(message.netId, out NetworkIdentity identity)) { using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload)) - identity.HandleRemoteCall(message.componentIndex, message.functionHash, RemoteCallType.ClientRpc, networkReader); + identity.HandleRemoteCall(message.componentIndex, message.functionIndex, RemoteCallType.ClientRpc, networkReader); } } diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 6e5fbaf2d53..9865d798578 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -1061,12 +1061,12 @@ internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState) } // Helper function to handle Command/Rpc - internal void HandleRemoteCall(byte componentIndex, int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) + internal void HandleRemoteCall(byte componentIndex, ushort functionIndex, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) { // check if unity object has been destroyed if (this == null) { - Debug.LogWarning($"{remoteCallType} [{functionHash}] received for deleted object [netId={netId}]"); + Debug.LogWarning($"{remoteCallType} [{functionIndex}] received for deleted object [netId={netId}]"); return; } @@ -1078,9 +1078,9 @@ internal void HandleRemoteCall(byte componentIndex, int functionHash, RemoteCall } NetworkBehaviour invokeComponent = NetworkBehaviours[componentIndex]; - if (!RemoteProcedureCalls.Invoke(functionHash, remoteCallType, reader, invokeComponent, senderConnection)) + if (!RemoteProcedureCalls.Invoke(functionIndex, remoteCallType, reader, invokeComponent, senderConnection)) { - Debug.LogError($"Found no receiver for incoming {remoteCallType} [{functionHash}] on {gameObject.name}, the server and client should have the same NetworkBehaviour instances [netId={netId}]."); + Debug.LogError($"Found no receiver for incoming {remoteCallType} [{functionIndex}] on {gameObject.name}, the server and client should have the same NetworkBehaviour instances [netId={netId}]."); } } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 45d00c491fb..f208f3c3b3a 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -966,7 +966,7 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // 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 - bool requiresAuthority = RemoteProcedureCalls.CommandRequiresAuthority(msg.functionHash); + bool requiresAuthority = RemoteProcedureCalls.CommandRequiresAuthority(msg.functionIndex); if (requiresAuthority && identity.connectionToClient != conn) { Debug.LogWarning($"Command for object without authority [netId={msg.netId}]"); @@ -976,7 +976,7 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}"); using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload)) - identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); + identity.HandleRemoteCall(msg.componentIndex, msg.functionIndex, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); } // spawning //////////////////////////////////////////////////////////// diff --git a/Assets/Mirror/Runtime/RemoteCalls.cs b/Assets/Mirror/Runtime/RemoteCalls.cs index 0005287cf11..18dfa172e5e 100644 --- a/Assets/Mirror/Runtime/RemoteCalls.cs +++ b/Assets/Mirror/Runtime/RemoteCalls.cs @@ -30,10 +30,44 @@ public bool AreEqual(Type componentType, RemoteCallType remoteCallType, RemoteCa /// Used to help manage remote calls for NetworkBehaviours public static class RemoteProcedureCalls { - // one lookup for all remote calls. - // allows us to easily add more remote call types without duplicating code. - // note: do not clear those with [RuntimeInitializeOnLoad] - static readonly Dictionary remoteCallDelegates = new Dictionary(); + // sending rpc/cmd function hash would require 4 bytes each time. + // instead, let's only send the index to save bandwidth. + // => 1 byte index with 255 rpcs in total would not be enough. + // => 1 byte index with 255 rpcs per type is doable but lookup is hard, + // because an rpc might be in the actual type or in the base type etc + // => 2 byte index allows for 64k Rpcs and is very easy to implement + // with a SortedList + .IndexOfKey. + // + // NOTE: this could be 1 byte most of the time via VarInt! + // but requires custom serialization for Command/RpcMessages. + // + // SortedList still doesn't allow duplicate keys, which is good. + // But it allows accessing keys by index. + static readonly SortedList remoteCallDelegates = new SortedList(); + + // hash -> index reverse lookup to cache .IndexOfKey() binary search. + static readonly Dictionary remoteCallIndexLookup = new Dictionary(); + + // helper function to get rpc/cmd index from function name / hash. + internal static ushort GetIndexFromFunctionHash(string functionFullName) + { + int hash = functionFullName.GetStableHashCode(); + + // IndexOfKey runs a binary search. + // cache results in lookup. + // IMPORTANT: can't cache results when registering rpcs/cmds as the + // indices would only be valid after ALL were registered. + // return (ushort)remoteCallDelegates.IndexOfKey(hash); + + // reuse cached index if possible + if (remoteCallIndexLookup.TryGetValue(hash, out ushort index)) + return index; + + // otherwise search and cache + ushort searchedIndex = (ushort)remoteCallDelegates.IndexOfKey(hash); + remoteCallIndexLookup[hash] = searchedIndex; + return searchedIndex; + } static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, int functionHash) { @@ -64,6 +98,7 @@ internal static int RegisterDelegate(Type componentType, string functionFullName if (CheckIfDelegateExists(componentType, remoteCallType, func, hash)) return hash; + // register invoker by hash remoteCallDelegates[hash] = new Invoker { callType = remoteCallType, @@ -93,18 +128,30 @@ internal static void RemoveDelegate(int hash) => // note: no need to throw an error if not found. // an attacker might just try to call a cmd with an rpc's hash etc. // returning false is enough. - static bool GetInvokerForHash(int functionHash, RemoteCallType remoteCallType, out Invoker invoker) => - remoteCallDelegates.TryGetValue(functionHash, out invoker) && - invoker != null && - invoker.callType == remoteCallType; + static bool GetInvoker(ushort functionIndex, RemoteCallType remoteCallType, out Invoker invoker) + { + // valid index? + if (functionIndex <= remoteCallDelegates.Count) + { + // get key by index + int functionHash = remoteCallDelegates.Keys[functionIndex]; + invoker = remoteCallDelegates[functionHash]; + // check rpc type. don't allow calling cmds from rpcs, etc. + return invoker != null && + invoker.callType == remoteCallType; + } + invoker = null; + return false; + } // InvokeCmd/Rpc Delegate can all use the same function here - internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) + // => invoke by index to save bandwidth (2 bytes instead of 4 bytes) + internal static bool Invoke(ushort functionIndex, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) { // IMPORTANT: we check if the message's componentIndex component is // actually of the right type. prevents attackers trying // to invoke remote calls on wrong components. - if (GetInvokerForHash(functionHash, remoteCallType, out Invoker invoker) && + if (GetInvoker(functionIndex, remoteCallType, out Invoker invoker) && invoker.componentType.IsInstanceOfType(component)) { // invoke function on this component @@ -115,8 +162,8 @@ internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, Net } // check if the command 'requiresAuthority' which is set in the attribute - internal static bool CommandRequiresAuthority(int cmdHash) => - GetInvokerForHash(cmdHash, RemoteCallType.Command, out Invoker invoker) && + internal static bool CommandRequiresAuthority(ushort cmdIndex) => + GetInvoker(cmdIndex, RemoteCallType.Command, out Invoker invoker) && invoker.cmdRequiresAuthority; /// Gets the handler function by hash. Useful for profilers and debuggers. @@ -124,6 +171,14 @@ public static RemoteCallDelegate GetDelegate(int functionHash) => remoteCallDelegates.TryGetValue(functionHash, out Invoker invoker) ? invoker.function : null; + + // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload + [RuntimeInitializeOnLoadMethod] + internal static void ResetStatics() + { + // clear rpc lookup every time. + // otherwise tests may have issues. + remoteCallIndexLookup.Clear(); + } } } - diff --git a/Assets/Mirror/Tests/Common/MirrorTest.cs b/Assets/Mirror/Tests/Common/MirrorTest.cs index 9559b9e2f79..fbf3620a620 100644 --- a/Assets/Mirror/Tests/Common/MirrorTest.cs +++ b/Assets/Mirror/Tests/Common/MirrorTest.cs @@ -1,6 +1,7 @@ // base class for networking tests to make things easier. using System.Collections.Generic; using System.Linq; +using Mirror.RemoteCalls; using NUnit.Framework; using UnityEngine; @@ -48,6 +49,10 @@ public virtual void TearDown() GameObject.DestroyImmediate(transport.gameObject); Transport.activeTransport = null; NetworkManager.singleton = null; + + // clear rpc lookup caches. + // this can cause problems in tests otherwise. + RemoteProcedureCalls.ResetStatics(); } // create a tracked GameObject for tests without Networkidentity From 5fcbd1d55000444a9be7a654425700ba545c6448 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 19:19:43 +0800 Subject: [PATCH 041/824] perf: CompressVarInt uses WriteByte instead of Write to avoid Action call overhead --- Assets/Mirror/Runtime/Compression.cs | 90 ++++++++++++++-------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/Assets/Mirror/Runtime/Compression.cs b/Assets/Mirror/Runtime/Compression.cs index f53639da202..3e4b0f6110f 100644 --- a/Assets/Mirror/Runtime/Compression.cs +++ b/Assets/Mirror/Runtime/Compression.cs @@ -201,84 +201,84 @@ public static void CompressVarUInt(NetworkWriter writer, ulong value) { if (value <= 240) { - writer.Write((byte)value); + writer.WriteByte((byte)value); return; } if (value <= 2287) { - writer.Write((byte)(((value - 240) >> 8) + 241)); - writer.Write((byte)((value - 240) & 0xFF)); + writer.WriteByte((byte)(((value - 240) >> 8) + 241)); + writer.WriteByte((byte)((value - 240) & 0xFF)); return; } if (value <= 67823) { - writer.Write((byte)249); - writer.Write((byte)((value - 2288) >> 8)); - writer.Write((byte)((value - 2288) & 0xFF)); + writer.WriteByte((byte)249); + writer.WriteByte((byte)((value - 2288) >> 8)); + writer.WriteByte((byte)((value - 2288) & 0xFF)); return; } if (value <= 16777215) { - writer.Write((byte)250); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)250); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); return; } if (value <= 4294967295) { - writer.Write((byte)251); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); - writer.Write((byte)((value >> 24) & 0xFF)); + writer.WriteByte((byte)251); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)((value >> 24) & 0xFF)); return; } if (value <= 1099511627775) { - writer.Write((byte)252); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); - writer.Write((byte)((value >> 24) & 0xFF)); - writer.Write((byte)((value >> 32) & 0xFF)); + writer.WriteByte((byte)252); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)((value >> 24) & 0xFF)); + writer.WriteByte((byte)((value >> 32) & 0xFF)); return; } if (value <= 281474976710655) { - writer.Write((byte)253); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); - writer.Write((byte)((value >> 24) & 0xFF)); - writer.Write((byte)((value >> 32) & 0xFF)); - writer.Write((byte)((value >> 40) & 0xFF)); + writer.WriteByte((byte)253); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)((value >> 24) & 0xFF)); + writer.WriteByte((byte)((value >> 32) & 0xFF)); + writer.WriteByte((byte)((value >> 40) & 0xFF)); return; } if (value <= 72057594037927935) { - writer.Write((byte)254); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); - writer.Write((byte)((value >> 24) & 0xFF)); - writer.Write((byte)((value >> 32) & 0xFF)); - writer.Write((byte)((value >> 40) & 0xFF)); - writer.Write((byte)((value >> 48) & 0xFF)); + writer.WriteByte((byte)254); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)((value >> 24) & 0xFF)); + writer.WriteByte((byte)((value >> 32) & 0xFF)); + writer.WriteByte((byte)((value >> 40) & 0xFF)); + writer.WriteByte((byte)((value >> 48) & 0xFF)); return; } // all others { - writer.Write((byte)255); - writer.Write((byte)(value & 0xFF)); - writer.Write((byte)((value >> 8) & 0xFF)); - writer.Write((byte)((value >> 16) & 0xFF)); - writer.Write((byte)((value >> 24) & 0xFF)); - writer.Write((byte)((value >> 32) & 0xFF)); - writer.Write((byte)((value >> 40) & 0xFF)); - writer.Write((byte)((value >> 48) & 0xFF)); - writer.Write((byte)((value >> 56) & 0xFF)); + writer.WriteByte((byte)255); + writer.WriteByte((byte)(value & 0xFF)); + writer.WriteByte((byte)((value >> 8) & 0xFF)); + writer.WriteByte((byte)((value >> 16) & 0xFF)); + writer.WriteByte((byte)((value >> 24) & 0xFF)); + writer.WriteByte((byte)((value >> 32) & 0xFF)); + writer.WriteByte((byte)((value >> 40) & 0xFF)); + writer.WriteByte((byte)((value >> 48) & 0xFF)); + writer.WriteByte((byte)((value >> 56) & 0xFF)); } } From 45e1afc812c48e600c35f6311ab384d57d732ac0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 19:46:00 +0800 Subject: [PATCH 042/824] perf: inline GetIndexFromFunctionHash because it's called for every RPC --- Assets/Mirror/Runtime/RemoteCalls.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Assets/Mirror/Runtime/RemoteCalls.cs b/Assets/Mirror/Runtime/RemoteCalls.cs index 18dfa172e5e..4e17004237e 100644 --- a/Assets/Mirror/Runtime/RemoteCalls.cs +++ b/Assets/Mirror/Runtime/RemoteCalls.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Runtime.CompilerServices; using UnityEngine; namespace Mirror.RemoteCalls @@ -49,6 +50,7 @@ public static class RemoteProcedureCalls static readonly Dictionary remoteCallIndexLookup = new Dictionary(); // helper function to get rpc/cmd index from function name / hash. + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static ushort GetIndexFromFunctionHash(string functionFullName) { int hash = functionFullName.GetStableHashCode(); From 18170d16b85d1353faf8b32c7ca3adc4a1182333 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 20:26:02 +0800 Subject: [PATCH 043/824] perf: NetworkTime.localTime inlined --- Assets/Mirror/Runtime/NetworkTime.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkTime.cs b/Assets/Mirror/Runtime/NetworkTime.cs index ff8f19340ef..337c6c0bf2c 100644 --- a/Assets/Mirror/Runtime/NetworkTime.cs +++ b/Assets/Mirror/Runtime/NetworkTime.cs @@ -1,4 +1,5 @@ using System; +using System.Runtime.CompilerServices; using UnityEngine; #if !UNITY_2020_3_OR_NEWER using Stopwatch = System.Diagnostics.Stopwatch; @@ -26,7 +27,11 @@ public static class NetworkTime /// Returns double precision clock time _in this system_, unaffected by the network. #if UNITY_2020_3_OR_NEWER - public static double localTime => Time.timeAsDouble; + public static double localTime + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Time.timeAsDouble; + } #else // need stopwatch for older Unity versions, but it's quite slow. // CAREFUL: unlike Time.time, this is not a FRAME time. From d979880c5f5c107e8578233eda61cd99b20122c0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 20:26:20 +0800 Subject: [PATCH 044/824] perf: NetworkTime.time inlined --- Assets/Mirror/Runtime/NetworkTime.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkTime.cs b/Assets/Mirror/Runtime/NetworkTime.cs index 337c6c0bf2c..1721524ccf7 100644 --- a/Assets/Mirror/Runtime/NetworkTime.cs +++ b/Assets/Mirror/Runtime/NetworkTime.cs @@ -53,7 +53,11 @@ public static double localTime // and you cast down to float, then the time will jump in 0.4s intervals. // // TODO consider using Unbatcher's remoteTime for NetworkTime - public static double time => localTime - _offset.Value; + public static double time + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => localTime - _offset.Value; + } /// Time measurement variance. The higher, the less accurate the time is. // TODO does this need to be public? user should only need NetworkTime.time From f4ceb067c9072f9120856f186d4b045c098338b9 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 12 Mar 2022 20:26:40 +0800 Subject: [PATCH 045/824] perf: NetworkTime ExponentialMovingAverage .Value & .Var as fields instead of properties to avoid call overhead --- Assets/Mirror/Runtime/ExponentialMovingAverage.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs index 9463168daa1..64b91e13b4e 100644 --- a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs +++ b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs @@ -8,8 +8,8 @@ public class ExponentialMovingAverage readonly float alpha; bool initialized; - public double Value { get; private set; } - public double Var { get; private set; } + public double Value; + public double Var; public ExponentialMovingAverage(int n) { From 8f357d5961f15cc615078e94f279dcd32aa61aa9 Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 15 Mar 2022 00:23:11 +0800 Subject: [PATCH 046/824] fix: NetworkStatistics Unity 2019 support --- Assets/Mirror/Components/NetworkStatistics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index 3f8d2aa603a..98bca9e01fb 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -123,7 +123,7 @@ void UpdateServer() void Update() { // calculate results every second - if (Time.timeAsDouble >= intervalStartTime + 1) + if (NetworkTime.localTime >= intervalStartTime + 1) { if (NetworkClient.active) UpdateClient(); if (NetworkServer.active) UpdateServer(); From 4b207d2994477e90e15bcecf8265f97b386318ff Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 19 Mar 2022 01:45:13 +0800 Subject: [PATCH 047/824] fix: NetworkStatistics Unity 2019 support (part two) --- Assets/Mirror/Components/NetworkStatistics.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index 98bca9e01fb..2938191f0bd 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -128,7 +128,7 @@ void Update() if (NetworkClient.active) UpdateClient(); if (NetworkServer.active) UpdateServer(); - intervalStartTime = Time.timeAsDouble; + intervalStartTime = NetworkTime.localTime; } } From 012d7a5d1ed0561833612643b6b552a561e374ca Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 22 Mar 2022 22:33:29 +0800 Subject: [PATCH 048/824] Tests: runtime clientscene tests simplified and they don't rely on host mode anymore --- ...lientSceneTests_DestroyAllClientObjects.cs | 84 +++++++------------ 1 file changed, 30 insertions(+), 54 deletions(-) diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs index 22197d03362..b52d1cd81e1 100644 --- a/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs @@ -36,6 +36,8 @@ public class ClientSceneTests_DestroyAllClientObjects : MirrorPlayModeTest { Dictionary unspawnHandlers => NetworkClient.unspawnHandlers; + NetworkConnectionToClient connectionToClient; + [UnitySetUp] public override IEnumerator UnitySetUp() { @@ -43,7 +45,7 @@ public override IEnumerator UnitySetUp() // start server & client and wait 1 frame NetworkServer.Listen(1); - ConnectHostClientBlockingAuthenticatedAndReady(); + ConnectClientBlockingAuthenticatedAndReady(out connectionToClient); yield return null; } @@ -55,105 +57,78 @@ public override IEnumerator UnityTearDown() yield return null; } - TestListenerBehaviour CreateAndAddObject(ulong sceneId) + void CreateAndAddObject(ulong sceneId, out TestListenerBehaviour serverComp, out TestListenerBehaviour clientComp) { - CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out TestListenerBehaviour listener); - identity.sceneId = sceneId; - return listener; + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity serverIdentity, out serverComp, + out _, out NetworkIdentity clientIdentity, out clientComp, + connectionToClient); + serverIdentity.sceneId = clientIdentity.sceneId = sceneId; } [UnityTest] public IEnumerator DestroysAllNetworkPrefabsInScene() { // sceneId 0 is prefab - TestListenerBehaviour listener1 = CreateAndAddObject(0); - TestListenerBehaviour listener2 = CreateAndAddObject(0); - - int destroyCalled1 = 0; - int destroyCalled2 = 0; + CreateAndAddObject(0, out TestListenerBehaviour serverComp, out TestListenerBehaviour clientComp); - listener1.onDestroyCalled += () => destroyCalled1++; - listener2.onDestroyCalled += () => destroyCalled2++; + int destroyCalled = 0; + clientComp.onDestroyCalled += () => destroyCalled++; NetworkClient.DestroyAllClientObjects(); // wait for frame to make sure unity events are called yield return null; - - Assert.That(destroyCalled1, Is.EqualTo(1)); - Assert.That(destroyCalled2, Is.EqualTo(1)); + Assert.That(destroyCalled, Is.EqualTo(1)); } [UnityTest] public IEnumerator DisablesAllNetworkSceneObjectsInScene() { // sceneId 0 is prefab - TestListenerBehaviour listener1 = CreateAndAddObject(101); - TestListenerBehaviour listener2 = CreateAndAddObject(102); - - int disableCalled1 = 0; - int disableCalled2 = 0; + CreateAndAddObject(101, out TestListenerBehaviour serverComp, out TestListenerBehaviour clientComp); - listener1.onDisableCalled += () => disableCalled1++; - listener2.onDisableCalled += () => disableCalled2++; + int disableCalled = 0; + clientComp.onDisableCalled += () => disableCalled++; - int destroyCalled1 = 0; - int destroyCalled2 = 0; - - listener1.onDestroyCalled += () => destroyCalled1++; - listener2.onDestroyCalled += () => destroyCalled2++; + int destroyCalled = 0; + clientComp.onDestroyCalled += () => destroyCalled++; NetworkClient.DestroyAllClientObjects(); // wait for frame to make sure unity events are called yield return null; - Assert.That(disableCalled1, Is.EqualTo(1)); - Assert.That(disableCalled2, Is.EqualTo(1)); - - Assert.That(destroyCalled1, Is.EqualTo(0), "Scene objects should not be destroyed"); - Assert.That(destroyCalled2, Is.EqualTo(0), "Scene objects should not be destroyed"); + Assert.That(disableCalled, Is.EqualTo(1)); + Assert.That(destroyCalled, Is.EqualTo(0), "Scene objects should not be destroyed"); } [Test] public void CallsUnspawnHandlerInsteadOfDestroy() { // sceneId 0 is prefab - TestListenerBehaviour listener1 = CreateAndAddObject(0); - TestListenerBehaviour listener2 = CreateAndAddObject(0); + CreateAndAddObject(0, out TestListenerBehaviour serverComp, out TestListenerBehaviour clientComp); Guid guid1 = Guid.NewGuid(); - Guid guid2 = Guid.NewGuid(); - int unspawnCalled1 = 0; - int unspawnCalled2 = 0; + int unspawnCalled = 0; + unspawnHandlers.Add(guid1, x => unspawnCalled++); + clientComp.GetComponent().assetId = guid1; - unspawnHandlers.Add(guid1, x => unspawnCalled1++); - unspawnHandlers.Add(guid2, x => unspawnCalled2++); - listener1.GetComponent().assetId = guid1; - listener2.GetComponent().assetId = guid2; + int disableCalled = 0; - int disableCalled1 = 0; - int disableCalled2 = 0; - - listener1.onDisableCalled += () => disableCalled1++; - listener2.onDisableCalled += () => disableCalled2++; + clientComp.onDisableCalled += () => disableCalled++; NetworkClient.DestroyAllClientObjects(); - Assert.That(unspawnCalled1, Is.EqualTo(1)); - Assert.That(unspawnCalled2, Is.EqualTo(1)); - - Assert.That(disableCalled1, Is.EqualTo(0), "Object with UnspawnHandler should not be destroyed"); - Assert.That(disableCalled2, Is.EqualTo(0), "Object with UnspawnHandler should not be destroyed"); + Assert.That(unspawnCalled, Is.EqualTo(1)); + Assert.That(disableCalled, Is.EqualTo(0), "Object with UnspawnHandler should not be destroyed"); } [Test] public void ClearsSpawnedList() { // sceneId 0 is prefab - TestListenerBehaviour listener1 = CreateAndAddObject(0); - TestListenerBehaviour listener2 = CreateAndAddObject(0); + CreateAndAddObject(0, out TestListenerBehaviour serverComp, out TestListenerBehaviour clientComp); NetworkClient.DestroyAllClientObjects(); @@ -164,7 +139,8 @@ public void ClearsSpawnedList() public void CatchesAndLogsExeptionWhenSpawnedListIsChanged() { // create spawned (needs to be added to .spawned!) - CreateNetworkedAndSpawn(out GameObject badGameObject, out NetworkIdentity identity, out BadBehaviour bad); + CreateNetworkedAndSpawn(out _, out _, out BadBehaviour serverComp, + out _, out _, out BadBehaviour clientComp); LogAssert.Expect(LogType.Exception, new Regex("InvalidOperationException")); LogAssert.Expect(LogType.Error, "Could not DestroyAllClientObjects because spawned list was modified during loop, make sure you are not modifying NetworkIdentity.spawned by calling NetworkServer.Destroy or NetworkServer.Spawn in OnDestroy or OnDisable."); From 2c138c5d71810487bdcf332e3c403ca3a7620018 Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 22 Mar 2022 22:38:58 +0800 Subject: [PATCH 049/824] hasAuthority comment updated --- Assets/Mirror/Runtime/NetworkBehaviour.cs | 3 +-- Assets/Mirror/Runtime/NetworkIdentity.cs | 6 +----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index 25d39063792..7d3b652ce58 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -45,8 +45,7 @@ public abstract class NetworkBehaviour : MonoBehaviour /// True if this object is on the client-only, not host. public bool isClientOnly => netIdentity.isClientOnly; - /// This returns true if this object is the authoritative version of the object in the distributed network application. - // keeping this ridiculous summary as a reminder of a time long gone... + /// True on client if that component has been assigned to the client. E.g. player, pets, henchmen. public bool hasAuthority => netIdentity.hasAuthority; /// The unique network Id of this object (unique at runtime). diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 9865d798578..a14e6ff295f 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -88,11 +88,7 @@ public sealed class NetworkIdentity : MonoBehaviour /// True if this object exists on a client that is not also acting as a server. public bool isClientOnly => isClient && !isServer; - /// True if this object is the authoritative player object on the client. - // Determined at runtime. For most objects, authority is held by the server. - // For objects that had their authority set by AssignClientAuthority on - // the server, this will be true on the client that owns the object. NOT - // on other clients. + /// True on client if that component has been assigned to the client. E.g. player, pets, henchmen. public bool hasAuthority { get; internal set; } /// The set of network connections (players) that can see this object. From 30244279335bf4dee7f6b9db711eb04c78ac43cd Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 23 Mar 2022 07:18:22 -0400 Subject: [PATCH 050/824] Fixed comments --- Assets/Mirror/Examples/Basic/Scripts/PlayerUI.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Assets/Mirror/Examples/Basic/Scripts/PlayerUI.cs b/Assets/Mirror/Examples/Basic/Scripts/PlayerUI.cs index 2d41ce4986f..e3fe6c94732 100644 --- a/Assets/Mirror/Examples/Basic/Scripts/PlayerUI.cs +++ b/Assets/Mirror/Examples/Basic/Scripts/PlayerUI.cs @@ -12,11 +12,7 @@ public class PlayerUI : MonoBehaviour public Text playerNameText; public Text playerDataText; - /// - /// Caches the controlling Player object, subscribes to its events - /// - /// Player object that controls this UI - /// true if the Player object is the Local Player + // Sets a highlight color for the local player public void SetLocalPlayer() { // add a visual background for the local player in the UI From cca7afdd0845a1ae026da23a07865bdd23a6acd6 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 23 Mar 2022 07:19:19 -0400 Subject: [PATCH 051/824] MultipleMatches example fixed --- .../Examples/MultipleMatches/Scripts/CanvasController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Examples/MultipleMatches/Scripts/CanvasController.cs b/Assets/Mirror/Examples/MultipleMatches/Scripts/CanvasController.cs index b9cdda57504..872892b251f 100644 --- a/Assets/Mirror/Examples/MultipleMatches/Scripts/CanvasController.cs +++ b/Assets/Mirror/Examples/MultipleMatches/Scripts/CanvasController.cs @@ -31,7 +31,7 @@ public class CanvasController : MonoBehaviour /// /// Player informations by Network Connection /// - internal static readonly Dictionary playerInfos = new Dictionary(); + internal static readonly Dictionary playerInfos = new Dictionary(); /// /// Network Connections that have neither started nor joined a match yet @@ -319,7 +319,7 @@ internal void OnStopServer() internal void OnClientConnect() { - playerInfos.Add((NetworkConnectionToClient)NetworkClient.connection, new PlayerInfo { playerIndex = this.playerIndex, ready = false }); + playerInfos.Add(NetworkClient.connection, new PlayerInfo { playerIndex = this.playerIndex, ready = false }); } internal void OnStartClient() From 3d6c41a595d4e0863910aafd3c8b11a233864260 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 23 Mar 2022 23:54:58 +0800 Subject: [PATCH 052/824] fix: #3122 DestroyAllClientObjects now also resets after unspawn handler was called (#3124) --- Assets/Mirror/Runtime/NetworkClient.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 4ca4d143c2a..48d0d864325 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1433,7 +1433,14 @@ public static void DestroyAllClientObjects() identity.OnStopClient(); bool wasUnspawned = InvokeUnSpawnHandler(identity.assetId, identity.gameObject); - if (!wasUnspawned) + + // unspawned objects should be reset for reuse later. + if (wasUnspawned) + { + identity.Reset(); + } + // without unspawn handler, we need to disable/destroy. + else { // scene objects are reset and disabled. // they always stay in the scene, we don't destroy them. From 6f981224a93e1d16387af454d5da6d4f85806510 Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 22 Mar 2022 02:26:02 +0800 Subject: [PATCH 053/824] fix: #2954 calling StopClient in host mode does not reset nextNetId anymore --- Assets/Mirror/Runtime/NetworkClient.cs | 11 +++++++---- Assets/Mirror/Runtime/NetworkIdentity.cs | 22 ++++++++++++++++++---- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 48d0d864325..95244fe4ed9 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1485,10 +1485,13 @@ public static void Shutdown() handlers.Clear(); spawnableObjects.Clear(); - // sets nextNetworkId to 1 - // sets clientAuthorityCallback to null - // sets previousLocalPlayer to null - NetworkIdentity.ResetStatics(); + // IMPORTANT: do NOT call NetworkIdentity.ResetStatics() here! + // calling StopClient() in host mode would reset nextNetId to 1, + // causing next connection to have a duplicate netId accidentally. + // => see also: https://github.com/vis2k/Mirror/issues/2954 + //NetworkIdentity.ResetStatics(); + // => instead, reset only the client sided statics. + NetworkIdentity.ResetClientStatics(); // disconnect the client connection. // we do NOT call Transport.Shutdown, because someone only called diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index a14e6ff295f..cd8bbe8d179 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -246,14 +246,28 @@ internal set static readonly Dictionary sceneIds = new Dictionary(); + // reset only client sided statics. + // don't touch server statics when calling StopClient in host mode. + // https://github.com/vis2k/Mirror/issues/2954 + internal static void ResetClientStatics() + { + previousLocalPlayer = null; + clientAuthorityCallback = null; + } + + internal static void ResetServerStatics() + { + nextNetworkId = 1; + } + // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload // internal so it can be called from NetworkServer & NetworkClient [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] internal static void ResetStatics() { - nextNetworkId = 1; - clientAuthorityCallback = null; - previousLocalPlayer = null; + // reset ALL statics + ResetClientStatics(); + ResetServerStatics(); } /// Gets the NetworkIdentity from the sceneIds dictionary with the corresponding id @@ -754,7 +768,7 @@ internal void OnStopClient() // ownership change due to sync to owner feature. // Without this static, the second time we get the spawn message we // would call OnStartLocalPlayer again on the same object - static NetworkIdentity previousLocalPlayer = null; + internal static NetworkIdentity previousLocalPlayer = null; internal void OnStartLocalPlayer() { if (previousLocalPlayer == this) From d8774ecd478b51c6404816c0d8a681ad6d8916fd Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 23 Mar 2022 23:58:47 +0800 Subject: [PATCH 054/824] fix: #2954 calling StopClient in host mode does not destroy other client's objects anymore --- Assets/Mirror/Runtime/NetworkClient.cs | 42 +++++++++++++++++--------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 95244fe4ed9..1f0803a4cde 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1432,27 +1432,39 @@ public static void DestroyAllClientObjects() identity.OnStopClient(); - bool wasUnspawned = InvokeUnSpawnHandler(identity.assetId, identity.gameObject); - - // unspawned objects should be reset for reuse later. - if (wasUnspawned) - { - identity.Reset(); - } - // without unspawn handler, we need to disable/destroy. - else + // NetworkClient.Shutdown calls DestroyAllClientObjects. + // which destroys all objects in NetworkClient.spawned. + // => NC.spawned contains owned & observed objects + // => in host mode, we CAN NOT destroy observed objects. + // => that would destroy them other connection's objects + // on the host server, making them disconnect. + // https://github.com/vis2k/Mirror/issues/2954 + bool hostOwned = identity.connectionToServer is LocalConnectionToServer; + bool shouldDestroy = !identity.isServer || hostOwned; + if (shouldDestroy) { - // scene objects are reset and disabled. - // they always stay in the scene, we don't destroy them. - if (identity.sceneId != 0) + bool wasUnspawned = InvokeUnSpawnHandler(identity.assetId, identity.gameObject); + + // unspawned objects should be reset for reuse later. + if (wasUnspawned) { identity.Reset(); - identity.gameObject.SetActive(false); } - // spawned objects are destroyed + // without unspawn handler, we need to disable/destroy. else { - GameObject.Destroy(identity.gameObject); + // scene objects are reset and disabled. + // they always stay in the scene, we don't destroy them. + if (identity.sceneId != 0) + { + identity.Reset(); + identity.gameObject.SetActive(false); + } + // spawned objects are destroyed + else + { + GameObject.Destroy(identity.gameObject); + } } } } From 1631402bd7cad3f349f12a4e0d4eb82821c382a9 Mon Sep 17 00:00:00 2001 From: Robin Rolf Date: Sat, 26 Mar 2022 06:06:30 +0100 Subject: [PATCH 055/824] fix: Byte format test uses current culture (#3126) Since some may use , instead of . as their decimal place seperator Error: String lengths are both 7. Strings differ at index 1. Expected: "1.00 KB" But was: "1,00 KB" --- Assets/Mirror/Tests/Editor/UtilsTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Tests/Editor/UtilsTests.cs b/Assets/Mirror/Tests/Editor/UtilsTests.cs index f964d74d59b..048e6a44d7d 100644 --- a/Assets/Mirror/Tests/Editor/UtilsTests.cs +++ b/Assets/Mirror/Tests/Editor/UtilsTests.cs @@ -37,19 +37,19 @@ public void PrettyBytes() Assert.That(Utils.PrettyBytes(1023), Is.EqualTo("1023 B")); // kilobytes - Assert.That(Utils.PrettyBytes(1024), Is.EqualTo("1.00 KB")); - Assert.That(Utils.PrettyBytes(1024 + 512), Is.EqualTo("1.50 KB")); - Assert.That(Utils.PrettyBytes(2048), Is.EqualTo("2.00 KB")); + Assert.That(Utils.PrettyBytes(1024), Is.EqualTo($"{1.0:F2} KB")); + Assert.That(Utils.PrettyBytes(1024 + 512), Is.EqualTo($"{1.5:F2} KB")); + Assert.That(Utils.PrettyBytes(2048), Is.EqualTo($"{2.0:F2} KB")); // megabytes - Assert.That(Utils.PrettyBytes(1024 * 1024), Is.EqualTo("1.00 MB")); - Assert.That(Utils.PrettyBytes(1024 * (1024 + 512)), Is.EqualTo("1.50 MB")); - Assert.That(Utils.PrettyBytes(1024 * 1024 * 2), Is.EqualTo("2.00 MB")); + Assert.That(Utils.PrettyBytes(1024 * 1024), Is.EqualTo($"{1.0:F2} MB")); + Assert.That(Utils.PrettyBytes(1024 * (1024 + 512)), Is.EqualTo($"{1.5:F2} MB")); + Assert.That(Utils.PrettyBytes(1024 * 1024 * 2), Is.EqualTo($"{2.0:F2} MB")); // gigabytes - Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L), Is.EqualTo("1.00 GB")); - Assert.That(Utils.PrettyBytes(1024L * 1024L * (1024L + 512L)), Is.EqualTo("1.50 GB")); - Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L * 2L), Is.EqualTo("2.00 GB")); + Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L), Is.EqualTo($"{1.0:F2} GB")); + Assert.That(Utils.PrettyBytes(1024L * 1024L * (1024L + 512L)), Is.EqualTo($"{1.5:F2} GB")); + Assert.That(Utils.PrettyBytes(1024L * 1024L * 1024L * 2L), Is.EqualTo($"{2.0:F2} GB")); } } } From 6c2559a73e2c11ff5f2b4c9269f7cd54c7986b4f Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 1 Apr 2022 13:46:38 +0800 Subject: [PATCH 056/824] perf: Build batches directly instead of queuing serializations (#3130) * rename * so far * better * fixx * old * error * typo * comment --- Assets/Mirror/Runtime/Batching/Batcher.cs | 104 +++++++++++------- .../Mirror/Runtime/LocalConnectionToServer.cs | 8 +- Assets/Mirror/Runtime/NetworkConnection.cs | 4 +- .../Tests/Editor/Batching/BatcherTests.cs | 70 ++++++------ 4 files changed, 108 insertions(+), 78 deletions(-) diff --git a/Assets/Mirror/Runtime/Batching/Batcher.cs b/Assets/Mirror/Runtime/Batching/Batcher.cs index 2d52b7a824c..3a8d457ec3e 100644 --- a/Assets/Mirror/Runtime/Batching/Batcher.cs +++ b/Assets/Mirror/Runtime/Batching/Batcher.cs @@ -32,10 +32,16 @@ public class Batcher // TimeStamp header size for those who need it public const int HeaderSize = sizeof(double); - // batched messages - // IMPORTANT: we queue the serialized messages! - // queueing NetworkMessage would box and allocate! - Queue messages = new Queue(); + // full batches ready to be sent. + // DO NOT queue NetworkMessage, it would box. + // DO NOT queue each serialization separately. + // it would allocate too many writers. + // https://github.com/vis2k/Mirror/pull/3127 + // => best to build batches on the fly. + Queue batches = new Queue(); + + // current batch in progress + NetworkWriterPooled batch; public Batcher(int threshold) { @@ -45,53 +51,77 @@ public Batcher(int threshold) // add a message for batching // we allow any sized messages. // caller needs to make sure they are within max packet size. - public void AddMessage(ArraySegment message) + public void AddMessage(ArraySegment message, double timeStamp) { - // put into a (pooled) writer + // when appending to a batch in progress, check final size. + // if it expands beyond threshold, then we should finalize it first. + // => less than or exactly threshold is fine. + // GetBatch() will finalize it. + // => see unit tests. + if (batch != null && + batch.Position + message.Count > threshold) + { + batches.Enqueue(batch); + batch = null; + } + + // initialize a new batch if necessary + if (batch == null) + { + // borrow from pool. we return it in GetBatch. + batch = NetworkWriterPool.Get(); + + // write timestamp first. + // -> double precision for accuracy over long periods of time + // -> batches are per-frame, it doesn't matter which message's + // timestamp we use. + batch.WriteDouble(timeStamp); + } + + // add serialization to current batch. even if > threshold. + // -> we do allow > threshold sized messages as single batch // -> WriteBytes instead of WriteSegment because the latter // would add a size header. we want to write directly. - // -> will be returned to pool when making the batch! - // IMPORTANT: NOT adding a size header / msg saves LOTS of bandwidth - NetworkWriterPooled writer = NetworkWriterPool.Get(); - writer.WriteBytes(message.Array, message.Offset, message.Count); - messages.Enqueue(writer); + batch.WriteBytes(message.Array, message.Offset, message.Count); } - // batch as many messages as possible into writer - // returns true if any batch was made. - public bool MakeNextBatch(NetworkWriter writer, double timeStamp) + // helper function to copy a batch to writer and return it to pool + static void CopyAndReturn(NetworkWriterPooled batch, NetworkWriter writer) { - // if we have no messages then there's nothing to do - if (messages.Count == 0) - return false; - // make sure the writer is fresh to avoid uncertain situations if (writer.Position != 0) - throw new ArgumentException($"MakeNextBatch needs a fresh writer!"); + throw new ArgumentException($"GetBatch needs a fresh writer!"); - // write timestamp first - // -> double precision for accuracy over long periods of time - writer.WriteDouble(timeStamp); + // copy to the target writer + ArraySegment segment = batch.ToArraySegment(); + writer.WriteBytes(segment.Array, segment.Offset, segment.Count); + + // return batch to pool for reuse + NetworkWriterPool.Return(batch); + } - // do start no matter what - do + // get the next batch which is available for sending (if any). + // TODO safely get & return a batch instead of copying to writer? + // TODO could return pooled writer & use GetBatch in a 'using' statement! + public bool GetBatch(NetworkWriter writer) + { + // get first batch from queue (if any) + if (batches.TryDequeue(out NetworkWriterPooled first)) { - // add next message no matter what. even if > threshold. - // (we do allow > threshold sized messages as single batch) - NetworkWriterPooled message = messages.Dequeue(); - ArraySegment segment = message.ToArraySegment(); - writer.WriteBytes(segment.Array, segment.Offset, segment.Count); + CopyAndReturn(first, writer); + return true; + } - // return the writer to pool - NetworkWriterPool.Return(message); + // if queue was empty, we can send the batch in progress. + if (batch != null) + { + CopyAndReturn(batch, writer); + batch = null; + return true; } - // keep going as long as we have more messages, - // AND the next one would fit into threshold. - while (messages.Count > 0 && - writer.Position + messages.Peek().Position <= threshold); - // we had messages, so a batch was made - return true; + // nothing was written + return false; } } } diff --git a/Assets/Mirror/Runtime/LocalConnectionToServer.cs b/Assets/Mirror/Runtime/LocalConnectionToServer.cs index f1428baa254..378ffdb2ec6 100644 --- a/Assets/Mirror/Runtime/LocalConnectionToServer.cs +++ b/Assets/Mirror/Runtime/LocalConnectionToServer.cs @@ -33,14 +33,14 @@ internal override void Send(ArraySegment segment, int channelId = Channels // OnTransportData assumes batching. // so let's make a batch with proper timestamp prefix. Batcher batcher = GetBatchForChannelId(channelId); - batcher.AddMessage(segment); + batcher.AddMessage(segment, NetworkTime.localTime); // flush it to the server's OnTransportData immediately. // local connection to server always invokes immediately. using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) - if (batcher.MakeNextBatch(writer, NetworkTime.localTime)) + if (batcher.GetBatch(writer)) { NetworkServer.OnTransportData(connectionId, writer.ToArraySegment(), channelId); } @@ -69,12 +69,12 @@ internal override void Update() // OnTransportData assumes a proper batch with timestamp etc. // let's make a proper batch and pass it to OnTransportData. Batcher batcher = GetBatchForChannelId(Channels.Reliable); - batcher.AddMessage(message); + batcher.AddMessage(message, NetworkTime.localTime); using (NetworkWriterPooled batchWriter = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) - if (batcher.MakeNextBatch(batchWriter, NetworkTime.localTime)) + if (batcher.GetBatch(batchWriter)) { NetworkClient.OnTransportData(batchWriter.ToArraySegment(), Channels.Reliable); } diff --git a/Assets/Mirror/Runtime/NetworkConnection.cs b/Assets/Mirror/Runtime/NetworkConnection.cs index 3aefeee4032..14729c66890 100644 --- a/Assets/Mirror/Runtime/NetworkConnection.cs +++ b/Assets/Mirror/Runtime/NetworkConnection.cs @@ -164,7 +164,7 @@ internal virtual void Send(ArraySegment segment, int channelId = Channels. // // NOTE: we do NOT ValidatePacketSize here yet. the final packet // will be the full batch, including timestamp. - GetBatchForChannelId(channelId).AddMessage(segment); + GetBatchForChannelId(channelId).AddMessage(segment, NetworkTime.localTime); } // Send stage three: hand off to transport @@ -183,7 +183,7 @@ internal virtual void Update() using (NetworkWriterPooled writer = NetworkWriterPool.Get()) { // make a batch with our local time (double precision) - while (batcher.MakeNextBatch(writer, NetworkTime.localTime)) + while (batcher.GetBatch(writer)) { // validate packet before handing the batch to the // transport. this guarantees that we always stay diff --git a/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs b/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs index 4c5bc21e8f7..0d78a63fa6a 100644 --- a/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs +++ b/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs @@ -33,17 +33,17 @@ public static byte[] ConcatTimestamp(double tickTimeStamp, byte[] data) public void AddMessage() { byte[] message = {0x01, 0x02}; - batcher.AddMessage(new ArraySegment(message)); + batcher.AddMessage(new ArraySegment(message), TimeStamp); } [Test] public void MakeNextBatch_OnlyAcceptsFreshWriter() { - batcher.AddMessage(new ArraySegment(new byte[]{0x01})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01}), TimeStamp); writer.WriteByte(0); Assert.Throws(() => { - batcher.MakeNextBatch(writer, TimeStamp); + batcher.GetBatch(writer); }); } @@ -51,7 +51,7 @@ public void MakeNextBatch_OnlyAcceptsFreshWriter() public void MakeNextBatch_NoMessage() { // make batch with no message - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(false)); } @@ -60,10 +60,10 @@ public void MakeNextBatch_OneMessage() { // add message byte[] message = {0x01, 0x02}; - batcher.AddMessage(new ArraySegment(message)); + batcher.AddMessage(new ArraySegment(message), TimeStamp); // make batch - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -73,46 +73,46 @@ public void MakeNextBatch_OneMessage() [Test] public void MakeNextBatch_MultipleMessages_AlmostFullBatch() { - batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); - batcher.AddMessage(new ArraySegment(new byte[]{0x03})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x03}), TimeStamp); // make batch - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02, 0x03}))); // there should be no more batches to make - Assert.That(batcher.MakeNextBatch(writer, TimeStamp), Is.False); + Assert.That(batcher.GetBatch(writer), Is.False); } [Test] public void MakeNextBatch_MultipleMessages_ExactlyFullBatch() { - batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); - batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04}), TimeStamp); // make batch - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02, 0x03, 0x04}))); // there should be no more batches to make - Assert.That(batcher.MakeNextBatch(writer, TimeStamp), Is.False); + Assert.That(batcher.GetBatch(writer), Is.False); } [Test] public void MakeNextBatch_MultipleMessages_MoreThanOneBatch() { - batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); - batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04})); - batcher.AddMessage(new ArraySegment(new byte[]{0x05})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x05}), TimeStamp); // first batch - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -122,7 +122,7 @@ public void MakeNextBatch_MultipleMessages_MoreThanOneBatch() writer.Position = 0; // second batch - result = batcher.MakeNextBatch(writer, TimeStamp); + result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -133,12 +133,12 @@ public void MakeNextBatch_MultipleMessages_MoreThanOneBatch() public void MakeNextBatch_MultipleMessages_Small_Giant_Small() { // small, too big to include in batch, small - batcher.AddMessage(new ArraySegment(new byte[]{0x01})); - batcher.AddMessage(new ArraySegment(new byte[]{0x02, 0x03, 0x04, 0x05})); - batcher.AddMessage(new ArraySegment(new byte[]{0x06, 0x07})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x02, 0x03, 0x04, 0x05}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x06, 0x07}), TimeStamp); // first batch - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -148,7 +148,7 @@ public void MakeNextBatch_MultipleMessages_Small_Giant_Small() writer.Position = 0; // second batch - result = batcher.MakeNextBatch(writer, TimeStamp); + result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -158,7 +158,7 @@ public void MakeNextBatch_MultipleMessages_Small_Giant_Small() writer.Position = 0; // third batch - result = batcher.MakeNextBatch(writer, TimeStamp); + result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); // check result: <> @@ -176,10 +176,10 @@ public void MakeNextBatch_LargerThanThreshold() byte[] large = new byte[Threshold + 1]; for (int i = 0; i < Threshold + 1; ++i) large[i] = (byte)i; - batcher.AddMessage(new ArraySegment(large)); + batcher.AddMessage(new ArraySegment(large), TimeStamp); // result should be only the large message - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, large))); } @@ -199,14 +199,14 @@ public void MakeNextBatch_LargerThanThreshold_BetweenSmallerMessages() // add two small, one large, two small messages. // to make sure everything around it is still batched, // and the large one is a separate batch. - batcher.AddMessage(new ArraySegment(new byte[]{0x01})); - batcher.AddMessage(new ArraySegment(new byte[]{0x02})); - batcher.AddMessage(new ArraySegment(large)); - batcher.AddMessage(new ArraySegment(new byte[]{0x03})); - batcher.AddMessage(new ArraySegment(new byte[]{0x04})); + batcher.AddMessage(new ArraySegment(new byte[]{0x01}), TimeStamp); + batcher.AddMessage(new ArraySegment(new byte[]{0x02}), TimeStamp); + batcher.AddMessage(new ArraySegment(large), TimeStamp + 1); + batcher.AddMessage(new ArraySegment(new byte[]{0x03}), TimeStamp + 2); + batcher.AddMessage(new ArraySegment(new byte[]{0x04}), TimeStamp + 2); // first batch should be the two small messages - bool result = batcher.MakeNextBatch(writer, TimeStamp); + bool result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02}))); @@ -214,7 +214,7 @@ public void MakeNextBatch_LargerThanThreshold_BetweenSmallerMessages() writer.Position = 0; // second batch should be only the large message - result = batcher.MakeNextBatch(writer, TimeStamp + 1); + result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp + 1, large))); @@ -222,7 +222,7 @@ public void MakeNextBatch_LargerThanThreshold_BetweenSmallerMessages() writer.Position = 0; // third batch be the two small messages - result = batcher.MakeNextBatch(writer, TimeStamp + 2); + result = batcher.GetBatch(writer); Assert.That(result, Is.EqualTo(true)); Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp + 2, new byte[]{0x03, 0x04}))); } From 3d471db938f11dc4e1d4607adac1fb9a148b2350 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 1 Apr 2022 17:18:12 +0800 Subject: [PATCH 057/824] fix: added Queue.TryDequeue extension for Unity 2019 --- Assets/Mirror/Runtime/Extensions.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Assets/Mirror/Runtime/Extensions.cs b/Assets/Mirror/Runtime/Extensions.cs index 4c48394cd4f..13c69c36561 100644 --- a/Assets/Mirror/Runtime/Extensions.cs +++ b/Assets/Mirror/Runtime/Extensions.cs @@ -38,5 +38,20 @@ public static void CopyTo(this IEnumerable source, List destination) // foreach allocates. use AddRange. destination.AddRange(source); } + +#if !UNITY_2020_1_OR_NEWER + // Unity 2019 doesn't have Queue.TryDeque which we need for batching. + public static bool TryDequeue(this Queue source, out T element) + { + if (source.Count > 0) + { + element = source.Dequeue(); + return true; + } + + element = default; + return false; + } +#endif } } From c79c6d1e70e61fceefe4b432f956875bdb4c4b84 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 1 Apr 2022 17:19:37 -0400 Subject: [PATCH 058/824] fix: Correct version for Queue.TryDequeue - fixes #3131 --- Assets/Mirror/Runtime/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/Extensions.cs b/Assets/Mirror/Runtime/Extensions.cs index 13c69c36561..623b8dba831 100644 --- a/Assets/Mirror/Runtime/Extensions.cs +++ b/Assets/Mirror/Runtime/Extensions.cs @@ -39,7 +39,7 @@ public static void CopyTo(this IEnumerable source, List destination) destination.AddRange(source); } -#if !UNITY_2020_1_OR_NEWER +#if !UNITY_2021_OR_NEWER // Unity 2019 doesn't have Queue.TryDeque which we need for batching. public static bool TryDequeue(this Queue source, out T element) { From c81a4374b2ac0229c1d4653b60f2e0c25f9e9f7b Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 1 Apr 2022 17:29:23 -0400 Subject: [PATCH 059/824] fixed comment --- Assets/Mirror/Runtime/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/Extensions.cs b/Assets/Mirror/Runtime/Extensions.cs index 623b8dba831..3d285e93504 100644 --- a/Assets/Mirror/Runtime/Extensions.cs +++ b/Assets/Mirror/Runtime/Extensions.cs @@ -40,7 +40,7 @@ public static void CopyTo(this IEnumerable source, List destination) } #if !UNITY_2021_OR_NEWER - // Unity 2019 doesn't have Queue.TryDeque which we need for batching. + // Unity 2019 / 2020 don't have Queue.TryDeque which we need for batching. public static bool TryDequeue(this Queue source, out T element) { if (source.Count > 0) From a92189b272b28e0efcd87202f3da9473863b2dbe Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 3 Apr 2022 10:47:54 +0800 Subject: [PATCH 060/824] ReadNetworkIdentity: reuse Utils.GetSpawnedInServerOrClient --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 1061a0349e9..fb3b291abe5 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -215,21 +215,11 @@ public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader) if (netId == 0) return null; - // look in server spawned - if (NetworkServer.active && - NetworkServer.spawned.TryGetValue(netId, out NetworkIdentity serverIdentity)) - return serverIdentity; - - // look in client spawned - if (NetworkClient.active && - NetworkClient.spawned.TryGetValue(netId, out NetworkIdentity clientIdentity)) - return clientIdentity; - - // a netId not being in spawned is common. + // NOTE: a netId not being in spawned is common. // for example, "[SyncVar] NetworkIdentity target" netId would not // be known on client if the monster walks out of proximity for a // moment. no need to log any error or warning here. - return null; + return Utils.GetSpawnedInServerOrClient(netId); } [MethodImpl(MethodImplOptions.AggressiveInlining)] From 243821f5b1e40de8c8e95f5a77703646358ec529 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 3 Apr 2022 10:50:04 +0800 Subject: [PATCH 061/824] fix: #2972 ReadNetworkBehaviour now reads the correct amount of data even if the NetworkIdentity has disappeared on the client. added test to guarantee it never happens again. --- .../Mirror/Runtime/NetworkReaderExtensions.cs | 29 ++++++++++++------ .../Mirror/Tests/Editor/NetworkWriterTest.cs | 30 +++++++++++++++++++ 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index fb3b291abe5..6137866e6bf 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -225,19 +225,30 @@ public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NetworkBehaviour ReadNetworkBehaviour(this NetworkReader reader) { - // reuse ReadNetworkIdentity, get the component at index - NetworkIdentity identity = ReadNetworkIdentity(reader); + // read netId first. + // + // IMPORTANT: if netId != 0, writer always writes componentIndex. + // reusing ReadNetworkIdentity() might return a null NetworkIdentity + // even if netId was != 0 but the identity disappeared on the client, + // resulting in unequal amounts of data being written / read. + // https://github.com/vis2k/Mirror/issues/2972 + uint netId = reader.ReadUInt(); + if (netId == 0) + return null; + + // read component index in any case, BEFORE searching the spawned + // NetworkIdentity by netId. + byte componentIndex = reader.ReadByte(); - // a netId not being in spawned is common. - // for example, "[SyncVar] NetworkBehaviour target" netId would not + // NOTE: a netId not being in spawned is common. + // for example, "[SyncVar] NetworkIdentity target" netId would not // be known on client if the monster walks out of proximity for a // moment. no need to log any error or warning here. - if (identity == null) - return null; + NetworkIdentity identity = Utils.GetSpawnedInServerOrClient(netId); - // if identity isn't null, then index is also sent to read before returning - byte componentIndex = reader.ReadByte(); - return identity.NetworkBehaviours[componentIndex]; + return identity != null + ? identity.NetworkBehaviours[componentIndex] + : null; } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index 20d0b22f1fd..74c2d8c2108 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1368,6 +1368,36 @@ public void TestNetworkBehaviourNull() Assert.That(reader.Position, Is.EqualTo(4), "should read 4 bytes when netid is 0"); } + // test to prevent https://github.com/vis2k/Mirror/issues/2972 + [Test] + public void TestNetworkBehaviourDoesntExistOnClient() + { + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn(out _, out _, out RpcNetworkIdentityBehaviour serverComponent, + out _, out _, out RpcNetworkIdentityBehaviour clientComponent); + + // write on server where it's != null and exists + NetworkWriter writer = new NetworkWriter(); + writer.WriteNetworkBehaviour(serverComponent); + + byte[] bytes = writer.ToArray(); + Assert.That(bytes.Length, Is.EqualTo(5), "Networkbehaviour should be 5 bytes long."); + + // make it disappear / despawn on client + NetworkServer.spawned.Remove(serverComponent.netId); + NetworkClient.spawned.Remove(clientComponent.netId); + + // reading should return null component + NetworkReader reader = new NetworkReader(bytes); + RpcNetworkIdentityBehaviour actual = reader.ReadNetworkBehaviour(); + Assert.That(actual, Is.Null); + + // IMPORTANT: should have read EXACTLY as much as was written. + // even if NetworkBehaviour wasn't found on client. + // otherwise data gets corrupted. + Assert.That(reader.Position, Is.EqualTo(writer.Position)); + } + [Test] [Description("Uses Generic read function to check weaver correctly creates it")] public void TestNetworkBehaviourWeaverGenerated() From 3ec5ca758e7182d4b8195f8f636c8318012f3e81 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 3 Apr 2022 11:39:30 +0800 Subject: [PATCH 062/824] comment --- Assets/Mirror/Editor/Weaver/Weaver.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Assets/Mirror/Editor/Weaver/Weaver.cs b/Assets/Mirror/Editor/Weaver/Weaver.cs index 0a1b7f95789..2644e68de26 100644 --- a/Assets/Mirror/Editor/Weaver/Weaver.cs +++ b/Assets/Mirror/Editor/Weaver/Weaver.cs @@ -24,6 +24,12 @@ internal class Weaver AssemblyDefinition CurrentAssembly; Writers writers; Readers readers; + + // in case of weaver errors, we don't stop immediately. + // we log all errors and then eventually return false if + // weaving has failed. + // this way the user can fix multiple errors at once, instead of having + // to fix -> recompile -> fix -> recompile for one error at a time. bool WeavingFailed; // logger functions can be set from the outside. From 1a6c5de8acde863805fe27dbbf22a99c60bda6a4 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 01:24:35 +0800 Subject: [PATCH 063/824] fix: #2060 serializing GameObjects / NetworkIdentities now throws an obvious exception for prefabs / unspawned objects (#3132) * fix: #2060 serializing GameObjects / NetworkIdentities now throws an obvious exception for prefabs / unspawned objects * NetworkWriter: WriteGameObject reuses WriteNetworkIdentity * warning instead of exception --- .../Mirror/Runtime/NetworkWriterExtensions.cs | 26 ++++++++++----- .../Mirror/Tests/Editor/NetworkWriterTest.cs | 32 +++++++++++++++++++ 2 files changed, 50 insertions(+), 8 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index 8a596c92afa..3a601fc6f5c 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -311,6 +311,17 @@ public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdenti writer.WriteUInt(0); return; } + + // users might try to use unspawned / prefab GameObjects in + // rpcs/cmds/syncvars/messages. they would be null on the other + // end, and it might not be obvious why. let's make it obvious. + // https://github.com/vis2k/Mirror/issues/2060 + // + // => warning (instead of exception) because we also use a warning + // if a GameObject doesn't have a NetworkIdentity component etc. + if (value.netId == 0) + Debug.LogWarning($"Attempted to serialize unspawned GameObject: {value.name}. Prefabs and unspawned GameObjects would always be null on the other side. Please spawn it before using it in [SyncVar]s/Rpcs/Cmds/NetworkMessages etc."); + writer.WriteUInt(value.netId); } @@ -354,16 +365,15 @@ public static void WriteGameObject(this NetworkWriter writer, GameObject value) writer.WriteUInt(0); return; } + + // warn if the GameObject doesn't have a NetworkIdentity, NetworkIdentity identity = value.GetComponent(); - if (identity != null) - { - writer.WriteUInt(identity.netId); - } - else - { + if (identity == null) Debug.LogWarning($"NetworkWriter {value} has no NetworkIdentity"); - writer.WriteUInt(0); - } + + // serialize the correct amount of data in any case to make sure + // that the other end can read the expected amount of data too. + writer.WriteNetworkIdentity(identity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index 74c2d8c2108..0d42899e0f3 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Text.RegularExpressions; using Mirror.Tests.RemoteAttrributeTest; using NUnit.Framework; using UnityEngine; +using UnityEngine.TestTools; namespace Mirror.Tests { @@ -1417,5 +1419,35 @@ public void TestNetworkBehaviourWeaverGenerated() RpcNetworkIdentityBehaviour actual = reader.Read(); Assert.That(actual, Is.EqualTo(behaviour), "Read should find the same behaviour as written"); } + + // test to make sure unspawned / prefab GameObjects can't be synced. + // they would be null on the other end, and it might not be obvious why. + // https://github.com/vis2k/Mirror/issues/2060 + [Test] + public void TestWritingUnspawnedGameObject() + { + // create GO + NI, but unspawned + CreateNetworked(out GameObject go, out _); + + // serializing in rpc/cmd/message should warn if unspawned. + LogAssert.Expect(LogType.Warning, new Regex("Attempted to serialize unspawned.*")); + NetworkWriter writer = new NetworkWriter(); + writer.WriteGameObject(go); + } + + // test to make sure unspawned / prefab GameObjects can't be synced. + // they would be null on the other end, and it might not be obvious why. + // https://github.com/vis2k/Mirror/issues/2060 + [Test] + public void TestWritingUnspawnedNetworkIdentity() + { + // create GO + NI, but unspawned + CreateNetworked(out _, out NetworkIdentity identity); + + // serializing in rpc/cmd/message should warn if unspawned. + LogAssert.Expect(LogType.Warning, new Regex("Attempted to serialize unspawned.*")); + NetworkWriter writer = new NetworkWriter(); + writer.WriteNetworkIdentity(identity); + } } } From 1a09f0dc58a0eabc0b280775d2ef26673d033036 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 11:39:44 +0800 Subject: [PATCH 064/824] Tanks Example: reuse DestroySelf() --- Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs index 32945bb0935..be7b3eda60b 100644 --- a/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs +++ b/Assets/Mirror/Examples/Tanks/Scripts/Projectile.cs @@ -30,9 +30,6 @@ void DestroySelf() // ServerCallback because we don't want a warning // if OnTriggerEnter is called on the client [ServerCallback] - void OnTriggerEnter(Collider co) - { - NetworkServer.Destroy(gameObject); - } + void OnTriggerEnter(Collider co) => DestroySelf(); } } From 1a256b5060059a0e9d27bdb3b94b552715859f81 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 11:51:57 +0800 Subject: [PATCH 065/824] Tanks Example: destroy projectile after 2 seconds instead of 5 seconds --- Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab index f4e03dfcff5..1f66d0be382 100644 --- a/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab +++ b/Assets/Mirror/Examples/Tanks/Prefabs/Projectile.prefab @@ -146,7 +146,7 @@ MonoBehaviour: m_EditorClassIdentifier: syncMode: 0 syncInterval: 0.1 - destroyAfter: 5 + destroyAfter: 2 rigidBody: {fileID: 4629190479245867726} force: 1000 --- !u!136 &2355290524794870353 From 7d4a9c46e4eb86bf3d1c9424e8fbc1cd8c122267 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 19:04:10 +0800 Subject: [PATCH 066/824] NetworkClient: RegisterPrefabIdentity double registration warning improved with explanation (related to #2705) --- Assets/Mirror/Runtime/NetworkClient.cs | 2 +- .../Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 1f0803a4cde..c2c9ff772be 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -536,7 +536,7 @@ static void RegisterPrefabIdentity(NetworkIdentity prefab) if (spawnHandlers.ContainsKey(prefab.assetId) || unspawnHandlers.ContainsKey(prefab.assetId)) { - Debug.LogWarning($"Adding prefab '{prefab.name}' with assetId '{prefab.assetId}' when spawnHandlers with same assetId already exists."); + Debug.LogWarning($"Adding prefab '{prefab.name}' with assetId '{prefab.assetId}' when spawnHandlers with same assetId already exists. If you want to use custom spawn handling, then remove the prefab from NetworkManager's registered prefabs first."); } // Debug.Log($"Registering prefab '{prefab.name}' as asset:{prefab.assetId}"); diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs index a63b1f62853..a2564097082 100644 --- a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs @@ -1,4 +1,5 @@ using System; +using System.Text.RegularExpressions; using NUnit.Framework; using UnityEngine; using UnityEngine.TestTools; @@ -197,9 +198,9 @@ public void WarningForAssetIdAlreadyExistingInHandlersDictionary(RegisterPrefabO string msg = OverloadWithHandler(overload) ? $"Replacing existing spawnHandlers for prefab '{validPrefab.name}' with assetId '{guid}'" - : $"Adding prefab '{validPrefab.name}' with assetId '{guid}' when spawnHandlers with same assetId already exists."; + : $"Adding prefab '{validPrefab.name}' with assetId '{guid}' when spawnHandlers with same assetId already exists..*"; - LogAssert.Expect(LogType.Warning, msg); + LogAssert.Expect(LogType.Warning, new Regex(msg)); CallRegisterPrefab(validPrefab, overload); } From eded0a2b15fd873e42217aee75abe101557fcbb0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 19:07:11 +0800 Subject: [PATCH 067/824] fix: #2705 NetworkClient.SpawnPrefab looks for spawn handlers before looking for registered prefabs, instead of the other way around --- Assets/Mirror/Runtime/NetworkClient.cs | 26 +++++++++++++------ .../Tests/Editor/ClientSceneTests_OnSpawn.cs | 7 +++-- 2 files changed, 21 insertions(+), 12 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index c2c9ff772be..9f7b75107c8 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1078,12 +1078,13 @@ static NetworkIdentity GetExistingObject(uint netid) static NetworkIdentity SpawnPrefab(SpawnMessage message) { - if (GetPrefab(message.assetId, out GameObject prefab)) - { - GameObject obj = GameObject.Instantiate(prefab, message.position, message.rotation); - //Debug.Log($"Client spawn handler instantiating [netId{message.netId} asset ID:{message.assetId} pos:{message.position} rotation:{message.rotation}]"); - return obj.GetComponent(); - } + // custom spawn handler for this prefab? (for prefab pools etc.) + // + // IMPORTANT: look for spawn handlers BEFORE looking for registered + // prefabs. Unspawning also looks for unspawn handlers + // before falling back to regular Destroy. this needs to + // be consistent. + // https://github.com/vis2k/Mirror/issues/2705 if (spawnHandlers.TryGetValue(message.assetId, out SpawnHandlerDelegate handler)) { GameObject obj = handler(message); @@ -1100,6 +1101,15 @@ static NetworkIdentity SpawnPrefab(SpawnMessage message) } return identity; } + + // otherwise look in NetworkManager registered prefabs + if (GetPrefab(message.assetId, out GameObject prefab)) + { + GameObject obj = GameObject.Instantiate(prefab, message.position, message.rotation); + //Debug.Log($"Client spawn handler instantiating [netId{message.netId} asset ID:{message.assetId} pos:{message.position} rotation:{message.rotation}]"); + return obj.GetComponent(); + } + Debug.LogError($"Failed to spawn server object, did you forget to add it to the NetworkManager? assetId={message.assetId} netId={message.netId}"); return null; } @@ -1348,13 +1358,13 @@ static void DestroyObject(uint netId) localObject.OnStopClient(); - // user handling + // custom unspawn handler for this prefab? (for prefab pools etc.) if (InvokeUnSpawnHandler(localObject.assetId, localObject.gameObject)) { // reset object after user's handler localObject.Reset(); } - // default handling + // otherwise fall back to default Destroy else if (localObject.sceneId == 0) { // don't call reset before destroy so that values are still set in OnDestroy diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs index 94f7135e85a..333cd24e3ab 100644 --- a/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs @@ -148,8 +148,9 @@ public void FindOrSpawnObject_ErrorWhenPrefabInNullInDictionary() Assert.IsNull(networkIdentity); } + // test to prevent https://github.com/vis2k/Mirror/issues/2705 [Test] - public void FindOrSpawnObject_SpawnsFromPrefabIfBothPrefabAndHandlerExists() + public void FindOrSpawnObject_SpawnsFromHandlerIfBothPrefabAndHandlerExists() { const uint netId = 1003; int handlerCalled = 0; @@ -167,13 +168,11 @@ public void FindOrSpawnObject_SpawnsFromPrefabIfBothPrefabAndHandlerExists() return go; }); - bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); Assert.IsTrue(success); Assert.IsNotNull(networkIdentity); - Assert.That(networkIdentity.name, Is.EqualTo($"{validPrefab.name}(Clone)")); - Assert.That(handlerCalled, Is.EqualTo(0), "Handler should not have been called"); + Assert.That(handlerCalled, Is.EqualTo(1)); } [Test] From 92c5b54034143f692fa1abe3e770f39f657ba431 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 4 Apr 2022 20:36:42 +0800 Subject: [PATCH 068/824] add comment --- Assets/Mirror/Runtime/NetworkClient.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 9f7b75107c8..7112db1955c 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -77,7 +77,9 @@ public static class NetworkClient public static readonly Dictionary prefabs = new Dictionary(); - // spawn handlers + // custom spawn / unspawn handlers. + // useful to support prefab pooling etc.: + // https://mirror-networking.gitbook.io/docs/guides/gameobjects/custom-spawnfunctions internal static readonly Dictionary spawnHandlers = new Dictionary(); internal static readonly Dictionary unspawnHandlers = From 2f76c73c413a24cc58d44c702c7e2f245f464f3c Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:12:13 -0400 Subject: [PATCH 069/824] fix: NetworkWriterExtensions:WriteTexture2D call WriteArray instead of Write --- Assets/Mirror/Runtime/NetworkWriterExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index 3a601fc6f5c..d7ccaea26e3 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -413,7 +413,7 @@ public static void WriteUri(this NetworkWriter writer, Uri uri) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D) { - writer.Write(texture2D.GetPixels32()); + writer.WriteArray(texture2D.GetPixels32()); } From 3188472f74fe332bfb0e7729a22438bf7b869704 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Mon, 11 Apr 2022 09:42:23 -0400 Subject: [PATCH 070/824] Syntax: same method grouping as NetworkReaderExtenstions --- .../Mirror/Runtime/NetworkWriterExtensions.cs | 60 +------------------ 1 file changed, 2 insertions(+), 58 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index d7ccaea26e3..cf0954e259e 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -20,82 +20,61 @@ public static class NetworkWriterExtensions [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteByteNullable(this NetworkWriter writer, byte? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSByteNullable(this NetworkWriter writer, sbyte? value) => writer.WriteBlittableNullable(value); // char is not blittable. convert to ushort. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteBlittable((ushort)value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteCharNullable(this NetworkWriter writer, char? value) => writer.WriteBlittableNullable((ushort?)value); // bool is not blittable. convert to byte. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBoolNullable(this NetworkWriter writer, bool? value) => writer.WriteBlittableNullable(value.HasValue ? ((byte)(value.Value ? 1 : 0)) : new byte?()); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteShortNullable(this NetworkWriter writer, short? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUShort(this NetworkWriter writer, ushort value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUShortNullable(this NetworkWriter writer, ushort? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteIntNullable(this NetworkWriter writer, int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUInt(this NetworkWriter writer, uint value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUIntNullable(this NetworkWriter writer, uint? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteLongNullable(this NetworkWriter writer, long? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteULong(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteULongNullable(this NetworkWriter writer, ulong? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteFloat(this NetworkWriter writer, float value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteFloatNullable(this NetworkWriter writer, float? value) => writer.WriteBlittableNullable(value); - + [StructLayout(LayoutKind.Explicit)] internal struct UIntDouble { @@ -116,18 +95,14 @@ public static void WriteDouble(this NetworkWriter writer, double value) writer.WriteBlittable(value); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDoubleNullable(this NetworkWriter writer, double? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDecimal(this NetworkWriter writer, decimal value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDecimalNullable(this NetworkWriter writer, decimal? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteString(this NetworkWriter writer, string value) { @@ -156,7 +131,6 @@ public static void WriteString(this NetworkWriter writer, string value) writer.WriteBytes(stringBuffer, 0, size); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment buffer) { @@ -191,7 +165,6 @@ public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, i writer.WriteBytes(buffer, offset, count); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteArraySegment(this NetworkWriter writer, ArraySegment segment) { @@ -203,98 +176,72 @@ public static void WriteArraySegment(this NetworkWriter writer, ArraySegment< } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2(this NetworkWriter writer, Vector2 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2Nullable(this NetworkWriter writer, Vector2? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3(this NetworkWriter writer, Vector3 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector4(this NetworkWriter writer, Vector4 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector4Nullable(this NetworkWriter writer, Vector4? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2IntNullable(this NetworkWriter writer, Vector2Int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3IntNullable(this NetworkWriter writer, Vector3Int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor(this NetworkWriter writer, Color value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColorNullable(this NetworkWriter writer, Color? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor32(this NetworkWriter writer, Color32 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor32Nullable(this NetworkWriter writer, Color32? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteQuaternion(this NetworkWriter writer, Quaternion value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRect(this NetworkWriter writer, Rect value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRectNullable(this NetworkWriter writer, Rect? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WritePlane(this NetworkWriter writer, Plane value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WritePlaneNullable(this NetworkWriter writer, Plane? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRay(this NetworkWriter writer, Ray value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRayNullable(this NetworkWriter writer, Ray? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteMatrix4x4Nullable(this NetworkWriter writer, Matrix4x4? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteGuid(this NetworkWriter writer, Guid value) { byte[] data = value.ToByteArray(); writer.WriteBytes(data, 0, data.Length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteGuidNullable(this NetworkWriter writer, Guid? value) { @@ -302,7 +249,7 @@ public static void WriteGuidNullable(this NetworkWriter writer, Guid? value) if (value.HasValue) writer.WriteGuid(value.Value); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value) { @@ -402,21 +349,18 @@ public static void WriteArray(this NetworkWriter writer, T[] array) writer.Write(array[i]); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUri(this NetworkWriter writer, Uri uri) { writer.WriteString(uri?.ToString()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D) { writer.WriteArray(texture2D.GetPixels32()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSprite(this NetworkWriter writer, Sprite sprite) { From b0624b2f64f578192c73e5edd5ceffe291f84341 Mon Sep 17 00:00:00 2001 From: Robin Rolf Date: Tue, 12 Apr 2022 06:00:15 +0200 Subject: [PATCH 071/824] fix: Weaved static constructors need to always run (#3135) Static constructors are lazily called when the class is first "used" https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors > It is called automatically before the first instance is created or any static members are referenced. This means, in particular circumstances, client and server may diverge on which classes they have "loaded" and thus the rpc index will be desynced One such example would be on-demand-loading of prefabs via custom spawn handlers: A game might not want to preload all its monster prefabs since there's quite many of them and it is practically impossible to encounter them all (or even a majority of them) in a single play session. So a custom spawn handler is used to load the monster prefabs on demand. All monsters would have the "Monster" NetworkBehaviour class, which would have a few RPCs on it. Since the monster prefabs are loaded ONLY when needed via a custom spawn handler and the Monster class itself isn't referenced or "loaded" by anything else other than the prefab, the monster classes static constructor will not have been called when a client first joins a game, while it may have been called on the server/host. This will in turn cause Rpc indexes to not match up between client/server By just forcing the static constructors to run via the [RuntimeInitializeOnLoadMethod] attribute this is not a problem anymore, since all static constructors are always run and all RPCs will always be registered by the time they are used --- Assets/Mirror/Editor/Weaver/Helpers.cs | 43 ++++++++++++++++++ .../Processors/NetworkBehaviourProcessor.cs | 27 +++++++++++ .../Processors/ReaderWriterProcessor.cs | 45 +------------------ 3 files changed, 72 insertions(+), 43 deletions(-) diff --git a/Assets/Mirror/Editor/Weaver/Helpers.cs b/Assets/Mirror/Editor/Weaver/Helpers.cs index 56b73851005..14d632abc74 100644 --- a/Assets/Mirror/Editor/Weaver/Helpers.cs +++ b/Assets/Mirror/Editor/Weaver/Helpers.cs @@ -2,6 +2,8 @@ using System.Linq; using System.Reflection; using Mono.CecilX; +using Mono.CecilX.Rocks; +using UnityEngine; namespace Mirror.Weaver { @@ -22,5 +24,46 @@ public static bool IsEditorAssembly(AssemblyDefinition currentAssembly) assemblyReference.Name.StartsWith(nameof(UnityEditor)) ); } + + // helper function to add [RuntimeInitializeOnLoad] attribute to method + public static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) + { + // NOTE: previously we used reflection because according paul, + // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong + // order, which breaks rewired' + // it's not obvious why importing an attribute via reflection instead + // of cecil would break anything. let's use cecil. + + // to add a CustomAttribute, we need the attribute's constructor. + // in this case, there are two: empty, and RuntimeInitializeOnLoadType. + // we want the last one, with the type parameter. + MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); + //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); + // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported + // we need to import it first. + CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); + // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor + attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import(), RuntimeInitializeLoadType.BeforeSceneLoad)); + method.CustomAttributes.Add(attribute); + } + + // helper function to add [InitializeOnLoad] attribute to method + // (only works in Editor assemblies. check IsEditorAssembly first.) + public static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) + { + // NOTE: previously we used reflection because according paul, + // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong + // order, which breaks rewired' + // it's not obvious why importing an attribute via reflection instead + // of cecil would break anything. let's use cecil. + + // to add a CustomAttribute, we need the attribute's constructor. + // in this case, there's only one - and it's an empty constructor. + MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); + // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported + // we need to import it first. + CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); + method.CustomAttributes.Add(attribute); + } } } diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index ac00f65b3a3..9337048d5c1 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Mono.CecilX; using Mono.CecilX.Cil; +using UnityEngine; namespace Mirror.Weaver { @@ -276,6 +277,32 @@ void InjectIntoStaticConstructor(ref bool WeavingFailed) weaverTypes.Import(typeof(void))); } + // Static constructors are lazily called when the class is first "used" + // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors + // > It is called automatically before the first instance is created or any static members are referenced. + // + // This means, in particular circumstances, client and server may diverge on which classes they have "loaded" + // and thus the rpc index will be desynced + // + // One such example would be on-demand-loading of prefabs via custom spawn handlers: + // A game might not want to preload all its monster prefabs since there's quite many of them + // and it is practically impossible to encounter them all (or even a majority of them) in a single play + // session. + // So a custom spawn handler is used to load the monster prefabs on demand. + // All monsters would have the "Monster" NetworkBehaviour class, which would have a few RPCs on it. + // Since the monster prefabs are loaded ONLY when needed via a custom spawn handler and the Monster class + // itself isn't referenced or "loaded" by anything else other than the prefab, the monster classes static + // constructor will not have been called when a client first joins a game, while it may have been called + // on the server/host. This will in turn cause Rpc indexes to not match up between client/server + // + // By just forcing the static constructors to run via the [RuntimeInitializeOnLoadMethod] attribute + // this is not a problem anymore, since all static constructors are always run and all RPCs will + // always be registered by the time they are used + if (!cctor.HasCustomAttribute()) + { + Helpers.AddRuntimeInitializeOnLoadAttribute(assembly, weaverTypes, cctor); + } + ILProcessor cctorWorker = cctor.Body.GetILProcessor(); // register all commands in cctor diff --git a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs index 280240c1abe..184278c2ecd 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs @@ -138,47 +138,6 @@ static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefiniti return modified; } - // helper function to add [RuntimeInitializeOnLoad] attribute to method - static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) - { - // NOTE: previously we used reflection because according paul, - // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong - // order, which breaks rewired' - // it's not obvious why importing an attribute via reflection instead - // of cecil would break anything. let's use cecil. - - // to add a CustomAttribute, we need the attribute's constructor. - // in this case, there are two: empty, and RuntimeInitializeOnLoadType. - // we want the last one, with the type parameter. - MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); - //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); - // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported - // we need to import it first. - CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); - // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor - attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import(), RuntimeInitializeLoadType.BeforeSceneLoad)); - method.CustomAttributes.Add(attribute); - } - - // helper function to add [InitializeOnLoad] attribute to method - // (only works in Editor assemblies. check IsEditorAssembly first.) - static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) - { - // NOTE: previously we used reflection because according paul, - // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong - // order, which breaks rewired' - // it's not obvious why importing an attribute via reflection instead - // of cecil would break anything. let's use cecil. - - // to add a CustomAttribute, we need the attribute's constructor. - // in this case, there's only one - and it's an empty constructor. - MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); - // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported - // we need to import it first. - CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); - method.CustomAttributes.Add(attribute); - } - // adds Mirror.GeneratedNetworkCode.InitReadWriters() method that // registers all generated writers into Mirror.Writer static class. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime @@ -193,12 +152,12 @@ public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly weaverTypes.Import(typeof(void))); // add [RuntimeInitializeOnLoad] in any case - AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); + Helpers.AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); // add [InitializeOnLoad] if UnityEditor is referenced if (Helpers.IsEditorAssembly(currentAssembly)) { - AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); + Helpers.AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); } // fill function body with reader/writer initializers From aa6ff514cb0c453da3c874bea27c24a146ef37e4 Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 12 Apr 2022 22:14:29 +0800 Subject: [PATCH 072/824] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7973b5696b0..7326d3c8951 100644 --- a/README.md +++ b/README.md @@ -94,13 +94,13 @@ If you are migrating from UNET, then please check out our [Migration Guide](http Inferna NightZ Roze Blud - + Wawa United - + From 3dba6a3833a7bc46b7fae298c7a719179fde57c5 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 15 Apr 2022 11:25:14 -0400 Subject: [PATCH 073/824] NetworkRoomManager log update --- Assets/Mirror/Components/NetworkRoomManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Components/NetworkRoomManager.cs b/Assets/Mirror/Components/NetworkRoomManager.cs index 535b0eb6eba..d432fbb1288 100644 --- a/Assets/Mirror/Components/NetworkRoomManager.cs +++ b/Assets/Mirror/Components/NetworkRoomManager.cs @@ -152,7 +152,7 @@ public void ReadyStatusChanged() /// Connection from client. public override void OnServerReady(NetworkConnectionToClient conn) { - Debug.Log("NetworkRoomManager OnServerReady"); + Debug.Log($"NetworkRoomManager OnServerReady {conn}"); base.OnServerReady(conn); if (conn != null && conn.identity != null) From 4d0cd1054a9011ee4492f91ed4bd16c671fea470 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 16 Apr 2022 19:53:55 +0700 Subject: [PATCH 074/824] fix: #3138, 3135 revert Cmd/Rpc from 2 byte indices back to 4 byte function hashes for stability (#3139) * Revert "fix: Weaved static constructors need to always run (#3135)" This reverts commit b0624b2f64f578192c73e5edd5ceffe291f84341. * Revert "perf: Rpcs/Cmds functionHash bandwidth reduced from 4 => 2 bytes (with the potential for 1 byte VarInt) (#3119)" This reverts commit a868368ecac7574410bed15476ef03db3b2559df. * comment --- Assets/Mirror/Editor/Weaver/Helpers.cs | 43 --------- .../Processors/NetworkBehaviourProcessor.cs | 27 ------ .../Processors/ReaderWriterProcessor.cs | 45 +++++++++- Assets/Mirror/Runtime/Messages.cs | 8 +- Assets/Mirror/Runtime/NetworkBehaviour.cs | 10 ++- Assets/Mirror/Runtime/NetworkClient.cs | 2 +- Assets/Mirror/Runtime/NetworkIdentity.cs | 8 +- Assets/Mirror/Runtime/NetworkServer.cs | 4 +- Assets/Mirror/Runtime/RemoteCalls.cs | 88 +++++-------------- Assets/Mirror/Tests/Common/MirrorTest.cs | 5 -- 10 files changed, 78 insertions(+), 162 deletions(-) diff --git a/Assets/Mirror/Editor/Weaver/Helpers.cs b/Assets/Mirror/Editor/Weaver/Helpers.cs index 14d632abc74..56b73851005 100644 --- a/Assets/Mirror/Editor/Weaver/Helpers.cs +++ b/Assets/Mirror/Editor/Weaver/Helpers.cs @@ -2,8 +2,6 @@ using System.Linq; using System.Reflection; using Mono.CecilX; -using Mono.CecilX.Rocks; -using UnityEngine; namespace Mirror.Weaver { @@ -24,46 +22,5 @@ public static bool IsEditorAssembly(AssemblyDefinition currentAssembly) assemblyReference.Name.StartsWith(nameof(UnityEditor)) ); } - - // helper function to add [RuntimeInitializeOnLoad] attribute to method - public static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) - { - // NOTE: previously we used reflection because according paul, - // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong - // order, which breaks rewired' - // it's not obvious why importing an attribute via reflection instead - // of cecil would break anything. let's use cecil. - - // to add a CustomAttribute, we need the attribute's constructor. - // in this case, there are two: empty, and RuntimeInitializeOnLoadType. - // we want the last one, with the type parameter. - MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); - //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); - // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported - // we need to import it first. - CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); - // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor - attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import(), RuntimeInitializeLoadType.BeforeSceneLoad)); - method.CustomAttributes.Add(attribute); - } - - // helper function to add [InitializeOnLoad] attribute to method - // (only works in Editor assemblies. check IsEditorAssembly first.) - public static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) - { - // NOTE: previously we used reflection because according paul, - // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong - // order, which breaks rewired' - // it's not obvious why importing an attribute via reflection instead - // of cecil would break anything. let's use cecil. - - // to add a CustomAttribute, we need the attribute's constructor. - // in this case, there's only one - and it's an empty constructor. - MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); - // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported - // we need to import it first. - CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); - method.CustomAttributes.Add(attribute); - } } } diff --git a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs index 9337048d5c1..ac00f65b3a3 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/NetworkBehaviourProcessor.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using Mono.CecilX; using Mono.CecilX.Cil; -using UnityEngine; namespace Mirror.Weaver { @@ -277,32 +276,6 @@ void InjectIntoStaticConstructor(ref bool WeavingFailed) weaverTypes.Import(typeof(void))); } - // Static constructors are lazily called when the class is first "used" - // https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/static-constructors - // > It is called automatically before the first instance is created or any static members are referenced. - // - // This means, in particular circumstances, client and server may diverge on which classes they have "loaded" - // and thus the rpc index will be desynced - // - // One such example would be on-demand-loading of prefabs via custom spawn handlers: - // A game might not want to preload all its monster prefabs since there's quite many of them - // and it is practically impossible to encounter them all (or even a majority of them) in a single play - // session. - // So a custom spawn handler is used to load the monster prefabs on demand. - // All monsters would have the "Monster" NetworkBehaviour class, which would have a few RPCs on it. - // Since the monster prefabs are loaded ONLY when needed via a custom spawn handler and the Monster class - // itself isn't referenced or "loaded" by anything else other than the prefab, the monster classes static - // constructor will not have been called when a client first joins a game, while it may have been called - // on the server/host. This will in turn cause Rpc indexes to not match up between client/server - // - // By just forcing the static constructors to run via the [RuntimeInitializeOnLoadMethod] attribute - // this is not a problem anymore, since all static constructors are always run and all RPCs will - // always be registered by the time they are used - if (!cctor.HasCustomAttribute()) - { - Helpers.AddRuntimeInitializeOnLoadAttribute(assembly, weaverTypes, cctor); - } - ILProcessor cctorWorker = cctor.Body.GetILProcessor(); // register all commands in cctor diff --git a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs index 184278c2ecd..280240c1abe 100644 --- a/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs +++ b/Assets/Mirror/Editor/Weaver/Processors/ReaderWriterProcessor.cs @@ -138,6 +138,47 @@ static bool LoadDeclaredReaders(AssemblyDefinition currentAssembly, TypeDefiniti return modified; } + // helper function to add [RuntimeInitializeOnLoad] attribute to method + static void AddRuntimeInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) + { + // NOTE: previously we used reflection because according paul, + // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong + // order, which breaks rewired' + // it's not obvious why importing an attribute via reflection instead + // of cecil would break anything. let's use cecil. + + // to add a CustomAttribute, we need the attribute's constructor. + // in this case, there are two: empty, and RuntimeInitializeOnLoadType. + // we want the last one, with the type parameter. + MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().Last(); + //MethodDefinition ctor = weaverTypes.runtimeInitializeOnLoadMethodAttribute.GetConstructors().First(); + // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported + // we need to import it first. + CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); + // add the RuntimeInitializeLoadType.BeforeSceneLoad argument to ctor + attribute.ConstructorArguments.Add(new CustomAttributeArgument(weaverTypes.Import(), RuntimeInitializeLoadType.BeforeSceneLoad)); + method.CustomAttributes.Add(attribute); + } + + // helper function to add [InitializeOnLoad] attribute to method + // (only works in Editor assemblies. check IsEditorAssembly first.) + static void AddInitializeOnLoadAttribute(AssemblyDefinition assembly, WeaverTypes weaverTypes, MethodDefinition method) + { + // NOTE: previously we used reflection because according paul, + // 'weaving Mirror.dll caused unity to rebuild all dlls but in wrong + // order, which breaks rewired' + // it's not obvious why importing an attribute via reflection instead + // of cecil would break anything. let's use cecil. + + // to add a CustomAttribute, we need the attribute's constructor. + // in this case, there's only one - and it's an empty constructor. + MethodDefinition ctor = weaverTypes.initializeOnLoadMethodAttribute.GetConstructors().First(); + // using ctor directly throws: ArgumentException: Member 'System.Void UnityEditor.InitializeOnLoadMethodAttribute::.ctor()' is declared in another module and needs to be imported + // we need to import it first. + CustomAttribute attribute = new CustomAttribute(assembly.MainModule.ImportReference(ctor)); + method.CustomAttributes.Add(attribute); + } + // adds Mirror.GeneratedNetworkCode.InitReadWriters() method that // registers all generated writers into Mirror.Writer static class. // -> uses [RuntimeInitializeOnLoad] attribute so it's invoke at runtime @@ -152,12 +193,12 @@ public static void InitializeReaderAndWriters(AssemblyDefinition currentAssembly weaverTypes.Import(typeof(void))); // add [RuntimeInitializeOnLoad] in any case - Helpers.AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); + AddRuntimeInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); // add [InitializeOnLoad] if UnityEditor is referenced if (Helpers.IsEditorAssembly(currentAssembly)) { - Helpers.AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); + AddInitializeOnLoadAttribute(currentAssembly, weaverTypes, initReadWriters); } // fill function body with reader/writer initializers diff --git a/Assets/Mirror/Runtime/Messages.cs b/Assets/Mirror/Runtime/Messages.cs index 2ec729d127e..d3816f8ac53 100644 --- a/Assets/Mirror/Runtime/Messages.cs +++ b/Assets/Mirror/Runtime/Messages.cs @@ -28,9 +28,7 @@ public struct CommandMessage : NetworkMessage { public uint netId; public byte componentIndex; - // NOTE: this could be 1 byte most of the time via VarInt! - // but requires custom serialization for Command/RpcMessages. - public ushort functionIndex; + public int functionHash; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; @@ -40,9 +38,7 @@ public struct RpcMessage : NetworkMessage { public uint netId; public byte componentIndex; - // NOTE: this could be 1 byte most of the time via VarInt! - // but requires custom serialization for Command/RpcMessages. - public ushort functionIndex; + public int functionHash; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index 7d3b652ce58..94cd930ad14 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -3,7 +3,6 @@ using System.ComponentModel; using System.Runtime.CompilerServices; using UnityEngine; -using Mirror.RemoteCalls; namespace Mirror { @@ -235,7 +234,8 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer { netId = netId, componentIndex = (byte)ComponentIndex, - functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), + // type+func so Inventory.RpcUse != Equipment.RpcUse + functionHash = functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -270,7 +270,8 @@ protected void SendRPCInternal(string functionFullName, NetworkWriter writer, in { netId = netId, componentIndex = (byte)ComponentIndex, - functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), + // type+func so Inventory.RpcUse != Equipment.RpcUse + functionHash = functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -317,7 +318,8 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull { netId = netId, componentIndex = (byte)ComponentIndex, - functionIndex = RemoteProcedureCalls.GetIndexFromFunctionHash(functionFullName), + // type+func so Inventory.RpcUse != Equipment.RpcUse + functionHash = functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 7112db1955c..e5dabe38a0c 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1276,7 +1276,7 @@ static void OnRPCMessage(RpcMessage message) if (spawned.TryGetValue(message.netId, out NetworkIdentity identity)) { using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(message.payload)) - identity.HandleRemoteCall(message.componentIndex, message.functionIndex, RemoteCallType.ClientRpc, networkReader); + identity.HandleRemoteCall(message.componentIndex, message.functionHash, RemoteCallType.ClientRpc, networkReader); } } diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index cd8bbe8d179..6c3c12203f9 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -1071,12 +1071,12 @@ internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState) } // Helper function to handle Command/Rpc - internal void HandleRemoteCall(byte componentIndex, ushort functionIndex, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) + internal void HandleRemoteCall(byte componentIndex, int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) { // check if unity object has been destroyed if (this == null) { - Debug.LogWarning($"{remoteCallType} [{functionIndex}] received for deleted object [netId={netId}]"); + Debug.LogWarning($"{remoteCallType} [{functionHash}] received for deleted object [netId={netId}]"); return; } @@ -1088,9 +1088,9 @@ internal void HandleRemoteCall(byte componentIndex, ushort functionIndex, Remote } NetworkBehaviour invokeComponent = NetworkBehaviours[componentIndex]; - if (!RemoteProcedureCalls.Invoke(functionIndex, remoteCallType, reader, invokeComponent, senderConnection)) + if (!RemoteProcedureCalls.Invoke(functionHash, remoteCallType, reader, invokeComponent, senderConnection)) { - Debug.LogError($"Found no receiver for incoming {remoteCallType} [{functionIndex}] on {gameObject.name}, the server and client should have the same NetworkBehaviour instances [netId={netId}]."); + Debug.LogError($"Found no receiver for incoming {remoteCallType} [{functionHash}] on {gameObject.name}, the server and client should have the same NetworkBehaviour instances [netId={netId}]."); } } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index f208f3c3b3a..45d00c491fb 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -966,7 +966,7 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // 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 - bool requiresAuthority = RemoteProcedureCalls.CommandRequiresAuthority(msg.functionIndex); + bool requiresAuthority = RemoteProcedureCalls.CommandRequiresAuthority(msg.functionHash); if (requiresAuthority && identity.connectionToClient != conn) { Debug.LogWarning($"Command for object without authority [netId={msg.netId}]"); @@ -976,7 +976,7 @@ static void OnCommandMessage(NetworkConnectionToClient conn, CommandMessage msg, // Debug.Log($"OnCommandMessage for netId:{msg.netId} conn:{conn}"); using (NetworkReaderPooled networkReader = NetworkReaderPool.Get(msg.payload)) - identity.HandleRemoteCall(msg.componentIndex, msg.functionIndex, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); + identity.HandleRemoteCall(msg.componentIndex, msg.functionHash, RemoteCallType.Command, networkReader, conn as NetworkConnectionToClient); } // spawning //////////////////////////////////////////////////////////// diff --git a/Assets/Mirror/Runtime/RemoteCalls.cs b/Assets/Mirror/Runtime/RemoteCalls.cs index 4e17004237e..127e2413892 100644 --- a/Assets/Mirror/Runtime/RemoteCalls.cs +++ b/Assets/Mirror/Runtime/RemoteCalls.cs @@ -31,45 +31,18 @@ public bool AreEqual(Type componentType, RemoteCallType remoteCallType, RemoteCa /// Used to help manage remote calls for NetworkBehaviours public static class RemoteProcedureCalls { - // sending rpc/cmd function hash would require 4 bytes each time. - // instead, let's only send the index to save bandwidth. - // => 1 byte index with 255 rpcs in total would not be enough. - // => 1 byte index with 255 rpcs per type is doable but lookup is hard, - // because an rpc might be in the actual type or in the base type etc - // => 2 byte index allows for 64k Rpcs and is very easy to implement - // with a SortedList + .IndexOfKey. + // one lookup for all remote calls. + // allows us to easily add more remote call types without duplicating code. + // note: do not clear those with [RuntimeInitializeOnLoad] // - // NOTE: this could be 1 byte most of the time via VarInt! - // but requires custom serialization for Command/RpcMessages. - // - // SortedList still doesn't allow duplicate keys, which is good. - // But it allows accessing keys by index. - static readonly SortedList remoteCallDelegates = new SortedList(); - - // hash -> index reverse lookup to cache .IndexOfKey() binary search. - static readonly Dictionary remoteCallIndexLookup = new Dictionary(); - - // helper function to get rpc/cmd index from function name / hash. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ushort GetIndexFromFunctionHash(string functionFullName) - { - int hash = functionFullName.GetStableHashCode(); - - // IndexOfKey runs a binary search. - // cache results in lookup. - // IMPORTANT: can't cache results when registering rpcs/cmds as the - // indices would only be valid after ALL were registered. - // return (ushort)remoteCallDelegates.IndexOfKey(hash); - - // reuse cached index if possible - if (remoteCallIndexLookup.TryGetValue(hash, out ushort index)) - return index; - - // otherwise search and cache - ushort searchedIndex = (ushort)remoteCallDelegates.IndexOfKey(hash); - remoteCallIndexLookup[hash] = searchedIndex; - return searchedIndex; - } + // IMPORTANT: cmd/rpc functions are identified via **HASHES**. + // an index would requires half the bandwidth, but introduces issues + // where static constructors are lazily called, so index order isn't + // guaranteed: + // https://github.com/vis2k/Mirror/pull/3135 + // https://github.com/vis2k/Mirror/issues/3138 + // keep the 4 byte hash for stability! + static readonly Dictionary remoteCallDelegates = new Dictionary(); static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, int functionHash) { @@ -100,7 +73,6 @@ internal static int RegisterDelegate(Type componentType, string functionFullName if (CheckIfDelegateExists(componentType, remoteCallType, func, hash)) return hash; - // register invoker by hash remoteCallDelegates[hash] = new Invoker { callType = remoteCallType, @@ -130,30 +102,18 @@ internal static void RemoveDelegate(int hash) => // note: no need to throw an error if not found. // an attacker might just try to call a cmd with an rpc's hash etc. // returning false is enough. - static bool GetInvoker(ushort functionIndex, RemoteCallType remoteCallType, out Invoker invoker) - { - // valid index? - if (functionIndex <= remoteCallDelegates.Count) - { - // get key by index - int functionHash = remoteCallDelegates.Keys[functionIndex]; - invoker = remoteCallDelegates[functionHash]; - // check rpc type. don't allow calling cmds from rpcs, etc. - return invoker != null && - invoker.callType == remoteCallType; - } - invoker = null; - return false; - } + static bool GetInvokerForHash(int functionHash, RemoteCallType remoteCallType, out Invoker invoker) => + remoteCallDelegates.TryGetValue(functionHash, out invoker) && + invoker != null && + invoker.callType == remoteCallType; // InvokeCmd/Rpc Delegate can all use the same function here - // => invoke by index to save bandwidth (2 bytes instead of 4 bytes) - internal static bool Invoke(ushort functionIndex, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) + internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) { // IMPORTANT: we check if the message's componentIndex component is // actually of the right type. prevents attackers trying // to invoke remote calls on wrong components. - if (GetInvoker(functionIndex, remoteCallType, out Invoker invoker) && + if (GetInvokerForHash(functionHash, remoteCallType, out Invoker invoker) && invoker.componentType.IsInstanceOfType(component)) { // invoke function on this component @@ -164,8 +124,8 @@ internal static bool Invoke(ushort functionIndex, RemoteCallType remoteCallType, } // check if the command 'requiresAuthority' which is set in the attribute - internal static bool CommandRequiresAuthority(ushort cmdIndex) => - GetInvoker(cmdIndex, RemoteCallType.Command, out Invoker invoker) && + internal static bool CommandRequiresAuthority(int cmdHash) => + GetInvokerForHash(cmdHash, RemoteCallType.Command, out Invoker invoker) && invoker.cmdRequiresAuthority; /// Gets the handler function by hash. Useful for profilers and debuggers. @@ -173,14 +133,6 @@ public static RemoteCallDelegate GetDelegate(int functionHash) => remoteCallDelegates.TryGetValue(functionHash, out Invoker invoker) ? invoker.function : null; - - // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload - [RuntimeInitializeOnLoadMethod] - internal static void ResetStatics() - { - // clear rpc lookup every time. - // otherwise tests may have issues. - remoteCallIndexLookup.Clear(); - } } } + diff --git a/Assets/Mirror/Tests/Common/MirrorTest.cs b/Assets/Mirror/Tests/Common/MirrorTest.cs index fbf3620a620..9559b9e2f79 100644 --- a/Assets/Mirror/Tests/Common/MirrorTest.cs +++ b/Assets/Mirror/Tests/Common/MirrorTest.cs @@ -1,7 +1,6 @@ // base class for networking tests to make things easier. using System.Collections.Generic; using System.Linq; -using Mirror.RemoteCalls; using NUnit.Framework; using UnityEngine; @@ -49,10 +48,6 @@ public virtual void TearDown() GameObject.DestroyImmediate(transport.gameObject); Transport.activeTransport = null; NetworkManager.singleton = null; - - // clear rpc lookup caches. - // this can cause problems in tests otherwise. - RemoteProcedureCalls.ResetStatics(); } // create a tracked GameObject for tests without Networkidentity From c86deedbd96ff31abd7fcf9cb28979905d916903 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 16 Apr 2022 12:24:15 -0400 Subject: [PATCH 075/824] Updated NetworkStatistics - Reorganized to method call order - Added component attributes - XML comments for docs --- Assets/Mirror/Components/NetworkStatistics.cs | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index 2938191f0bd..5e7210e3c3b 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -3,6 +3,15 @@ namespace Mirror { + /// + /// Shows Network messages and bytes sent & received per second. + /// + /// + /// Add this component to the same object as Network Manager. + /// + [AddComponentMenu("Network/Network Statistics")] + [DisallowMultipleComponent] + [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-statistics")] public class NetworkStatistics : MonoBehaviour { // update interval @@ -94,6 +103,18 @@ void OnServerSend(int connectionId, ArraySegment data, int channelId) serverIntervalSentBytes += data.Count; } + void Update() + { + // calculate results every second + if (NetworkTime.localTime >= intervalStartTime + 1) + { + if (NetworkClient.active) UpdateClient(); + if (NetworkServer.active) UpdateServer(); + + intervalStartTime = NetworkTime.localTime; + } + } + void UpdateClient() { clientReceivedPacketsPerSecond = clientIntervalReceivedPackets; @@ -120,15 +141,21 @@ void UpdateServer() serverIntervalSentBytes = 0; } - void Update() + void OnGUI() { - // calculate results every second - if (NetworkTime.localTime >= intervalStartTime + 1) + // only show if either server or client active + if (NetworkClient.active || NetworkServer.active) { - if (NetworkClient.active) UpdateClient(); - if (NetworkServer.active) UpdateServer(); + // create main GUI area + // 105 is below NetworkManager HUD in all cases. + GUILayout.BeginArea(new Rect(10, 105, 215, 300)); - intervalStartTime = NetworkTime.localTime; + // show client / server stats if active + if (NetworkClient.active) OnClientGUI(); + if (NetworkServer.active) OnServerGUI(); + + // end of GUI area + GUILayout.EndArea(); } } @@ -163,23 +190,5 @@ void OnServerGUI() // end background GUILayout.EndVertical(); } - - void OnGUI() - { - // only show if either server or client active - if (NetworkClient.active || NetworkServer.active) - { - // create main GUI area - // 105 is below NetworkManager HUD in all cases. - GUILayout.BeginArea(new Rect(10, 105, 215, 300)); - - // show client / server stats if active - if (NetworkClient.active) OnClientGUI(); - if (NetworkServer.active) OnServerGUI(); - - // end of GUI area - GUILayout.EndArea(); - } - } } } From 9c47d87bce08946030cd88342589dee9b42da0f7 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 16 Apr 2022 12:46:33 -0400 Subject: [PATCH 076/824] XML Comments --- Assets/Mirror/Components/NetworkStatistics.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index 5e7210e3c3b..a95d4a958f8 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -5,10 +5,8 @@ namespace Mirror { /// /// Shows Network messages and bytes sent & received per second. - /// - /// /// Add this component to the same object as Network Manager. - /// + /// [AddComponentMenu("Network/Network Statistics")] [DisallowMultipleComponent] [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-statistics")] From bec8e01c69de1ef44bd7b533cf05172f70108936 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 17 Apr 2022 13:57:39 +0800 Subject: [PATCH 077/824] MIRROR_66_0_OR_NEWER --- Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs | 3 ++- ProjectSettings/ProjectSettings.asset | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs b/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs index 2e973531aab..89867c1e836 100644 --- a/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs +++ b/Assets/Mirror/CompilerSymbols/PreprocessorDefine.cs @@ -42,7 +42,8 @@ public static void AddDefineSymbols() "MIRROR_55_0_OR_NEWER", "MIRROR_57_0_OR_NEWER", "MIRROR_58_0_OR_NEWER", - "MIRROR_65_0_OR_NEWER" + "MIRROR_65_0_OR_NEWER", + "MIRROR_66_0_OR_NEWER" }; // only touch PlayerSettings if we actually modified it. diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index b72d55b51b7..f516f07a11d 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -576,7 +576,7 @@ PlayerSettings: webGLThreadsSupport: 0 webGLWasmStreaming: 0 scriptingDefineSymbols: - 1: MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER + 1: MIRROR;MIRROR_17_0_OR_NEWER;MIRROR_18_0_OR_NEWER;MIRROR_24_0_OR_NEWER;MIRROR_26_0_OR_NEWER;MIRROR_27_0_OR_NEWER;MIRROR_28_0_OR_NEWER;MIRROR_29_0_OR_NEWER;MIRROR_30_0_OR_NEWER;MIRROR_30_5_2_OR_NEWER;MIRROR_32_1_2_OR_NEWER;MIRROR_32_1_4_OR_NEWER;MIRROR_35_0_OR_NEWER;MIRROR_35_1_OR_NEWER;MIRROR_37_0_OR_NEWER;MIRROR_38_0_OR_NEWER;MIRROR_39_0_OR_NEWER;MIRROR_40_0_OR_NEWER;MIRROR_41_0_OR_NEWER;MIRROR_42_0_OR_NEWER;MIRROR_43_0_OR_NEWER;MIRROR_44_0_OR_NEWER;MIRROR_46_0_OR_NEWER;MIRROR_47_0_OR_NEWER;MIRROR_53_0_OR_NEWER;MIRROR_55_0_OR_NEWER;MIRROR_57_0_OR_NEWER;MIRROR_58_0_OR_NEWER;MIRROR_65_0_OR_NEWER;MIRROR_66_0_OR_NEWER platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} From 3839fafade63b0ef4933a41feed703113e65e76a Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 17 Apr 2022 14:16:36 +0800 Subject: [PATCH 078/824] remove unused import --- Assets/Mirror/Runtime/RemoteCalls.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/Mirror/Runtime/RemoteCalls.cs b/Assets/Mirror/Runtime/RemoteCalls.cs index 127e2413892..3284feed341 100644 --- a/Assets/Mirror/Runtime/RemoteCalls.cs +++ b/Assets/Mirror/Runtime/RemoteCalls.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using UnityEngine; namespace Mirror.RemoteCalls From 63fdd539037b9eb2901f3d6596ecbf33ad963383 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sun, 17 Apr 2022 12:23:44 -0400 Subject: [PATCH 079/824] fixed comment --- Assets/Mirror/Runtime/Extensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/Extensions.cs b/Assets/Mirror/Runtime/Extensions.cs index 3d285e93504..0f6f62d7086 100644 --- a/Assets/Mirror/Runtime/Extensions.cs +++ b/Assets/Mirror/Runtime/Extensions.cs @@ -40,7 +40,7 @@ public static void CopyTo(this IEnumerable source, List destination) } #if !UNITY_2021_OR_NEWER - // Unity 2019 / 2020 don't have Queue.TryDeque which we need for batching. + // Unity 2020 and earlier doesn't have Queue.TryDequeue which we need for batching. public static bool TryDequeue(this Queue source, out T element) { if (source.Count > 0) From 8d07be97ed0490c0930f9976312b75e7ad97f9be Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sun, 17 Apr 2022 12:40:17 -0400 Subject: [PATCH 080/824] fixed XML comment --- Assets/Mirror/Components/NetworkStatistics.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Components/NetworkStatistics.cs b/Assets/Mirror/Components/NetworkStatistics.cs index a95d4a958f8..e1fac19328a 100644 --- a/Assets/Mirror/Components/NetworkStatistics.cs +++ b/Assets/Mirror/Components/NetworkStatistics.cs @@ -4,9 +4,11 @@ namespace Mirror { /// - /// Shows Network messages and bytes sent & received per second. - /// Add this component to the same object as Network Manager. + /// Shows Network messages and bytes sent and received per second. /// + /// + /// Add this component to the same object as Network Manager. + /// [AddComponentMenu("Network/Network Statistics")] [DisallowMultipleComponent] [HelpURL("https://mirror-networking.gitbook.io/docs/components/network-statistics")] From 0e9d86f56350a8ea429911d58e1c60644b12bf4d Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sun, 17 Apr 2022 21:05:53 -0400 Subject: [PATCH 081/824] breaking: Removed Obsoletes (#3142) * Removed Experimental NetworkTransform * NetworkRoomManager: Removed Obsoletes * NetworkTransformBase: Removed Obsoletes * NetworkBehaviour: Removed Obsoletes * NetworkManager: Removed Obsoletes * NetworkServer: Removed Obsoletes * SyncObject: Removed Obsoletes * fixed comment * fixed XML comment --- .../Experimental/NetworkTransform.cs | 14 - .../Experimental/NetworkTransform.cs.meta | 11 - .../Experimental/NetworkTransformBase.cs | 531 ------------------ .../Experimental/NetworkTransformBase.cs.meta | 11 - .../Experimental/NetworkTransformChild.cs | 20 - .../NetworkTransformChild.cs.meta | 11 - .../Mirror/Components/NetworkRoomManager.cs | 26 +- .../NetworkTransformBase.cs | 18 - Assets/Mirror/Runtime/NetworkBehaviour.cs | 12 - Assets/Mirror/Runtime/NetworkManager.cs | 54 +- Assets/Mirror/Runtime/NetworkServer.cs | 17 - Assets/Mirror/Runtime/SyncObject.cs | 4 - 12 files changed, 9 insertions(+), 720 deletions(-) delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransform.cs delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransformBase.cs delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransformChild.cs delete mode 100644 Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta diff --git a/Assets/Mirror/Components/Experimental/NetworkTransform.cs b/Assets/Mirror/Components/Experimental/NetworkTransform.cs deleted file mode 100644 index ca521413bc1..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransform.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; -using UnityEngine; - -namespace Mirror.Experimental -{ - [DisallowMultipleComponent] - // Deprecated 2022-01-18 - [Obsolete("Use the default NetworkTransform instead, it has proper snapshot interpolation.")] - [AddComponentMenu("")] - public class NetworkTransform : NetworkTransformBase - { - protected override Transform targetTransform => transform; - } -} diff --git a/Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta b/Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta deleted file mode 100644 index 2bc16dd87e4..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransform.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: 741bbe11f5357b44593b15c0d11b16bd -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs b/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs deleted file mode 100644 index 8bee5ec7e64..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs +++ /dev/null @@ -1,531 +0,0 @@ -// vis2k: -// base class for NetworkTransform and NetworkTransformChild. -// New method is simple and stupid. No more 1500 lines of code. -// -// Server sends current data. -// Client saves it and interpolates last and latest data points. -// Update handles transform movement / rotation -// FixedUpdate handles rigidbody movement / rotation -// -// Notes: -// * Built-in Teleport detection in case of lags / teleport / obstacles -// * Quaternion > EulerAngles because gimbal lock and Quaternion.Slerp -// * Syncs XYZ. Works 3D and 2D. Saving 4 bytes isn't worth 1000 lines of code. -// * Initial delay might happen if server sends packet immediately after moving -// just 1cm, hence we move 1cm and then wait 100ms for next packet -// * Only way for smooth movement is to use a fixed movement speed during -// interpolation. interpolation over time is never that good. -// -using System; -using UnityEngine; - -namespace Mirror.Experimental -{ - // Deprecated 2022-01-18 - [Obsolete("Use the default NetworkTransform instead, it has proper snapshot interpolation.")] - public abstract class NetworkTransformBase : NetworkBehaviour - { - // target transform to sync. can be on a child. - protected abstract Transform targetTransform { get; } - - [Header("Authority")] - - [Tooltip("Set to true if moves come from owner client, set to false if moves always come from server")] - [SyncVar] - public bool clientAuthority; - - [Tooltip("Set to true if updates from server should be ignored by owner")] - [SyncVar] - public bool excludeOwnerUpdate = true; - - [Header("Synchronization")] - - [Tooltip("Set to true if position should be synchronized")] - [SyncVar] - public bool syncPosition = true; - - [Tooltip("Set to true if rotation should be synchronized")] - [SyncVar] - public bool syncRotation = true; - - [Tooltip("Set to true if scale should be synchronized")] - [SyncVar] - public bool syncScale = true; - - [Header("Interpolation")] - - [Tooltip("Set to true if position should be interpolated")] - [SyncVar] - public bool interpolatePosition = true; - - [Tooltip("Set to true if rotation should be interpolated")] - [SyncVar] - public bool interpolateRotation = true; - - [Tooltip("Set to true if scale should be interpolated")] - [SyncVar] - public bool interpolateScale = true; - - // Sensitivity is added for VR where human players tend to have micro movements so this can quiet down - // the network traffic. Additionally, rigidbody drift should send less traffic, e.g very slow sliding / rolling. - [Header("Sensitivity")] - - [Tooltip("Changes to the transform must exceed these values to be transmitted on the network.")] - [SyncVar] - public float localPositionSensitivity = .01f; - - [Tooltip("If rotation exceeds this angle, it will be transmitted on the network")] - [SyncVar] - public float localRotationSensitivity = .01f; - - [Tooltip("Changes to the transform must exceed these values to be transmitted on the network.")] - [SyncVar] - public float localScaleSensitivity = .01f; - - [Header("Diagnostics")] - - // server - public Vector3 lastPosition; - public Quaternion lastRotation; - public Vector3 lastScale; - - // client - // use local position/rotation for VR support - [Serializable] - public struct DataPoint - { - public float timeStamp; - public Vector3 localPosition; - public Quaternion localRotation; - public Vector3 localScale; - public float movementSpeed; - - public bool isValid => timeStamp != 0; - } - - // Is this a client with authority over this transform? - // This component could be on the player object or any object that has been assigned authority to this client. - bool IsOwnerWithClientAuthority => hasAuthority && clientAuthority; - - // interpolation start and goal - public DataPoint start = new DataPoint(); - public DataPoint goal = new DataPoint(); - - // We need to store this locally on the server so clients can't request Authority when ever they like - bool clientAuthorityBeforeTeleport; - - void FixedUpdate() - { - // if server then always sync to others. - // let the clients know that this has moved - if (isServer && HasEitherMovedRotatedScaled()) - { - ServerUpdate(); - } - - if (isClient) - { - // send to server if we have local authority (and aren't the server) - // -> only if connectionToServer has been initialized yet too - if (IsOwnerWithClientAuthority) - { - ClientAuthorityUpdate(); - } - else if (goal.isValid) - { - ClientRemoteUpdate(); - } - } - } - - void ServerUpdate() - { - RpcMove(targetTransform.localPosition, Compression.CompressQuaternion(targetTransform.localRotation), targetTransform.localScale); - } - - void ClientAuthorityUpdate() - { - if (!isServer && HasEitherMovedRotatedScaled()) - { - // serialize - // local position/rotation for VR support - // send to server - CmdClientToServerSync(targetTransform.localPosition, Compression.CompressQuaternion(targetTransform.localRotation), targetTransform.localScale); - } - } - - void ClientRemoteUpdate() - { - // teleport or interpolate - if (NeedsTeleport()) - { - // local position/rotation for VR support - ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale); - - // reset data points so we don't keep interpolating - start = new DataPoint(); - goal = new DataPoint(); - } - else - { - // local position/rotation for VR support - ApplyPositionRotationScale(InterpolatePosition(start, goal, targetTransform.localPosition), - InterpolateRotation(start, goal, targetTransform.localRotation), - InterpolateScale(start, goal, targetTransform.localScale)); - } - } - - // moved or rotated or scaled since last time we checked it? - bool HasEitherMovedRotatedScaled() - { - // Save last for next frame to compare only if change was detected, otherwise - // slow moving objects might never sync because of C#'s float comparison tolerance. - // See also: https://github.com/vis2k/Mirror/pull/428) - bool changed = HasMoved || HasRotated || HasScaled; - if (changed) - { - // local position/rotation for VR support - if (syncPosition) lastPosition = targetTransform.localPosition; - if (syncRotation) lastRotation = targetTransform.localRotation; - if (syncScale) lastScale = targetTransform.localScale; - } - return changed; - } - - // local position/rotation for VR support - // SqrMagnitude is faster than Distance per Unity docs - // https://docs.unity3d.com/ScriptReference/Vector3-sqrMagnitude.html - - bool HasMoved => syncPosition && Vector3.SqrMagnitude(lastPosition - targetTransform.localPosition) > localPositionSensitivity * localPositionSensitivity; - bool HasRotated => syncRotation && Quaternion.Angle(lastRotation, targetTransform.localRotation) > localRotationSensitivity; - bool HasScaled => syncScale && Vector3.SqrMagnitude(lastScale - targetTransform.localScale) > localScaleSensitivity * localScaleSensitivity; - - // teleport / lag / stuck detection - // - checking distance is not enough since there could be just a tiny fence between us and the goal - // - checking time always works, this way we just teleport if we still didn't reach the goal after too much time has elapsed - bool NeedsTeleport() - { - // calculate time between the two data points - float startTime = start.isValid ? start.timeStamp : Time.time - Time.fixedDeltaTime; - float goalTime = goal.isValid ? goal.timeStamp : Time.time; - float difference = goalTime - startTime; - float timeSinceGoalReceived = Time.time - goalTime; - return timeSinceGoalReceived > difference * 5; - } - - // local authority client sends sync message to server for broadcasting - [Command(channel = Channels.Unreliable)] - void CmdClientToServerSync(Vector3 position, uint packedRotation, Vector3 scale) - { - // Ignore messages from client if not in client authority mode - if (!clientAuthority) - return; - - // deserialize payload - SetGoal(position, Compression.DecompressQuaternion(packedRotation), scale); - - // server-only mode does no interpolation to save computations, but let's set the position directly - if (isServer && !isClient) - ApplyPositionRotationScale(goal.localPosition, goal.localRotation, goal.localScale); - - RpcMove(position, packedRotation, scale); - } - - [ClientRpc(channel = Channels.Unreliable)] - void RpcMove(Vector3 position, uint packedRotation, Vector3 scale) - { - if (hasAuthority && excludeOwnerUpdate) return; - - if (!isServer) - SetGoal(position, Compression.DecompressQuaternion(packedRotation), scale); - } - - // serialization is needed by OnSerialize and by manual sending from authority - void SetGoal(Vector3 position, Quaternion rotation, Vector3 scale) - { - // put it into a data point immediately - DataPoint temp = new DataPoint - { - // deserialize position - localPosition = position, - localRotation = rotation, - localScale = scale, - timeStamp = Time.time - }; - - // movement speed: based on how far it moved since last time has to be calculated before 'start' is overwritten - temp.movementSpeed = EstimateMovementSpeed(goal, temp, targetTransform, Time.fixedDeltaTime); - - // reassign start wisely - // first ever data point? then make something up for previous one so that we can start interpolation without waiting for next. - if (start.timeStamp == 0) - { - start = new DataPoint - { - timeStamp = Time.time - Time.fixedDeltaTime, - // local position/rotation for VR support - localPosition = targetTransform.localPosition, - localRotation = targetTransform.localRotation, - localScale = targetTransform.localScale, - movementSpeed = temp.movementSpeed - }; - } - // second or nth data point? then update previous - // but: we start at where ever we are right now, so that it's perfectly smooth and we don't jump anywhere - // - // example if we are at 'x': - // - // A--x->B - // - // and then receive a new point C: - // - // A--x--B - // | - // | - // C - // - // then we don't want to just jump to B and start interpolation: - // - // x - // | - // | - // C - // - // we stay at 'x' and interpolate from there to C: - // - // x..B - // \ . - // \. - // C - // - else - { - float oldDistance = Vector3.Distance(start.localPosition, goal.localPosition); - float newDistance = Vector3.Distance(goal.localPosition, temp.localPosition); - - start = goal; - - // local position/rotation for VR support - // teleport / lag / obstacle detection: only continue at current position if we aren't too far away - // XC < AB + BC (see comments above) - if (Vector3.Distance(targetTransform.localPosition, start.localPosition) < oldDistance + newDistance) - { - start.localPosition = targetTransform.localPosition; - start.localRotation = targetTransform.localRotation; - start.localScale = targetTransform.localScale; - } - } - - // set new destination in any case. new data is best data. - goal = temp; - } - - // try to estimate movement speed for a data point based on how far it moved since the previous one - // - if this is the first time ever then we use our best guess: - // - delta based on transform.localPosition - // - elapsed based on send interval hoping that it roughly matches - static float EstimateMovementSpeed(DataPoint from, DataPoint to, Transform transform, float sendInterval) - { - Vector3 delta = to.localPosition - (from.localPosition != transform.localPosition ? from.localPosition : transform.localPosition); - float elapsed = from.isValid ? to.timeStamp - from.timeStamp : sendInterval; - - // avoid NaN - return elapsed > 0 ? delta.magnitude / elapsed : 0; - } - - // set position carefully depending on the target component - void ApplyPositionRotationScale(Vector3 position, Quaternion rotation, Vector3 scale) - { - // local position/rotation for VR support - if (syncPosition) targetTransform.localPosition = position; - if (syncRotation) targetTransform.localRotation = rotation; - if (syncScale) targetTransform.localScale = scale; - } - - // where are we in the timeline between start and goal? [0,1] - Vector3 InterpolatePosition(DataPoint start, DataPoint goal, Vector3 currentPosition) - { - if (!interpolatePosition) - return currentPosition; - - if (start.movementSpeed != 0) - { - // Option 1: simply interpolate based on time, but stutter will happen, it's not that smooth. - // This is especially noticeable if the camera automatically follows the player - // - Tell SonarCloud this isn't really commented code but actual comments and to stfu about it - // - float t = CurrentInterpolationFactor(); - // - return Vector3.Lerp(start.position, goal.position, t); - - // Option 2: always += speed - // speed is 0 if we just started after idle, so always use max for best results - float speed = Mathf.Max(start.movementSpeed, goal.movementSpeed); - return Vector3.MoveTowards(currentPosition, goal.localPosition, speed * Time.deltaTime); - } - - return currentPosition; - } - - Quaternion InterpolateRotation(DataPoint start, DataPoint goal, Quaternion defaultRotation) - { - if (!interpolateRotation) - return defaultRotation; - - if (start.localRotation != goal.localRotation) - { - float t = CurrentInterpolationFactor(start, goal); - return Quaternion.Slerp(start.localRotation, goal.localRotation, t); - } - - return defaultRotation; - } - - Vector3 InterpolateScale(DataPoint start, DataPoint goal, Vector3 currentScale) - { - if (!interpolateScale) - return currentScale; - - if (start.localScale != goal.localScale) - { - float t = CurrentInterpolationFactor(start, goal); - return Vector3.Lerp(start.localScale, goal.localScale, t); - } - - return currentScale; - } - - static float CurrentInterpolationFactor(DataPoint start, DataPoint goal) - { - if (start.isValid) - { - float difference = goal.timeStamp - start.timeStamp; - - // the moment we get 'goal', 'start' is supposed to start, so elapsed time is based on: - float elapsed = Time.time - goal.timeStamp; - - // avoid NaN - return difference > 0 ? elapsed / difference : 1; - } - return 1; - } - - #region Server Teleport (force move player) - - /// - /// This method will override this GameObject's current Transform.localPosition to the specified Vector3 and update all clients. - /// NOTE: position must be in LOCAL space if the transform has a parent - /// - /// Where to teleport this GameObject - [Server] - public void ServerTeleport(Vector3 localPosition) - { - Quaternion localRotation = targetTransform.localRotation; - ServerTeleport(localPosition, localRotation); - } - - /// - /// This method will override this GameObject's current Transform.localPosition and Transform.localRotation - /// to the specified Vector3 and Quaternion and update all clients. - /// NOTE: localPosition must be in LOCAL space if the transform has a parent - /// NOTE: localRotation must be in LOCAL space if the transform has a parent - /// - /// Where to teleport this GameObject - /// Which rotation to set this GameObject - [Server] - public void ServerTeleport(Vector3 localPosition, Quaternion localRotation) - { - // To prevent applying the position updates received from client (if they have ClientAuth) while being teleported. - // clientAuthorityBeforeTeleport defaults to false when not teleporting, if it is true then it means that teleport - // was previously called but not finished therefore we should keep it as true so that 2nd teleport call doesn't clear authority - clientAuthorityBeforeTeleport = clientAuthority || clientAuthorityBeforeTeleport; - clientAuthority = false; - - DoTeleport(localPosition, localRotation); - - // tell all clients about new values - RpcTeleport(localPosition, Compression.CompressQuaternion(localRotation), clientAuthorityBeforeTeleport); - } - - void DoTeleport(Vector3 newLocalPosition, Quaternion newLocalRotation) - { - targetTransform.localPosition = newLocalPosition; - targetTransform.localRotation = newLocalRotation; - - // Since we are overriding the position we don't need a goal and start. - // Reset them to null for fresh start - goal = new DataPoint(); - start = new DataPoint(); - lastPosition = newLocalPosition; - lastRotation = newLocalRotation; - } - - [ClientRpc(channel = Channels.Unreliable)] - void RpcTeleport(Vector3 newPosition, uint newPackedRotation, bool isClientAuthority) - { - DoTeleport(newPosition, Compression.DecompressQuaternion(newPackedRotation)); - - // only send finished if is owner and is ClientAuthority on server - if (hasAuthority && isClientAuthority) - CmdTeleportFinished(); - } - - /// - /// This RPC will be invoked on server after client finishes overriding the position. - /// - /// - [Command(channel = Channels.Unreliable)] - void CmdTeleportFinished() - { - if (clientAuthorityBeforeTeleport) - { - clientAuthority = true; - - // reset value so doesn't effect future calls, see note in ServerTeleport - clientAuthorityBeforeTeleport = false; - } - else - { - Debug.LogWarning("Client called TeleportFinished when clientAuthority was false on server", this); - } - } - - #endregion - - #region Debug Gizmos - - // draw the data points for easier debugging - void OnDrawGizmos() - { - // draw start and goal points and a line between them - if (start.localPosition != goal.localPosition) - { - DrawDataPointGizmo(start, Color.yellow); - DrawDataPointGizmo(goal, Color.green); - DrawLineBetweenDataPoints(start, goal, Color.cyan); - } - } - - static void DrawDataPointGizmo(DataPoint data, Color color) - { - // use a little offset because transform.localPosition might be in the ground in many cases - Vector3 offset = Vector3.up * 0.01f; - - // draw position - Gizmos.color = color; - Gizmos.DrawSphere(data.localPosition + offset, 0.5f); - - // draw forward and up like unity move tool - Gizmos.color = Color.blue; - Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.forward); - Gizmos.color = Color.green; - Gizmos.DrawRay(data.localPosition + offset, data.localRotation * Vector3.up); - } - - static void DrawLineBetweenDataPoints(DataPoint data1, DataPoint data2, Color color) - { - Gizmos.color = color; - Gizmos.DrawLine(data1.localPosition, data2.localPosition); - } - - #endregion - } -} diff --git a/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta b/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta deleted file mode 100644 index d737bed83aa..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransformBase.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: ea7c690c4fbf8c4439726f4c62eda6d3 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs b/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs deleted file mode 100644 index 1ade1de1757..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using UnityEngine; - -namespace Mirror.Experimental -{ - /// - /// A component to synchronize the position of child transforms of networked objects. - /// There must be a NetworkTransform on the root object of the hierarchy. There can be multiple NetworkTransformChild components on an object. This does not use physics for synchronization, it simply synchronizes the localPosition and localRotation of the child transform and lerps towards the received values. - /// - // Deprecated 2022-01-18 - [Obsolete("Use the default NetworkTransform instead, it has proper snapshot interpolation.")] - [AddComponentMenu("")] - public class NetworkTransformChild : NetworkTransformBase - { - [Header("Target")] - public Transform target; - - protected override Transform targetTransform => target; - } -} diff --git a/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta b/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta deleted file mode 100644 index 30f0d890b69..00000000000 --- a/Assets/Mirror/Components/Experimental/NetworkTransformChild.cs.meta +++ /dev/null @@ -1,11 +0,0 @@ -fileFormatVersion: 2 -guid: f65214da13a861f4a8ae309d3daea1c6 -MonoImporter: - externalObjects: {} - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {fileID: 2800000, guid: 7453abfe9e8b2c04a8a47eb536fe21eb, type: 3} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/Mirror/Components/NetworkRoomManager.cs b/Assets/Mirror/Components/NetworkRoomManager.cs index d432fbb1288..5ba441712df 100644 --- a/Assets/Mirror/Components/NetworkRoomManager.cs +++ b/Assets/Mirror/Components/NetworkRoomManager.cs @@ -472,10 +472,7 @@ public override void OnStartClient() /// public override void OnClientConnect() { -#pragma warning disable 618 - // obsolete method calls new method - OnRoomClientConnect(NetworkClient.connection); -#pragma warning restore 618 + OnRoomClientConnect(); base.OnClientConnect(); } @@ -485,9 +482,7 @@ public override void OnClientConnect() /// public override void OnClientDisconnect() { -#pragma warning disable 618 - OnRoomClientDisconnect(NetworkClient.connection); -#pragma warning restore 618 + OnRoomClientDisconnect(); base.OnClientDisconnect(); } @@ -516,10 +511,7 @@ public override void OnClientSceneChanged() CallOnClientExitRoom(); base.OnClientSceneChanged(); -#pragma warning disable 618 - // obsolete method calls new method - OnRoomClientSceneChanged(NetworkClient.connection); -#pragma warning restore 618 + OnRoomClientSceneChanged(); } #endregion @@ -647,19 +639,11 @@ public virtual void OnRoomClientExit() {} /// public virtual void OnRoomClientConnect() {} - // Deprecated 2021-10-30 - [Obsolete("Remove NetworkConnection from your override and use NetworkClient.connection instead.")] - public virtual void OnRoomClientConnect(NetworkConnection conn) => OnRoomClientConnect(); - /// /// This is called on the client when disconnected from a server. /// public virtual void OnRoomClientDisconnect() {} - // Deprecated 2021-10-30 - [Obsolete("Remove NetworkConnection from your override and use NetworkClient.connection instead.")] - public virtual void OnRoomClientDisconnect(NetworkConnection conn) => OnRoomClientDisconnect(); - /// /// This is called on the client when a client is started. /// @@ -675,10 +659,6 @@ public virtual void OnRoomStopClient() {} /// public virtual void OnRoomClientSceneChanged() {} - // Deprecated 2021-10-30 - [Obsolete("Remove NetworkConnection from your override and use NetworkClient.connection instead.")] - public virtual void OnRoomClientSceneChanged(NetworkConnection conn) => OnRoomClientSceneChanged(); - /// /// Called on the client when adding a player to the room fails. /// This could be because the room is full, or the connection is not allowed to have more players. diff --git a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs index 54e77a73dd6..839064c8855 100644 --- a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs @@ -570,14 +570,6 @@ public void RpcTeleport(Vector3 destination, Quaternion rotation) OnTeleport(destination, rotation); } - // Deprecated 2022-01-19 - [Obsolete("Use RpcTeleport(Vector3, Quaternion) instead.")] - [ClientRpc] - public void RpcTeleportAndRotate(Vector3 destination, Quaternion rotation) - { - OnTeleport(destination, rotation); - } - // client->server teleport to force position without interpolation. // otherwise it would interpolate to a (far away) new position. // => manually calling Teleport is the only 100% reliable solution. @@ -622,16 +614,6 @@ public void CmdTeleport(Vector3 destination, Quaternion rotation) RpcTeleport(destination, rotation); } - // Deprecated 2022-01-19 - [Obsolete("Use CmdTeleport(Vector3, Quaternion) instead.")] - [Command] - public void CmdTeleportAndRotate(Vector3 destination, Quaternion rotation) - { - if (!clientAuthority) return; - OnTeleport(destination, rotation); - RpcTeleport(destination, rotation); - } - public virtual void Reset() { // disabled objects aren't updated anymore. diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index 94cd930ad14..aabf988085a 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -102,10 +102,6 @@ public abstract class NetworkBehaviour : MonoBehaviour protected bool GetSyncVarHookGuard(ulong dirtyBit) => (syncVarHookGuard & dirtyBit) != 0UL; - // Deprecated 2021-09-16 (old weavers used it) - [Obsolete("Renamed to GetSyncVarHookGuard (uppercase)")] - protected bool getSyncVarHookGuard(ulong dirtyBit) => GetSyncVarHookGuard(dirtyBit); - // USED BY WEAVER to set syncvars in host mode without deadlocking protected void SetSyncVarHookGuard(ulong dirtyBit, bool value) { @@ -117,10 +113,6 @@ protected void SetSyncVarHookGuard(ulong dirtyBit, bool value) syncVarHookGuard &= ~dirtyBit; } - // Deprecated 2021-09-16 (old weavers used it) - [Obsolete("Renamed to SetSyncVarHookGuard (uppercase)")] - protected void setSyncVarHookGuard(ulong dirtyBit, bool value) => SetSyncVarHookGuard(dirtyBit, value); - /// Set as dirty so that it's synced to clients again. // these are masks, not bit numbers, ie. 110011b not '2' for 2nd bit. public void SetSyncVarDirtyBit(ulong dirtyBit) @@ -128,10 +120,6 @@ public void SetSyncVarDirtyBit(ulong dirtyBit) syncVarDirtyBits |= dirtyBit; } - // Deprecated 2021-09-19 - [Obsolete("SetDirtyBit was renamed to SetSyncVarDirtyBit because that's what it does")] - public void SetDirtyBit(ulong dirtyBit) => SetSyncVarDirtyBit(dirtyBit); - // true if syncInterval elapsed and any SyncVar or SyncObject is dirty public bool IsDirty() { diff --git a/Assets/Mirror/Runtime/NetworkManager.cs b/Assets/Mirror/Runtime/NetworkManager.cs index 37be9aec970..d708e729945 100644 --- a/Assets/Mirror/Runtime/NetworkManager.cs +++ b/Assets/Mirror/Runtime/NetworkManager.cs @@ -955,10 +955,7 @@ void FinishLoadSceneHost() if (clientReadyConnection != null) { -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientConnect(clientReadyConnection); -#pragma warning restore 618 + OnClientConnect(); clientLoadedScene = true; clientReadyConnection = null; } @@ -992,13 +989,7 @@ void FinishLoadSceneHost() OnServerSceneChanged(networkSceneName); if (NetworkClient.isConnected) - { - // let client know that we changed scene -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientSceneChanged(NetworkClient.connection); -#pragma warning restore 618 - } + OnClientSceneChanged(); } } @@ -1024,21 +1015,13 @@ void FinishLoadSceneClientOnly() if (clientReadyConnection != null) { -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientConnect(clientReadyConnection); -#pragma warning restore 618 + OnClientConnect(); clientLoadedScene = true; clientReadyConnection = null; } if (NetworkClient.isConnected) - { -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientSceneChanged(NetworkClient.connection); -#pragma warning restore 618 - } + OnClientSceneChanged(); } /// @@ -1184,10 +1167,7 @@ void OnClientAuthenticated() if (string.IsNullOrWhiteSpace(onlineScene) || onlineScene == offlineScene || IsSceneActive(onlineScene)) { clientLoadedScene = false; -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientConnect(NetworkClient.connection); -#pragma warning restore 618 + OnClientConnect(); } else { @@ -1200,19 +1180,13 @@ void OnClientAuthenticated() void OnClientDisconnectInternal() { //Debug.Log("NetworkManager.OnClientDisconnectInternal"); -#pragma warning disable 618 - // obsolete method calls new method because it's not empty - OnClientDisconnect(NetworkClient.connection); -#pragma warning restore 618 + OnClientDisconnect(); } void OnClientNotReadyMessageInternal(NotReadyMessage msg) { //Debug.Log("NetworkManager.OnClientNotReadyMessageInternal"); NetworkClient.ready = false; -#pragma warning disable 618 - OnClientNotReady(NetworkClient.connection); -#pragma warning restore 618 OnClientNotReady(); // NOTE: clientReadyConnection is not set here! don't want OnClientConnect to be invoked again after scene changes. @@ -1296,10 +1270,6 @@ public virtual void OnClientConnect() } } - // Deprecated 2021-12-11 - [Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")] - public virtual void OnClientConnect(NetworkConnection conn) => OnClientConnect(); - /// Called on clients when disconnected from a server. public virtual void OnClientDisconnect() { @@ -1309,20 +1279,12 @@ public virtual void OnClientDisconnect() StopClient(); } - // Deprecated 2021-12-11 - [Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")] - public virtual void OnClientDisconnect(NetworkConnection conn) => OnClientDisconnect(); - /// Called on client when transport raises an exception. public virtual void OnClientError(Exception exception) {} /// Called on clients when a servers tells the client it is no longer ready, e.g. when switching scenes. public virtual void OnClientNotReady() {} - // Deprecated 2021-12-11 - [Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")] - public virtual void OnClientNotReady(NetworkConnection conn) {} - /// Called from ClientChangeScene immediately before SceneManager.LoadSceneAsync is executed // customHandling: indicates if scene loading will be handled through overrides public virtual void OnClientChangeScene(string newSceneName, SceneOperation sceneOperation, bool customHandling) {} @@ -1344,10 +1306,6 @@ public virtual void OnClientSceneChanged() } } - // Deprecated 2021-12-11 - [Obsolete("Remove the NetworkConnection parameter in your override and use NetworkClient.connection instead.")] - public virtual void OnClientSceneChanged(NetworkConnection conn) => OnClientSceneChanged(); - // 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 diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 45d00c491fb..6102607b756 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -264,11 +264,6 @@ internal static void RemoveLocalConnection() RemoveConnection(0); } - /// True if we have no external connections (host is allowed) - // DEPRECATED 2022-02-05 - [Obsolete("Use !HasExternalConnections() instead of NoExternalConnections() to avoid double negatives.")] - public static bool NoExternalConnections() => !HasExternalConnections(); - /// True if we have external connections (that are not host) public static bool HasExternalConnections() { @@ -388,12 +383,6 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message, } } - // Deprecated 2021-09-19 - [Obsolete("SendToReady(identity, message, ...) was renamed to SendToReadyObservers because that's what it does.")] - public static void SendToReady(NetworkIdentity identity, T message, bool includeOwner = true, int channelId = Channels.Reliable) - where T : struct, NetworkMessage => - SendToReadyObservers(identity, message, includeOwner, channelId); - /// Send a message to only clients which are ready including the owner of the NetworkIdentity // TODO put rpcs into NetworkServer.Update WorldState packet, then finally remove SendToReady! public static void SendToReadyObservers(NetworkIdentity identity, T message, int channelId) @@ -402,12 +391,6 @@ public static void SendToReadyObservers(NetworkIdentity identity, T message, SendToReadyObservers(identity, message, true, channelId); } - // Deprecated 2021-09-19 - [Obsolete("SendToReady(identity, message, ...) was renamed to SendToReadyObservers because that's what it does.")] - public static void SendToReady(NetworkIdentity identity, T message, int channelId) - where T : struct, NetworkMessage => - SendToReadyObservers(identity, message, channelId); - // transport events //////////////////////////////////////////////////// // called by transport static void OnTransportConnected(int connectionId) diff --git a/Assets/Mirror/Runtime/SyncObject.cs b/Assets/Mirror/Runtime/SyncObject.cs index 7df3b6736fd..934f5657b4d 100644 --- a/Assets/Mirror/Runtime/SyncObject.cs +++ b/Assets/Mirror/Runtime/SyncObject.cs @@ -29,10 +29,6 @@ public abstract class SyncObject // Consider the object fully synchronized with clients public abstract void ClearChanges(); - // Deprecated 2021-09-17 - [Obsolete("Deprecated: Use ClearChanges instead.")] - public void Flush() => ClearChanges(); - /// Write a full copy of the object public abstract void OnSerializeAll(NetworkWriter writer); From 2db726df2e02b12807b458408a646607d08d2017 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Mon, 18 Apr 2022 09:07:15 -0400 Subject: [PATCH 082/824] fix: TeamInterestManagement OnDestroyed logic --- .../Team/TeamInterestManagement.cs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs index 22a8eb01751..22b4ace33ee 100644 --- a/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/Team/TeamInterestManagement.cs @@ -22,8 +22,8 @@ public override void OnSpawned(NetworkIdentity identity) string currentTeam = networkTeam.teamId; lastObjectTeam[identity] = currentTeam; - // string.Empty is never a valid teamId...do not add to teamObjects collection - if (currentTeam == string.Empty) + // Null / Empty string is never a valid teamId...do not add to teamObjects collection + if (string.IsNullOrWhiteSpace(currentTeam)) return; // Debug.Log($"MatchInterestManagement.OnSpawned({identity.name}) currentMatch: {currentTeam}"); @@ -38,10 +38,14 @@ public override void OnSpawned(NetworkIdentity identity) public override void OnDestroyed(NetworkIdentity identity) { - lastObjectTeam.TryGetValue(identity, out string currentTeam); - lastObjectTeam.Remove(identity); - if (currentTeam != string.Empty && teamObjects.TryGetValue(currentTeam, out HashSet objects) && objects.Remove(identity)) - RebuildTeamObservers(currentTeam); + if (lastObjectTeam.TryGetValue(identity, out string currentTeam)) + { + lastObjectTeam.Remove(identity); + if (!string.IsNullOrWhiteSpace(currentTeam) + && teamObjects.TryGetValue(currentTeam, out HashSet objects) + && objects.Remove(identity)) + RebuildTeamObservers(currentTeam); + } } // internal so we can update from tests @@ -62,7 +66,7 @@ internal void Update() if (!lastObjectTeam.TryGetValue(netIdentity, out string currentTeam)) continue; - // string.Empty is never a valid teamId + // Null / Empty string is never a valid teamId // Nothing to do if teamId hasn't changed if (string.IsNullOrWhiteSpace(newTeam) || newTeam == currentTeam) continue; @@ -84,8 +88,8 @@ internal void Update() void UpdateDirtyTeams(string newTeam, string currentTeam) { - // string.Empty is never a valid teamId - if (currentTeam != string.Empty) + // Null / Empty string is never a valid teamId + if (!string.IsNullOrWhiteSpace(currentTeam)) dirtyTeams.Add(currentTeam); dirtyTeams.Add(newTeam); @@ -136,7 +140,7 @@ public override bool OnCheckObserver(NetworkIdentity identity, NetworkConnection if (newObserverNetworkTeam.forceShown) return true; - // string.Empty is never a valid teamId + // Null / Empty string is never a valid teamId if (string.IsNullOrWhiteSpace(newObserverNetworkTeam.teamId)) return false; @@ -160,8 +164,8 @@ public override void OnRebuildObservers(NetworkIdentity identity, HashSet Date: Mon, 18 Apr 2022 22:58:16 -0400 Subject: [PATCH 083/824] Fixed Authenticator Comments - Fixes #3146 --- Assets/Mirror/Authenticators/BasicAuthenticator.cs | 4 ++-- Assets/Mirror/Authenticators/DeviceAuthenticator.cs | 4 ++-- Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs | 4 ++-- Assets/Mirror/Runtime/NetworkAuthenticator.cs | 4 ++-- ...rror__Network Authenticator-NewNetworkAuthenticator.cs.txt | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Assets/Mirror/Authenticators/BasicAuthenticator.cs b/Assets/Mirror/Authenticators/BasicAuthenticator.cs index 0fdc7e2e0b5..8b1e739613a 100644 --- a/Assets/Mirror/Authenticators/BasicAuthenticator.cs +++ b/Assets/Mirror/Authenticators/BasicAuthenticator.cs @@ -60,7 +60,7 @@ public override void OnStopServer() } /// - /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate + /// Called on server from OnServerConnectInternal when a client needs to authenticate /// /// Connection to client. public override void OnServerAuthenticate(NetworkConnectionToClient conn) @@ -153,7 +153,7 @@ public override void OnStopClient() } /// - /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate + /// Called on client from OnClientConnectInternal when a client needs to authenticate /// public override void OnClientAuthenticate() { diff --git a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs index 6723cc98d16..533d819f4d0 100644 --- a/Assets/Mirror/Authenticators/DeviceAuthenticator.cs +++ b/Assets/Mirror/Authenticators/DeviceAuthenticator.cs @@ -47,7 +47,7 @@ public override void OnStopServer() } /// - /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate + /// Called on server from OnServerConnectInternal when a client needs to authenticate /// /// Connection to client. public override void OnServerAuthenticate(NetworkConnectionToClient conn) @@ -94,7 +94,7 @@ public override void OnStopClient() } /// - /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate + /// Called on client from OnClientConnectInternal when a client needs to authenticate /// public override void OnClientAuthenticate() { diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs index bb37d7f9025..c00f33c021c 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatAuthenticator.cs @@ -58,7 +58,7 @@ public override void OnStopServer() } /// - /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate + /// Called on server from OnServerConnectInternal when a client needs to authenticate /// /// Connection to client. public override void OnServerAuthenticate(NetworkConnectionToClient conn) @@ -167,7 +167,7 @@ public override void OnStopClient() } /// - /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate + /// Called on client from OnClientConnectInternal when a client needs to authenticate /// public override void OnClientAuthenticate() { diff --git a/Assets/Mirror/Runtime/NetworkAuthenticator.cs b/Assets/Mirror/Runtime/NetworkAuthenticator.cs index 9f99b507046..aa1e7f7a59a 100644 --- a/Assets/Mirror/Runtime/NetworkAuthenticator.cs +++ b/Assets/Mirror/Runtime/NetworkAuthenticator.cs @@ -25,7 +25,7 @@ public virtual void OnStartServer() {} /// Called when server stops, used to unregister message handlers if needed. public virtual void OnStopServer() {} - /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate + /// Called on server from OnServerConnectInternal when a client needs to authenticate public virtual void OnServerAuthenticate(NetworkConnectionToClient conn) {} protected void ServerAccept(NetworkConnectionToClient conn) @@ -44,7 +44,7 @@ public virtual void OnStartClient() {} /// Called when client stops, used to unregister message handlers if needed. public virtual void OnStopClient() {} - /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate + /// Called on client from OnClientConnectInternal when a client needs to authenticate public virtual void OnClientAuthenticate() {} protected void ClientAccept() diff --git a/Assets/ScriptTemplates/51-Mirror__Network Authenticator-NewNetworkAuthenticator.cs.txt b/Assets/ScriptTemplates/51-Mirror__Network Authenticator-NewNetworkAuthenticator.cs.txt index 28b31619aa2..5ad10826422 100644 --- a/Assets/ScriptTemplates/51-Mirror__Network Authenticator-NewNetworkAuthenticator.cs.txt +++ b/Assets/ScriptTemplates/51-Mirror__Network Authenticator-NewNetworkAuthenticator.cs.txt @@ -32,7 +32,7 @@ public class #SCRIPTNAME# : NetworkAuthenticator } /// - /// Called on server from OnServerAuthenticateInternal when a client needs to authenticate + /// Called on server from OnServerConnectInternal when a client needs to authenticate /// /// Connection to client. public override void OnServerAuthenticate(NetworkConnectionToClient conn) { } @@ -67,7 +67,7 @@ public class #SCRIPTNAME# : NetworkAuthenticator } /// - /// Called on client from OnClientAuthenticateInternal when a client needs to authenticate + /// Called on client from OnClientConnectInternal when a client needs to authenticate /// public override void OnClientAuthenticate() { From cebfab010f227a457ba4caad9edff6cee06dc39e Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 19 Apr 2022 13:29:22 +0800 Subject: [PATCH 084/824] fix: Benchmark movement destinations set around start, not around position. prevents them from stopping to move because of the wander off protection. --- .../Examples/Benchmark/Scripts/MonsterMovement.cs | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/Assets/Mirror/Examples/Benchmark/Scripts/MonsterMovement.cs b/Assets/Mirror/Examples/Benchmark/Scripts/MonsterMovement.cs index 2ef7fae936e..fd7d3779e4f 100644 --- a/Assets/Mirror/Examples/Benchmark/Scripts/MonsterMovement.cs +++ b/Assets/Mirror/Examples/Benchmark/Scripts/MonsterMovement.cs @@ -38,15 +38,11 @@ void Update() { Vector2 circlePos = Random.insideUnitCircle; Vector3 dir = new Vector3(circlePos.x, 0, circlePos.y); - Vector3 dest = transform.position + dir * movementDistance; - // within move dist around start? + // set destination on random pos in a circle around start. // (don't want to wander off) - if (Vector3.Distance(start, dest) <= movementDistance) - { - destination = dest; - moving = true; - } + destination = start + dir * movementDistance; + moving = true; } } } From 4ffb9dfafc12ba55cc75aa6cb963734cde50ca71 Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 20 Apr 2022 10:25:42 +0800 Subject: [PATCH 085/824] NetworkWriter/Extensions: inlining WriteBlittable is enough. Don't inline WriteInt etc. too, we don't want WriteBlittable to be copied in place everywhere. --- Assets/Mirror/Runtime/NetworkWriter.cs | 5 +- .../Mirror/Runtime/NetworkWriterExtensions.cs | 72 +------------------ 2 files changed, 5 insertions(+), 72 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkWriter.cs b/Assets/Mirror/Runtime/NetworkWriter.cs index 442075f0623..2a1c43dfed8 100644 --- a/Assets/Mirror/Runtime/NetworkWriter.cs +++ b/Assets/Mirror/Runtime/NetworkWriter.cs @@ -82,6 +82,9 @@ public ArraySegment ToArraySegment() // WriteBlittable assumes same endianness for server & client. // All Unity 2018+ platforms are little endian. // => run NetworkWriterTests.BlittableOnThisPlatform() to verify! + // + // Note: inlining WriteBlittable is enough. don't inline WriteInt etc. + // we don't want WriteBlittable to be copied in place everywhere. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe void WriteBlittable(T value) where T : unmanaged @@ -148,12 +151,10 @@ internal void WriteBlittableNullable(T? value) WriteBlittable(value.Value); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteByte(byte value) => WriteBlittable(value); // for byte arrays with consistent size, where the reader knows how many to read // (like a packet opcode that's always the same) - [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WriteBytes(byte[] buffer, int offset, int count) { EnsureCapacity(Position + count); diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index cf0954e259e..fd5ccc0507f 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using UnityEngine; @@ -17,64 +16,41 @@ public static class NetworkWriterExtensions static readonly UTF8Encoding encoding = new UTF8Encoding(false, true); static readonly byte[] stringBuffer = new byte[NetworkWriter.MaxStringLength]; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteByte(this NetworkWriter writer, byte value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteByteNullable(this NetworkWriter writer, byte? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSByte(this NetworkWriter writer, sbyte value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSByteNullable(this NetworkWriter writer, sbyte? value) => writer.WriteBlittableNullable(value); // char is not blittable. convert to ushort. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteChar(this NetworkWriter writer, char value) => writer.WriteBlittable((ushort)value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteCharNullable(this NetworkWriter writer, char? value) => writer.WriteBlittableNullable((ushort?)value); // bool is not blittable. convert to byte. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBool(this NetworkWriter writer, bool value) => writer.WriteBlittable((byte)(value ? 1 : 0)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBoolNullable(this NetworkWriter writer, bool? value) => writer.WriteBlittableNullable(value.HasValue ? ((byte)(value.Value ? 1 : 0)) : new byte?()); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteShort(this NetworkWriter writer, short value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteShortNullable(this NetworkWriter writer, short? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUShort(this NetworkWriter writer, ushort value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUShortNullable(this NetworkWriter writer, ushort? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteInt(this NetworkWriter writer, int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteIntNullable(this NetworkWriter writer, int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUInt(this NetworkWriter writer, uint value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUIntNullable(this NetworkWriter writer, uint? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteLong(this NetworkWriter writer, long value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteLongNullable(this NetworkWriter writer, long? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteULong(this NetworkWriter writer, ulong value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteULongNullable(this NetworkWriter writer, ulong? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteFloat(this NetworkWriter writer, float value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteFloatNullable(this NetworkWriter writer, float? value) => writer.WriteBlittableNullable(value); - + [StructLayout(LayoutKind.Explicit)] internal struct UIntDouble { @@ -85,7 +61,6 @@ internal struct UIntDouble public ulong longValue; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDouble(this NetworkWriter writer, double value) { // DEBUG: try to find the exact value that fails. @@ -95,15 +70,11 @@ public static void WriteDouble(this NetworkWriter writer, double value) writer.WriteBlittable(value); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDoubleNullable(this NetworkWriter writer, double? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDecimal(this NetworkWriter writer, decimal value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteDecimalNullable(this NetworkWriter writer, decimal? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteString(this NetworkWriter writer, string value) { // write 0 for null support, increment real size by 1 @@ -131,7 +102,6 @@ public static void WriteString(this NetworkWriter writer, string value) writer.WriteBytes(stringBuffer, 0, size); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegment buffer) { writer.WriteBytesAndSize(buffer.Array, buffer.Offset, buffer.Count); @@ -140,7 +110,6 @@ public static void WriteBytesAndSizeSegment(this NetworkWriter writer, ArraySegm // Weaver needs a write function with just one byte[] parameter // (we don't name it .Write(byte[]) because it's really a WriteBytesAndSize since we write size / null info too) - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer) { // buffer might be null, so we can't use .Length in that case @@ -150,7 +119,6 @@ public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer) // for byte arrays with dynamic size, where the reader doesn't know how many will come // (like an inventory with different items etc.) - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, int offset, int count) { // null is supported because [SyncVar]s might be structs with null byte[] arrays @@ -165,7 +133,6 @@ public static void WriteBytesAndSize(this NetworkWriter writer, byte[] buffer, i writer.WriteBytes(buffer, offset, count); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteArraySegment(this NetworkWriter writer, ArraySegment segment) { int length = segment.Count; @@ -176,81 +143,54 @@ public static void WriteArraySegment(this NetworkWriter writer, ArraySegment< } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2(this NetworkWriter writer, Vector2 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2Nullable(this NetworkWriter writer, Vector2? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3(this NetworkWriter writer, Vector3 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3Nullable(this NetworkWriter writer, Vector3? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector4(this NetworkWriter writer, Vector4 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector4Nullable(this NetworkWriter writer, Vector4? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2Int(this NetworkWriter writer, Vector2Int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector2IntNullable(this NetworkWriter writer, Vector2Int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3Int(this NetworkWriter writer, Vector3Int value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteVector3IntNullable(this NetworkWriter writer, Vector3Int? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor(this NetworkWriter writer, Color value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColorNullable(this NetworkWriter writer, Color? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor32(this NetworkWriter writer, Color32 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteColor32Nullable(this NetworkWriter writer, Color32? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteQuaternion(this NetworkWriter writer, Quaternion value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteQuaternionNullable(this NetworkWriter writer, Quaternion? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRect(this NetworkWriter writer, Rect value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRectNullable(this NetworkWriter writer, Rect? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WritePlane(this NetworkWriter writer, Plane value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WritePlaneNullable(this NetworkWriter writer, Plane? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRay(this NetworkWriter writer, Ray value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteRayNullable(this NetworkWriter writer, Ray? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteMatrix4x4(this NetworkWriter writer, Matrix4x4 value) => writer.WriteBlittable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteMatrix4x4Nullable(this NetworkWriter writer, Matrix4x4? value) => writer.WriteBlittableNullable(value); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteGuid(this NetworkWriter writer, Guid value) { byte[] data = value.ToByteArray(); writer.WriteBytes(data, 0, data.Length); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteGuidNullable(this NetworkWriter writer, Guid? value) { writer.WriteBool(value.HasValue); if (value.HasValue) writer.WriteGuid(value.Value); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdentity value) { if (value == null) @@ -272,7 +212,6 @@ public static void WriteNetworkIdentity(this NetworkWriter writer, NetworkIdenti writer.WriteUInt(value.netId); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehaviour value) { if (value == null) @@ -284,7 +223,6 @@ public static void WriteNetworkBehaviour(this NetworkWriter writer, NetworkBehav writer.WriteByte((byte)value.ComponentIndex); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteTransform(this NetworkWriter writer, Transform value) { if (value == null) @@ -304,7 +242,6 @@ public static void WriteTransform(this NetworkWriter writer, Transform value) } } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteGameObject(this NetworkWriter writer, GameObject value) { if (value == null) @@ -323,7 +260,6 @@ public static void WriteGameObject(this NetworkWriter writer, GameObject value) writer.WriteNetworkIdentity(identity); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteList(this NetworkWriter writer, List list) { if (list is null) @@ -336,7 +272,6 @@ public static void WriteList(this NetworkWriter writer, List list) writer.Write(list[i]); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteArray(this NetworkWriter writer, T[] array) { if (array is null) @@ -349,19 +284,16 @@ public static void WriteArray(this NetworkWriter writer, T[] array) writer.Write(array[i]); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteUri(this NetworkWriter writer, Uri uri) { writer.WriteString(uri?.ToString()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D) { writer.WriteArray(texture2D.GetPixels32()); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void WriteSprite(this NetworkWriter writer, Sprite sprite) { writer.WriteTexture2D(sprite.texture); From 98d7a9d7d12c4b965077d69e3eff5864bc9c79df Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 20 Apr 2022 10:28:30 +0800 Subject: [PATCH 086/824] NetworkReader/Extensions: inlining ReadBlittable is enough. Don't inline ReadInt etc. too, we don't want ReadBlittable to be copied in place everywhere. --- Assets/Mirror/Runtime/NetworkReader.cs | 6 +- .../Mirror/Runtime/NetworkReaderExtensions.cs | 68 ------------------- 2 files changed, 3 insertions(+), 71 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkReader.cs b/Assets/Mirror/Runtime/NetworkReader.cs index 86eeef479c2..d344b60f71e 100644 --- a/Assets/Mirror/Runtime/NetworkReader.cs +++ b/Assets/Mirror/Runtime/NetworkReader.cs @@ -71,6 +71,9 @@ public void SetBuffer(ArraySegment segment) // Note: // ReadBlittable assumes same endianness for server & client. // All Unity 2018+ platforms are little endian. + // + // Note: inlining ReadBlittable is enough. don't inline ReadInt etc. + // we don't want ReadBlittable to be copied in place everywhere. [MethodImpl(MethodImplOptions.AggressiveInlining)] internal unsafe T ReadBlittable() where T : unmanaged @@ -134,12 +137,10 @@ internal unsafe T ReadBlittable() where T : unmanaged => ReadByte() != 0 ? ReadBlittable() : default(T?); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte ReadByte() => ReadBlittable(); /// Read 'count' bytes into the bytes array // NOTE: returns byte[] because all reader functions return something. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public byte[] ReadBytes(byte[] bytes, int count) { // check if passed byte array is big enough @@ -159,7 +160,6 @@ public byte[] ReadBytes(byte[] bytes, int count) } /// Read 'count' bytes allocation-free as ArraySegment that points to the internal array. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public ArraySegment ReadBytesSegment(int count) { // check if within buffer limits diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 6137866e6bf..223cc547b04 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -16,80 +16,52 @@ public static class NetworkReaderExtensions // 1000 readers after: 0.8MB GC, 18ms static readonly UTF8Encoding encoding = new UTF8Encoding(false, true); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte ReadByte(this NetworkReader reader) => reader.ReadBlittable(); - - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte? ReadByteNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte ReadSByte(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static sbyte? ReadSByteNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); // bool is not blittable. read as ushort. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static char ReadChar(this NetworkReader reader) => (char)reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static char? ReadCharNullable(this NetworkReader reader) => (char?)reader.ReadBlittableNullable(); // bool is not blittable. read as byte. - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool ReadBool(this NetworkReader reader) => reader.ReadBlittable() != 0; - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool? ReadBoolNullable(this NetworkReader reader) { byte? value = reader.ReadBlittableNullable(); return value.HasValue ? (value.Value != 0) : default(bool?); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short ReadShort(this NetworkReader reader) => (short)reader.ReadUShort(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static short? ReadShortNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort ReadUShort(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ushort? ReadUShortNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int ReadInt(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int? ReadIntNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint ReadUInt(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint? ReadUIntNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long ReadLong(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static long? ReadLongNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong ReadULong(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ulong? ReadULongNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float ReadFloat(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static float? ReadFloatNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double ReadDouble(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static double? ReadDoubleNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static decimal ReadDecimal(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static decimal? ReadDecimalNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); /// if an invalid utf8 string is sent - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string ReadString(this NetworkReader reader) { // read number of bytes @@ -114,7 +86,6 @@ public static string ReadString(this NetworkReader reader) } /// if count is invalid - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] ReadBytesAndSize(this NetworkReader reader) { // count = 0 means the array was null @@ -124,7 +95,6 @@ public static byte[] ReadBytesAndSize(this NetworkReader reader) return count == 0 ? null : reader.ReadBytes(checked((int)(count - 1u))); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static byte[] ReadBytes(this NetworkReader reader, int count) { byte[] bytes = new byte[count]; @@ -133,7 +103,6 @@ public static byte[] ReadBytes(this NetworkReader reader, int count) } /// if count is invalid - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static ArraySegment ReadBytesAndSizeSegment(this NetworkReader reader) { // count = 0 means the array was null @@ -143,72 +112,45 @@ public static ArraySegment ReadBytesAndSizeSegment(this NetworkReader read return count == 0 ? default : reader.ReadBytesSegment(checked((int)(count - 1u))); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2 ReadVector2(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2? ReadVector2Nullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3 ReadVector3(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3? ReadVector3Nullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4 ReadVector4(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector4? ReadVector4Nullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2Int ReadVector2Int(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector2Int? ReadVector2IntNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3Int ReadVector3Int(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Vector3Int? ReadVector3IntNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color ReadColor(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color? ReadColorNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color32 ReadColor32(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Color32? ReadColor32Nullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quaternion ReadQuaternion(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Quaternion? ReadQuaternionNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rect ReadRect(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Rect? ReadRectNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Plane ReadPlane(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Plane? ReadPlaneNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Ray ReadRay(this NetworkReader reader) => reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Ray? ReadRayNullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Matrix4x4 ReadMatrix4x4(this NetworkReader reader)=> reader.ReadBlittable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Matrix4x4? ReadMatrix4x4Nullable(this NetworkReader reader) => reader.ReadBlittableNullable(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Guid ReadGuid(this NetworkReader reader) => new Guid(reader.ReadBytes(16)); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Guid? ReadGuidNullable(this NetworkReader reader) => reader.ReadBool() ? ReadGuid(reader) : default(Guid?); - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader) { uint netId = reader.ReadUInt(); @@ -222,7 +164,6 @@ public static NetworkIdentity ReadNetworkIdentity(this NetworkReader reader) return Utils.GetSpawnedInServerOrClient(netId); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NetworkBehaviour ReadNetworkBehaviour(this NetworkReader reader) { // read netId first. @@ -251,13 +192,11 @@ public static NetworkBehaviour ReadNetworkBehaviour(this NetworkReader reader) : null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T ReadNetworkBehaviour(this NetworkReader reader) where T : NetworkBehaviour { return reader.ReadNetworkBehaviour() as T; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static NetworkBehaviour.NetworkBehaviourSyncVar ReadNetworkBehaviourSyncVar(this NetworkReader reader) { uint netId = reader.ReadUInt(); @@ -272,7 +211,6 @@ public static NetworkBehaviour.NetworkBehaviourSyncVar ReadNetworkBehaviourSyncV return new NetworkBehaviour.NetworkBehaviourSyncVar(netId, componentIndex); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Transform ReadTransform(this NetworkReader reader) { // Don't use null propagation here as it could lead to MissingReferenceException @@ -280,7 +218,6 @@ public static Transform ReadTransform(this NetworkReader reader) return networkIdentity != null ? networkIdentity.transform : null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static GameObject ReadGameObject(this NetworkReader reader) { // Don't use null propagation here as it could lead to MissingReferenceException @@ -288,7 +225,6 @@ public static GameObject ReadGameObject(this NetworkReader reader) return networkIdentity != null ? networkIdentity.gameObject : null; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static List ReadList(this NetworkReader reader) { int length = reader.ReadInt(); @@ -302,7 +238,6 @@ public static List ReadList(this NetworkReader reader) return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T[] ReadArray(this NetworkReader reader) { int length = reader.ReadInt(); @@ -329,14 +264,12 @@ public static T[] ReadArray(this NetworkReader reader) return result; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Uri ReadUri(this NetworkReader reader) { string uriString = reader.ReadString(); return (string.IsNullOrWhiteSpace(uriString) ? null : new Uri(uriString)); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Texture2D ReadTexture2D(this NetworkReader reader) { Texture2D texture2D = new Texture2D(32, 32); @@ -345,7 +278,6 @@ public static Texture2D ReadTexture2D(this NetworkReader reader) return texture2D; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] public static Sprite ReadSprite(this NetworkReader reader) { return Sprite.Create(reader.ReadTexture2D(), reader.ReadRect(), reader.ReadVector2()); From 1aaa4ce2a41d4113d405de28b274215958670aca Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 20 Apr 2022 10:33:14 +0700 Subject: [PATCH 087/824] perf: Cmd/Rpc bandwidth hash size reduced from 4 => 2 bytes (#3148) --- Assets/Mirror/Runtime/Messages.cs | 4 ++-- Assets/Mirror/Runtime/NetworkBehaviour.cs | 6 ++--- Assets/Mirror/Runtime/NetworkIdentity.cs | 2 +- Assets/Mirror/Runtime/RemoteCalls.cs | 23 ++++++++++--------- .../Tests/Editor/NetworkBehaviourTests.cs | 10 ++++---- 5 files changed, 23 insertions(+), 22 deletions(-) diff --git a/Assets/Mirror/Runtime/Messages.cs b/Assets/Mirror/Runtime/Messages.cs index d3816f8ac53..bd194c8a649 100644 --- a/Assets/Mirror/Runtime/Messages.cs +++ b/Assets/Mirror/Runtime/Messages.cs @@ -28,7 +28,7 @@ public struct CommandMessage : NetworkMessage { public uint netId; public byte componentIndex; - public int functionHash; + public ushort functionHash; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; @@ -38,7 +38,7 @@ public struct RpcMessage : NetworkMessage { public uint netId; public byte componentIndex; - public int functionHash; + public ushort functionHash; // the parameters for the Cmd function // -> ArraySegment to avoid unnecessary allocations public ArraySegment payload; diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index aabf988085a..ba4a9d921e2 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -223,7 +223,7 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer netId = netId, componentIndex = (byte)ComponentIndex, // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionHash = (ushort)functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -259,7 +259,7 @@ protected void SendRPCInternal(string functionFullName, NetworkWriter writer, in netId = netId, componentIndex = (byte)ComponentIndex, // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionHash = (ushort)functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; @@ -307,7 +307,7 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull netId = netId, componentIndex = (byte)ComponentIndex, // type+func so Inventory.RpcUse != Equipment.RpcUse - functionHash = functionFullName.GetStableHashCode(), + functionHash = (ushort)functionFullName.GetStableHashCode(), // segment to avoid reader allocations payload = writer.ToArraySegment() }; diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 6c3c12203f9..66d7ffdf577 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -1071,7 +1071,7 @@ internal void OnDeserializeAllSafely(NetworkReader reader, bool initialState) } // Helper function to handle Command/Rpc - internal void HandleRemoteCall(byte componentIndex, int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) + internal void HandleRemoteCall(byte componentIndex, ushort functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkConnectionToClient senderConnection = null) { // check if unity object has been destroyed if (this == null) diff --git a/Assets/Mirror/Runtime/RemoteCalls.cs b/Assets/Mirror/Runtime/RemoteCalls.cs index 3284feed341..c3f9235fae0 100644 --- a/Assets/Mirror/Runtime/RemoteCalls.cs +++ b/Assets/Mirror/Runtime/RemoteCalls.cs @@ -37,13 +37,14 @@ public static class RemoteProcedureCalls // IMPORTANT: cmd/rpc functions are identified via **HASHES**. // an index would requires half the bandwidth, but introduces issues // where static constructors are lazily called, so index order isn't - // guaranteed: + // guaranteed. keep hashes to avoid: // https://github.com/vis2k/Mirror/pull/3135 // https://github.com/vis2k/Mirror/issues/3138 - // keep the 4 byte hash for stability! - static readonly Dictionary remoteCallDelegates = new Dictionary(); + // BUT: 2 byte hash is enough if we check for collisions. that's what we + // do for NetworkMessage as well. + static readonly Dictionary remoteCallDelegates = new Dictionary(); - static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, int functionHash) + static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallType, RemoteCallDelegate func, ushort functionHash) { if (remoteCallDelegates.ContainsKey(functionHash)) { @@ -64,10 +65,10 @@ static bool CheckIfDelegateExists(Type componentType, RemoteCallType remoteCallT } // pass full function name to avoid ClassA.Func & ClassB.Func collisions - internal static int RegisterDelegate(Type componentType, string functionFullName, RemoteCallType remoteCallType, RemoteCallDelegate func, bool cmdRequiresAuthority = true) + internal static ushort RegisterDelegate(Type componentType, string functionFullName, RemoteCallType remoteCallType, RemoteCallDelegate func, bool cmdRequiresAuthority = true) { // type+func so Inventory.RpcUse != Equipment.RpcUse - int hash = functionFullName.GetStableHashCode(); + ushort hash = (ushort)(functionFullName.GetStableHashCode() & 0xFFFF); if (CheckIfDelegateExists(componentType, remoteCallType, func, hash)) return hash; @@ -95,19 +96,19 @@ public static void RegisterRpc(Type componentType, string functionFullName, Remo RegisterDelegate(componentType, functionFullName, RemoteCallType.ClientRpc, func); // to clean up tests - internal static void RemoveDelegate(int hash) => + internal static void RemoveDelegate(ushort hash) => remoteCallDelegates.Remove(hash); // note: no need to throw an error if not found. // an attacker might just try to call a cmd with an rpc's hash etc. // returning false is enough. - static bool GetInvokerForHash(int functionHash, RemoteCallType remoteCallType, out Invoker invoker) => + static bool GetInvokerForHash(ushort functionHash, RemoteCallType remoteCallType, out Invoker invoker) => remoteCallDelegates.TryGetValue(functionHash, out invoker) && invoker != null && invoker.callType == remoteCallType; // InvokeCmd/Rpc Delegate can all use the same function here - internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) + internal static bool Invoke(ushort functionHash, RemoteCallType remoteCallType, NetworkReader reader, NetworkBehaviour component, NetworkConnectionToClient senderConnection = null) { // IMPORTANT: we check if the message's componentIndex component is // actually of the right type. prevents attackers trying @@ -123,12 +124,12 @@ internal static bool Invoke(int functionHash, RemoteCallType remoteCallType, Net } // check if the command 'requiresAuthority' which is set in the attribute - internal static bool CommandRequiresAuthority(int cmdHash) => + internal static bool CommandRequiresAuthority(ushort cmdHash) => GetInvokerForHash(cmdHash, RemoteCallType.Command, out Invoker invoker) && invoker.cmdRequiresAuthority; /// Gets the handler function by hash. Useful for profilers and debuggers. - public static RemoteCallDelegate GetDelegate(int functionHash) => + public static RemoteCallDelegate GetDelegate(ushort functionHash) => remoteCallDelegates.TryGetValue(functionHash, out Invoker invoker) ? invoker.function : null; diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs index 058a831d629..2054d9c5959 100644 --- a/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs @@ -185,7 +185,7 @@ public void RegisterDelegateDoesntOverwrite() { // registerdelegate is protected, but we can use // RegisterCommandDelegate which calls RegisterDelegate - int registeredHash1 = RemoteProcedureCalls.RegisterDelegate( + ushort registeredHash1 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, @@ -194,7 +194,7 @@ public void RegisterDelegateDoesntOverwrite() // registering the exact same one should be fine. it should simply // do nothing. - int registeredHash2 = RemoteProcedureCalls.RegisterDelegate( + ushort registeredHash2 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, @@ -204,7 +204,7 @@ public void RegisterDelegateDoesntOverwrite() // registering the same name with a different callback shouldn't // work LogAssert.Expect(LogType.Error, $"Function {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate)} and {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate2)} have the same hash. Please rename one of them"); - int registeredHash3 = RemoteProcedureCalls.RegisterDelegate( + ushort registeredHash3 = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, @@ -222,7 +222,7 @@ public void GetDelegate() { // registerdelegate is protected, but we can use // RegisterCommandDelegate which calls RegisterDelegate - int registeredHash = RemoteProcedureCalls.RegisterDelegate( + ushort registeredHash = RemoteProcedureCalls.RegisterDelegate( typeof(NetworkBehaviourDelegateComponent), nameof(NetworkBehaviourDelegateComponent.Delegate), RemoteCallType.Command, @@ -230,7 +230,7 @@ public void GetDelegate() false); // get handler - int cmdHash = nameof(NetworkBehaviourDelegateComponent.Delegate).GetStableHashCode(); + ushort cmdHash = (ushort)nameof(NetworkBehaviourDelegateComponent.Delegate).GetStableHashCode(); RemoteCallDelegate func = RemoteProcedureCalls.GetDelegate(cmdHash); RemoteCallDelegate expected = NetworkBehaviourDelegateComponent.Delegate; Assert.That(func, Is.EqualTo(expected)); From 6c1dc5255ef76f6dcc8875fba47f46348e1d537c Mon Sep 17 00:00:00 2001 From: vis2k Date: Wed, 20 Apr 2022 10:33:53 +0800 Subject: [PATCH 088/824] remove unused import --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 223cc547b04..8fb61eacbcf 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Runtime.CompilerServices; using System.Text; using UnityEngine; From 2db24b3761ce380ab6b31702ebbda2c242ee4ddc Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 28 Apr 2022 12:59:53 +0800 Subject: [PATCH 089/824] gitignore 2021 Rider file --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 14878d3c140..05ac2ae9eb2 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ Obj *.mdb *.mdb.meta ProjectSettings/Packages/com.unity.testtools.codecoverage/Settings.json +ProjectSettings/RiderScriptEditorPersistedState.asset UserSettings/ # Packages/manifest.json is enough. lock is auto generated from it. Packages/packages-lock.json From 728e6e2fdf908cb75c6aa6cdc7a1914a01f6dbcb Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 28 Apr 2022 18:53:21 +0800 Subject: [PATCH 090/824] syntax --- Assets/Mirror/Runtime/NetworkServer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 6102607b756..2b35bdd0111 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -1418,7 +1418,8 @@ internal static void AddAllReadyServerConnectionsToObservers(NetworkIdentity ide // allocate newObservers helper HashSet only once // internal for tests - internal static readonly HashSet newObservers = new HashSet(); + internal static readonly HashSet newObservers = + new HashSet(); // rebuild observers default method (no AOI) - adds all connections static void RebuildObserversDefault(NetworkIdentity identity, bool initialize) From 3a742bb7da1ecf3991481eb28cf3921c98ece36f Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 09:38:36 +0800 Subject: [PATCH 091/824] Tests: NetworkReader/Writer Texture2D tests added --- .../Mirror/Tests/Editor/NetworkWriterTest.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index 0d42899e0f3..033dd0223bc 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using Mirror.Tests.RemoteAttrributeTest; using NUnit.Framework; @@ -1449,5 +1450,39 @@ public void TestWritingUnspawnedNetworkIdentity() NetworkWriter writer = new NetworkWriter(); writer.WriteNetworkIdentity(identity); } + + [Test] + public void WriteTexture2D_black() + { + // write + NetworkWriter writer = new NetworkWriter(); + writer.WriteTexture2D(Texture2D.blackTexture); + + // read + NetworkReader reader = new NetworkReader(writer.ToArray()); + Texture2D texture = reader.ReadTexture2D(); + + // compare + Assert.That(texture.width, Is.EqualTo(Texture2D.blackTexture.width)); + Assert.That(texture.height, Is.EqualTo(Texture2D.blackTexture.height)); + Assert.That(texture.GetPixels32().SequenceEqual(Texture2D.blackTexture.GetPixels32())); + } + + [Test] + public void WriteTexture2D_normal() + { + // write + NetworkWriter writer = new NetworkWriter(); + writer.WriteTexture2D(Texture2D.normalTexture); + + // read + NetworkReader reader = new NetworkReader(writer.ToArray()); + Texture2D texture = reader.ReadTexture2D(); + + // compare + Assert.That(texture.width, Is.EqualTo(Texture2D.normalTexture.width)); + Assert.That(texture.height, Is.EqualTo(Texture2D.normalTexture.height)); + Assert.That(texture.GetPixels32().SequenceEqual(Texture2D.normalTexture.GetPixels32())); + } } } From d235037954d5a235dfed84a4401dc81867ddc381 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 09:44:58 +0800 Subject: [PATCH 092/824] fix: NetworkReader.ReadTexture2D fixed 'reader not found for Color32[]' error when using it from tests --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 8fb61eacbcf..cc469223597 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -272,7 +272,7 @@ public static Uri ReadUri(this NetworkReader reader) public static Texture2D ReadTexture2D(this NetworkReader reader) { Texture2D texture2D = new Texture2D(32, 32); - texture2D.SetPixels32(reader.Read()); + texture2D.SetPixels32(reader.ReadArray()); texture2D.Apply(); return texture2D; } From b70945305763ee97ecea893b60fd289ca0f5f97c Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 09:47:09 +0800 Subject: [PATCH 093/824] fix: NetworkReader/Writer Texture2D now sends dimensions too. fixes "Texture2D.SetPixels32: size of data to be filled was larger than the size of data available in the source array. (Texture '')" --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 13 +++++++++++-- Assets/Mirror/Runtime/NetworkWriterExtensions.cs | 7 +++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index cc469223597..3edaabb2fae 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -271,8 +271,17 @@ public static Uri ReadUri(this NetworkReader reader) public static Texture2D ReadTexture2D(this NetworkReader reader) { - Texture2D texture2D = new Texture2D(32, 32); - texture2D.SetPixels32(reader.ReadArray()); + // TODO allocation protection when sending textures to server. + // currently can allocate 32k x 32k x 4 byte = 3.8 GB + + // read width & height + short width = reader.ReadShort(); + short height = reader.ReadShort(); + Texture2D texture2D = new Texture2D(width, height); + + // read pixel content + Color32[] pixels = reader.ReadArray(); + texture2D.SetPixels32(pixels); texture2D.Apply(); return texture2D; } diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index fd5ccc0507f..1d6434bc435 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -291,6 +291,13 @@ public static void WriteUri(this NetworkWriter writer, Uri uri) public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D) { + // TODO allocation protection when sending textures to server. + // currently can allocate 32k x 32k x 4 byte = 3.8 GB + + // write dimensions first so reader can create the texture with size + // 32k x 32k short is more than enough + writer.WriteShort((short)texture2D.width); + writer.WriteShort((short)texture2D.height); writer.WriteArray(texture2D.GetPixels32()); } From ae1c7c5fe4459ce9bacc59ec3229c4191be7c98e Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 09:59:52 +0800 Subject: [PATCH 094/824] fix: #3144 Reader/Writer Texture2D null support & test to guarantee it never happens again --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 6 +++++- Assets/Mirror/Runtime/NetworkWriterExtensions.cs | 9 +++++++++ Assets/Mirror/Tests/Editor/NetworkWriterTest.cs | 14 ++++++++++++++ 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 3edaabb2fae..6a3d29c8bf0 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -274,8 +274,12 @@ public static Texture2D ReadTexture2D(this NetworkReader reader) // TODO allocation protection when sending textures to server. // currently can allocate 32k x 32k x 4 byte = 3.8 GB - // read width & height + // support 'null' textures for [SyncVar]s etc. + // https://github.com/vis2k/Mirror/issues/3144 short width = reader.ReadShort(); + if (width == -1) return null; + + // read height short height = reader.ReadShort(); Texture2D texture2D = new Texture2D(width, height); diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index 1d6434bc435..340cd13e06a 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -294,6 +294,15 @@ public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D // TODO allocation protection when sending textures to server. // currently can allocate 32k x 32k x 4 byte = 3.8 GB + // support 'null' textures for [SyncVar]s etc. + // https://github.com/vis2k/Mirror/issues/3144 + // simply send -1 for width. + if (texture2D == null) + { + writer.WriteShort(-1); + return; + } + // write dimensions first so reader can create the texture with size // 32k x 32k short is more than enough writer.WriteShort((short)texture2D.width); diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index 033dd0223bc..22b4d4bdf0e 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1484,5 +1484,19 @@ public void WriteTexture2D_normal() Assert.That(texture.height, Is.EqualTo(Texture2D.normalTexture.height)); Assert.That(texture.GetPixels32().SequenceEqual(Texture2D.normalTexture.GetPixels32())); } + + // test to prevent https://github.com/vis2k/Mirror/issues/3144 + [Test] + public void WriteTexture2D_Null() + { + // write + NetworkWriter writer = new NetworkWriter(); + writer.WriteTexture2D(null); + + // read + NetworkReader reader = new NetworkReader(writer.ToArray()); + Texture2D texture = reader.ReadTexture2D(); + Assert.That(texture, Is.Null); + } } } From f413c91cdec72bbf6b60bfab7a4b2e860f523192 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 10:05:23 +0800 Subject: [PATCH 095/824] Test: Reader/Writer Sprite --- .../Mirror/Tests/Editor/NetworkWriterTest.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index 22b4d4bdf0e..c8e9ce189f7 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1498,5 +1498,27 @@ public void WriteTexture2D_Null() Texture2D texture = reader.ReadTexture2D(); Assert.That(texture, Is.Null); } + + [Test] + public void WriteSprite_normal() + { + // create a test sprite + Sprite example = Sprite.Create(Texture2D.normalTexture, new Rect(1, 1, 2, 2), Vector2.zero); + + // write + NetworkWriter writer = new NetworkWriter(); + writer.WriteSprite(example); + + // read + NetworkReader reader = new NetworkReader(writer.ToArray()); + Sprite sprite = reader.ReadSprite(); + + // compare + Assert.That(sprite.rect, Is.EqualTo(example.rect)); + Assert.That(sprite.pivot, Is.EqualTo(example.pivot)); + Assert.That(sprite.texture.width, Is.EqualTo(example.texture.width)); + Assert.That(sprite.texture.height, Is.EqualTo(example.texture.height)); + Assert.That(sprite.texture.GetPixels32().SequenceEqual(example.texture.GetPixels32())); + } } } From 298435001afe407c8ec76bc4268f0cb1caff4f96 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 10:08:33 +0800 Subject: [PATCH 096/824] fix: #3144 Reader/Writer Sprite null support & test to guarantee it never happens again --- Assets/Mirror/Runtime/NetworkReaderExtensions.cs | 8 +++++++- Assets/Mirror/Runtime/NetworkWriterExtensions.cs | 9 +++++++++ Assets/Mirror/Tests/Editor/NetworkWriterTest.cs | 14 ++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs index 6a3d29c8bf0..7140155a1a1 100644 --- a/Assets/Mirror/Runtime/NetworkReaderExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkReaderExtensions.cs @@ -292,7 +292,13 @@ public static Texture2D ReadTexture2D(this NetworkReader reader) public static Sprite ReadSprite(this NetworkReader reader) { - return Sprite.Create(reader.ReadTexture2D(), reader.ReadRect(), reader.ReadVector2()); + // support 'null' textures for [SyncVar]s etc. + // https://github.com/vis2k/Mirror/issues/3144 + Texture2D texture = reader.ReadTexture2D(); + if (texture == null) return null; + + // otherwise create a valid sprite + return Sprite.Create(texture, reader.ReadRect(), reader.ReadVector2()); } } } diff --git a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs index 340cd13e06a..9810a6efb2b 100644 --- a/Assets/Mirror/Runtime/NetworkWriterExtensions.cs +++ b/Assets/Mirror/Runtime/NetworkWriterExtensions.cs @@ -312,6 +312,15 @@ public static void WriteTexture2D(this NetworkWriter writer, Texture2D texture2D public static void WriteSprite(this NetworkWriter writer, Sprite sprite) { + // support 'null' textures for [SyncVar]s etc. + // https://github.com/vis2k/Mirror/issues/3144 + // simply send a 'null' for texture content. + if (sprite == null) + { + writer.WriteTexture2D(null); + return; + } + writer.WriteTexture2D(sprite.texture); writer.WriteRect(sprite.rect); writer.WriteVector2(sprite.pivot); diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs index c8e9ce189f7..86e0d580af2 100644 --- a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -1520,5 +1520,19 @@ public void WriteSprite_normal() Assert.That(sprite.texture.height, Is.EqualTo(example.texture.height)); Assert.That(sprite.texture.GetPixels32().SequenceEqual(example.texture.GetPixels32())); } + + // test to prevent https://github.com/vis2k/Mirror/issues/3144 + [Test] + public void WriteSprite_Null() + { + // write + NetworkWriter writer = new NetworkWriter(); + writer.WriteSprite(null); + + // read + NetworkReader reader = new NetworkReader(writer.ToArray()); + Sprite sprite = reader.ReadSprite(); + Assert.That(sprite, Is.Null); + } } } From 818e0ca9fdd2f20e0425fd1820917b1727f38273 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 29 Apr 2022 19:01:26 +0800 Subject: [PATCH 097/824] NetworkServer.SendToObservers: use explicit NetworkConnectionToClient type --- Assets/Mirror/Runtime/NetworkServer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 2b35bdd0111..2bc004a6356 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -344,7 +344,7 @@ static void SendToObservers(NetworkIdentity identity, T message, int channelI MessagePacking.Pack(message, writer); ArraySegment segment = writer.ToArraySegment(); - foreach (NetworkConnection conn in identity.observers.Values) + foreach (NetworkConnectionToClient conn in identity.observers.Values) { conn.Send(segment, channelId); } From 5c23ac69bc1018862fb6c6e58eb1f90609fe54ba Mon Sep 17 00:00:00 2001 From: vis2k Date: Sat, 30 Apr 2022 12:03:07 +0800 Subject: [PATCH 098/824] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 7326d3c8951..eabcd519a02 100644 --- a/README.md +++ b/README.md @@ -102,6 +102,18 @@ If you are migrating from UNET, then please check out our [Migration Guide](http + + MACE + + + + + + + + + + And [many more](https://mirror-networking.com/showcase/)... From 876d6d86dcc73cba0b966e9d45451182ded38a82 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 6 May 2022 12:19:50 -0400 Subject: [PATCH 099/824] removed private --- .../Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index cf0f13da190..8837a7d03c8 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -133,7 +133,7 @@ void Awake() Debug.Log("KcpTransport initialized!"); } - private void OnValidate() + void OnValidate() { // show max message sizes in inspector for convenience ReliableMaxMessageSize = KcpConnection.ReliableMaxMessageSize(ReceiveWindowSize); From efa2b3d4ca5b6f1de21dc9408aa5d27d7960c49f Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 6 May 2022 12:20:23 -0400 Subject: [PATCH 100/824] Syntax --- .../KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs index 1ff6968718e..00af45055df 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs @@ -12,8 +12,7 @@ public class KcpServerNonAlloc : KcpServer IPEndPointNonAlloc reusableClientEP; public KcpServerNonAlloc(Action OnConnected, - Action, - KcpChannel> OnData, + Action, KcpChannel> OnData, Action OnDisconnected, bool DualMode, bool NoDelay, From 35f1f225f32eaad5592eeacd8c84ef8364276c9e Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 8 May 2022 10:36:07 +0800 Subject: [PATCH 101/824] fix: kcp2k V1.18 - feature: OnError to allow higher level to show popups etc. - feature: KcpServer.GetClientAddress is now GetClientEndPoint in order to expose more details - ResolveHostname: include exception in log for easier debugging - fix: KcpClientConnection.RawReceive now logs the SocketException even if it was expected. makes debugging easier. - fix: KcpServer.TickIncoming now logs the SocketException even if it was expected. makes debugging easier. - fix: KcpClientConnection.RawReceive now calls Disconnect() if the other end has closed the connection. better than just remaining in a state with unusable sockets. => error handling based on #3155 => fixes #3143 --- .../KCP/MirrorTransport/KcpTransport.cs | 14 +++- .../KCP/kcp2k/{kcp2k.asmdef => KCP.asmdef} | 0 .../{kcp2k.asmdef.meta => KCP.asmdef.meta} | 0 .../Runtime/Transports/KCP/kcp2k/VERSION | 13 ++++ .../KCP/kcp2k/highlevel/KcpClient.cs | 17 +++-- .../kcp2k/highlevel/KcpClientConnection.cs | 23 +++++-- .../KCP/kcp2k/highlevel/KcpConnection.cs | 64 ++++++++++++------- .../KCP/kcp2k/highlevel/KcpServer.cs | 31 +++++++-- .../highlevel/NonAlloc/KcpClientNonAlloc.cs | 4 +- .../highlevel/NonAlloc/KcpServerNonAlloc.cs | 2 + 10 files changed, 126 insertions(+), 42 deletions(-) rename Assets/Mirror/Runtime/Transports/KCP/kcp2k/{kcp2k.asmdef => KCP.asmdef} (100%) rename Assets/Mirror/Runtime/Transports/KCP/kcp2k/{kcp2k.asmdef.meta => KCP.asmdef.meta} (100%) diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index 8837a7d03c8..a8fe17a4484 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -90,11 +90,13 @@ void Awake() ? new KcpClientNonAlloc( () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), - () => OnClientDisconnected.Invoke()) + () => OnClientDisconnected.Invoke(), + (error) => OnClientError.Invoke(new Exception(error))) : new KcpClient( () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), - () => OnClientDisconnected.Invoke()); + () => OnClientDisconnected.Invoke(), + (error) => OnClientError.Invoke(new Exception(error))); // server server = NonAlloc @@ -102,6 +104,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), + (connectionId, error) => OnServerError.Invoke(connectionId, new Exception(error)), DualMode, NoDelay, Interval, @@ -116,6 +119,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), + (connectionId, error) => OnServerError.Invoke(connectionId, new Exception(error)), DualMode, NoDelay, Interval, @@ -196,7 +200,11 @@ public override void ServerSend(int connectionId, ArraySegment segment, in OnServerDataSent?.Invoke(connectionId, segment, channelId); } public override void ServerDisconnect(int connectionId) => server.Disconnect(connectionId); - public override string ServerGetClientAddress(int connectionId) => server.GetClientAddress(connectionId); + public override string ServerGetClientAddress(int connectionId) + { + IPEndPoint endPoint = server.GetClientEndPoint(connectionId); + return endPoint != null ? endPoint.Address.ToString() : ""; + } public override void ServerStop() => server.Stop(); public override void ServerEarlyUpdate() { diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/KCP.asmdef similarity index 100% rename from Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/KCP.asmdef diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/KCP.asmdef.meta similarity index 100% rename from Assets/Mirror/Runtime/Transports/KCP/kcp2k/kcp2k.asmdef.meta rename to Assets/Mirror/Runtime/Transports/KCP/kcp2k/KCP.asmdef.meta diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION index 4787b41a475..1cf1fa87a60 100755 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION @@ -1,3 +1,16 @@ +V1.18 [2022-05-08] +- feature: OnError to allow higher level to show popups etc. +- feature: KcpServer.GetClientAddress is now GetClientEndPoint in order to + expose more details +- ResolveHostname: include exception in log for easier debugging +- fix: KcpClientConnection.RawReceive now logs the SocketException even if + it was expected. makes debugging easier. +- fix: KcpServer.TickIncoming now logs the SocketException even if it was + expected. makes debugging easier. +- fix: KcpClientConnection.RawReceive now calls Disconnect() if the other end + has closed the connection. better than just remaining in a state with unusable + sockets. + V1.17 [2022-01-09] - perf: server/client MaximizeSendReceiveBuffersToOSLimit option to set send/recv buffer sizes to OS limit. avoids drops due to small buffers under heavy load. diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs index e4a81a7f5c6..be42234a252 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs @@ -10,16 +10,21 @@ public class KcpClient public Action OnConnected; public Action, KcpChannel> OnData; public Action OnDisconnected; + // error callback instead of logging. + // allows libraries to show popups etc. + // (string instead of Exception for ease of use) + public Action OnError; // state public KcpClientConnection connection; public bool connected; - public KcpClient(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected) + public KcpClient(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, Action OnError) { this.OnConnected = OnConnected; this.OnData = OnData; this.OnDisconnected = OnDisconnected; + this.OnError = OnError; } // CreateConnection can be overwritten for where-allocation: @@ -53,19 +58,23 @@ public void Connect(string address, { Log.Info($"KCP: OnClientConnected"); connected = true; - OnConnected.Invoke(); + OnConnected(); }; connection.OnData = (message, channel) => { //Log.Debug($"KCP: OnClientData({BitConverter.ToString(message.Array, message.Offset, message.Count)})"); - OnData.Invoke(message, channel); + OnData(message, channel); }; connection.OnDisconnected = () => { Log.Info($"KCP: OnClientDisconnected"); connected = false; connection = null; - OnDisconnected.Invoke(); + OnDisconnected(); + }; + connection.OnError = (exception) => + { + OnError(exception); }; // connect diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs index 21d0e687abf..d6248596218 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs @@ -21,9 +21,9 @@ public static bool ResolveHostname(string hostname, out IPAddress[] addresses) addresses = Dns.GetHostAddresses(hostname); return addresses.Length >= 1; } - catch (SocketException) + catch (SocketException exception) { - Log.Info($"Failed to resolve host: {hostname}"); + Log.Info($"Failed to resolve host: {hostname} reason: {exception}"); addresses = null; return false; } @@ -95,7 +95,12 @@ public void Connect(string host, RawReceive(); } // otherwise call OnDisconnected to let the user know. - else OnDisconnected(); + else + { + // pass error to user callback. no need to log it manually. + OnError($"Failed to resolve host: {host}"); + OnDisconnected(); + } } // call from transport update @@ -119,14 +124,22 @@ public void RawReceive() } else { - Log.Error($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); + // pass error to user callback. no need to log it manually. + OnError($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); Disconnect(); } } } } // this is fine, the socket might have been closed in the other end - catch (SocketException) {} + catch (SocketException ex) + { + // the other end closing the connection is not an 'error'. + // but connections should never just end silently. + // at least log a message for easier debugging. + Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); + Disconnect(); + } } protected override void Dispose() diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs index 8abbc2eef4f..bfc32c5b7c6 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs @@ -19,6 +19,10 @@ public abstract class KcpConnection public Action OnAuthenticated; public Action, KcpChannel> OnData; public Action OnDisconnected; + // error callback instead of logging. + // allows libraries to show popups etc. + // (string instead of Exception for ease of use) + public Action OnError; // If we don't receive anything these many milliseconds // then consider us disconnected @@ -166,7 +170,8 @@ void HandleTimeout(uint time) // only ever happen if the connection is truly gone. if (time >= lastReceiveTime + timeout) { - Log.Warning($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); + // pass error to user callback. no need to log it manually. + OnError($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); Disconnect(); } } @@ -176,7 +181,8 @@ void HandleDeadLink() // kcp has 'dead_link' detection. might as well use it. if (kcp.state == -1) { - Log.Warning($"KCP Connection dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting."); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting."); Disconnect(); } } @@ -203,10 +209,11 @@ void HandleChoked() kcp.rcv_buf.Count + kcp.snd_buf.Count; if (total >= QueueDisconnectThreshold) { - Log.Warning($"KCP: disconnecting connection because it can't process data fast enough.\n" + - $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + - $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" + - $"* Or perhaps the network is simply too slow on our end, or on the other end.\n"); + // pass error to user callback. no need to log it manually. + OnError($"KCP: disconnecting connection because it can't process data fast enough.\n" + + $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + + $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" + + $"* Or perhaps the network is simply too slow on our end, or on the other end."); // let's clear all pending sends before disconnting with 'Bye'. // otherwise a single Flush in Disconnect() won't be enough to @@ -242,7 +249,8 @@ bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) else { // if receive failed, close everything - Log.Warning($"Receive failed with error={received}. closing connection."); + // pass error to user callback. no need to log it manually. + OnError($"Receive failed with error={received}. closing connection."); Disconnect(); } } @@ -250,7 +258,8 @@ bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) // attacker. let's disconnect to avoid allocation attacks etc. else { - Log.Warning($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); + // pass error to user callback. no need to log it manually. + OnError($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); Disconnect(); } } @@ -292,7 +301,8 @@ void TickIncoming_Connected(uint time) case KcpHeader.Disconnect: { // everything else is not allowed during handshake! - Log.Warning($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); + // pass error to user callback. no need to log it manually. + OnError($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); Disconnect(); break; } @@ -332,7 +342,8 @@ void TickIncoming_Authenticated(uint time) // empty data = attacker, or something went wrong else { - Log.Warning("KCP: received empty Data message while Authenticated. Disconnecting the connection."); + // pass error to user callback. no need to log it manually. + OnError("KCP: received empty Data message while Authenticated. Disconnecting the connection."); Disconnect(); } break; @@ -381,19 +392,22 @@ public void TickIncoming() catch (SocketException exception) { // this is ok, the connection was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (ObjectDisposedException exception) { // fine, socket was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } - catch (Exception ex) + catch (Exception exception) { // unexpected - Log.Error(ex.ToString()); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: unexpected Exception: {exception}"); Disconnect(); } } @@ -423,19 +437,22 @@ public void TickOutgoing() catch (SocketException exception) { // this is ok, the connection was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (ObjectDisposedException exception) { // fine, socket was closed - Log.Info($"KCP Connection: Disconnecting because {exception}. This is fine."); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } - catch (Exception ex) + catch (Exception exception) { // unexpected - Log.Error(ex.ToString()); + // pass error to user callback. no need to log it manually. + OnError($"KCP Connection: unexpected exception: {exception}"); Disconnect(); } } @@ -496,8 +513,9 @@ public void RawInput(byte[] buffer, int msgLength) } else { - // should never - Log.Warning($"KCP: received unreliable message in state {state}. Disconnecting the connection."); + // should never happen + // pass error to user callback. no need to log it manually. + OnError($"KCP: received unreliable message in state {state}. Disconnecting the connection."); Disconnect(); } break; @@ -505,7 +523,8 @@ public void RawInput(byte[] buffer, int msgLength) default: { // not a valid channel. random data or attacks. - Log.Info($"Disconnecting connection because of invalid channel header: {channel}"); + // pass error to user callback. no need to log it manually. + OnError($"Disconnecting connection because of invalid channel header: {channel}"); Disconnect(); break; } @@ -580,7 +599,8 @@ public void SendData(ArraySegment data, KcpChannel channel) // let's make it obvious so it's easy to debug. if (data.Count == 0) { - Log.Warning("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); + // pass error to user callback. no need to log it manually. + OnError("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); Disconnect(); return; } diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs index 97bbe91e95a..e55109ae167 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs @@ -13,6 +13,10 @@ public class KcpServer public Action OnConnected; public Action, KcpChannel> OnData; public Action OnDisconnected; + // error callback instead of logging. + // allows libraries to show popups etc. + // (string instead of Exception for ease of use) + public Action OnError; // socket configuration // DualMode uses both IPv6 and IPv4. not all platforms support it. @@ -66,6 +70,7 @@ public class KcpServer public KcpServer(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, + Action OnError, bool DualMode, bool NoDelay, uint Interval, @@ -80,6 +85,7 @@ public KcpServer(Action OnConnected, this.OnConnected = OnConnected; this.OnData = OnData; this.OnDisconnected = OnDisconnected; + this.OnError = OnError; this.DualMode = DualMode; this.NoDelay = NoDelay; this.Interval = Interval; @@ -161,13 +167,14 @@ public void Disconnect(int connectionId) } } - public string GetClientAddress(int connectionId) + // expose the whole IPEndPoint, not just the IP address. some need it. + public IPEndPoint GetClientEndPoint(int connectionId) { if (connections.TryGetValue(connectionId, out KcpServerConnection connection)) { - return (connection.GetRemoteEndPoint() as IPEndPoint).Address.ToString(); + return (connection.GetRemoteEndPoint() as IPEndPoint); } - return ""; + return null; } // EndPoint & Receive functions can be overwritten for where-allocation: @@ -276,12 +283,18 @@ public void TickIncoming() // call mirror event Log.Info($"KCP: OnServerDisconnected({connectionId})"); - OnDisconnected.Invoke(connectionId); + OnDisconnected(connectionId); + }; + + // setup error event + connection.OnError = (error) => + { + OnError(connectionId, error); }; // finally, call mirror OnConnected event Log.Info($"KCP: OnServerConnected({connectionId})"); - OnConnected.Invoke(connectionId); + OnConnected(connectionId); }; // now input the message & process received ones @@ -308,7 +321,13 @@ public void TickIncoming() } } // this is fine, the socket might have been closed in the other end - catch (SocketException) {} + catch (SocketException ex) + { + // the other end closing the connection is not an 'error'. + // but connections should never just end silently. + // at least log a message for easier debugging. + Log.Info($"KCP ClientConnection: looks like the other end has closed the connection. This is fine: {ex}"); + } } // process inputs for all server connections diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs index e39920c445f..9b45bdc38d7 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs @@ -6,8 +6,8 @@ namespace kcp2k { public class KcpClientNonAlloc : KcpClient { - public KcpClientNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected) - : base(OnConnected, OnData, OnDisconnected) + public KcpClientNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, Action OnError) + : base(OnConnected, OnData, OnDisconnected, OnError) { } diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs index 00af45055df..42f9df63994 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs @@ -14,6 +14,7 @@ public class KcpServerNonAlloc : KcpServer public KcpServerNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, + Action OnError, bool DualMode, bool NoDelay, uint Interval, @@ -27,6 +28,7 @@ public KcpServerNonAlloc(Action OnConnected, : base(OnConnected, OnData, OnDisconnected, + OnError, DualMode, NoDelay, Interval, From 4c758c25dc6a000e4b1f18987f6c4a85fc7faa48 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 8 May 2022 10:45:31 +0800 Subject: [PATCH 102/824] NetworkClient.OnError renamed to OnTransportError for consistency --- Assets/Mirror/Runtime/NetworkClient.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index e5dabe38a0c..82b35bb6875 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -109,7 +109,7 @@ static void AddTransportHandlers() Transport.activeTransport.OnClientConnected += OnTransportConnected; Transport.activeTransport.OnClientDataReceived += OnTransportData; Transport.activeTransport.OnClientDisconnected += OnTransportDisconnected; - Transport.activeTransport.OnClientError += OnError; + Transport.activeTransport.OnClientError += OnTransportError; } static void RemoveTransportHandlers() @@ -118,7 +118,7 @@ static void RemoveTransportHandlers() Transport.activeTransport.OnClientConnected -= OnTransportConnected; Transport.activeTransport.OnClientDataReceived -= OnTransportData; Transport.activeTransport.OnClientDisconnected -= OnTransportDisconnected; - Transport.activeTransport.OnClientError -= OnError; + Transport.activeTransport.OnClientError -= OnTransportError; } internal static void RegisterSystemHandlers(bool hostMode) @@ -432,7 +432,8 @@ internal static void OnTransportDisconnected() RemoveTransportHandlers(); } - static void OnError(Exception exception) + // transport errors are forwarded to high level + static void OnTransportError(Exception exception) { Debug.LogException(exception); OnErrorEvent?.Invoke(exception); From ed8911dfef1cfb32ac880edd538a0990106d46a0 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 8 May 2022 10:46:04 +0800 Subject: [PATCH 103/824] NetworkServer.OnError renamed to OnTransportError for consistency --- Assets/Mirror/Runtime/NetworkServer.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 2bc004a6356..e32c4891465 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -84,7 +84,7 @@ static void AddTransportHandlers() Transport.activeTransport.OnServerConnected += OnTransportConnected; Transport.activeTransport.OnServerDataReceived += OnTransportData; Transport.activeTransport.OnServerDisconnected += OnTransportDisconnected; - Transport.activeTransport.OnServerError += OnError; + Transport.activeTransport.OnServerError += OnTransportError; } static void RemoveTransportHandlers() @@ -93,7 +93,7 @@ static void RemoveTransportHandlers() Transport.activeTransport.OnServerConnected -= OnTransportConnected; Transport.activeTransport.OnServerDataReceived -= OnTransportData; Transport.activeTransport.OnServerDisconnected -= OnTransportDisconnected; - Transport.activeTransport.OnServerError -= OnError; + Transport.activeTransport.OnServerError -= OnTransportError; } // calls OnStartClient for all SERVER objects in host mode once. @@ -590,7 +590,8 @@ internal static void OnTransportDisconnected(int connectionId) } } - static void OnError(int connectionId, Exception exception) + // transport errors are forwarded to high level + static void OnTransportError(int connectionId, Exception exception) { Debug.LogException(exception); // try get connection. passes null otherwise. From 1e40bb4f00412a53bb60ff8d2012e0e94d7e8097 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 8 May 2022 10:51:22 +0800 Subject: [PATCH 104/824] NetworkClient/NetworkServer OnTransportError now logs a warning instead of an exception, indicating that it's a transport error and that it's fine. otherwise all kcp info/warnings would now be logged as exceptions --- Assets/Mirror/Runtime/NetworkClient.cs | 4 +++- Assets/Mirror/Runtime/NetworkServer.cs | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 82b35bb6875..d9c58b7d57a 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -435,7 +435,9 @@ internal static void OnTransportDisconnected() // transport errors are forwarded to high level static void OnTransportError(Exception exception) { - Debug.LogException(exception); + // transport errors will happen. logging a warning is enough. + // make sure the user does not panic. + Debug.LogWarning($"Client Transport Error: {exception}. This is fine."); OnErrorEvent?.Invoke(exception); } diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index e32c4891465..7180bdbf921 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -593,7 +593,9 @@ internal static void OnTransportDisconnected(int connectionId) // transport errors are forwarded to high level static void OnTransportError(int connectionId, Exception exception) { - Debug.LogException(exception); + // transport errors will happen. logging a warning is enough. + // make sure the user does not panic. + Debug.LogWarning($"Server Transport Error for connId={connectionId}: {exception}. This is fine."); // try get connection. passes null otherwise. connections.TryGetValue(connectionId, out NetworkConnectionToClient conn); OnErrorEvent?.Invoke(conn, exception); From c35ddd20db60648a697cd8c6a50edddd13d77016 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 11 May 2022 10:41:10 -0400 Subject: [PATCH 105/824] fix: NetworkClient - Check for duplicate sceneid --- Assets/Mirror/Runtime/NetworkClient.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index d9c58b7d57a..865ebd92779 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1167,7 +1167,15 @@ public static void PrepareToSpawnSceneObjects() // add all unspawned NetworkIdentities to spawnable objects if (ConsiderForSpawning(identity)) { - spawnableObjects.Add(identity.sceneId, identity); + if (spawnableObjects.TryGetValue(identity.sceneId, out NetworkIdentity existingIdentity)) + { + string errorMsg = $"NetworkClient: Duplicate sceneId {identity.sceneId} detected on {identity.gameObject.name} and {existingIdentity.gameObject.name}\n" + + $"This can happen if a networked object is persisted in DontDestroyOnLoad through loading / changing to the scene where it originated,\n" + + $"otherwise you may need to open and re-save the {identity.gameObject.scene} to reset scene id's."; + Debug.LogWarning(errorMsg, identity.gameObject); + } + else + spawnableObjects.Add(identity.sceneId, identity); } } } From a8202e5d0c3c0268babe0303872a77f2c5499f80 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 11 May 2022 10:59:10 -0400 Subject: [PATCH 106/824] renamed local var --- Assets/Mirror/Runtime/NetworkClient.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 865ebd92779..e5dc31c6b90 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1169,10 +1169,10 @@ public static void PrepareToSpawnSceneObjects() { if (spawnableObjects.TryGetValue(identity.sceneId, out NetworkIdentity existingIdentity)) { - string errorMsg = $"NetworkClient: Duplicate sceneId {identity.sceneId} detected on {identity.gameObject.name} and {existingIdentity.gameObject.name}\n" + + string msg = $"NetworkClient: Duplicate sceneId {identity.sceneId} detected on {identity.gameObject.name} and {existingIdentity.gameObject.name}\n" + $"This can happen if a networked object is persisted in DontDestroyOnLoad through loading / changing to the scene where it originated,\n" + $"otherwise you may need to open and re-save the {identity.gameObject.scene} to reset scene id's."; - Debug.LogWarning(errorMsg, identity.gameObject); + Debug.LogWarning(msg, identity.gameObject); } else spawnableObjects.Add(identity.sceneId, identity); From d3fa0c67e59d8e8f8e6904407d059f9ebb7e1cca Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 11 May 2022 12:06:43 -0400 Subject: [PATCH 107/824] fix: NetworkClient - Check for duplicate sceneId (#3157) * fix: NetworkClient - Check for duplicate sceneid * renamed local var * Update NetworkClient.cs Co-authored-by: vis2k --- Assets/Mirror/Runtime/NetworkClient.cs | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index d9c58b7d57a..324f3a319eb 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -1167,7 +1167,17 @@ public static void PrepareToSpawnSceneObjects() // add all unspawned NetworkIdentities to spawnable objects if (ConsiderForSpawning(identity)) { - spawnableObjects.Add(identity.sceneId, identity); + if (spawnableObjects.TryGetValue(identity.sceneId, out NetworkIdentity existingIdentity)) + { + string msg = $"NetworkClient: Duplicate sceneId {identity.sceneId} detected on {identity.gameObject.name} and {existingIdentity.gameObject.name}\n" + + $"This can happen if a networked object is persisted in DontDestroyOnLoad through loading / changing to the scene where it originated,\n" + + $"otherwise you may need to open and re-save the {identity.gameObject.scene} to reset scene id's."; + Debug.LogWarning(msg, identity.gameObject); + } + else + { + spawnableObjects.Add(identity.sceneId, identity); + } } } } From 91e2a0f79be42be25adfb2f770cf1c23c7fc9d39 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 12 May 2022 10:26:58 +0800 Subject: [PATCH 108/824] feature: kcp2k V1.19: OnError ErrorCodes --- .../KCP/MirrorTransport/KcpTransport.cs | 8 ++-- .../Runtime/Transports/KCP/kcp2k/VERSION | 3 ++ .../KCP/kcp2k/highlevel/ErrorCode.cs | 15 ++++++++ .../KCP/kcp2k/highlevel/ErrorCode.cs.meta | 3 ++ .../KCP/kcp2k/highlevel/KcpClient.cs | 14 ++++--- .../kcp2k/highlevel/KcpClientConnection.cs | 4 +- .../KCP/kcp2k/highlevel/KcpConnection.cs | 37 ++++++++++--------- .../KCP/kcp2k/highlevel/KcpServer.cs | 10 ++--- .../highlevel/NonAlloc/KcpClientNonAlloc.cs | 5 ++- .../highlevel/NonAlloc/KcpServerNonAlloc.cs | 2 +- 10 files changed, 65 insertions(+), 36 deletions(-) create mode 100644 Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs create mode 100644 Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs.meta diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index a8fe17a4484..026e66b980c 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -91,12 +91,12 @@ void Awake() () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), () => OnClientDisconnected.Invoke(), - (error) => OnClientError.Invoke(new Exception(error))) + (error, reason) => OnClientError.Invoke(new Exception(reason))) : new KcpClient( () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), () => OnClientDisconnected.Invoke(), - (error) => OnClientError.Invoke(new Exception(error))); + (error, reason) => OnClientError.Invoke(new Exception(reason))); // server server = NonAlloc @@ -104,7 +104,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), - (connectionId, error) => OnServerError.Invoke(connectionId, new Exception(error)), + (connectionId, error, reason) => OnServerError.Invoke(connectionId, new Exception(reason)), DualMode, NoDelay, Interval, @@ -119,7 +119,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), - (connectionId, error) => OnServerError.Invoke(connectionId, new Exception(error)), + (connectionId, error, reason) => OnServerError.Invoke(connectionId, new Exception(reason)), DualMode, NoDelay, Interval, diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION index 1cf1fa87a60..992fe9f0d93 100755 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/VERSION @@ -1,3 +1,6 @@ +V1.19 [2022-05-12] +- feature: OnError ErrorCodes + V1.18 [2022-05-08] - feature: OnError to allow higher level to show popups etc. - feature: KcpServer.GetClientAddress is now GetClientEndPoint in order to diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs new file mode 100644 index 00000000000..15b872f30fe --- /dev/null +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs @@ -0,0 +1,15 @@ +// kcp specific error codes to allow for error switching, localization, +// translation to Mirror errors, etc. +namespace kcp2k +{ + public enum ErrorCode : byte + { + DnsResolve, // failed to resolve a host name + Timeout, // ping timeout or dead link + Congestion, // more messages than transport / network can process + InvalidReceive, // recv invalid packet (possibly intentional attack) + InvalidSend, // user tried to send invalid data + ConnectionClosed, // connection closed voluntarily or lost involuntarily + Unexpected // unexpected error / exception, requires fix. + } +} \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs.meta b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs.meta new file mode 100644 index 00000000000..42f163fc18f --- /dev/null +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/ErrorCode.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3abbeffc1d794f11a45b7fcf110353f5 +timeCreated: 1652320712 \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs index be42234a252..58249e7373d 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClient.cs @@ -12,14 +12,18 @@ public class KcpClient public Action OnDisconnected; // error callback instead of logging. // allows libraries to show popups etc. - // (string instead of Exception for ease of use) - public Action OnError; + // (string instead of Exception for ease of use and to avoid user panic) + public Action OnError; // state public KcpClientConnection connection; public bool connected; - public KcpClient(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, Action OnError) + public KcpClient(Action OnConnected, + Action, + KcpChannel> OnData, + Action OnDisconnected, + Action OnError) { this.OnConnected = OnConnected; this.OnData = OnData; @@ -72,9 +76,9 @@ public void Connect(string address, connection = null; OnDisconnected(); }; - connection.OnError = (exception) => + connection.OnError = (error, reason) => { - OnError(exception); + OnError(error, reason); }; // connect diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs index d6248596218..a843a8ddf01 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpClientConnection.cs @@ -98,7 +98,7 @@ public void Connect(string host, else { // pass error to user callback. no need to log it manually. - OnError($"Failed to resolve host: {host}"); + OnError(ErrorCode.DnsResolve, $"Failed to resolve host: {host}"); OnDisconnected(); } } @@ -125,7 +125,7 @@ public void RawReceive() else { // pass error to user callback. no need to log it manually. - OnError($"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); + OnError(ErrorCode.InvalidReceive, $"KCP ClientConnection: message of size {msgLength} does not fit into buffer of size {rawReceiveBuffer.Length}. The excess was silently dropped. Disconnecting."); Disconnect(); } } diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs index bfc32c5b7c6..e5bc0f3ec8b 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpConnection.cs @@ -21,8 +21,8 @@ public abstract class KcpConnection public Action OnDisconnected; // error callback instead of logging. // allows libraries to show popups etc. - // (string instead of Exception for ease of use) - public Action OnError; + // (string instead of Exception for ease of use and to avoid user panic) + public Action OnError; // If we don't receive anything these many milliseconds // then consider us disconnected @@ -171,7 +171,7 @@ void HandleTimeout(uint time) if (time >= lastReceiveTime + timeout) { // pass error to user callback. no need to log it manually. - OnError($"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); + OnError(ErrorCode.Timeout, $"KCP: Connection timed out after not receiving any message for {timeout}ms. Disconnecting."); Disconnect(); } } @@ -182,7 +182,7 @@ void HandleDeadLink() if (kcp.state == -1) { // pass error to user callback. no need to log it manually. - OnError($"KCP Connection dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting."); + OnError(ErrorCode.Timeout, $"KCP Connection dead_link detected: a message was retransmitted {kcp.dead_link} times without ack. Disconnecting."); Disconnect(); } } @@ -210,7 +210,8 @@ void HandleChoked() if (total >= QueueDisconnectThreshold) { // pass error to user callback. no need to log it manually. - OnError($"KCP: disconnecting connection because it can't process data fast enough.\n" + + OnError(ErrorCode.Congestion, + $"KCP: disconnecting connection because it can't process data fast enough.\n" + $"Queue total {total}>{QueueDisconnectThreshold}. rcv_queue={kcp.rcv_queue.Count} snd_queue={kcp.snd_queue.Count} rcv_buf={kcp.rcv_buf.Count} snd_buf={kcp.snd_buf.Count}\n" + $"* Try to Enable NoDelay, decrease INTERVAL, disable Congestion Window (= enable NOCWND!), increase SEND/RECV WINDOW or compress data.\n" + $"* Or perhaps the network is simply too slow on our end, or on the other end."); @@ -250,7 +251,7 @@ bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) { // if receive failed, close everything // pass error to user callback. no need to log it manually. - OnError($"Receive failed with error={received}. closing connection."); + OnError(ErrorCode.InvalidReceive, $"Receive failed with error={received}. closing connection."); Disconnect(); } } @@ -259,7 +260,7 @@ bool ReceiveNextReliable(out KcpHeader header, out ArraySegment message) else { // pass error to user callback. no need to log it manually. - OnError($"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); + OnError(ErrorCode.InvalidReceive, $"KCP: possible allocation attack for msgSize {msgSize} > buffer {kcpMessageBuffer.Length}. Disconnecting the connection."); Disconnect(); } } @@ -302,7 +303,7 @@ void TickIncoming_Connected(uint time) { // everything else is not allowed during handshake! // pass error to user callback. no need to log it manually. - OnError($"KCP: received invalid header {header} while Connected. Disconnecting the connection."); + OnError(ErrorCode.InvalidReceive, $"KCP: received invalid header {header} while Connected. Disconnecting the connection."); Disconnect(); break; } @@ -343,7 +344,7 @@ void TickIncoming_Authenticated(uint time) else { // pass error to user callback. no need to log it manually. - OnError("KCP: received empty Data message while Authenticated. Disconnecting the connection."); + OnError(ErrorCode.InvalidReceive, "KCP: received empty Data message while Authenticated. Disconnecting the connection."); Disconnect(); } break; @@ -393,21 +394,21 @@ public void TickIncoming() { // this is ok, the connection was closed // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); + OnError(ErrorCode.ConnectionClosed, $"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (ObjectDisposedException exception) { // fine, socket was closed // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); + OnError(ErrorCode.ConnectionClosed, $"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (Exception exception) { // unexpected // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: unexpected Exception: {exception}"); + OnError(ErrorCode.Unexpected, $"KCP Connection: unexpected Exception: {exception}"); Disconnect(); } } @@ -438,21 +439,21 @@ public void TickOutgoing() { // this is ok, the connection was closed // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); + OnError(ErrorCode.ConnectionClosed, $"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (ObjectDisposedException exception) { // fine, socket was closed // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: Disconnecting because {exception}. This is fine."); + OnError(ErrorCode.ConnectionClosed, $"KCP Connection: Disconnecting because {exception}. This is fine."); Disconnect(); } catch (Exception exception) { // unexpected // pass error to user callback. no need to log it manually. - OnError($"KCP Connection: unexpected exception: {exception}"); + OnError(ErrorCode.Unexpected, $"KCP Connection: unexpected exception: {exception}"); Disconnect(); } } @@ -515,7 +516,7 @@ public void RawInput(byte[] buffer, int msgLength) { // should never happen // pass error to user callback. no need to log it manually. - OnError($"KCP: received unreliable message in state {state}. Disconnecting the connection."); + OnError(ErrorCode.InvalidReceive, $"KCP: received unreliable message in state {state}. Disconnecting the connection."); Disconnect(); } break; @@ -524,7 +525,7 @@ public void RawInput(byte[] buffer, int msgLength) { // not a valid channel. random data or attacks. // pass error to user callback. no need to log it manually. - OnError($"Disconnecting connection because of invalid channel header: {channel}"); + OnError(ErrorCode.InvalidReceive, $"Disconnecting connection because of invalid channel header: {channel}"); Disconnect(); break; } @@ -600,7 +601,7 @@ public void SendData(ArraySegment data, KcpChannel channel) if (data.Count == 0) { // pass error to user callback. no need to log it manually. - OnError("KcpConnection: tried sending empty message. This should never happen. Disconnecting."); + OnError(ErrorCode.InvalidSend, "KcpConnection: tried sending empty message. This should never happen. Disconnecting."); Disconnect(); return; } diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs index e55109ae167..5e486880a73 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/KcpServer.cs @@ -15,8 +15,8 @@ public class KcpServer public Action OnDisconnected; // error callback instead of logging. // allows libraries to show popups etc. - // (string instead of Exception for ease of use) - public Action OnError; + // (string instead of Exception for ease of use and to avoid user panic) + public Action OnError; // socket configuration // DualMode uses both IPv6 and IPv4. not all platforms support it. @@ -70,7 +70,7 @@ public class KcpServer public KcpServer(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, - Action OnError, + Action OnError, bool DualMode, bool NoDelay, uint Interval, @@ -287,9 +287,9 @@ public void TickIncoming() }; // setup error event - connection.OnError = (error) => + connection.OnError = (error, reason) => { - OnError(connectionId, error); + OnError(connectionId, error, reason); }; // finally, call mirror OnConnected event diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs index 9b45bdc38d7..2417408ac21 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpClientNonAlloc.cs @@ -6,7 +6,10 @@ namespace kcp2k { public class KcpClientNonAlloc : KcpClient { - public KcpClientNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, Action OnError) + public KcpClientNonAlloc(Action OnConnected, + Action, KcpChannel> OnData, + Action OnDisconnected, + Action OnError) : base(OnConnected, OnData, OnDisconnected, OnError) { } diff --git a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs index 42f9df63994..001a64b568a 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/kcp2k/highlevel/NonAlloc/KcpServerNonAlloc.cs @@ -14,7 +14,7 @@ public class KcpServerNonAlloc : KcpServer public KcpServerNonAlloc(Action OnConnected, Action, KcpChannel> OnData, Action OnDisconnected, - Action OnError, + Action OnError, bool DualMode, bool NoDelay, uint Interval, From a2d35b82b6f90f18d9e07e78eee488d61aa47768 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 12 May 2022 12:36:48 +0800 Subject: [PATCH 109/824] Transport syntax: group fields together --- Assets/Mirror/Runtime/Transport.cs | 45 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Assets/Mirror/Runtime/Transport.cs b/Assets/Mirror/Runtime/Transport.cs index 13f2b9f0b68..f831bf1d744 100644 --- a/Assets/Mirror/Runtime/Transport.cs +++ b/Assets/Mirror/Runtime/Transport.cs @@ -30,12 +30,14 @@ namespace Mirror /// Abstract transport layer component public abstract class Transport : MonoBehaviour { + // common ////////////////////////////////////////////////////////////// /// The current transport used by Mirror. public static Transport activeTransport; /// Is this transport available in the current platform? public abstract bool Available(); + // client ////////////////////////////////////////////////////////////// /// Called by Transport when the client connected to the server. public Action OnClientConnected; @@ -55,6 +57,28 @@ public abstract class Transport : MonoBehaviour /// Called by Transport when the client disconnected from the server. public Action OnClientDisconnected; + // server ////////////////////////////////////////////////////////////// + /// Called by Transport when a new client connected to the server. + public Action OnServerConnected; + + /// Called by Transport when the server received a message from a client. + public Action, int> OnServerDataReceived; + + /// Called by Transport when the server sent a message to a client. + // Transports are responsible for calling it because: + // - groups it together with OnReceived responsibility + // - allows transports to decide if anything was sent or not + // - allows transports to decide the actual used channel (i.e. tcp always sending reliable) + public Action, int> OnServerDataSent; + + /// Called by Transport when a server's connection encountered a problem. + /// If a Disconnect will also be raised, raise the Error first. + public Action OnServerError; + + /// Called by Transport when a client disconnected from the server. + public Action OnServerDisconnected; + + // client functions //////////////////////////////////////////////////// /// True if the client is currently connected to the server. public abstract bool ClientConnected(); @@ -76,30 +100,11 @@ public virtual void ClientConnect(Uri uri) /// Disconnects the client from the server public abstract void ClientDisconnect(); + // server functions //////////////////////////////////////////////////// /// Returns server address as Uri. // Useful for NetworkDiscovery. public abstract Uri ServerUri(); - /// Called by Transport when a new client connected to the server. - public Action OnServerConnected; - - /// Called by Transport when the server received a message from a client. - public Action, int> OnServerDataReceived; - - /// Called by Transport when the server sent a message to a client. - // Transports are responsible for calling it because: - // - groups it together with OnReceived responsibility - // - allows transports to decide if anything was sent or not - // - allows transports to decide the actual used channel (i.e. tcp always sending reliable) - public Action, int> OnServerDataSent; - - /// Called by Transport when a server's connection encountered a problem. - /// If a Disconnect will also be raised, raise the Error first. - public Action OnServerError; - - /// Called by Transport when a client disconnected from the server. - public Action OnServerDisconnected; - /// True if the server is currently listening for connections. public abstract bool ServerActive(); From 096fe3808012e564db28c1e5ed1c1dbcaea3964f Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 13 May 2022 16:28:47 +0700 Subject: [PATCH 110/824] breaking: OnError(Exception) changed to OnError(enum, reason) (#3158) * TransportError * adjust mirror * obsoletes * adjust transports * Refused --- Assets/Mirror/Runtime/NetworkClient.cs | 8 +++---- Assets/Mirror/Runtime/NetworkManager.cs | 20 ++++++++++++++-- Assets/Mirror/Runtime/NetworkServer.cs | 8 +++---- Assets/Mirror/Runtime/Transport.cs | 4 ++-- Assets/Mirror/Runtime/TransportError.cs | 17 +++++++++++++ Assets/Mirror/Runtime/TransportError.cs.meta | 3 +++ .../KCP/MirrorTransport/KcpTransport.cs | 23 ++++++++++++++---- .../Runtime/Transports/MultiplexTransport.cs | 4 ++-- .../SimpleWebTransport/SimpleWebTransport.cs | 4 ++-- .../Tests/Editor/MiddlewareTransportTest.cs | 24 +++++++------------ 10 files changed, 79 insertions(+), 36 deletions(-) create mode 100644 Assets/Mirror/Runtime/TransportError.cs create mode 100644 Assets/Mirror/Runtime/TransportError.cs.meta diff --git a/Assets/Mirror/Runtime/NetworkClient.cs b/Assets/Mirror/Runtime/NetworkClient.cs index 324f3a319eb..77e8932903c 100644 --- a/Assets/Mirror/Runtime/NetworkClient.cs +++ b/Assets/Mirror/Runtime/NetworkClient.cs @@ -71,7 +71,7 @@ public static class NetworkClient // => public so that custom NetworkManagers can hook into it public static Action OnConnectedEvent; public static Action OnDisconnectedEvent; - public static Action OnErrorEvent; + public static Action OnErrorEvent; /// Registered spawnable prefabs by assetId. public static readonly Dictionary prefabs = @@ -433,12 +433,12 @@ internal static void OnTransportDisconnected() } // transport errors are forwarded to high level - static void OnTransportError(Exception exception) + static void OnTransportError(TransportError error, string reason) { // transport errors will happen. logging a warning is enough. // make sure the user does not panic. - Debug.LogWarning($"Client Transport Error: {exception}. This is fine."); - OnErrorEvent?.Invoke(exception); + Debug.LogWarning($"Client Transport Error: {error}: {reason}. This is fine."); + OnErrorEvent?.Invoke(error, reason); } // send //////////////////////////////////////////////////////////////// diff --git a/Assets/Mirror/Runtime/NetworkManager.cs b/Assets/Mirror/Runtime/NetworkManager.cs index d708e729945..5fd8dc482f8 100644 --- a/Assets/Mirror/Runtime/NetworkManager.cs +++ b/Assets/Mirror/Runtime/NetworkManager.cs @@ -1243,8 +1243,16 @@ public virtual void OnServerAddPlayer(NetworkConnectionToClient conn) NetworkServer.AddPlayerForConnection(conn, player); } - /// Called on server when transport raises an exception. NetworkConnection may be null. + // DEPRECATED 2022-05-12 + [Obsolete("OnServerError(conn, Exception) was changed to OnServerError(conn, TransportError, string)")] public virtual void OnServerError(NetworkConnectionToClient conn, Exception exception) {} + /// Called on server when transport raises an exception. NetworkConnection may be null. + public virtual void OnServerError(NetworkConnectionToClient conn, TransportError error, string reason) + { +#pragma warning disable CS0618 + OnServerError(conn, new Exception(reason)); +#pragma warning restore CS0618 + } /// Called from ServerChangeScene immediately before SceneManager.LoadSceneAsync is executed public virtual void OnServerChangeScene(string newSceneName) {} @@ -1279,8 +1287,16 @@ public virtual void OnClientDisconnect() StopClient(); } - /// Called on client when transport raises an exception. + // DEPRECATED 2022-05-12 + [Obsolete("OnClientError(Exception) was changed to OnClientError(TransportError, string)")] public virtual void OnClientError(Exception exception) {} + /// Called on client when transport raises an exception. + public virtual void OnClientError(TransportError error, string reason) + { +#pragma warning disable CS0618 + OnClientError(new Exception(reason)); +#pragma warning restore CS0618 + } /// Called on clients when a servers tells the client it is no longer ready, e.g. when switching scenes. public virtual void OnClientNotReady() {} diff --git a/Assets/Mirror/Runtime/NetworkServer.cs b/Assets/Mirror/Runtime/NetworkServer.cs index 7180bdbf921..422164ec797 100644 --- a/Assets/Mirror/Runtime/NetworkServer.cs +++ b/Assets/Mirror/Runtime/NetworkServer.cs @@ -52,7 +52,7 @@ public static class NetworkServer // => public so that custom NetworkManagers can hook into it public static Action OnConnectedEvent; public static Action OnDisconnectedEvent; - public static Action OnErrorEvent; + public static Action OnErrorEvent; // initialization / shutdown /////////////////////////////////////////// static void Initialize() @@ -591,14 +591,14 @@ internal static void OnTransportDisconnected(int connectionId) } // transport errors are forwarded to high level - static void OnTransportError(int connectionId, Exception exception) + static void OnTransportError(int connectionId, TransportError error, string reason) { // transport errors will happen. logging a warning is enough. // make sure the user does not panic. - Debug.LogWarning($"Server Transport Error for connId={connectionId}: {exception}. This is fine."); + Debug.LogWarning($"Server Transport Error for connId={connectionId}: {error}: {reason}. This is fine."); // try get connection. passes null otherwise. connections.TryGetValue(connectionId, out NetworkConnectionToClient conn); - OnErrorEvent?.Invoke(conn, exception); + OnErrorEvent?.Invoke(conn, error, reason); } // message handlers //////////////////////////////////////////////////// diff --git a/Assets/Mirror/Runtime/Transport.cs b/Assets/Mirror/Runtime/Transport.cs index f831bf1d744..22b08739dcc 100644 --- a/Assets/Mirror/Runtime/Transport.cs +++ b/Assets/Mirror/Runtime/Transport.cs @@ -52,7 +52,7 @@ public abstract class Transport : MonoBehaviour public Action, int> OnClientDataSent; /// Called by Transport when the client encountered an error. - public Action OnClientError; + public Action OnClientError; /// Called by Transport when the client disconnected from the server. public Action OnClientDisconnected; @@ -73,7 +73,7 @@ public abstract class Transport : MonoBehaviour /// Called by Transport when a server's connection encountered a problem. /// If a Disconnect will also be raised, raise the Error first. - public Action OnServerError; + public Action OnServerError; /// Called by Transport when a client disconnected from the server. public Action OnServerDisconnected; diff --git a/Assets/Mirror/Runtime/TransportError.cs b/Assets/Mirror/Runtime/TransportError.cs new file mode 100644 index 00000000000..b45201529fe --- /dev/null +++ b/Assets/Mirror/Runtime/TransportError.cs @@ -0,0 +1,17 @@ +// Mirror transport error code enum. +// most transport implementations should use a subset of this, +// and then translate the transport error codes to mirror error codes. +namespace Mirror +{ + public enum TransportError : byte + { + DnsResolve, // failed to resolve a host name + Refused, // connection refused by other end. server full etc. + Timeout, // ping timeout or dead link + Congestion, // more messages than transport / network can process + InvalidReceive, // recv invalid packet (possibly intentional attack) + InvalidSend, // user tried to send invalid data + ConnectionClosed, // connection closed voluntarily or lost involuntarily + Unexpected // unexpected error / exception, requires fix. + } +} diff --git a/Assets/Mirror/Runtime/TransportError.cs.meta b/Assets/Mirror/Runtime/TransportError.cs.meta new file mode 100644 index 00000000000..3aa7db1823a --- /dev/null +++ b/Assets/Mirror/Runtime/TransportError.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ce162bdedd704db9b8c35d163f0c1d54 +timeCreated: 1652330240 \ No newline at end of file diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index 026e66b980c..5e9f530ea28 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -68,6 +68,21 @@ static int FromKcpChannel(KcpChannel channel) => static KcpChannel ToKcpChannel(int channel) => channel == Channels.Reliable ? KcpChannel.Reliable : KcpChannel.Unreliable; + TransportError ToTransportError(ErrorCode error) + { + return error switch + { + ErrorCode.DnsResolve => TransportError.DnsResolve, + ErrorCode.Timeout => TransportError.Timeout, + ErrorCode.Congestion => TransportError.Congestion, + ErrorCode.InvalidReceive => TransportError.InvalidReceive, + ErrorCode.InvalidSend => TransportError.InvalidSend, + ErrorCode.ConnectionClosed => TransportError.ConnectionClosed, + ErrorCode.Unexpected => TransportError.Unexpected, + _ => throw new InvalidCastException($"KCP: missing error translation for {error}") + }; + } + void Awake() { // logging @@ -91,12 +106,12 @@ void Awake() () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), () => OnClientDisconnected.Invoke(), - (error, reason) => OnClientError.Invoke(new Exception(reason))) + (error, reason) => OnClientError.Invoke(ToTransportError(error), reason)) : new KcpClient( () => OnClientConnected.Invoke(), (message, channel) => OnClientDataReceived.Invoke(message, FromKcpChannel(channel)), () => OnClientDisconnected.Invoke(), - (error, reason) => OnClientError.Invoke(new Exception(reason))); + (error, reason) => OnClientError.Invoke(ToTransportError(error), reason)); // server server = NonAlloc @@ -104,7 +119,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), - (connectionId, error, reason) => OnServerError.Invoke(connectionId, new Exception(reason)), + (connectionId, error, reason) => OnServerError.Invoke(connectionId, ToTransportError(error), reason), DualMode, NoDelay, Interval, @@ -119,7 +134,7 @@ void Awake() (connectionId) => OnServerConnected.Invoke(connectionId), (connectionId, message, channel) => OnServerDataReceived.Invoke(connectionId, message, FromKcpChannel(channel)), (connectionId) => OnServerDisconnected.Invoke(connectionId), - (connectionId, error, reason) => OnServerError.Invoke(connectionId, new Exception(reason)), + (connectionId, error, reason) => OnServerError.Invoke(connectionId, ToTransportError(error), reason), DualMode, NoDelay, Interval, diff --git a/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs b/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs index 0d0503d5cfb..86ca6e4f0c7 100644 --- a/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs +++ b/Assets/Mirror/Runtime/Transports/MultiplexTransport.cs @@ -185,9 +185,9 @@ void AddServerCallbacks() OnServerDataReceived.Invoke(FromBaseId(locali, baseConnectionId), data, channel); }; - transport.OnServerError = (baseConnectionId, error) => + transport.OnServerError = (baseConnectionId, error, reason) => { - OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error); + OnServerError.Invoke(FromBaseId(locali, baseConnectionId), error, reason); }; transport.OnServerDisconnected = baseConnectionId => { diff --git a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs index 66badc39af1..f6a3a81f08b 100644 --- a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs +++ b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/SimpleWebTransport.cs @@ -147,7 +147,7 @@ public override void ClientConnect(string hostname) client.onData += (ArraySegment data) => OnClientDataReceived.Invoke(data, Channels.Reliable); client.onError += (Exception e) => { - OnClientError.Invoke(e); + OnClientError.Invoke(TransportError.Unexpected, e.ToString()); ClientDisconnect(); }; @@ -212,7 +212,7 @@ public override void ServerStart() server.onConnect += OnServerConnected.Invoke; server.onDisconnect += OnServerDisconnected.Invoke; server.onData += (int connId, ArraySegment data) => OnServerDataReceived.Invoke(connId, data, Channels.Reliable); - server.onError += OnServerError.Invoke; + server.onError += (connId, exception) => OnServerError(connId, TransportError.Unexpected, exception.ToString()); SendLoopConfig.batchSend = batchSend || waitBeforeSend; SendLoopConfig.sleepBeforeSend = waitBeforeSend; diff --git a/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs index bb24fd8d9e8..0c70221d813 100644 --- a/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs +++ b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs @@ -256,23 +256,19 @@ public void TestClientDisconnectedCallback() [Test] public void TestClientErrorCallback() { - Exception exception = new InvalidDataException(); - int called = 0; - middleware.OnClientError = (e) => + middleware.OnClientError = (error, reason) => { called++; - Assert.That(e, Is.EqualTo(exception)); + Assert.That(error, Is.EqualTo(TransportError.Unexpected)); }; // connect to give callback to inner middleware.ClientConnect("localhost"); - inner.OnClientError.Invoke(exception); + inner.OnClientError.Invoke(TransportError.Unexpected, ""); Assert.That(called, Is.EqualTo(1)); - exception = new NullReferenceException(); - - inner.OnClientError.Invoke(exception); + inner.OnClientError.Invoke(TransportError.Unexpected, ""); Assert.That(called, Is.EqualTo(2)); } @@ -362,24 +358,20 @@ public void TestServerDisconnectedCallback(int id) [TestCase(19)] public void TestServerErrorCallback(int id) { - Exception exception = new InvalidDataException(); - int called = 0; - middleware.OnServerError = (i, e) => + middleware.OnServerError = (i, error, reason) => { called++; Assert.That(i, Is.EqualTo(id)); - Assert.That(e, Is.EqualTo(exception)); + Assert.That(error, Is.EqualTo(TransportError.Unexpected)); }; // start to give callback to inner middleware.ServerStart(); - inner.OnServerError.Invoke(id, exception); + inner.OnServerError.Invoke(id, TransportError.Unexpected, ""); Assert.That(called, Is.EqualTo(1)); - exception = new NullReferenceException(); - - inner.OnServerError.Invoke(id, exception); + inner.OnServerError.Invoke(id, TransportError.Unexpected, ""); Assert.That(called, Is.EqualTo(2)); } } From e27a1cd1e70abc3d87ad87cb488106182d24ec34 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 13 May 2022 09:39:22 -0400 Subject: [PATCH 111/824] Improved Chat example --- Assets/Mirror/Examples/Chat/Scenes/Main.unity | 407 ++++++++++++++++-- .../Chat/Scripts/ChatNetworkManager.cs | 3 + Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs | 74 ++-- .../Mirror/Examples/Chat/Scripts/LoginUI.cs | 8 +- Assets/Mirror/Examples/Chat/Scripts/Player.cs | 15 +- 5 files changed, 432 insertions(+), 75 deletions(-) diff --git a/Assets/Mirror/Examples/Chat/Scenes/Main.unity b/Assets/Mirror/Examples/Chat/Scenes/Main.unity index ef20276c541..27fd0f08790 100644 --- a/Assets/Mirror/Examples/Chat/Scenes/Main.unity +++ b/Assets/Mirror/Examples/Chat/Scenes/Main.unity @@ -43,7 +43,7 @@ RenderSettings: --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 12 m_GIWorkflowMode: 0 m_GISettings: serializedVersion: 2 @@ -98,7 +98,7 @@ LightmapSettings: m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 m_LightingDataAsset: {fileID: 0} - m_UseShadowmask: 1 + m_LightingSettings: {fileID: 212571282} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 @@ -118,6 +118,8 @@ NavMeshSettings: manualTileSize: 0 tileSize: 256 accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} @@ -171,8 +173,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.39215687} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -212,7 +215,7 @@ GameObject: - component: {fileID: 75860998} - component: {fileID: 75860997} m_Layer: 5 - m_Name: Chat + m_Name: ChatPanel m_TagString: ChatWindow m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -230,9 +233,10 @@ RectTransform: m_LocalScale: {x: 1, y: 1, z: 1} m_Children: - {fileID: 762534976} + - {fileID: 1731300362} + - {fileID: 1863915625} - {fileID: 1231350850} - {fileID: 1286463573} - - {fileID: 1863915625} m_Father: {fileID: 719573003} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} @@ -271,6 +275,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 0.92941177} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -345,6 +350,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -421,8 +427,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_Color: {r: 0, g: 0.5019608, b: 0, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -449,6 +456,67 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 107824418} m_CullTransparentMesh: 0 +--- !u!850595691 &212571282 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: Settings.lighting + serializedVersion: 3 + m_GIWorkflowMode: 0 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_BakeResolution: 40 + m_Padding: 2 + m_TextureCompression: 1 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_FinalGather: 0 + m_FinalGatherRayCount: 256 + m_FinalGatherFiltering: 1 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 500 + m_PVREnvironmentSampleCount: 500 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 2 + m_PVREnvironmentMIS: 0 + m_PVRFilteringMode: 2 + m_PVRDenoiserTypeDirect: 0 + m_PVRDenoiserTypeIndirect: 0 + m_PVRDenoiserTypeAO: 0 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 --- !u!1 &423302019 GameObject: m_ObjectHideFlags: 0 @@ -502,6 +570,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -551,6 +620,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -625,6 +695,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -703,6 +774,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -729,6 +801,85 @@ CanvasRenderer: m_PrefabAsset: {fileID: 0} m_GameObject: {fileID: 591385423} m_CullTransparentMesh: 0 +--- !u!1 &637644698 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 637644699} + - component: {fileID: 637644701} + - component: {fileID: 637644700} + m_Layer: 5 + m_Name: Text + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &637644699 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 637644698} + 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: 1731300362} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &637644700 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 637644698} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 5f7201a12d95ffc409449d95f23cf332, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 0, b: 0, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_FontData: + m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} + m_FontSize: 18 + m_FontStyle: 1 + m_BestFit: 0 + m_MinSize: 1 + m_MaxSize: 40 + m_Alignment: 4 + m_AlignByGeometry: 0 + m_RichText: 1 + m_HorizontalOverflow: 0 + m_VerticalOverflow: 0 + m_LineSpacing: 1 + m_Text: X +--- !u!222 &637644701 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 637644698} + m_CullTransparentMesh: 1 --- !u!1 &719572997 GameObject: m_ObjectHideFlags: 0 @@ -749,7 +900,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 0 + m_IsActive: 1 --- !u!114 &719572998 MonoBehaviour: m_ObjectHideFlags: 0 @@ -764,10 +915,10 @@ MonoBehaviour: m_EditorClassIdentifier: syncMode: 0 syncInterval: 0.1 - chatMessage: {fileID: 1231350851} chatHistory: {fileID: 827598817} scrollbar: {fileID: 423302021} - localPlayerName: + chatMessage: {fileID: 1231350851} + sendButton: {fileID: 1286463574} --- !u!114 &719572999 MonoBehaviour: m_ObjectHideFlags: 0 @@ -824,6 +975,7 @@ MonoBehaviour: m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 --- !u!223 &719573002 Canvas: m_ObjectHideFlags: 0 @@ -915,8 +1067,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.39215687} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -934,7 +1087,7 @@ MonoBehaviour: m_HorizontalOverflow: 0 m_VerticalOverflow: 0 m_LineSpacing: 1 - m_Text: Enter text... + m_Text: Enter text and press Enter or click Send... --- !u!222 &719610388 CanvasRenderer: m_ObjectHideFlags: 0 @@ -1014,7 +1167,7 @@ RectTransform: m_AnchorMin: {x: 0.5, y: 1} m_AnchorMax: {x: 0.5, y: 1} m_AnchoredPosition: {x: 0, y: -30} - m_SizeDelta: {x: 300, y: 40} + m_SizeDelta: {x: 1211, y: 40} m_Pivot: {x: 0.5, y: 0.5} --- !u!114 &762534977 MonoBehaviour: @@ -1029,8 +1182,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_Color: {r: 0, g: 0, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1038,11 +1192,11 @@ MonoBehaviour: m_FontData: m_Font: {fileID: 10102, guid: 0000000000000000e000000000000000, type: 0} m_FontSize: 30 - m_FontStyle: 0 + m_FontStyle: 1 m_BestFit: 0 m_MinSize: 3 m_MaxSize: 40 - m_Alignment: 1 + m_Alignment: 4 m_AlignByGeometry: 0 m_RichText: 1 m_HorizontalOverflow: 0 @@ -1092,9 +1246,9 @@ RectTransform: m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} + m_AnchorMax: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0} - m_SizeDelta: {x: -17, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0, y: 1} --- !u!114 &780870087 MonoBehaviour: @@ -1111,6 +1265,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1178,10 +1333,10 @@ RectTransform: m_Father: {fileID: 1335915325} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} - m_AnchorMin: {x: 0, y: 1} - m_AnchorMax: {x: 0, y: 1} - m_AnchoredPosition: {x: 556, y: -5} - m_SizeDelta: {x: 1102, y: 22} + m_AnchorMin: {x: 0, y: 0} + m_AnchorMax: {x: 0, y: 0} + m_AnchoredPosition: {x: 0, y: 0} + m_SizeDelta: {x: 0, y: 0} m_Pivot: {x: 0.5, y: 1} --- !u!114 &827598817 MonoBehaviour: @@ -1198,6 +1353,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 0 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1278,6 +1434,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1321,6 +1478,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 1783103024} + m_TargetAssemblyTypeName: m_MethodName: SetPlayername m_Mode: 0 m_Arguments: @@ -1332,6 +1490,7 @@ MonoBehaviour: m_BoolArgument: 0 m_CallState: 2 - m_Target: {fileID: 1453327789} + m_TargetAssemblyTypeName: m_MethodName: ToggleButtons m_Mode: 0 m_Arguments: @@ -1365,6 +1524,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1437,8 +1597,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_Color: {r: 0, g: 0.5019608, b: 0, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1521,6 +1682,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1564,6 +1726,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 1783103025} + m_TargetAssemblyTypeName: m_MethodName: SetHostname m_Mode: 0 m_Arguments: @@ -1597,6 +1760,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1672,6 +1836,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1702,6 +1867,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 1453327784} + m_TargetAssemblyTypeName: m_MethodName: SetActive m_Mode: 6 m_Arguments: @@ -1713,6 +1879,7 @@ MonoBehaviour: m_BoolArgument: 0 m_CallState: 2 - m_Target: {fileID: 1783103025} + m_TargetAssemblyTypeName: m_MethodName: StartClient m_Mode: 1 m_Arguments: @@ -1738,6 +1905,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1810,8 +1978,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.5} + m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 0.39215687} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -1871,7 +2040,7 @@ RectTransform: - {fileID: 719610386} - {fileID: 90143747} m_Father: {fileID: 75860996} - m_RootOrder: 1 + m_RootOrder: 3 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -1892,6 +2061,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -1932,6 +2102,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 719572998} + m_TargetAssemblyTypeName: m_MethodName: OnEndEdit m_Mode: 0 m_Arguments: @@ -1944,7 +2115,19 @@ MonoBehaviour: m_CallState: 2 m_OnValueChanged: m_PersistentCalls: - m_Calls: [] + m_Calls: + - m_Target: {fileID: 719572998} + m_TargetAssemblyTypeName: Mirror.Examples.Chat.ChatUI, Mirror.Examples + m_MethodName: ToggleButton + m_Mode: 0 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 m_CaretColor: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_CustomCaretColor: 0 m_SelectionColor: {r: 0.65882355, g: 0.80784315, b: 1, a: 0.7529412} @@ -1968,6 +2151,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2022,7 +2206,7 @@ RectTransform: m_Children: - {fileID: 1018203014} m_Father: {fileID: 75860996} - m_RootOrder: 2 + m_RootOrder: 4 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 1, y: 0} m_AnchorMax: {x: 1, y: 0} @@ -2043,6 +2227,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -2067,12 +2252,13 @@ MonoBehaviour: m_PressedTrigger: Pressed m_SelectedTrigger: Highlighted m_DisabledTrigger: Disabled - m_Interactable: 1 + m_Interactable: 0 m_TargetGraphic: {fileID: 1286463575} m_OnClick: m_PersistentCalls: m_Calls: - m_Target: {fileID: 719572998} + m_TargetAssemblyTypeName: m_MethodName: SendMessage m_Mode: 1 m_Arguments: @@ -2098,6 +2284,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2156,7 +2343,7 @@ RectTransform: m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} m_AnchoredPosition: {x: 1, y: 0} - m_SizeDelta: {x: -1, y: -595} + m_SizeDelta: {x: -1, y: 0} m_Pivot: {x: 0, y: 1} --- !u!114 &1335915326 MonoBehaviour: @@ -2197,6 +2384,7 @@ MonoBehaviour: m_ChildControlHeight: 1 m_ChildScaleWidth: 0 m_ChildScaleHeight: 0 + m_ReverseArrangement: 0 --- !u!1 &1453327784 GameObject: m_ObjectHideFlags: 0 @@ -2256,6 +2444,7 @@ MonoBehaviour: m_FallbackScreenDPI: 96 m_DefaultSpriteDPI: 96 m_DynamicPixelsPerUnit: 1 + m_PresetInfoIsWorld: 0 --- !u!223 &1453327787 Canvas: m_ObjectHideFlags: 0 @@ -2365,6 +2554,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2404,7 +2594,7 @@ GameObject: - component: {fileID: 1499096251} - component: {fileID: 1499096250} m_Layer: 5 - m_Name: Server + m_Name: LoginPanel m_TagString: Untagged m_Icon: {fileID: 0} m_NavMeshLayer: 0 @@ -2467,6 +2657,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 0.92941177} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2539,8 +2730,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_Color: {r: 0, g: 0.5019608, b: 0, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2600,6 +2792,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 0, b: 0, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2678,7 +2871,7 @@ RectTransform: m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} - m_AnchorMax: {x: 1, y: 1} + m_AnchorMax: {x: 0, y: 0} m_AnchoredPosition: {x: 0, y: 0} m_SizeDelta: {x: 20, y: 20} m_Pivot: {x: 0.5, y: 0.5} @@ -2697,6 +2890,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -2795,6 +2989,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &1667679451 @@ -2811,6 +3006,139 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 1 m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!1 &1731300361 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1731300362} + - component: {fileID: 1731300365} + - component: {fileID: 1731300364} + - component: {fileID: 1731300363} + m_Layer: 5 + m_Name: ExitButton + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!224 &1731300362 +RectTransform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1731300361} + 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: 637644699} + m_Father: {fileID: 75860996} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} + m_AnchorMin: {x: 1, y: 1} + m_AnchorMax: {x: 1, y: 1} + m_AnchoredPosition: {x: -20, y: -20} + m_SizeDelta: {x: 25, y: 25} + m_Pivot: {x: 0.5, y: 0.5} +--- !u!114 &1731300363 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1731300361} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Navigation: + m_Mode: 3 + m_WrapAround: 0 + m_SelectOnUp: {fileID: 0} + m_SelectOnDown: {fileID: 0} + m_SelectOnLeft: {fileID: 0} + m_SelectOnRight: {fileID: 0} + m_Transition: 1 + m_Colors: + m_NormalColor: {r: 1, g: 1, b: 1, a: 1} + m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1} + m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1} + m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608} + m_ColorMultiplier: 1 + m_FadeDuration: 0.1 + m_SpriteState: + m_HighlightedSprite: {fileID: 0} + m_PressedSprite: {fileID: 0} + m_SelectedSprite: {fileID: 0} + m_DisabledSprite: {fileID: 0} + m_AnimationTriggers: + m_NormalTrigger: Normal + m_HighlightedTrigger: Highlighted + m_PressedTrigger: Pressed + m_SelectedTrigger: Selected + m_DisabledTrigger: Disabled + m_Interactable: 1 + m_TargetGraphic: {fileID: 1731300364} + m_OnClick: + m_PersistentCalls: + m_Calls: + - m_Target: {fileID: 719572998} + m_TargetAssemblyTypeName: Mirror.Examples.Chat.ChatUI, Mirror.Examples + m_MethodName: ExitButtonClick + m_Mode: 1 + m_Arguments: + m_ObjectArgument: {fileID: 0} + m_ObjectArgumentAssemblyTypeName: UnityEngine.Object, UnityEngine + m_IntArgument: 0 + m_FloatArgument: 0 + m_StringArgument: + m_BoolArgument: 0 + m_CallState: 2 +--- !u!114 &1731300364 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1731300361} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3} + m_Name: + m_EditorClassIdentifier: + m_Material: {fileID: 0} + m_Color: {r: 1, g: 1, b: 1, a: 1} + m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} + m_Maskable: 1 + m_OnCullStateChanged: + m_PersistentCalls: + m_Calls: [] + m_Sprite: {fileID: 10905, guid: 0000000000000000f000000000000000, type: 0} + m_Type: 1 + m_PreserveAspect: 0 + m_FillCenter: 1 + m_FillMethod: 4 + m_FillAmount: 1 + m_FillClockwise: 1 + m_FillOrigin: 0 + m_UseSpriteMesh: 0 + m_PixelsPerUnitMultiplier: 1 +--- !u!222 &1731300365 +CanvasRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1731300361} + m_CullTransparentMesh: 1 --- !u!1 &1783103022 GameObject: m_ObjectHideFlags: 0 @@ -2853,6 +3181,7 @@ MonoBehaviour: ReceiveWindowSize: 4096 MaxRetransmit: 40 NonAlloc: 1 + MaximizeSendReceiveBuffersToOSLimit: 1 ReliableMaxMessageSize: 298449 UnreliableMaxMessageSize: 1199 debugLog: 0 @@ -2889,7 +3218,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d0cd72391a563461f88eb3ddf120efef, type: 3} m_Name: m_EditorClassIdentifier: - dontDestroyOnLoad: 1 + dontDestroyOnLoad: 0 runInBackground: 1 autoStartServerBuild: 1 serverTickRate: 30 @@ -2899,8 +3228,7 @@ MonoBehaviour: networkAddress: localhost maxConnections: 100 authenticator: {fileID: 1783103024} - playerPrefab: {fileID: 5075528875289742095, guid: e5905ffa27de84009b346b49d518ba03, - type: 3} + playerPrefab: {fileID: 5075528875289742095, guid: e5905ffa27de84009b346b49d518ba03, type: 3} autoCreatePlayer: 1 playerSpawnMethod: 0 spawnPrefabs: [] @@ -2951,7 +3279,7 @@ RectTransform: - {fileID: 780870086} - {fileID: 423302020} m_Father: {fileID: 75860996} - m_RootOrder: 3 + m_RootOrder: 2 m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} m_AnchorMin: {x: 0, y: 0} m_AnchorMax: {x: 1, y: 1} @@ -2973,6 +3301,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 0.392} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -3152,6 +3481,7 @@ MonoBehaviour: m_EditorClassIdentifier: m_Navigation: m_Mode: 3 + m_WrapAround: 0 m_SelectOnUp: {fileID: 0} m_SelectOnDown: {fileID: 0} m_SelectOnLeft: {fileID: 0} @@ -3182,6 +3512,7 @@ MonoBehaviour: m_PersistentCalls: m_Calls: - m_Target: {fileID: 1453327784} + m_TargetAssemblyTypeName: m_MethodName: SetActive m_Mode: 6 m_Arguments: @@ -3193,6 +3524,7 @@ MonoBehaviour: m_BoolArgument: 0 m_CallState: 2 - m_Target: {fileID: 1783103025} + m_TargetAssemblyTypeName: m_MethodName: StartHost m_Mode: 1 m_Arguments: @@ -3218,6 +3550,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -3290,8 +3623,9 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: m_Material: {fileID: 0} - m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} + m_Color: {r: 0, g: 0, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: @@ -3436,6 +3770,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 0.19607843, g: 0.19607843, b: 0.19607843, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatNetworkManager.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatNetworkManager.cs index d593003ffda..be9d43a911b 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatNetworkManager.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatNetworkManager.cs @@ -22,6 +22,9 @@ public override void OnServerDisconnect(NetworkConnectionToClient conn) if (conn.authenticationData != null) Player.playerNames.Remove((string)conn.authenticationData); + // remove connection from Dictionary of conn > names + ChatUI.connNames.Remove(conn); + base.OnServerDisconnect(conn); } diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs index 0eaddf62820..f64864e3ddc 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs @@ -8,24 +8,29 @@ namespace Mirror.Examples.Chat public class ChatUI : NetworkBehaviour { [Header("UI Elements")] - public InputField chatMessage; - public Text chatHistory; - public Scrollbar scrollbar; + [SerializeField] Text chatHistory; + [SerializeField] Scrollbar scrollbar; + [SerializeField] InputField chatMessage; + [SerializeField] Button sendButton; - [Header("Diagnostic - Do Not Edit")] - public string localPlayerName; + // This is only set on client to the name of the local player + internal static string localPlayerName; - Dictionary connNames = new Dictionary(); + // Server-only cross-reference of connections to player names + internal static readonly Dictionary connNames = new Dictionary(); - public static ChatUI instance; + public override void OnStartServer() + { + connNames.Clear(); + } - void Awake() + public override void OnStartClient() { - instance = this; + chatHistory.text = ""; } [Command(requiresAuthority = false)] - public void CmdSend(string message, NetworkConnectionToClient sender = null) + void CmdSend(string message, NetworkConnectionToClient sender = null) { if (!connNames.ContainsKey(sender)) connNames.Add(sender, sender.identity.GetComponent().playerName); @@ -35,7 +40,7 @@ public void CmdSend(string message, NetworkConnectionToClient sender = null) } [ClientRpc] - public void RpcReceive(string playerName, string message) + void RpcReceive(string playerName, string message) { string prettyMessage = playerName == localPlayerName ? $"{playerName}: {message}" : @@ -43,6 +48,36 @@ public void RpcReceive(string playerName, string message) AppendMessage(prettyMessage); } + void AppendMessage(string message) + { + StartCoroutine(AppendAndScroll(message)); + } + + IEnumerator AppendAndScroll(string message) + { + chatHistory.text += message + "\n"; + + // it takes 2 frames for the UI to update ?!?! + yield return null; + yield return null; + + // slam the scrollbar down + scrollbar.value = 0; + } + + public void ExitButtonClick() + { + // StopHost calls both StopClient and StopServer + // StopServer does nothing on remote clients + NetworkManager.singleton.StopHost(); + } + + // Called by UI element MessageField.OnValueChanged + public void ToggleButton(string input) + { + sendButton.interactable = !string.IsNullOrWhiteSpace(input); + } + // Called by UI element MessageField.OnEndEdit public void OnEndEdit(string input) { @@ -60,22 +95,5 @@ public void SendMessage() chatMessage.ActivateInputField(); } } - - internal void AppendMessage(string message) - { - StartCoroutine(AppendAndScroll(message)); - } - - IEnumerator AppendAndScroll(string message) - { - chatHistory.text += message + "\n"; - - // it takes 2 frames for the UI to update ?!?! - yield return null; - yield return null; - - // slam the scrollbar down - scrollbar.value = 0; - } } } diff --git a/Assets/Mirror/Examples/Chat/Scripts/LoginUI.cs b/Assets/Mirror/Examples/Chat/Scripts/LoginUI.cs index a52f90aeeae..a42f9849f1a 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/LoginUI.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/LoginUI.cs @@ -6,10 +6,10 @@ namespace Mirror.Examples.Chat public class LoginUI : MonoBehaviour { [Header("UI Elements")] - public InputField usernameInput; - public Button hostButton; - public Button clientButton; - public Text errorText; + [SerializeField] internal InputField usernameInput; + [SerializeField] internal Button hostButton; + [SerializeField] internal Button clientButton; + [SerializeField] internal Text errorText; public static LoginUI instance; diff --git a/Assets/Mirror/Examples/Chat/Scripts/Player.cs b/Assets/Mirror/Examples/Chat/Scripts/Player.cs index b26fe0f936a..253c004a07d 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/Player.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/Player.cs @@ -1,13 +1,14 @@ using System.Collections.Generic; +using UnityEngine; namespace Mirror.Examples.Chat { public class Player : NetworkBehaviour { - public static readonly HashSet playerNames = new HashSet(); + internal static readonly HashSet playerNames = new HashSet(); - [SyncVar(hook = nameof(OnPlayerNameChanged))] - public string playerName; + [SerializeField, SyncVar] + internal string playerName; // RuntimeInitializeOnLoadMethod -> fast playmode without domain reload [UnityEngine.RuntimeInitializeOnLoadMethod] @@ -16,14 +17,14 @@ static void ResetStatics() playerNames.Clear(); } - void OnPlayerNameChanged(string _, string newName) + public override void OnStartServer() { - ChatUI.instance.localPlayerName = playerName; + playerName = (string)connectionToClient.authenticationData; } - public override void OnStartServer() + public override void OnStartLocalPlayer() { - playerName = (string)connectionToClient.authenticationData; + ChatUI.localPlayerName = playerName; } } } From 6ac03a02d67101632989a400a196176f0c189d5a Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 13 May 2022 10:09:49 -0400 Subject: [PATCH 112/824] Improved Chat example --- Assets/Mirror/Examples/Chat/Scenes/Main.unity | 2 +- Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Examples/Chat/Scenes/Main.unity b/Assets/Mirror/Examples/Chat/Scenes/Main.unity index 27fd0f08790..b138484e02b 100644 --- a/Assets/Mirror/Examples/Chat/Scenes/Main.unity +++ b/Assets/Mirror/Examples/Chat/Scenes/Main.unity @@ -3091,7 +3091,7 @@ MonoBehaviour: m_Calls: - m_Target: {fileID: 719572998} m_TargetAssemblyTypeName: Mirror.Examples.Chat.ChatUI, Mirror.Examples - m_MethodName: ExitButtonClick + m_MethodName: ExitButtonOnClick m_Mode: 1 m_Arguments: m_ObjectArgument: {fileID: 0} diff --git a/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs b/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs index f64864e3ddc..e8c8d0f838d 100644 --- a/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs +++ b/Assets/Mirror/Examples/Chat/Scripts/ChatUI.cs @@ -65,7 +65,8 @@ IEnumerator AppendAndScroll(string message) scrollbar.value = 0; } - public void ExitButtonClick() + // Called by UI element ExitButton.OnClick + public void ExitButtonOnClick() { // StopHost calls both StopClient and StopServer // StopServer does nothing on remote clients From e0e098531ec08bffe2ca0f2bed17bbd61867f2a8 Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 13 May 2022 10:32:44 -0400 Subject: [PATCH 113/824] fixed NetworkManager Script Tempate - broken by PR #3158 --- .../50-Mirror__Network Manager-NewNetworkManager.cs.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/ScriptTemplates/50-Mirror__Network Manager-NewNetworkManager.cs.txt b/Assets/ScriptTemplates/50-Mirror__Network Manager-NewNetworkManager.cs.txt index 7db37569aa1..b2a4dec8abf 100644 --- a/Assets/ScriptTemplates/50-Mirror__Network Manager-NewNetworkManager.cs.txt +++ b/Assets/ScriptTemplates/50-Mirror__Network Manager-NewNetworkManager.cs.txt @@ -169,7 +169,7 @@ public class #SCRIPTNAME# : NetworkManager /// /// Connection of the client...may be null /// Exception thrown from the Transport. - public override void OnServerError(NetworkConnectionToClient conn, Exception exception) { } + public override void OnServerError(NetworkConnectionToClient conn, TransportError transportError, string message) { } #endregion @@ -203,7 +203,7 @@ public class #SCRIPTNAME# : NetworkManager /// Called on client when transport raises an exception. /// /// Exception thrown from the Transport. - public override void OnClientError(Exception exception) { } + public override void OnClientError(TransportError transportError, string message) { } #endregion From 4c52c1316022aaa418797d5356ab26314f8f74dd Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 14 May 2022 12:10:27 -0400 Subject: [PATCH 114/824] Removed CanvasRenderer --- .../Examples/AdditiveLevels/Prefabs/Portal.prefab | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab b/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab index 48e24191ce1..0813663bac1 100644 --- a/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab +++ b/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab @@ -59,6 +59,7 @@ MeshRenderer: m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 + m_RayTraceProcedural: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -83,6 +84,7 @@ MeshRenderer: m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} --- !u!136 &1098173225717622924 CapsuleCollider: m_ObjectHideFlags: 0 @@ -131,6 +133,7 @@ MonoBehaviour: destinationScene: startPosition: {x: 0, y: 0, z: 0} label: {fileID: 5446595135713311426} + labelText: --- !u!1 &5961932215084527574 GameObject: m_ObjectHideFlags: 0 @@ -141,7 +144,6 @@ GameObject: m_Component: - component: {fileID: 1355348187805494562} - component: {fileID: 5428053421152709616} - - component: {fileID: 4525528713057871397} - component: {fileID: 5446595135713311426} - component: {fileID: 3243959486819493908} m_Layer: 9 @@ -185,6 +187,7 @@ MeshRenderer: m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 + m_RayTraceProcedural: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -209,14 +212,7 @@ MeshRenderer: m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 ---- !u!222 &4525528713057871397 -CanvasRenderer: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 5961932215084527574} - m_CullTransparentMesh: 0 + m_AdditionalVertexStreams: {fileID: 0} --- !u!114 &5446595135713311426 MonoBehaviour: m_ObjectHideFlags: 0 @@ -232,6 +228,7 @@ MonoBehaviour: m_Material: {fileID: 0} m_Color: {r: 1, g: 1, b: 1, a: 1} m_RaycastTarget: 1 + m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0} m_Maskable: 1 m_OnCullStateChanged: m_PersistentCalls: From 8139c28ff0def1759ddebed0e315b84a40e5ed7c Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 14 May 2022 12:19:55 -0400 Subject: [PATCH 115/824] Online scene lighting --- .../AdditiveLevels/Scenes/Online.unity | 17 +++-- .../Scenes/OnlineSettings.lighting | 63 +++++++++++++++++++ .../Scenes/OnlineSettings.lighting.meta | 8 +++ 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting create mode 100644 Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting.meta diff --git a/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity b/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity index 4b02b95a14f..6843a9a1203 100644 --- a/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity +++ b/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity @@ -38,12 +38,12 @@ RenderSettings: m_ReflectionIntensity: 1 m_CustomReflection: {fileID: 0} m_Sun: {fileID: 0} - m_IndirectSpecularColor: {r: 0.043674428, g: 0.044857424, b: 0.059592403, a: 1} + m_IndirectSpecularColor: {r: 0.2312071, g: 0.2344121, b: 0.27077314, a: 1} m_UseRadianceAmbientProbe: 0 --- !u!157 &3 LightmapSettings: m_ObjectHideFlags: 0 - serializedVersion: 11 + serializedVersion: 12 m_GIWorkflowMode: 1 m_GISettings: serializedVersion: 2 @@ -97,9 +97,8 @@ LightmapSettings: m_ExportTrainingData: 0 m_TrainingDataDestination: TrainingData m_LightProbeSampleCountMultiplier: 4 - m_LightingDataAsset: {fileID: 112000002, guid: 7868b86bff1943140826320e66c66468, - type: 2} - m_UseShadowmask: 1 + m_LightingDataAsset: {fileID: 112000002, guid: 7868b86bff1943140826320e66c66468, type: 2} + m_LightingSettings: {fileID: 4890085278179872738, guid: b14d22a581510774fa4c3d6017f61944, type: 2} --- !u!196 &4 NavMeshSettings: serializedVersion: 2 @@ -119,6 +118,8 @@ NavMeshSettings: manualTileSize: 0 tileSize: 256 accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 debug: m_Flags: 0 m_NavMeshData: {fileID: 0} @@ -198,6 +199,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &203151411 @@ -318,7 +320,7 @@ GameObject: m_Icon: {fileID: 0} m_NavMeshLayer: 0 m_StaticEditorFlags: 0 - m_IsActive: 1 + m_IsActive: 0 --- !u!65 &499382193 BoxCollider: m_ObjectHideFlags: 0 @@ -483,6 +485,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &1110529629 @@ -575,6 +578,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &1309024826 @@ -667,6 +671,7 @@ Light: m_UseColorTemperature: 0 m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 m_ShadowRadius: 0 m_ShadowAngle: 0 --- !u!4 &1606864535 diff --git a/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting b/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting new file mode 100644 index 00000000000..fd679167481 --- /dev/null +++ b/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting @@ -0,0 +1,63 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!850595691 &4890085278179872738 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: OnlineSettings + serializedVersion: 3 + m_GIWorkflowMode: 1 + m_EnableBakedLightmaps: 0 + m_EnableRealtimeLightmaps: 0 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_BakeResolution: 40 + m_Padding: 2 + m_TextureCompression: 1 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_FinalGather: 0 + m_FinalGatherRayCount: 256 + m_FinalGatherFiltering: 1 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 2 + m_PVREnvironmentMIS: 1 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 diff --git a/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting.meta b/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting.meta new file mode 100644 index 00000000000..96b73eb3f81 --- /dev/null +++ b/Assets/Mirror/Examples/AdditiveLevels/Scenes/OnlineSettings.lighting.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b14d22a581510774fa4c3d6017f61944 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 4890085278179872738 + userData: + assetBundleName: + assetBundleVariant: From 50e6bb11016257c505c39380b6aa7f957bb6048e Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Sat, 14 May 2022 12:20:30 -0400 Subject: [PATCH 116/824] Online scene removed Safety plane --- .../AdditiveLevels/Scenes/Online.unity | 110 ------------------ 1 file changed, 110 deletions(-) diff --git a/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity b/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity index 6843a9a1203..3b49a89753c 100644 --- a/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity +++ b/Assets/Mirror/Examples/AdditiveLevels/Scenes/Online.unity @@ -299,116 +299,6 @@ Transform: m_Father: {fileID: 0} m_RootOrder: 0 m_LocalEulerAnglesHint: {x: 0, y: -30.84, z: 0} ---- !u!1 &499382192 -GameObject: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - serializedVersion: 6 - m_Component: - - component: {fileID: 499382200} - - component: {fileID: 499382199} - - component: {fileID: 499382197} - - component: {fileID: 499382196} - - component: {fileID: 499382195} - - component: {fileID: 499382194} - - component: {fileID: 499382193} - m_Layer: 0 - m_Name: SafetyPlane - m_TagString: Untagged - m_Icon: {fileID: 0} - m_NavMeshLayer: 0 - m_StaticEditorFlags: 0 - m_IsActive: 0 ---- !u!65 &499382193 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 10, y: 10, z: 0.1} - m_Center: {x: 0, y: 5, z: 5} ---- !u!65 &499382194 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 0.1, y: 10, z: 10} - m_Center: {x: 5, y: 5, z: 0} ---- !u!65 &499382195 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 0.1, y: 10, z: 10} - m_Center: {x: -5, y: 5, z: 0} ---- !u!65 &499382196 -BoxCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 2 - m_Size: {x: 10, y: 10, z: 0.1} - m_Center: {x: 0, y: 5, z: -5} ---- !u!64 &499382197 -MeshCollider: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Material: {fileID: 0} - m_IsTrigger: 0 - m_Enabled: 1 - serializedVersion: 4 - m_Convex: 0 - m_CookingOptions: 30 - m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} ---- !u!33 &499382199 -MeshFilter: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_Mesh: {fileID: 10209, guid: 0000000000000000e000000000000000, type: 0} ---- !u!4 &499382200 -Transform: - m_ObjectHideFlags: 0 - m_CorrespondingSourceObject: {fileID: 0} - m_PrefabInstance: {fileID: 0} - m_PrefabAsset: {fileID: 0} - m_GameObject: {fileID: 499382192} - m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} - m_LocalPosition: {x: 0, y: 0, z: 0} - m_LocalScale: {x: 3, y: 1, z: 3} - m_Children: [] - m_Father: {fileID: 0} - m_RootOrder: 6 - m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} --- !u!1 &1110529627 GameObject: m_ObjectHideFlags: 0 From adb66b427c42d4a5ae43f2d77913e74ec9fececc Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Wed, 25 May 2022 14:33:23 -0400 Subject: [PATCH 117/824] NetworkBehaviour: Improved logging (#3165) * NetworkBehaviour: Improved logging * NetworkBehaviour: Improved logging --- Assets/Mirror/Runtime/NetworkBehaviour.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Assets/Mirror/Runtime/NetworkBehaviour.cs b/Assets/Mirror/Runtime/NetworkBehaviour.cs index ba4a9d921e2..d58bf39e680 100644 --- a/Assets/Mirror/Runtime/NetworkBehaviour.cs +++ b/Assets/Mirror/Runtime/NetworkBehaviour.cs @@ -183,7 +183,7 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer // to avoid Wrapper functions. a lot of people requested this. if (!NetworkClient.active) { - Debug.LogError($"Command Function {functionFullName} called without an active client."); + Debug.LogError($"Command Function {functionFullName} called on {name} without an active client.", gameObject); return; } @@ -195,14 +195,14 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer // or client may have been set NotReady intentionally, so // only warn if on the reliable channel. if (channelId == Channels.Reliable) - Debug.LogWarning("Send command attempted while NetworkClient is not ready.\nThis may be ignored if client intentionally set NotReady."); + Debug.LogWarning($"Command Function {functionFullName} called on {name} while NetworkClient is not ready.\nThis may be ignored if client intentionally set NotReady.", gameObject); return; } // local players can always send commands, regardless of authority, other objects must have authority. if (!(!requiresAuthority || isLocalPlayer || hasAuthority)) { - Debug.LogWarning($"Trying to send command for object without authority. {functionFullName}"); + Debug.LogWarning($"Command Function {functionFullName} called on {name} without authority.", gameObject); return; } @@ -213,7 +213,7 @@ protected void SendCommandInternal(string functionFullName, NetworkWriter writer // => see also: https://github.com/vis2k/Mirror/issues/2629 if (NetworkClient.connection == null) { - Debug.LogError("Send command attempted with no client running."); + Debug.LogError($"Command Function {functionFullName} called on {name} with no client running.", gameObject); return; } @@ -242,14 +242,14 @@ protected void SendRPCInternal(string functionFullName, NetworkWriter writer, in // this was in Weaver before if (!NetworkServer.active) { - Debug.LogError($"RPC Function {functionFullName} called on Client."); + Debug.LogError($"RPC Function {functionFullName} called on Client.", gameObject); return; } // This cannot use NetworkServer.active, as that is not specific to this object. if (!isServer) { - Debug.LogWarning($"ClientRpc {functionFullName} called on un-spawned object: {name}"); + Debug.LogWarning($"ClientRpc {functionFullName} called on un-spawned object: {name}", gameObject); return; } @@ -272,13 +272,13 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull { if (!NetworkServer.active) { - Debug.LogError($"TargetRPC {functionFullName} called when server not active"); + Debug.LogError($"TargetRPC {functionFullName} called on {name} when server not active", gameObject); return; } if (!isServer) { - Debug.LogWarning($"TargetRpc {functionFullName} called on {name} but that object has not been spawned or has been unspawned"); + Debug.LogWarning($"TargetRpc {functionFullName} called on {name} but that object has not been spawned or has been unspawned", gameObject); return; } @@ -291,13 +291,13 @@ protected void SendTargetRPCInternal(NetworkConnection conn, string functionFull // if still null if (conn is null) { - Debug.LogError($"TargetRPC {functionFullName} was given a null connection, make sure the object has an owner or you pass in the target connection"); + Debug.LogError($"TargetRPC {functionFullName} was given a null connection, make sure the object {name} has an owner or you pass in the target connection", gameObject); return; } if (!(conn is NetworkConnectionToClient)) { - Debug.LogError($"TargetRPC {functionFullName} requires a NetworkConnectionToClient but was given {conn.GetType().Name}"); + Debug.LogError($"TargetRPC {functionFullName} called on {name} requires a NetworkConnectionToClient but was given {conn.GetType().Name}", gameObject); return; } From bb8412d5e773d81202ef03458dfdc89848a06e45 Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 26 May 2022 23:32:08 +0700 Subject: [PATCH 118/824] NetworkIdentity Serialization: explain write index --- Assets/Mirror/Runtime/NetworkIdentity.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/NetworkIdentity.cs b/Assets/Mirror/Runtime/NetworkIdentity.cs index 66d7ffdf577..0e37b2b20bd 100644 --- a/Assets/Mirror/Runtime/NetworkIdentity.cs +++ b/Assets/Mirror/Runtime/NetworkIdentity.cs @@ -930,7 +930,9 @@ internal void OnSerializeAllSafely(bool initialState, NetworkWriter ownerWriter, // observers writer too int startPosition = ownerWriter.Position; - // write index as byte [0..255] + // write index as byte [0..255]. + // necessary because deserialize may only get data for some + // components because not dirty, not owner, etc. ownerWriter.WriteByte((byte)i); // serialize into ownerWriter first From f65b9cadbee82cbf588ebcf8a8e9b8563db11699 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 27 May 2022 22:45:12 +0700 Subject: [PATCH 119/824] Write/ReadBlittable explanation why private --- Assets/Mirror/Runtime/NetworkReader.cs | 17 +++++++++++++++++ Assets/Mirror/Runtime/NetworkWriter.cs | 17 +++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/Assets/Mirror/Runtime/NetworkReader.cs b/Assets/Mirror/Runtime/NetworkReader.cs index d344b60f71e..2e1e8833a82 100644 --- a/Assets/Mirror/Runtime/NetworkReader.cs +++ b/Assets/Mirror/Runtime/NetworkReader.cs @@ -72,6 +72,23 @@ public void SetBuffer(ArraySegment segment) // ReadBlittable assumes same endianness for server & client. // All Unity 2018+ platforms are little endian. // + // This is not safe to expose to random structs. + // * StructLayout.Sequential is the default, which is safe. + // if the struct contains a reference type, it is converted to Auto. + // but since all structs here are unmanaged blittable, it's safe. + // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8#system-runtime-interopservices-layoutkind-sequential + // * StructLayout.Pack depends on CPU word size. + // this may be different 4 or 8 on some ARM systems, etc. + // this is not safe, and would cause bytes/shorts etc. to be padded. + // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-6.0 + // * If we force pack all to '1', they would have no padding which is + // great for bandwidth. but on some android systems, CPU can't read + // unaligned memory. + // see also: https://github.com/vis2k/Mirror/issues/3044 + // * The only option would be to force explicit layout with multiples + // of word size. but this requires lots of weaver checking and is + // still questionable (IL2CPP etc.). + // // Note: inlining ReadBlittable is enough. don't inline ReadInt etc. // we don't want ReadBlittable to be copied in place everywhere. [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/Assets/Mirror/Runtime/NetworkWriter.cs b/Assets/Mirror/Runtime/NetworkWriter.cs index 2a1c43dfed8..6663b095889 100644 --- a/Assets/Mirror/Runtime/NetworkWriter.cs +++ b/Assets/Mirror/Runtime/NetworkWriter.cs @@ -83,6 +83,23 @@ public ArraySegment ToArraySegment() // All Unity 2018+ platforms are little endian. // => run NetworkWriterTests.BlittableOnThisPlatform() to verify! // + // This is not safe to expose to random structs. + // * StructLayout.Sequential is the default, which is safe. + // if the struct contains a reference type, it is converted to Auto. + // but since all structs here are unmanaged blittable, it's safe. + // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.layoutkind?view=netframework-4.8#system-runtime-interopservices-layoutkind-sequential + // * StructLayout.Pack depends on CPU word size. + // this may be different 4 or 8 on some ARM systems, etc. + // this is not safe, and would cause bytes/shorts etc. to be padded. + // see also: https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?view=net-6.0 + // * If we force pack all to '1', they would have no padding which is + // great for bandwidth. but on some android systems, CPU can't read + // unaligned memory. + // see also: https://github.com/vis2k/Mirror/issues/3044 + // * The only option would be to force explicit layout with multiples + // of word size. but this requires lots of weaver checking and is + // still questionable (IL2CPP etc.). + // // Note: inlining WriteBlittable is enough. don't inline WriteInt etc. // we don't want WriteBlittable to be copied in place everywhere. [MethodImpl(MethodImplOptions.AggressiveInlining)] From 77259e1c317947012f2c1f396a710da90a5e229f Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Tue, 31 May 2022 08:28:52 -0400 Subject: [PATCH 120/824] change label text --- Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab b/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab index 0813663bac1..36fc9be3c58 100644 --- a/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab +++ b/Assets/Mirror/Examples/AdditiveLevels/Prefabs/Portal.prefab @@ -233,7 +233,7 @@ MonoBehaviour: m_OnCullStateChanged: m_PersistentCalls: m_Calls: [] - m_text: Sublevel2 + m_text: Sub Level 2 m_isRightToLeft: 0 m_fontAsset: {fileID: 11400000, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} m_sharedMaterial: {fileID: 2180264, guid: 8f586378b4e144a9851e7b34d9b748ee, type: 2} From e04293f980466882ee19b11bf467409d8eb007c4 Mon Sep 17 00:00:00 2001 From: vis2k Date: Fri, 3 Jun 2022 16:36:23 +0700 Subject: [PATCH 121/824] breaking: SnapshotInterpolation.Compute(): add 'out catchup' for debugging --- .../NetworkTransformBase.cs | 8 ++++--- .../SnapshotInterpolation.cs | 8 ++++--- .../Editor/SnapshotInterpolationTests.cs | 22 +++++++++---------- Packages/manifest.json | 12 +++++----- Packages/packages-lock.json | 16 +++++++------- ProjectSettings/ProjectVersion.txt | 4 ++-- UserSettings/EditorUserSettings.asset | 7 ++++++ 7 files changed, 44 insertions(+), 33 deletions(-) diff --git a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs index 839064c8855..15622805be8 100644 --- a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs @@ -398,7 +398,8 @@ void UpdateServer() bufferTime, serverBuffer, catchupThreshold, catchupMultiplier, Interpolate, - out NTSnapshot computed)) + out NTSnapshot computed, + out _)) { NTSnapshot start = serverBuffer.Values[0]; NTSnapshot goal = serverBuffer.Values[1]; @@ -487,7 +488,8 @@ void UpdateClient() bufferTime, clientBuffer, catchupThreshold, catchupMultiplier, Interpolate, - out NTSnapshot computed)) + out NTSnapshot computed, + out _)) { NTSnapshot start = clientBuffer.Values[0]; NTSnapshot goal = clientBuffer.Values[1]; @@ -645,7 +647,7 @@ protected virtual void OnValidate() // buffer limit should be at least multiplier to have enough in there bufferSizeLimit = Mathf.Max(bufferTimeMultiplier, bufferSizeLimit); } - + public override bool OnSerialize(NetworkWriter writer, bool initialState) { // sync target component's position on spawn. diff --git a/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs b/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs index bc685d77502..ab836a8c652 100644 --- a/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs +++ b/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs @@ -151,6 +151,7 @@ public static void GetFirstSecondAndDelta(SortedList buffer, out T // => needs to be Func instead of a function in the Snapshot // interface because that would require boxing. // => make sure to only allocate that function once. + // out catchup: useful for debugging only. // // returns // 'true' if it spit out a snapshot to apply. @@ -164,7 +165,8 @@ public static bool Compute( int catchupThreshold, float catchupMultiplier, Func Interpolate, - out T computed) + out T computed, + out double catchup) where T : Snapshot { // we buffer snapshots for 'bufferTime' @@ -183,7 +185,7 @@ public static bool Compute( // with high latency // -> at any given time, we are interpolating from snapshot A to B // => seems like A.timestamp += deltaTime is a good way to do it - + catchup = 0; computed = default; //Debug.Log($"{name} snapshotbuffer={buffer.Count}"); @@ -201,7 +203,7 @@ public static bool Compute( // // if '0' catchup then we multiply by '1', which changes nothing. // (faster branch prediction) - double catchup = CalculateCatchup(buffer, catchupThreshold, catchupMultiplier); + catchup = CalculateCatchup(buffer, catchupThreshold, catchupMultiplier); deltaTime *= (1 + catchup); // interpolationTime starts at 0 and we add deltaTime to move diff --git a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs index 1ba07b1f953..b08776e37a5 100644 --- a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs +++ b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs @@ -240,7 +240,7 @@ public void Compute_Step1_DefaultDoesNothing() float bufferTime = 0; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should not spit out any snapshot to apply Assert.That(result, Is.False); @@ -273,7 +273,7 @@ public void Compute_Step3_WaitsUntilBufferTime() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should not spit out any snapshot to apply Assert.That(result, Is.False); @@ -301,7 +301,7 @@ public void Compute_Step3_WaitsForSecondSnapshot() float bufferTime = 1; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should not spit out any snapshot to apply Assert.That(result, Is.False); @@ -334,7 +334,7 @@ public void Compute_Step4_InterpolateWithTwoOldEnoughSnapshots() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -371,7 +371,7 @@ public void Compute_Step4_InterpolateWithThreeOldEnoughSnapshots() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -408,7 +408,7 @@ public void Compute_Step4_InterpolateAfterLongPause() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -458,7 +458,7 @@ public void Compute_Step4_InterpolateWithCatchup() double deltaTime = 0.5; double interpolationTime = 0; float bufferTime = 2; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -519,7 +519,7 @@ public void Compute_Step5_OvershootWithoutEnoughSnapshots() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -587,7 +587,7 @@ public void Compute_Step5_OvershootWithEnoughSnapshots_NextIsntOldEnough() float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should still spit out a result between first & second. Assert.That(result, Is.True); @@ -647,7 +647,7 @@ public void Compute_Step5_OvershootWithEnoughSnapshots_MovesToNextSnapshotIfOldE float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); @@ -703,7 +703,7 @@ public void Compute_Step5_OvershootWithEnoughSnapshots_2x_MovesToSecondNextSnaps float bufferTime = 2; int catchupThreshold = Int32.MaxValue; float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); // should spit out the interpolated snapshot Assert.That(result, Is.True); diff --git a/Packages/manifest.json b/Packages/manifest.json index 61f0830111f..7fc4ba28697 100644 --- a/Packages/manifest.json +++ b/Packages/manifest.json @@ -2,14 +2,14 @@ "dependencies": { "com.unity.2d.sprite": "1.0.0", "com.unity.2d.tilemap": "1.0.0", - "com.unity.ide.rider": "2.0.7", - "com.unity.ide.visualstudio": "2.0.12", - "com.unity.ide.vscode": "1.2.4", - "com.unity.test-framework": "1.1.30", - "com.unity.testtools.codecoverage": "1.0.0", + "com.unity.ide.rider": "3.0.13", + "com.unity.ide.visualstudio": "2.0.14", + "com.unity.ide.vscode": "1.2.5", + "com.unity.test-framework": "1.1.31", + "com.unity.testtools.codecoverage": "1.0.1", "com.unity.textmeshpro": "3.0.6", "com.unity.ugui": "1.0.0", - "com.unity.xr.legacyinputhelpers": "2.1.8", + "com.unity.xr.legacyinputhelpers": "2.1.9", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", diff --git a/Packages/packages-lock.json b/Packages/packages-lock.json index 90dfa276685..c022e44206f 100644 --- a/Packages/packages-lock.json +++ b/Packages/packages-lock.json @@ -20,16 +20,16 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "2.0.7", + "version": "3.0.13", "depth": 0, "source": "registry", "dependencies": { - "com.unity.test-framework": "1.1.1" + "com.unity.ext.nunit": "1.0.6" }, "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.12", + "version": "2.0.14", "depth": 0, "source": "registry", "dependencies": { @@ -38,21 +38,21 @@ "url": "https://packages.unity.com" }, "com.unity.ide.vscode": { - "version": "1.2.4", + "version": "1.2.5", "depth": 0, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.settings-manager": { - "version": "1.0.1", + "version": "1.0.3", "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.30", + "version": "1.1.31", "depth": 0, "source": "registry", "dependencies": { @@ -63,7 +63,7 @@ "url": "https://packages.unity.com" }, "com.unity.testtools.codecoverage": { - "version": "1.0.0", + "version": "1.0.1", "depth": 0, "source": "registry", "dependencies": { @@ -91,7 +91,7 @@ } }, "com.unity.xr.legacyinputhelpers": { - "version": "2.1.8", + "version": "2.1.9", "depth": 0, "source": "registry", "dependencies": { diff --git a/ProjectSettings/ProjectVersion.txt b/ProjectSettings/ProjectVersion.txt index def29338cb3..90d6509f828 100644 --- a/ProjectSettings/ProjectVersion.txt +++ b/ProjectSettings/ProjectVersion.txt @@ -1,2 +1,2 @@ -m_EditorVersion: 2020.3.26f1 -m_EditorVersionWithRevision: 2020.3.26f1 (7298b473bc1a) +m_EditorVersion: 2021.3.1f1 +m_EditorVersionWithRevision: 2021.3.1f1 (3b70a0754835) diff --git a/UserSettings/EditorUserSettings.asset b/UserSettings/EditorUserSettings.asset index 22d1c44e994..85aac73ad15 100644 --- a/UserSettings/EditorUserSettings.asset +++ b/UserSettings/EditorUserSettings.asset @@ -5,6 +5,9 @@ EditorUserSettings: m_ObjectHideFlags: 0 serializedVersion: 4 m_ConfigSettings: + RecentlyUsedSceneGuid-0: + value: 5500060752055c0b5a590f734677084912154f287d7e71312f78486abbe13160 + flags: 0 RecentlyUsedScenePath-0: value: 2242470311464676041c1e2d026c7a08171a0826293b691228271e3befe12633add435ece93f2c730a01ea3201701431fb1e10 flags: 0 @@ -16,9 +19,13 @@ EditorUserSettings: m_VCDebugCmd: 0 m_VCDebugOut: 0 m_SemanticMergeMode: 2 + m_DesiredImportWorkerCount: 2 + m_StandbyImportWorkerCount: 2 + m_IdleImportWorkerShutdownDelay: 60000 m_VCShowFailedCheckout: 1 m_VCOverwriteFailedCheckoutAssets: 1 m_VCProjectOverlayIcons: 1 m_VCHierarchyOverlayIcons: 1 m_VCOtherOverlayIcons: 1 m_VCAllowAsyncUpdate: 0 + m_ArtifactGarbageCollection: 1 From 7bfa0906b0296c9489813bf7dac247a01ee103be Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 9 Jun 2022 09:53:49 +0700 Subject: [PATCH 122/824] Snapshot Interpolation visual example (#3171) --- .../Examples/Snapshot Interpolation.meta | 8 + .../Snapshot Interpolation/ClientCube.cs | 103 ++++ .../Snapshot Interpolation/ClientCube.cs.meta | 3 + .../Snapshot Interpolation/ClientMaterial.mat | 80 ++++ .../ClientMaterial.mat.meta | 8 + .../Snapshot Interpolation/ServerCube.cs | 110 +++++ .../Snapshot Interpolation/ServerCube.cs.meta | 11 + .../Snapshot Interpolation/ServerMaterial.mat | 80 ++++ .../ServerMaterial.mat.meta | 8 + .../Snapshot Interpolation/Snapshot3D.cs | 30 ++ .../Snapshot Interpolation/Snapshot3D.cs.meta | 11 + .../SnapshotInterpolation.unity | 449 ++++++++++++++++++ .../SnapshotInterpolation.unity.meta | 7 + 13 files changed, 908 insertions(+) create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs.meta create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity.meta diff --git a/Assets/Mirror/Examples/Snapshot Interpolation.meta b/Assets/Mirror/Examples/Snapshot Interpolation.meta new file mode 100644 index 00000000000..fd92c282cb7 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 51718c4f05a6a4125967a38f5df2eb8c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs new file mode 100644 index 00000000000..8425f302099 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.Examples.SnapshotInterpolation +{ + public class ClientCube : MonoBehaviour + { + [Header("Components")] + public ServerCube server; + public Renderer render; + + [Header("Toggle")] + public bool interpolate = true; + + [Tooltip("Start to accelerate interpolation if buffer size is >= threshold. Needs to be larger than bufferTimeMultiplier.")] + public int catchupThreshold = 4; + + [Tooltip("Once buffer is larger catchupThreshold, accelerate by multiplier % per excess entry.")] + [Range(0, 1)] public float catchupMultiplier = 0.10f; + + [Header("Buffering")] + [Tooltip("Snapshots are buffered for sendInterval * multiplier seconds. If your expected client base is to run at non-ideal connection quality (2-5% packet loss), 3x supposedly works best.")] + public int bufferTimeMultiplier = 1; + public float bufferTime => server.sendInterval * bufferTimeMultiplier; + + [Header("Debug")] + public Color catchupColor = Color.green; + Color defaultColor; + + // absolute interpolation time, moved along with deltaTime + // (roughly between [0, delta] where delta is snapshot B - A timestamp) + // (can be bigger than delta when overshooting) + double serverInterpolationTime; + + // for debugging + double lastCatchup; + + // + public SortedList snapshots = new SortedList(); + Func Interpolate = Snapshot3D.Interpolate; + + void Awake() + { + defaultColor = render.sharedMaterial.color; + } + + public void OnMessage(Snapshot3D snap) + { + snap.localTimestamp = Time.time; + Mirror.SnapshotInterpolation.InsertIfNewEnough(snap, snapshots); + } + + void Update() + { + // snapshot interpolation + if (interpolate) + { + // compute snapshot interpolation & apply if any was spit out + // TODO we don't have Time.deltaTime double yet. float is fine. + if (Mirror.SnapshotInterpolation.Compute( + Time.time, + Time.deltaTime, + ref serverInterpolationTime, + bufferTime, + snapshots, + catchupThreshold, catchupMultiplier, + Interpolate, + out Snapshot3D computed, + out lastCatchup)) + { + transform.position = computed.position; + } + } + // apply raw + else + { + if (snapshots.Count > 0) + { + Snapshot3D snap = snapshots.Values[0]; + transform.position = snap.position; + snapshots.RemoveAt(0); + } + } + + // color material while catching up + render.sharedMaterial.color = lastCatchup > 0 + ? catchupColor + : defaultColor; + } + + void OnGUI() + { + // display buffer size as number for easier debugging. + // catchup is displayed as color state in Update() already. + const int width = 10; + const int height = 20; + Vector2 screen = Camera.main.WorldToScreenPoint(transform.position); + string str = $"{snapshots.Count}"; + GUI.Label(new Rect(screen.x - width / 2, screen.y - height / 2, width, height), str); + } + } +} diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs.meta b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs.meta new file mode 100644 index 00000000000..f3ca4284317 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 51b244c3535d474aaf0a7a679f86185f +timeCreated: 1654065994 \ No newline at end of file diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat b/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat new file mode 100644 index 00000000000..558d87254e9 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat @@ -0,0 +1,80 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ClientMaterial + m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0} + m_ValidKeywords: [] + m_InvalidKeywords: [] + 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_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _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: 0, g: 0, b: 0, a: 1} + - _EmissionColor: {r: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat.meta b/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat.meta new file mode 100644 index 00000000000..3caa45c523e --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f17cbcb3229954975ab0818845a2c17f +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs new file mode 100644 index 00000000000..55b772ba819 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs @@ -0,0 +1,110 @@ +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.Examples.SnapshotInterpolation +{ + public class ServerCube : MonoBehaviour + { + [Header("Components")] + public ClientCube client; + + [Header("Movement")] + public float distance = 10; + public float speed = 3; + Vector3 start; + + [Header("Snapshot Interpolation")] + public float sendInterval = 0.05f; // send every 50 ms + float lastSendTime; + + [Header("Latency Simulation")] + [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] + [Range(0, 1)] public float latencySpikeMultiplier = 0.5f; + [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] + public float latencySpikeSpeedMultiplier = 0.5f; + [Tooltip("Packet loss in %")] + [Range(0, 1)] public float loss = 0.1f; + [Tooltip("Latency in seconds")] + public float latency = 0.05f; // 50 ms + [Tooltip("Scramble % of unreliable messages, just like over the real network. Mirror unreliable is unordered.")] + [Range(0, 1)] public float scramble = 0.1f; + + // random + // UnityEngine.Random.value is [0, 1] with both upper and lower bounds inclusive + // but we need the upper bound to be exclusive, so using System.Random instead. + // => NextDouble() is NEVER < 0 so loss=0 never drops! + // => NextDouble() is ALWAYS < 1 so loss=1 always drops! + System.Random random = new System.Random(); + + // hold on to snapshots for a little while to simulate latency + List<(float, Vector3)> queue = new List<(float, Vector3)>(); + + // noise for latency simulation + static float Noise(float t) => Mathf.PerlinNoise(t, t); + + // latency simulation + float SimulateLatency() + { + // spike over perlin noise. + // no spikes isn't realistic. + // sin is too predictable / not realistic. + // perlin is still deterministic and random enough. + float spike = Noise(Time.unscaledTime * latencySpikeSpeedMultiplier) * latencySpikeMultiplier; + return latency + spike; + } + + void Start() + { + start = transform.position; + } + + void Update() + { + // move on XY plane + float x = Mathf.PingPong(Time.time * speed, distance); + transform.position = new Vector3(start.x + x, start.y, start.z); + + // broadcast snapshots every interval + if (Time.time >= lastSendTime + sendInterval) + { + Send(transform.position); + lastSendTime = Time.time; + } + + Flush(); + } + + void Send(Vector3 position) + { + // simulate packet loss + bool drop = random.NextDouble() < loss; + if (!drop) + { + // simulate scramble (Random.Next is < max, so +1) + bool doScramble = random.NextDouble() < scramble; + int last = queue.Count; + int index = doScramble ? random.Next(0, last + 1) : last; + + // simulate latency + float simulatedLatency = SimulateLatency(); + queue.Insert(index, (Time.time + simulatedLatency, position)); + } + } + + void Flush() + { + // flush ready snapshots to client + for (int i = 0; i < queue.Count; ++i) + { + (float threshold, Vector3 position) = queue[i]; + if (Time.time >= threshold) + { + Snapshot3D snap = new Snapshot3D(Time.time, 0, position); + client.OnMessage(snap); + queue.RemoveAt(i); + --i; + } + } + } + } +} diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs.meta b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs.meta new file mode 100644 index 00000000000..6c5aad8ab1f --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9a53730695f274a8aaa7ffdcf50d1008 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat b/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat new file mode 100644 index 00000000000..2046f0cfe09 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat @@ -0,0 +1,80 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!21 &2100000 +Material: + serializedVersion: 8 + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: ServerMaterial + m_Shader: {fileID: 10755, guid: 0000000000000000f000000000000000, type: 0} + m_ValidKeywords: [] + m_InvalidKeywords: [] + 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_Ints: [] + m_Floats: + - _BumpScale: 1 + - _Cutoff: 0.5 + - _DetailNormalMapScale: 1 + - _DstBlend: 0 + - _GlossMapScale: 1 + - _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: 0, g: 0, b: 0, a: 1} + m_BuildTextureStacks: [] diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat.meta b/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat.meta new file mode 100644 index 00000000000..9c6d7618a02 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerMaterial.mat.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 163b909ba60cc435a95bb35396edda15 +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 2100000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs new file mode 100644 index 00000000000..daed4b44a46 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs @@ -0,0 +1,30 @@ +// simple 2D snapshot for the demo. +// this way we can place cubes on different Y +using UnityEngine; + +namespace Mirror.Examples.SnapshotInterpolation +{ + // a simple snapshot with timestamp & interpolation + public struct Snapshot3D : Snapshot + { + public double remoteTimestamp { get; set; } + public double localTimestamp { get; set; } + public Vector2 position; + + public Snapshot3D(double remoteTimestamp, double localTimestamp, Vector2 position) + { + this.remoteTimestamp = remoteTimestamp; + this.localTimestamp = localTimestamp; + this.position = position; + } + + public static Snapshot3D Interpolate(Snapshot3D from, Snapshot3D to, double t) => + new Snapshot3D( + // TODO + // interpolated snapshot is applied directly. don't need timestamps. + 0, 0, + // TODO + // lerp unclamped in case we ever need to extrapolate. + Vector2.LerpUnclamped(from.position, to.position, (float)t)); + } +} diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs.meta b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs.meta new file mode 100644 index 00000000000..e8b02066b0d --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a2c9bdffb0b934b52b2c337a5c8a5698 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity new file mode 100644 index 00000000000..e35a613102d --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity @@ -0,0 +1,449 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.37311915, g: 0.3807396, b: 0.35872662, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 0} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &89338751 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 89338755} + - component: {fileID: 89338756} + m_Layer: 0 + m_Name: Client Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &89338755 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 89338751} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: + - {fileID: 1292704308} + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &89338756 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 89338751} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 51b244c3535d474aaf0a7a679f86185f, type: 3} + m_Name: + m_EditorClassIdentifier: + server: {fileID: 474480122} + render: {fileID: 1292704310} + interpolate: 1 + catchupThreshold: 4 + catchupMultiplier: 0.1 + bufferTimeMultiplier: 1 + catchupColor: {r: 1, g: 0, b: 0, a: 1} +--- !u!1 &474480117 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 474480121} + - component: {fileID: 474480120} + - component: {fileID: 474480119} + - component: {fileID: 474480122} + m_Layer: 0 + m_Name: Server Cube + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!23 &474480119 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 474480117} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: 163b909ba60cc435a95bb35396edda15, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + 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 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &474480120 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 474480117} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &474480121 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 474480117} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -5, y: 0.5, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &474480122 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 474480117} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9a53730695f274a8aaa7ffdcf50d1008, type: 3} + m_Name: + m_EditorClassIdentifier: + client: {fileID: 89338756} + distance: 10 + speed: 3 + sendInterval: 0.05 + latencySpikeMultiplier: 0.05 + latencySpikeSpeedMultiplier: 0.5 + loss: 0.05 + latency: 0.05 + scramble: 0.05 +--- !u!1 &1292704307 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1292704308} + - component: {fileID: 1292704311} + - component: {fileID: 1292704310} + m_Layer: 0 + m_Name: Visual Offset + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1292704308 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1292704307} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: -1, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 89338755} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!23 &1292704310 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1292704307} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 2100000, guid: f17cbcb3229954975ab0818845a2c17f, type: 2} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + 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 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1292704311 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1292704307} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!1 &1961486736 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1961486739} + - component: {fileID: 1961486738} + - component: {fileID: 1961486737} + m_Layer: 0 + m_Name: Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &1961486737 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1961486736} + m_Enabled: 1 +--- !u!20 &1961486738 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1961486736} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 2 + m_BackGroundColor: {r: 0.26415092, g: 0.26415092, b: 0.26415092, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 1 + orthographic size: 7 + m_Depth: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &1961486739 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1961486736} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: -11.22} + m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity.meta b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity.meta new file mode 100644 index 00000000000..3ca62559891 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 612a705077c16479db7b167ab1599ae8 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From d48d59cc08c59171957935b3fd54add29d8ea13c Mon Sep 17 00:00:00 2001 From: Robin Rolf Date: Thu, 9 Jun 2022 04:54:23 +0200 Subject: [PATCH 123/824] fix: SWT header lookup needs to be case insensitive (#3176) * fix: SWT header lookup needs to be case insensitive See https://github.com/vis2k/Mirror/issues/3175 Closes #3175 * fix: SWT skips headers just ending in the key name See https://github.com/James-Frowen/SimpleWebTransport/commit/3495845b8c3fa6838f0838660b652302a20b714a --- .../Transports/SimpleWebTransport/Server/ServerHandshake.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs index b13820187f8..4a7304beeea 100644 --- a/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs +++ b/Assets/Mirror/Runtime/Transports/SimpleWebTransport/Server/ServerHandshake.cs @@ -15,7 +15,7 @@ internal class ServerHandshake const int ResponseLength = 129; const int KeyLength = 24; const int MergedKeyLength = 60; - const string KeyHeaderString = "Sec-WebSocket-Key: "; + const string KeyHeaderString = "\r\nSec-WebSocket-Key: "; // this isn't an official max, just a reasonable size for a websocket handshake readonly int maxHttpHeaderSize = 3000; @@ -112,7 +112,7 @@ void AcceptHandshake(Stream stream, string msg) static void GetKey(string msg, byte[] keyBuffer) { - int start = msg.IndexOf(KeyHeaderString) + KeyHeaderString.Length; + int start = msg.IndexOf(KeyHeaderString, StringComparison.InvariantCultureIgnoreCase) + KeyHeaderString.Length; Log.Verbose($"Handshake Key: {msg.Substring(start, KeyLength)}"); Encoding.ASCII.GetBytes(msg, start, KeyLength, keyBuffer, 0); From 4b40dea7007b1292378308d3fd04eb2a9b01075c Mon Sep 17 00:00:00 2001 From: Justin Nolan Date: Fri, 10 Jun 2022 15:25:38 +0200 Subject: [PATCH 124/824] switch expressions are not supported in unity 2020 (#3178) --- .../KCP/MirrorTransport/KcpTransport.cs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index 5e9f530ea28..cba1b658137 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -70,17 +70,17 @@ static KcpChannel ToKcpChannel(int channel) => TransportError ToTransportError(ErrorCode error) { - return error switch + switch(error) { - ErrorCode.DnsResolve => TransportError.DnsResolve, - ErrorCode.Timeout => TransportError.Timeout, - ErrorCode.Congestion => TransportError.Congestion, - ErrorCode.InvalidReceive => TransportError.InvalidReceive, - ErrorCode.InvalidSend => TransportError.InvalidSend, - ErrorCode.ConnectionClosed => TransportError.ConnectionClosed, - ErrorCode.Unexpected => TransportError.Unexpected, - _ => throw new InvalidCastException($"KCP: missing error translation for {error}") - }; + case ErrorCode.DnsResolve: return TransportError.DnsResolve; + case ErrorCode.Timeout: return TransportError.Timeout; + case ErrorCode.Congestion: return TransportError.Congestion; + case ErrorCode.InvalidReceive: return TransportError.InvalidReceive; + case ErrorCode.InvalidSend: return TransportError.InvalidSend; + case ErrorCode.ConnectionClosed: return TransportError.ConnectionClosed; + case ErrorCode.Unexpected: return TransportError.Unexpected; + default: return throw new InvalidCastException($"KCP: missing error translation for {error}"); + } } void Awake() From 19efa2dd862e0d974f52ff1fd0e39a2a2eabb879 Mon Sep 17 00:00:00 2001 From: Justin Nolan Date: Fri, 10 Jun 2022 15:53:05 +0200 Subject: [PATCH 125/824] Fix compile error (#3179) --- .../Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs index cba1b658137..08120cc5f39 100644 --- a/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs +++ b/Assets/Mirror/Runtime/Transports/KCP/MirrorTransport/KcpTransport.cs @@ -79,7 +79,7 @@ TransportError ToTransportError(ErrorCode error) case ErrorCode.InvalidSend: return TransportError.InvalidSend; case ErrorCode.ConnectionClosed: return TransportError.ConnectionClosed; case ErrorCode.Unexpected: return TransportError.Unexpected; - default: return throw new InvalidCastException($"KCP: missing error translation for {error}"); + default: throw new InvalidCastException($"KCP: missing error translation for {error}"); } } From 999cf0fe3242a8177db38ad137371bd4ef734948 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 12 Jun 2022 00:15:08 +0700 Subject: [PATCH 126/824] Snap Interp demo GUI width --- Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs index 8425f302099..9b24584c36d 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs @@ -93,7 +93,7 @@ void OnGUI() { // display buffer size as number for easier debugging. // catchup is displayed as color state in Update() already. - const int width = 10; + const int width = 30; // fit 3 digits const int height = 20; Vector2 screen = Camera.main.WorldToScreenPoint(transform.position); string str = $"{snapshots.Count}"; From 5063fdd08f233844b9bda14a33b16be9b58d242d Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 13 Jun 2022 12:18:28 +0700 Subject: [PATCH 127/824] LatencySimulation: latencySpike renamed to jitter --- .../Mirror/Runtime/Transports/LatencySimulation.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Assets/Mirror/Runtime/Transports/LatencySimulation.cs b/Assets/Mirror/Runtime/Transports/LatencySimulation.cs index 2feb073d277..1ad0b1a3b63 100644 --- a/Assets/Mirror/Runtime/Transports/LatencySimulation.cs +++ b/Assets/Mirror/Runtime/Transports/LatencySimulation.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using UnityEngine; +using UnityEngine.Serialization; namespace Mirror { @@ -26,10 +27,12 @@ public class LatencySimulation : Transport public Transport wrap; [Header("Common")] - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - [Range(0, 1)] public float latencySpikeMultiplier; - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - public float latencySpikeSpeedMultiplier = 1; + [Tooltip("Jitter latency via perlin(Time * jitterSpeed) * jitter")] + [FormerlySerializedAs("latencySpikeMultiplier")] + [Range(0, 1)] public float jitter; + [Tooltip("Jitter latency via perlin(Time * jitterSpeed) * jitter")] + [FormerlySerializedAs("latencySpikeSpeedMultiplier")] + public float jitterSpeed = 1; [Header("Reliable Messages")] [Tooltip("Reliable latency in seconds")] @@ -80,7 +83,7 @@ float SimulateLatency(int channeldId) // no spikes isn't realistic. // sin is too predictable / no realistic. // perlin is still deterministic and random enough. - float spike = Noise(Time.unscaledTime * latencySpikeSpeedMultiplier) * latencySpikeMultiplier; + float spike = Noise(Time.unscaledTime * jitterSpeed) * jitter; // base latency switch (channeldId) From 9915f4c0b3a6b3817ab9fbb6c4acaa80aba2cdca Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 13 Jun 2022 17:46:31 +0700 Subject: [PATCH 128/824] Snapshot Interpolation Demo: simplify latency jitter for easier testing --- .../Snapshot Interpolation/ServerCube.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs index 55b772ba819..8ca98657aef 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs @@ -18,14 +18,12 @@ public class ServerCube : MonoBehaviour float lastSendTime; [Header("Latency Simulation")] - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - [Range(0, 1)] public float latencySpikeMultiplier = 0.5f; - [Tooltip("Spike latency via perlin(Time * speedMultiplier) * spikeMultiplier")] - public float latencySpikeSpeedMultiplier = 0.5f; - [Tooltip("Packet loss in %")] - [Range(0, 1)] public float loss = 0.1f; [Tooltip("Latency in seconds")] public float latency = 0.05f; // 50 ms + [Tooltip("Latency jitter, randomly added to latency.")] + [Range(0, 1)] public float jitter = 0.05f; + [Tooltip("Packet loss in %")] + [Range(0, 1)] public float loss = 0.1f; [Tooltip("Scramble % of unreliable messages, just like over the real network. Mirror unreliable is unordered.")] [Range(0, 1)] public float scramble = 0.1f; @@ -39,19 +37,9 @@ public class ServerCube : MonoBehaviour // hold on to snapshots for a little while to simulate latency List<(float, Vector3)> queue = new List<(float, Vector3)>(); - // noise for latency simulation - static float Noise(float t) => Mathf.PerlinNoise(t, t); - - // latency simulation - float SimulateLatency() - { - // spike over perlin noise. - // no spikes isn't realistic. - // sin is too predictable / not realistic. - // perlin is still deterministic and random enough. - float spike = Noise(Time.unscaledTime * latencySpikeSpeedMultiplier) * latencySpikeMultiplier; - return latency + spike; - } + // latency simulation: + // always a fixed value + some jitter. + float SimulateLatency() => latency + Random.value * jitter; void Start() { From 1b0901e966b80984a7fe487cd54c89c5b5d4ef24 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 13 Jun 2022 17:48:09 +0700 Subject: [PATCH 129/824] fix: Snapshot Interpolation Example: latency was accidentally applied to remoteTime instead of delivery time --- .../Snapshot Interpolation/ServerCube.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs index 8ca98657aef..bb3f388da86 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs @@ -34,8 +34,9 @@ public class ServerCube : MonoBehaviour // => NextDouble() is ALWAYS < 1 so loss=1 always drops! System.Random random = new System.Random(); - // hold on to snapshots for a little while to simulate latency - List<(float, Vector3)> queue = new List<(float, Vector3)>(); + // hold on to snapshots for a little while before delivering + // + List<(double, Snapshot3D)> queue = new List<(double, Snapshot3D)>(); // latency simulation: // always a fixed value + some jitter. @@ -64,6 +65,9 @@ void Update() void Send(Vector3 position) { + // create snapshot + Snapshot3D snap = new Snapshot3D(Time.timeAsDouble, 0, position); + // simulate packet loss bool drop = random.NextDouble() < loss; if (!drop) @@ -75,7 +79,8 @@ void Send(Vector3 position) // simulate latency float simulatedLatency = SimulateLatency(); - queue.Insert(index, (Time.time + simulatedLatency, position)); + double deliveryTime = Time.timeAsDouble + simulatedLatency; + queue.Insert(index, (deliveryTime, snap)); } } @@ -84,10 +89,9 @@ void Flush() // flush ready snapshots to client for (int i = 0; i < queue.Count; ++i) { - (float threshold, Vector3 position) = queue[i]; - if (Time.time >= threshold) + (double deliveryTime, Snapshot3D snap) = queue[i]; + if (Time.timeAsDouble >= deliveryTime) { - Snapshot3D snap = new Snapshot3D(Time.time, 0, position); client.OnMessage(snap); queue.RemoveAt(i); --i; From f92942bb5f4391aeb1d6d34eb883673eca95ee31 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 13 Jun 2022 18:17:50 +0700 Subject: [PATCH 130/824] Snapshot Interpolation Demo: vsync notice --- .../Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_ | 2 ++ .../Examples/Snapshot Interpolation/_DISABLE VSYNC_.meta | 7 +++++++ 2 files changed, 9 insertions(+) create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_ create mode 100644 Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_.meta diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_ b/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_ new file mode 100644 index 00000000000..c1a879df4b1 --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_ @@ -0,0 +1,2 @@ +otherwise it may not look entirely smooth. +even on 120 hz. \ No newline at end of file diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_.meta b/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_.meta new file mode 100644 index 00000000000..ee6b19c9b3d --- /dev/null +++ b/Assets/Mirror/Examples/Snapshot Interpolation/_DISABLE VSYNC_.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 7db50a51601fe4b3c8c929f6868bffbc +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From db50c39b918d42b21eb9d0b24319e8ca75f30d19 Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 13 Jun 2022 18:18:59 +0700 Subject: [PATCH 131/824] fix: Snapshot Interpolation Demo: assign material color to instanced material, fixes material asset changing color permanently --- Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs index 9b24584c36d..905ebfc9e11 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs @@ -84,7 +84,7 @@ void Update() } // color material while catching up - render.sharedMaterial.color = lastCatchup > 0 + render.material.color = lastCatchup > 0 ? catchupColor : defaultColor; } From 880a4eebd633c6e3a5d999aa3c315320a6f708cd Mon Sep 17 00:00:00 2001 From: vis2k Date: Thu, 16 Jun 2022 13:26:38 +0700 Subject: [PATCH 132/824] Snapshot Interpolation Demo: adjust namespaces so we can use 'SnapshotInterpolation.' to actual --- .../Mirror/Examples/Snapshot Interpolation/ClientCube.cs | 7 ++++--- .../Mirror/Examples/Snapshot Interpolation/ServerCube.cs | 2 +- .../Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs index 905ebfc9e11..4c114d53405 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs @@ -2,7 +2,8 @@ using System.Collections.Generic; using UnityEngine; -namespace Mirror.Examples.SnapshotInterpolation + +namespace Mirror.Examples.SnapshotInterpolationDemo { public class ClientCube : MonoBehaviour { @@ -48,7 +49,7 @@ void Awake() public void OnMessage(Snapshot3D snap) { snap.localTimestamp = Time.time; - Mirror.SnapshotInterpolation.InsertIfNewEnough(snap, snapshots); + SnapshotInterpolation.InsertIfNewEnough(snap, snapshots); } void Update() @@ -58,7 +59,7 @@ void Update() { // compute snapshot interpolation & apply if any was spit out // TODO we don't have Time.deltaTime double yet. float is fine. - if (Mirror.SnapshotInterpolation.Compute( + if (SnapshotInterpolation.Compute( Time.time, Time.deltaTime, ref serverInterpolationTime, diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs index bb3f388da86..61da92387f7 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using UnityEngine; -namespace Mirror.Examples.SnapshotInterpolation +namespace Mirror.Examples.SnapshotInterpolationDemo { public class ServerCube : MonoBehaviour { diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs index daed4b44a46..742cafee7d9 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs @@ -2,7 +2,7 @@ // this way we can place cubes on different Y using UnityEngine; -namespace Mirror.Examples.SnapshotInterpolation +namespace Mirror.Examples.SnapshotInterpolationDemo { // a simple snapshot with timestamp & interpolation public struct Snapshot3D : Snapshot From 0cff9d6f5dc7f50f31edc2fd3565e7aa41b716df Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 26 Jun 2022 21:03:42 +0900 Subject: [PATCH 133/824] feature: Snapshot Interpolation V2 (make sure call base Awake() when inheriting!; note this removes the interpolatePosition/Rotation/Scale options) (#3184) * yo * fix bug exposed by tests * tests and latest changes * NT --- .../NetworkTransformBase.cs | 361 ++++--- .../NetworkTransformSnapshot.cs | 10 +- .../Examples/Benchmark/Prefabs/Monster.prefab | 28 +- .../Examples/Benchmark/Prefabs/Player.prefab | 28 +- .../Snapshot Interpolation/ClientCube.cs | 187 +++- .../Snapshot Interpolation/ServerCube.cs | 4 +- .../Snapshot Interpolation/Snapshot3D.cs | 20 +- .../SnapshotInterpolation.unity | 18 +- .../Runtime/ExponentialMovingAverage.cs | 22 +- Assets/Mirror/Runtime/NetworkTime.cs | 4 +- .../Runtime/SnapshotInterpolation/Snapshot.cs | 17 +- .../SnapshotInterpolation.cs | 538 +++++------ ...st.cs => ExponentialMovingAverageTests.cs} | 21 +- ... => ExponentialMovingAverageTests.cs.meta} | 2 +- .../Tests/Editor/NetworkTransform2kTests.cs | 75 +- .../Editor/SnapshotInterpolationTests.cs | 898 ++++++------------ 16 files changed, 1042 insertions(+), 1191 deletions(-) rename Assets/Mirror/Tests/Editor/{ExponentialMovingAverageTest.cs => ExponentialMovingAverageTests.cs} (57%) rename Assets/Mirror/Tests/Editor/{ExponentialMovingAverageTest.cs.meta => ExponentialMovingAverageTests.cs.meta} (83%) diff --git a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs index 15622805be8..c1c1dcf2747 100644 --- a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs +++ b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformBase.cs @@ -40,43 +40,109 @@ public abstract class NetworkTransformBase : NetworkBehaviour protected abstract Transform targetComponent { get; } [Header("Synchronization")] - [Range(0, 1)] public float sendInterval = 0.050f; - public bool syncPosition = true; - public bool syncRotation = true; - // scale sync is rare. off by default. - public bool syncScale = false; - - double lastClientSendTime; - double lastServerSendTime; + [Tooltip("Send N snapshots per second. Multiples of frame rate make sense.")] + public int sendRate = 30; // in Hz. easier to work with as int for EMA. easier to display '30' than '0.333333333' + public float sendInterval => 1f / sendRate; - // not all games need to interpolate. a board game might jump to the - // final position immediately. - [Header("Interpolation")] - public bool interpolatePosition = true; - public bool interpolateRotation = true; - public bool interpolateScale = false; - - // "Experimentally I’ve found that the amount of delay that works best - // at 2-5% packet loss is 3X the packet send rate" - // NOTE: we do NOT use a dyanmically changing buffer size. - // it would come with a lot of complications, e.g. buffer time - // advantages/disadvantages for different connections. - // Glenn Fiedler's recommendation seems solid, and should cover - // the vast majority of connections. - // (a player with 2000ms latency will have issues no matter what) + // decrease bufferTime at runtime to see the catchup effect. + // increase to see slowdown. + // 'double' so we can have very precise dynamic adjustment without rounding [Header("Buffering")] - [Tooltip("Snapshots are buffered for sendInterval * multiplier seconds. If your expected client base is to run at non-ideal connection quality (2-5% packet loss), 3x supposedly works best.")] - public int bufferTimeMultiplier = 1; - public float bufferTime => sendInterval * bufferTimeMultiplier; + [Tooltip("Local simulation is behind by sendInterval * multiplier seconds.\n\nThis guarantees that we always have enough snapshots in the buffer to mitigate lags & jitter.\n\nIncrease this if the simulation isn't smooth. By default, it should be around 2.")] + public double bufferTimeMultiplier = 2; + public double bufferTime => sendInterval * bufferTimeMultiplier; + [Tooltip("Buffer size limit to avoid ever growing list memory consumption attacks.")] public int bufferSizeLimit = 64; - [Tooltip("Start to accelerate interpolation if buffer size is >= threshold. Needs to be larger than bufferTimeMultiplier.")] - public int catchupThreshold = 4; + // catchup ///////////////////////////////////////////////////////////// + // catchup thresholds in 'frames'. + // half a frame might be too aggressive. + [Header("Catchup / Slowdown")] + [Tooltip("Slowdown begins when the local timeline is moving too fast towards remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be negative.\n\nDon't modify unless you know what you are doing.")] + public float catchupNegativeThreshold = -1; // careful, don't want to run out of snapshots + + [Tooltip("Catchup begins when the local timeline is moving too slow and getting too far away from remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be positive.\n\nDon't modify unless you know what you are doing.")] + public float catchupPositiveThreshold = 1; + + [Tooltip("Local timeline acceleration in % while catching up.")] + [Range(0, 1)] + public double catchupSpeed = 0.01f; // 1% + + [Tooltip("Local timeline slowdown in % while slowing down.")] + [Range(0, 1)] + public double slowdownSpeed = 0.01f; // 1% + + [Tooltip("Catchup/Slowdown is adjusted over n-second exponential moving average.")] + public int driftEmaDuration = 1; // shouldn't need to modify this, but expose it anyway + + // we use EMA to average the last second worth of snapshot time diffs. + // manually averaging the last second worth of values with a for loop + // would be the same, but a moving average is faster because we only + // ever add one value. + ExponentialMovingAverage serverDriftEma; + ExponentialMovingAverage clientDriftEma; + + // dynamic buffer time adjustment ////////////////////////////////////// + // dynamically adjusts bufferTimeMultiplier for smooth results. + // to understand how this works, try this manually: + // + // - disable dynamic adjustment + // - set jitter = 0.2 (20% is a lot!) + // - notice some stuttering + // - disable interpolation to see just how much jitter this really is(!) + // - enable interpolation again + // - manually increase bufferTimeMultiplier to 3-4 + // ... the cube slows down (blue) until it's smooth + // - with dynamic adjustment enabled, it will set 4 automatically + // ... the cube slows down (blue) until it's smooth as well + // + // note that 20% jitter is extreme. + // for this to be perfectly smooth, set the safety tolerance to '2'. + // but realistically this is not necessary, and '1' is enough. + [Header("Dynamic Adjustment")] + [Tooltip("Automatically adjust bufferTimeMultiplier for smooth results.\nSets a low multiplier on stable connections, and a high multiplier on jittery connections.")] + public bool dynamicAdjustment = true; + + [Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")] + public float dynamicAdjustmentTolerance = 1; // 1 is realistically just fine, 2 is very very safe even for 20% jitter. can be half a frame too. (see above comments) - [Tooltip("Once buffer is larger catchupThreshold, accelerate by multiplier % per excess entry.")] - [Range(0, 1)] public float catchupMultiplier = 0.10f; + [Tooltip("Dynamic adjustment is computed over n-second exponential moving average standard deviation.")] + public int deliveryTimeEmaDuration = 2; // 1-2s recommended to capture average delivery time + ExponentialMovingAverage serverDeliveryTimeEma; // average delivery time (standard deviation gives average jitter) + ExponentialMovingAverage clientDeliveryTimeEma; // average delivery time (standard deviation gives average jitter) + + // buffers & time ////////////////////////////////////////////////////// + // snapshots sorted by timestamp + // in the original article, glenn fiedler drops any snapshots older than + // the last received snapshot. + // -> instead, we insert into a sorted buffer + // -> the higher the buffer information density, the better + // -> we still drop anything older than the first element in the buffer + // => internal for testing + // + // IMPORTANT: of explicit 'NTSnapshot' type instead of 'Snapshot' + // interface because List allocates through boxing + internal SortedList serverSnapshots = new SortedList(); + internal SortedList clientSnapshots = new SortedList(); + + // only convert the static Interpolation function to Func once to + // avoid allocations + Func Interpolate = NTSnapshot.Interpolate; + + // for smooth interpolation, we need to interpolate along server time. + // any other time (arrival on client, client local time, etc.) is not + // going to give smooth results. + double serverTimeline; + double serverTimescale; + + // catchup / slowdown adjustments are applied to timescale, + // to be adjusted in every update instead of when receiving messages. + double clientTimeline; + double clientTimescale; + + // only sync when changed hack ///////////////////////////////////////// #if onlySyncOnChange_BANDWIDTH_SAVING [Header("Sync Only If Changed")] [Tooltip("When true, changes are not sent unless greater than sensitivity values below.")] @@ -100,35 +166,34 @@ public abstract class NetworkTransformBase : NetworkBehaviour protected bool cachedSnapshotComparison; protected bool hasSentUnchangedPosition; #endif + // selective sync ////////////////////////////////////////////////////// + [Header("Selective Sync & interpolation")] + public bool syncPosition = true; + public bool syncRotation = true; + public bool syncScale = false; // rare. off by default. - // snapshots sorted by timestamp - // in the original article, glenn fiedler drops any snapshots older than - // the last received snapshot. - // -> instead, we insert into a sorted buffer - // -> the higher the buffer information density, the better - // -> we still drop anything older than the first element in the buffer - // => internal for testing - // - // IMPORTANT: of explicit 'NTSnapshot' type instead of 'Snapshot' - // interface because List allocates through boxing - internal SortedList serverBuffer = new SortedList(); - internal SortedList clientBuffer = new SortedList(); - - // absolute interpolation time, moved along with deltaTime - // (roughly between [0, delta] where delta is snapshot B - A timestamp) - // (can be bigger than delta when overshooting) - double serverInterpolationTime; - double clientInterpolationTime; - - // only convert the static Interpolation function to Func once to - // avoid allocations - Func Interpolate = NTSnapshot.Interpolate; + double lastClientSendTime; + double lastServerSendTime; + // debugging /////////////////////////////////////////////////////////// [Header("Debug")] public bool showGizmos; public bool showOverlay; public Color overlayColor = new Color(0, 0, 0, 0.5f); + // initialization ////////////////////////////////////////////////////// + // make sure to call this when inheriting too! + protected virtual void Awake() + { + // initialize EMA with 'emaDuration' seconds worth of history. + // 1 second holds 'sendRate' worth of values. + // multiplied by emaDuration gives n-seconds. + serverDriftEma = new ExponentialMovingAverage(sendRate * driftEmaDuration); + clientDriftEma = new ExponentialMovingAverage(sendRate * driftEmaDuration); + serverDeliveryTimeEma = new ExponentialMovingAverage(sendRate * deliveryTimeEmaDuration); + clientDeliveryTimeEma = new ExponentialMovingAverage(sendRate * deliveryTimeEmaDuration); + } + // snapshot functions ////////////////////////////////////////////////// // construct a snapshot of the current state // => internal for testing @@ -156,7 +221,7 @@ protected virtual NTSnapshot ConstructSnapshot() // // NOTE: stuck detection is unnecessary here. // we always set transform.position anyway, we can't get stuck. - protected virtual void ApplySnapshot(NTSnapshot start, NTSnapshot goal, NTSnapshot interpolated) + protected virtual void ApplySnapshot(NTSnapshot interpolated) { // local position/rotation for VR support // @@ -166,14 +231,15 @@ protected virtual void ApplySnapshot(NTSnapshot start, NTSnapshot goal, NTSnapsh // -> but simply don't apply it. if the user doesn't want to sync // scale, then we should not touch scale etc. if (syncPosition) - targetComponent.localPosition = interpolatePosition ? interpolated.position : goal.position; + targetComponent.localPosition = interpolated.position; if (syncRotation) - targetComponent.localRotation = interpolateRotation ? interpolated.rotation : goal.rotation; + targetComponent.localRotation = interpolated.rotation; if (syncScale) - targetComponent.localScale = interpolateScale ? interpolated.scale : goal.scale; + targetComponent.localScale = interpolated.scale; } + #if onlySyncOnChange_BANDWIDTH_SAVING // Returns true if position, rotation AND scale are unchanged, within given sensitivity range. protected virtual bool CompareSnapshots(NTSnapshot currentSnapshot) @@ -206,7 +272,7 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat if (!clientAuthority) return; // protect against ever growing buffer size attacks - if (serverBuffer.Count >= bufferSizeLimit) return; + if (serverSnapshots.Count >= bufferSizeLimit) return; // only player owned objects (with a connection) can send to // server. we can get the timestamp from the connection. @@ -216,7 +282,7 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat { double timeIntervalCheck = bufferResetMultiplier * sendInterval; - if (serverBuffer.Count > 0 && serverBuffer.Values[serverBuffer.Count - 1].remoteTimestamp + timeIntervalCheck < timestamp) + if (serverSnapshots.Count > 0 && serverSnapshots.Values[serverSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) { Reset(); } @@ -231,9 +297,9 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat // client sends snapshot at t=10 // then the server would assume that it's one super slow move and // replay it for 10 seconds. - if (!position.HasValue) position = serverBuffer.Count > 0 ? serverBuffer.Values[serverBuffer.Count - 1].position : targetComponent.localPosition; - if (!rotation.HasValue) rotation = serverBuffer.Count > 0 ? serverBuffer.Values[serverBuffer.Count - 1].rotation : targetComponent.localRotation; - if (!scale.HasValue) scale = serverBuffer.Count > 0 ? serverBuffer.Values[serverBuffer.Count - 1].scale : targetComponent.localScale; + if (!position.HasValue) position = serverSnapshots.Count > 0 ? serverSnapshots.Values[serverSnapshots.Count - 1].position : targetComponent.localPosition; + if (!rotation.HasValue) rotation = serverSnapshots.Count > 0 ? serverSnapshots.Values[serverSnapshots.Count - 1].rotation : targetComponent.localRotation; + if (!scale.HasValue) scale = serverSnapshots.Count > 0 ? serverSnapshots.Values[serverSnapshots.Count - 1].scale : targetComponent.localScale; // construct snapshot with batch timestamp to save bandwidth NTSnapshot snapshot = new NTSnapshot( @@ -242,8 +308,34 @@ protected virtual void OnClientToServerSync(Vector3? position, Quaternion? rotat position.Value, rotation.Value, scale.Value ); - // add to buffer (or drop if older than first element) - SnapshotInterpolation.InsertIfNewEnough(snapshot, serverBuffer); + // (optional) dynamic adjustment + if (dynamicAdjustment) + { + // set bufferTime on the fly. + // shows in inspector for easier debugging :) + bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment( + sendInterval, + serverDeliveryTimeEma.StandardDeviation, + dynamicAdjustmentTolerance + ); + // Debug.Log($"[Server]: {name} delivery std={serverDeliveryTimeEma.StandardDeviation} bufferTimeMult := {bufferTimeMultiplier} "); + } + + // insert into the server buffer & initialize / adjust / catchup + SnapshotInterpolation.Insert( + serverSnapshots, + snapshot, + ref serverTimeline, + ref serverTimescale, + sendInterval, + bufferTime, + catchupSpeed, + slowdownSpeed, + ref serverDriftEma, + catchupNegativeThreshold, + catchupPositiveThreshold, + ref serverDeliveryTimeEma + ); } // rpc ///////////////////////////////////////////////////////////////// @@ -267,7 +359,7 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat if (IsClientWithAuthority) return; // protect against ever growing buffer size attacks - if (clientBuffer.Count >= bufferSizeLimit) return; + if (clientSnapshots.Count >= bufferSizeLimit) return; // on the client, we receive rpcs for all entities. // not all of them have a connectionToServer. @@ -279,7 +371,7 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat { double timeIntervalCheck = bufferResetMultiplier * sendInterval; - if (clientBuffer.Count > 0 && clientBuffer.Values[clientBuffer.Count - 1].remoteTimestamp + timeIntervalCheck < timestamp) + if (clientSnapshots.Count > 0 && clientSnapshots.Values[clientSnapshots.Count - 1].remoteTime + timeIntervalCheck < timestamp) { Reset(); } @@ -294,9 +386,9 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat // client sends snapshot at t=10 // then the server would assume that it's one super slow move and // replay it for 10 seconds. - if (!position.HasValue) position = clientBuffer.Count > 0 ? clientBuffer.Values[clientBuffer.Count - 1].position : targetComponent.localPosition; - if (!rotation.HasValue) rotation = clientBuffer.Count > 0 ? clientBuffer.Values[clientBuffer.Count - 1].rotation : targetComponent.localRotation; - if (!scale.HasValue) scale = clientBuffer.Count > 0 ? clientBuffer.Values[clientBuffer.Count - 1].scale : targetComponent.localScale; + if (!position.HasValue) position = clientSnapshots.Count > 0 ? clientSnapshots.Values[clientSnapshots.Count - 1].position : targetComponent.localPosition; + if (!rotation.HasValue) rotation = clientSnapshots.Count > 0 ? clientSnapshots.Values[clientSnapshots.Count - 1].rotation : targetComponent.localRotation; + if (!scale.HasValue) scale = clientSnapshots.Count > 0 ? clientSnapshots.Values[clientSnapshots.Count - 1].scale : targetComponent.localScale; // construct snapshot with batch timestamp to save bandwidth NTSnapshot snapshot = new NTSnapshot( @@ -305,8 +397,34 @@ protected virtual void OnServerToClientSync(Vector3? position, Quaternion? rotat position.Value, rotation.Value, scale.Value ); - // add to buffer (or drop if older than first element) - SnapshotInterpolation.InsertIfNewEnough(snapshot, clientBuffer); + // (optional) dynamic adjustment + if (dynamicAdjustment) + { + // set bufferTime on the fly. + // shows in inspector for easier debugging :) + bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment( + sendInterval, + clientDeliveryTimeEma.StandardDeviation, + dynamicAdjustmentTolerance + ); + // Debug.Log($"[Client]: {name} delivery std={clientDeliveryTimeEma.StandardDeviation} bufferTimeMult := {bufferTimeMultiplier} "); + } + + // insert into the client buffer & initialize / adjust / catchup + SnapshotInterpolation.Insert( + clientSnapshots, + snapshot, + ref clientTimeline, + ref clientTimescale, + sendInterval, + bufferTime, + catchupSpeed, + slowdownSpeed, + ref clientDriftEma, + catchupNegativeThreshold, + catchupPositiveThreshold, + ref clientDeliveryTimeEma + ); } // update ////////////////////////////////////////////////////////////// @@ -390,20 +508,19 @@ void UpdateServer() // then we don't need to do anything. if (clientAuthority && !hasAuthority) { - // compute snapshot interpolation & apply if any was spit out - // TODO we don't have Time.deltaTime double yet. float is fine. - if (SnapshotInterpolation.Compute( - NetworkTime.localTime, Time.deltaTime, - ref serverInterpolationTime, - bufferTime, serverBuffer, - catchupThreshold, catchupMultiplier, - Interpolate, - out NTSnapshot computed, - out _)) + if (serverSnapshots.Count > 0) { - NTSnapshot start = serverBuffer.Values[0]; - NTSnapshot goal = serverBuffer.Values[1]; - ApplySnapshot(start, goal, computed); + // compute snapshot interpolation & apply if any was spit out + if (SnapshotInterpolation.Step( + serverSnapshots, + Time.unscaledDeltaTime, + ref serverTimeline, + serverTimescale, + Interpolate, + out NTSnapshot computed)) + { + ApplySnapshot(computed); + } } } } @@ -480,20 +597,19 @@ void UpdateClient() // we need to apply snapshots from the buffer else { - // compute snapshot interpolation & apply if any was spit out - // TODO we don't have Time.deltaTime double yet. float is fine. - if (SnapshotInterpolation.Compute( - NetworkTime.localTime, Time.deltaTime, - ref clientInterpolationTime, - bufferTime, clientBuffer, - catchupThreshold, catchupMultiplier, - Interpolate, - out NTSnapshot computed, - out _)) + if (clientSnapshots.Count > 0) { - NTSnapshot start = clientBuffer.Values[0]; - NTSnapshot goal = clientBuffer.Values[1]; - ApplySnapshot(start, goal, computed); + // compute snapshot interpolation & apply if any was spit out + if (SnapshotInterpolation.Step( + clientSnapshots, + Time.unscaledDeltaTime, + ref clientTimeline, + clientTimescale, + Interpolate, + out NTSnapshot computed)) + { + ApplySnapshot(computed); + } } } } @@ -620,12 +736,14 @@ public virtual void Reset() { // disabled objects aren't updated anymore. // so let's clear the buffers. - serverBuffer.Clear(); - clientBuffer.Clear(); + serverSnapshots.Clear(); + clientSnapshots.Clear(); // reset interpolation time too so we start at t=0 next time - serverInterpolationTime = 0; - clientInterpolationTime = 0; + serverTimeline = 0; + serverTimescale = 0; + clientTimeline = 0; + clientTimescale = 0; } protected virtual void OnDisable() => Reset(); @@ -633,19 +751,12 @@ public virtual void Reset() protected virtual void OnValidate() { - // make sure that catchup threshold is > buffer multiplier. - // for a buffer multiplier of '3', we usually have at _least_ 3 - // buffered snapshots. often 4-5 even. - // - // catchUpThreshold should be a minimum of bufferTimeMultiplier + 3, - // to prevent clashes with SnapshotInterpolation looking for at least - // 3 old enough buffers, else catch up will be implemented while there - // is not enough old buffers, and will result in jitter. - // (validated with several real world tests by ninja & imer) - catchupThreshold = Mathf.Max(bufferTimeMultiplier + 3, catchupThreshold); + // thresholds need to be <0 and >0 + catchupNegativeThreshold = Math.Min(catchupNegativeThreshold, 0); + catchupPositiveThreshold = Math.Max(catchupPositiveThreshold, 0); // buffer limit should be at least multiplier to have enough in there - bufferSizeLimit = Mathf.Max(bufferTimeMultiplier, bufferSizeLimit); + bufferSizeLimit = Mathf.Max((int)bufferTimeMultiplier, bufferSizeLimit); } public override bool OnSerialize(NetworkWriter writer, bool initialState) @@ -694,24 +805,22 @@ protected virtual void OnGUI() // enough alpha, in front of camera and in screen? if (point.z >= 0 && Utils.IsPointInScreen(point)) { - // catchup is useful to show too - int serverBufferExcess = Mathf.Max(serverBuffer.Count - catchupThreshold, 0); - int clientBufferExcess = Mathf.Max(clientBuffer.Count - catchupThreshold, 0); - float serverCatchup = serverBufferExcess * catchupMultiplier; - float clientCatchup = clientBufferExcess * catchupMultiplier; - GUI.color = overlayColor; GUILayout.BeginArea(new Rect(point.x, Screen.height - point.y, 200, 100)); // always show both client & server buffers so it's super // obvious if we accidentally populate both. - GUILayout.Label($"Server Buffer:{serverBuffer.Count}"); - if (serverCatchup > 0) - GUILayout.Label($"Server Catchup:{serverCatchup * 100:F2}%"); + if (serverSnapshots.Count > 0) + { + GUILayout.Label($"Server Buffer:{serverSnapshots.Count}"); + GUILayout.Label($"Server Timescale:{serverTimescale * 100:F2}%"); + } - GUILayout.Label($"Client Buffer:{clientBuffer.Count}"); - if (clientCatchup > 0) - GUILayout.Label($"Client Catchup:{clientCatchup * 100:F2}%"); + if (clientSnapshots.Count > 0) + { + GUILayout.Label($"Client Buffer:{clientSnapshots.Count}"); + GUILayout.Label($"Client Timescale:{clientTimescale * 100:F2}%"); + } GUILayout.EndArea(); GUI.color = Color.white; @@ -734,7 +843,7 @@ protected virtual void DrawGizmos(SortedList buffer) { // color depends on if old enough or not NTSnapshot entry = buffer.Values[i]; - bool oldEnough = entry.localTimestamp <= threshold; + bool oldEnough = entry.localTime <= threshold; Gizmos.color = oldEnough ? oldEnoughColor : notOldEnoughColor; Gizmos.DrawCube(entry.position, Vector3.one); } @@ -752,8 +861,8 @@ protected virtual void OnDrawGizmos() if (!Application.isPlaying) return; if (!showGizmos) return; - if (isServer) DrawGizmos(serverBuffer); - if (isClient) DrawGizmos(clientBuffer); + if (isServer) DrawGizmos(serverSnapshots); + if (isClient) DrawGizmos(clientSnapshots); } #endif } diff --git a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformSnapshot.cs b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformSnapshot.cs index efd91c0ac15..9935b4bcd53 100644 --- a/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformSnapshot.cs +++ b/Assets/Mirror/Components/NetworkTransform2k/NetworkTransformSnapshot.cs @@ -23,19 +23,19 @@ public struct NTSnapshot : Snapshot // // [REMOTE TIME, NOT LOCAL TIME] // => DOUBLE for long term accuracy & batching gives us double anyway - public double remoteTimestamp { get; set; } + public double remoteTime { get; set; } // the local timestamp (when we received it) // used to know if the first two snapshots are old enough to start. - public double localTimestamp { get; set; } + public double localTime { get; set; } public Vector3 position; public Quaternion rotation; public Vector3 scale; - public NTSnapshot(double remoteTimestamp, double localTimestamp, Vector3 position, Quaternion rotation, Vector3 scale) + public NTSnapshot(double remoteTime, double localTime, Vector3 position, Quaternion rotation, Vector3 scale) { - this.remoteTimestamp = remoteTimestamp; - this.localTimestamp = localTimestamp; + this.remoteTime = remoteTime; + this.localTime = localTime; this.position = position; this.rotation = rotation; this.scale = scale; diff --git a/Assets/Mirror/Examples/Benchmark/Prefabs/Monster.prefab b/Assets/Mirror/Examples/Benchmark/Prefabs/Monster.prefab index 3625c3dbc3a..60f96ce633c 100644 --- a/Assets/Mirror/Examples/Benchmark/Prefabs/Monster.prefab +++ b/Assets/Mirror/Examples/Benchmark/Prefabs/Monster.prefab @@ -31,6 +31,7 @@ Transform: 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_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -54,10 +55,12 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 + m_RayTraceProcedural: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -82,6 +85,7 @@ MeshRenderer: m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} --- !u!114 &1078519278818213949 MonoBehaviour: m_ObjectHideFlags: 0 @@ -114,17 +118,25 @@ MonoBehaviour: syncMode: 0 syncInterval: 0.1 clientAuthority: 0 - sendInterval: 0.05 + sendRate: 30 + bufferTimeMultiplier: 1 + bufferSizeLimit: 64 + catchupNegativeThreshold: -1 + catchupPositiveThreshold: 1 + catchupSpeed: 0.009999999776482582 + slowdownSpeed: 0.009999999776482582 + driftEmaDuration: 1 + dynamicAdjustment: 1 + dynamicAdjustmentTolerance: 1 + deliveryTimeEmaDuration: 2 + onlySyncOnChange: 0 + bufferResetMultiplier: 5 + positionSensitivity: 0.01 + rotationSensitivity: 0.01 + scaleSensitivity: 0.01 syncPosition: 1 syncRotation: 0 syncScale: 0 - interpolatePosition: 1 - interpolateRotation: 0 - interpolateScale: 0 - bufferTimeMultiplier: 1 - bufferSizeLimit: 64 - catchupThreshold: 4 - catchupMultiplier: 0.1 showGizmos: 0 showOverlay: 0 overlayColor: {r: 0, g: 0, b: 0, a: 0.5} diff --git a/Assets/Mirror/Examples/Benchmark/Prefabs/Player.prefab b/Assets/Mirror/Examples/Benchmark/Prefabs/Player.prefab index a7e4fdde734..1e51d8ba7c2 100644 --- a/Assets/Mirror/Examples/Benchmark/Prefabs/Player.prefab +++ b/Assets/Mirror/Examples/Benchmark/Prefabs/Player.prefab @@ -31,6 +31,7 @@ Transform: 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_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -54,10 +55,12 @@ MeshRenderer: m_CastShadows: 1 m_ReceiveShadows: 1 m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 m_MotionVectors: 1 m_LightProbeUsage: 1 m_ReflectionProbeUsage: 1 m_RayTracingMode: 2 + m_RayTraceProcedural: 0 m_RenderingLayerMask: 1 m_RendererPriority: 0 m_Materials: @@ -82,6 +85,7 @@ MeshRenderer: m_SortingLayerID: 0 m_SortingLayer: 0 m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} --- !u!114 &1078519278818213949 MonoBehaviour: m_ObjectHideFlags: 0 @@ -114,17 +118,25 @@ MonoBehaviour: syncMode: 0 syncInterval: 0.1 clientAuthority: 1 - sendInterval: 0.05 + sendRate: 30 + bufferTimeMultiplier: 1 + bufferSizeLimit: 64 + catchupNegativeThreshold: -1 + catchupPositiveThreshold: 1 + catchupSpeed: 0.009999999776482582 + slowdownSpeed: 0.009999999776482582 + driftEmaDuration: 1 + dynamicAdjustment: 1 + dynamicAdjustmentTolerance: 1 + deliveryTimeEmaDuration: 2 + onlySyncOnChange: 0 + bufferResetMultiplier: 5 + positionSensitivity: 0.01 + rotationSensitivity: 0.01 + scaleSensitivity: 0.01 syncPosition: 1 syncRotation: 0 syncScale: 0 - interpolatePosition: 1 - interpolateRotation: 0 - interpolateScale: 0 - bufferTimeMultiplier: 1 - bufferSizeLimit: 64 - catchupThreshold: 4 - catchupMultiplier: 0.1 showGizmos: 0 showOverlay: 0 overlayColor: {r: 0, g: 0, b: 0, a: 0.5} diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs index 4c114d53405..60e879a8961 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ClientCube.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using UnityEngine; - namespace Mirror.Examples.SnapshotInterpolationDemo { public class ClientCube : MonoBehaviour @@ -14,69 +13,153 @@ public class ClientCube : MonoBehaviour [Header("Toggle")] public bool interpolate = true; - [Tooltip("Start to accelerate interpolation if buffer size is >= threshold. Needs to be larger than bufferTimeMultiplier.")] - public int catchupThreshold = 4; + // decrease bufferTime at runtime to see the catchup effect. + // increase to see slowdown. + // 'double' so we can have very precise dynamic adjustment without rounding + [Header("Buffering")] + [Tooltip("Local simulation is behind by sendInterval * multiplier seconds.\n\nThis guarantees that we always have enough snapshots in the buffer to mitigate lags & jitter.\n\nIncrease this if the simulation isn't smooth. By default, it should be around 2.")] + public double bufferTimeMultiplier = 2; + public double bufferTime => server.sendInterval * bufferTimeMultiplier; - [Tooltip("Once buffer is larger catchupThreshold, accelerate by multiplier % per excess entry.")] - [Range(0, 1)] public float catchupMultiplier = 0.10f; + // + public SortedList snapshots = new SortedList(); + Func Interpolate = Snapshot3D.Interpolate; - [Header("Buffering")] - [Tooltip("Snapshots are buffered for sendInterval * multiplier seconds. If your expected client base is to run at non-ideal connection quality (2-5% packet loss), 3x supposedly works best.")] - public int bufferTimeMultiplier = 1; - public float bufferTime => server.sendInterval * bufferTimeMultiplier; + // for smooth interpolation, we need to interpolate along server time. + // any other time (arrival on client, client local time, etc.) is not + // going to give smooth results. + double localTimeline; - [Header("Debug")] - public Color catchupColor = Color.green; - Color defaultColor; + // catchup / slowdown adjustments are applied to timescale, + // to be adjusted in every update instead of when receiving messages. + double localTimescale = 1; - // absolute interpolation time, moved along with deltaTime - // (roughly between [0, delta] where delta is snapshot B - A timestamp) - // (can be bigger than delta when overshooting) - double serverInterpolationTime; + // catchup ///////////////////////////////////////////////////////////// + // catchup thresholds in 'frames'. + // half a frame might be too aggressive. + [Header("Catchup / Slowdown")] + [Tooltip("Slowdown begins when the local timeline is moving too fast towards remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be negative.\n\nDon't modify unless you know what you are doing.")] + public float catchupNegativeThreshold = -1; // careful, don't want to run out of snapshots - // for debugging - double lastCatchup; + [Tooltip("Catchup begins when the local timeline is moving too slow and getting too far away from remote time. Threshold is in frames worth of snapshots.\n\nThis needs to be positive.\n\nDon't modify unless you know what you are doing.")] + public float catchupPositiveThreshold = 1; - // - public SortedList snapshots = new SortedList(); - Func Interpolate = Snapshot3D.Interpolate; + [Tooltip("Local timeline acceleration in % while catching up.")] + [Range(0, 1)] + public double catchupSpeed = 0.01f; // 1% + + [Tooltip("Local timeline slowdown in % while slowing down.")] + [Range(0, 1)] + public double slowdownSpeed = 0.01f; // 1% + + [Tooltip("Catchup/Slowdown is adjusted over n-second exponential moving average.")] + public int driftEmaDuration = 1; // shouldn't need to modify this, but expose it anyway + + // we use EMA to average the last second worth of snapshot time diffs. + // manually averaging the last second worth of values with a for loop + // would be the same, but a moving average is faster because we only + // ever add one value. + ExponentialMovingAverage driftEma; + + // dynamic buffer time adjustment ////////////////////////////////////// + // dynamically adjusts bufferTimeMultiplier for smooth results. + // to understand how this works, try this manually: + // + // - disable dynamic adjustment + // - set jitter = 0.2 (20% is a lot!) + // - notice some stuttering + // - disable interpolation to see just how much jitter this really is(!) + // - enable interpolation again + // - manually increase bufferTimeMultiplier to 3-4 + // ... the cube slows down (blue) until it's smooth + // - with dynamic adjustment enabled, it will set 4 automatically + // ... the cube slows down (blue) until it's smooth as well + // + // note that 20% jitter is extreme. + // for this to be perfectly smooth, set the safety tolerance to '2'. + // but realistically this is not necessary, and '1' is enough. + [Header("Dynamic Adjustment")] + [Tooltip("Automatically adjust bufferTimeMultiplier for smooth results.\nSets a low multiplier on stable connections, and a high multiplier on jittery connections.")] + public bool dynamicAdjustment = true; + + [Tooltip("Safety buffer that is always added to the dynamic bufferTimeMultiplier adjustment.")] + public float dynamicAdjustmentTolerance = 1; // 1 is realistically just fine, 2 is very very safe even for 20% jitter. can be half a frame too. (see above comments) + + [Tooltip("Dynamic adjustment is computed over n-second exponential moving average standard deviation.")] + public int deliveryTimeEmaDuration = 2; // 1-2s recommended to capture average delivery time + ExponentialMovingAverage deliveryTimeEma; // average delivery time (standard deviation gives average jitter) + + // debugging /////////////////////////////////////////////////////////// + [Header("Debug")] + public Color catchupColor = Color.red; + public Color slowdownColor = Color.blue; + Color defaultColor; void Awake() { defaultColor = render.sharedMaterial.color; + + // initialize EMA with 'emaDuration' seconds worth of history. + // 1 second holds 'sendRate' worth of values. + // multiplied by emaDuration gives n-seconds. + driftEma = new ExponentialMovingAverage(server.sendRate * driftEmaDuration); + deliveryTimeEma = new ExponentialMovingAverage(server.sendRate * deliveryTimeEmaDuration); } + // add snapshot & initialize client interpolation time if needed public void OnMessage(Snapshot3D snap) { - snap.localTimestamp = Time.time; - SnapshotInterpolation.InsertIfNewEnough(snap, snapshots); + // set local timestamp (= when it was received on our end) + snap.localTime = Time.timeAsDouble; + + // (optional) dynamic adjustment + if (dynamicAdjustment) + { + // set bufferTime on the fly. + // shows in inspector for easier debugging :) + bufferTimeMultiplier = SnapshotInterpolation.DynamicAdjustment( + server.sendInterval, + deliveryTimeEma.StandardDeviation, + dynamicAdjustmentTolerance + ); + } + + // insert into the buffer & initialize / adjust / catchup + SnapshotInterpolation.Insert( + snapshots, + snap, + ref localTimeline, + ref localTimescale, + server.sendInterval, + bufferTime, + catchupSpeed, + slowdownSpeed, + ref driftEma, + catchupNegativeThreshold, + catchupPositiveThreshold, + ref deliveryTimeEma); } void Update() { - // snapshot interpolation - if (interpolate) + if (snapshots.Count > 0) { - // compute snapshot interpolation & apply if any was spit out - // TODO we don't have Time.deltaTime double yet. float is fine. - if (SnapshotInterpolation.Compute( - Time.time, - Time.deltaTime, - ref serverInterpolationTime, - bufferTime, - snapshots, - catchupThreshold, catchupMultiplier, - Interpolate, - out Snapshot3D computed, - out lastCatchup)) + // snapshot interpolation + if (interpolate) { - transform.position = computed.position; + if (SnapshotInterpolation.Step( + snapshots, + Time.unscaledDeltaTime, + ref localTimeline, + localTimescale, + Interpolate, + out Snapshot3D computed)) + { + transform.position = computed.position; + } } - } - // apply raw - else - { - if (snapshots.Count > 0) + // apply raw + else { Snapshot3D snap = snapshots.Values[0]; transform.position = snap.position; @@ -84,10 +167,13 @@ void Update() } } - // color material while catching up - render.material.color = lastCatchup > 0 - ? catchupColor - : defaultColor; + // color material while catching up / slowing down + if (localTimescale < 1) + render.material.color = slowdownColor; + else if (localTimescale > 1) + render.material.color = catchupColor; + else + render.material.color = defaultColor; } void OnGUI() @@ -100,5 +186,12 @@ void OnGUI() string str = $"{snapshots.Count}"; GUI.Label(new Rect(screen.x - width / 2, screen.y - height / 2, width, height), str); } + + void OnValidate() + { + // thresholds need to be <0 and >0 + catchupNegativeThreshold = Math.Min(catchupNegativeThreshold, 0); + catchupPositiveThreshold = Math.Max(catchupPositiveThreshold, 0); + } } } diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs index 61da92387f7..2d54698aa80 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/ServerCube.cs @@ -14,7 +14,9 @@ public class ServerCube : MonoBehaviour Vector3 start; [Header("Snapshot Interpolation")] - public float sendInterval = 0.05f; // send every 50 ms + [Tooltip("Send N snapshots per second. Multiples of frame rate make sense.")] + public int sendRate = 30; // in Hz. easier to work with as int for EMA. easier to display '30' than '0.333333333' + public float sendInterval => 1f / sendRate; float lastSendTime; [Header("Latency Simulation")] diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs index 742cafee7d9..7f14b29ea4c 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs +++ b/Assets/Mirror/Examples/Snapshot Interpolation/Snapshot3D.cs @@ -1,30 +1,26 @@ -// simple 2D snapshot for the demo. -// this way we can place cubes on different Y +// a simple snapshot with timestamp & interpolation using UnityEngine; namespace Mirror.Examples.SnapshotInterpolationDemo { - // a simple snapshot with timestamp & interpolation public struct Snapshot3D : Snapshot { - public double remoteTimestamp { get; set; } - public double localTimestamp { get; set; } - public Vector2 position; + public double remoteTime { get; set; } + public double localTime { get; set; } + public Vector3 position; - public Snapshot3D(double remoteTimestamp, double localTimestamp, Vector2 position) + public Snapshot3D(double remoteTime, double localTime, Vector3 position) { - this.remoteTimestamp = remoteTimestamp; - this.localTimestamp = localTimestamp; + this.remoteTime = remoteTime; + this.localTime = localTime; this.position = position; } public static Snapshot3D Interpolate(Snapshot3D from, Snapshot3D to, double t) => new Snapshot3D( - // TODO // interpolated snapshot is applied directly. don't need timestamps. 0, 0, - // TODO // lerp unclamped in case we ever need to extrapolate. - Vector2.LerpUnclamped(from.position, to.position, (float)t)); + Vector3.LerpUnclamped(from.position, to.position, (float)t)); } } diff --git a/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity index e35a613102d..2a218cab383 100644 --- a/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity +++ b/Assets/Mirror/Examples/Snapshot Interpolation/SnapshotInterpolation.unity @@ -171,10 +171,17 @@ MonoBehaviour: server: {fileID: 474480122} render: {fileID: 1292704310} interpolate: 1 - catchupThreshold: 4 - catchupMultiplier: 0.1 bufferTimeMultiplier: 1 + catchupNegativeThreshold: -1 + catchupPositiveThreshold: 1 + catchupSpeed: 0.009999999776482582 + slowdownSpeed: 0.009999999776482582 + driftEmaDuration: 1 + dynamicAdjustment: 1 + dynamicAdjustmentTolerance: 1 + deliveryTimeEmaDuration: 2 catchupColor: {r: 1, g: 0, b: 0, a: 1} + slowdownColor: {r: 0, g: 0, b: 1, a: 1} --- !u!1 &474480117 GameObject: m_ObjectHideFlags: 0 @@ -274,11 +281,10 @@ MonoBehaviour: client: {fileID: 89338756} distance: 10 speed: 3 - sendInterval: 0.05 - latencySpikeMultiplier: 0.05 - latencySpikeSpeedMultiplier: 0.5 - loss: 0.05 + sendRate: 30 latency: 0.05 + jitter: 0.05 + loss: 0.05 scramble: 0.05 --- !u!1 &1292704307 GameObject: diff --git a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs index 64b91e13b4e..a6f6849cf16 100644 --- a/Assets/Mirror/Runtime/ExponentialMovingAverage.cs +++ b/Assets/Mirror/Runtime/ExponentialMovingAverage.cs @@ -1,20 +1,29 @@ +// N-day EMA implementation from Mirror with a few changes (struct etc.) +// it calculates an exponential moving average roughly equivalent to the last n observations +// https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average +using System; + namespace Mirror { - // implementation of N-day EMA - // it calculates an exponential moving average roughly equivalent to the last n observations - // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average - public class ExponentialMovingAverage + public struct ExponentialMovingAverage { readonly float alpha; bool initialized; public double Value; - public double Var; + public double Variance; + [Obsolete("Var was renamed to Variance")] // 2022-06-17 + public double Var => Variance; + public double StandardDeviation; // absolute value, see test public ExponentialMovingAverage(int n) { // standard N-day EMA alpha calculation alpha = 2.0f / (n + 1); + initialized = false; + Value = 0; + Variance = 0; + StandardDeviation = 0; } public void Add(double newValue) @@ -25,7 +34,8 @@ public void Add(double newValue) { double delta = newValue - Value; Value += alpha * delta; - Var = (1 - alpha) * (Var + alpha * delta * delta); + Variance = (1 - alpha) * (Variance + alpha * delta * delta); + StandardDeviation = Math.Sqrt(Variance); } else { diff --git a/Assets/Mirror/Runtime/NetworkTime.cs b/Assets/Mirror/Runtime/NetworkTime.cs index 1721524ccf7..f655f244b1f 100644 --- a/Assets/Mirror/Runtime/NetworkTime.cs +++ b/Assets/Mirror/Runtime/NetworkTime.cs @@ -61,7 +61,7 @@ public static double time /// Time measurement variance. The higher, the less accurate the time is. // TODO does this need to be public? user should only need NetworkTime.time - public static double timeVariance => _offset.Var; + public static double timeVariance => _offset.Variance; /// Time standard deviation. The highe, the less accurate the time is. // TODO does this need to be public? user should only need NetworkTime.time @@ -75,7 +75,7 @@ public static double time /// Round trip time variance. The higher, the less accurate the rtt is. // TODO does this need to be public? user should only need NetworkTime.time - public static double rttVariance => _rtt.Var; + public static double rttVariance => _rtt.Variance; /// Round trip time standard deviation. The higher, the less accurate the rtt is. // TODO does this need to be public? user should only need NetworkTime.time diff --git a/Assets/Mirror/Runtime/SnapshotInterpolation/Snapshot.cs b/Assets/Mirror/Runtime/SnapshotInterpolation/Snapshot.cs index fbf2c24c095..6b8ba8adf04 100644 --- a/Assets/Mirror/Runtime/SnapshotInterpolation/Snapshot.cs +++ b/Assets/Mirror/Runtime/SnapshotInterpolation/Snapshot.cs @@ -6,15 +6,12 @@ namespace Mirror { public interface Snapshot { - // snapshots have two timestamps: - // -> the remote timestamp (when it was sent by the remote) - // used to interpolate. - // -> the local timestamp (when we received it) - // used to know if the first two snapshots are old enough to start. - // - // IMPORTANT: the timestamp does _NOT_ need to be sent over the - // network. simply get it from batching. - double remoteTimestamp { get; set; } - double localTimestamp { get; set; } + // the remote timestamp (when it was sent by the remote) + double remoteTime { get; set; } + + // the local timestamp (when it was received on our end) + // technically not needed for basic snapshot interpolation. + // only for dynamic buffer time adjustment. + double localTime { get; set; } } } diff --git a/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs b/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs index ab836a8c652..e526351e589 100644 --- a/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs +++ b/Assets/Mirror/Runtime/SnapshotInterpolation/SnapshotInterpolation.cs @@ -1,326 +1,290 @@ -// snapshot interpolation algorithms only, -// independent from Unity/NetworkTransform/MonoBehaviour/Mirror/etc. -// the goal is to remove all the magic from it. -// => a standalone snapshot interpolation algorithm -// => that can be simulated with unit tests easily +// snapshot interpolation V2 by mischa // -// BOXING: in C#, uses does not box! passing the interface would box! +// Unity independent to be engine agnostic & easy to test. +// boxing: in C#, uses does not box! passing the interface would box! +// +// credits: +// glenn fiedler: https://gafferongames.com/post/snapshot_interpolation/ +// fholm: netcode streams +// fakebyte: standard deviation for dynamic adjustment +// ninjakicka: math & debugging using System; using System.Collections.Generic; namespace Mirror { - public static class SnapshotInterpolation + public static class SortedListExtensions { - // insert into snapshot buffer if newer than first entry - // this should ALWAYS be used when inserting into a snapshot buffer! - public static void InsertIfNewEnough(T snapshot, SortedList buffer) - where T : Snapshot + // removes the first 'amount' elements from the sorted list + public static void RemoveRange(this SortedList list, int amount) { - // we need to drop any snapshot which is older ('<=') - // the snapshots we are already working with. - double timestamp = snapshot.remoteTimestamp; + // remove the first element 'amount' times. + // handles -1 and > count safely. + for (int i = 0; i < amount && i < list.Count; ++i) + list.RemoveAt(0); + } + } - // if size == 1, then only add snapshots that are newer. - // for example, a snapshot before the first one might have been - // lagging. - if (buffer.Count == 1 && - timestamp <= buffer.Values[0].remoteTimestamp) - return; + public static class SnapshotInterpolation + { + // calculate timescale for catch-up / slow-down + // note that negative threshold should be <0. + // caller should verify (i.e. Unity OnValidate). + // improves branch prediction. + public static double Timescale( + double drift, // how far we are off from bufferTime + double catchupSpeed, // in % [0,1] + double slowdownSpeed, // in % [0,1] + double catchupNegativeThreshold, // in % of sendInteral (careful, we may run out of snapshots) + double catchupPositiveThreshold) // in % of sendInterval) + { + // if the drift time is too large, it means we are behind more time. + // so we need to speed up the timescale. + // note the threshold should be sendInterval * catchupThreshold. + if (drift > catchupPositiveThreshold) + { + // localTimeline += 0.001; // too simple, this would ping pong + return 1 + catchupSpeed; // n% faster + } - // for size >= 2, we are already interpolating between the first two - // so only add snapshots that are newer than the second entry. - // aka the 'ACB' problem: - // if we have a snapshot A at t=0 and C at t=2, - // we start interpolating between them. - // if suddenly B at t=1 comes in unexpectely, - // we should NOT suddenly steer towards B. - if (buffer.Count >= 2 && - timestamp <= buffer.Values[1].remoteTimestamp) - return; + // if the drift time is too small, it means we are ahead of time. + // so we need to slow down the timescale. + // note the threshold should be sendInterval * catchupThreshold. + if (drift < catchupNegativeThreshold) + { + // localTimeline -= 0.001; // too simple, this would ping pong + return 1 - slowdownSpeed; // n% slower + } - // otherwise sort it into the list - // an UDP messages might arrive twice sometimes. - // SortedList throws if key already exists, so check. - if (!buffer.ContainsKey(timestamp)) - buffer.Add(timestamp, snapshot); + // keep constant timescale while within threshold. + // this way we have perfectly smooth speed most of the time. + return 1; } - // helper function to check if we have 'bufferTime' worth of snapshots - // to start. - // - // glenn fiedler article: - // "Now for the trick with snapshots. What we do is instead of - // immediately rendering snapshot data received is that we buffer - // snapshots for a short amount of time in an interpolation buffer. - // This interpolation buffer holds on to snapshots for a period of time - // such that you have not only the snapshot you want to render but also, - // statistically speaking, you are very likely to have the next snapshot - // as well." - // - // => 'statistically' implies that we always wait for a fixed amount - // aka LOCAL TIME has passed. - // => it does NOT imply to wait for a remoteTime span of bufferTime. - // that would not be 'statistically'. it would be 'exactly'. - public static bool HasAmountOlderThan(SortedList buffer, double threshold, int amount) - where T : Snapshot => - buffer.Count >= amount && - buffer.Values[amount - 1].localTimestamp <= threshold; - - // for convenience, hide the 'bufferTime worth of snapshots' check in an - // easy to use function. this way we can have several conditions etc. - public static bool HasEnough(SortedList buffer, double time, double bufferTime) - where T : Snapshot => - // two snapshots with local time older than threshold? - HasAmountOlderThan(buffer, time - bufferTime, 2); + // calculate dynamic buffer time adjustment + public static double DynamicAdjustment( + double sendInterval, + double jitterStandardDeviation, + double dynamicAdjustmentTolerance) + { + // jitter is equal to delivery time standard variation. + // delivery time is made up of 'sendInterval+jitter'. + // .Average would be dampened by the constant sendInterval + // .StandardDeviation is the changes in 'jitter' that we want + // so add it to send interval again. + double intervalWithJitter = sendInterval + jitterStandardDeviation; - // sometimes we need to know if it's still safe to skip past the first - // snapshot. - public static bool HasEnoughWithoutFirst(SortedList buffer, double time, double bufferTime) - where T : Snapshot => - // still two snapshots with local time older than threshold if - // we remove the first one? (in other words, need three older) - HasAmountOlderThan(buffer, time - bufferTime, 3); + // how many multiples of sendInterval is that? + // we want to convert to bufferTimeMultiplier later. + double multiples = intervalWithJitter / sendInterval; - // calculate catchup. - // the goal is to buffer 'bufferTime' snapshots. - // for whatever reason, we might see growing buffers. - // in which case we should speed up to avoid ever growing delay. - // -> everything after 'threshold' is multiplied by 'multiplier' - public static double CalculateCatchup(SortedList buffer, int catchupThreshold, double catchupMultiplier) - where T : Snapshot - { - // NOTE: we count ALL buffer entires > threshold as excess. - // not just the 'old enough' ones. - // if buffer keeps growing, we have to catch up no matter what. - int excess = buffer.Count - catchupThreshold; - return excess > 0 ? excess * catchupMultiplier : 0; + // add the tolerance + double safezone = multiples + dynamicAdjustmentTolerance; + // UnityEngine.Debug.Log($"sendInterval={sendInterval:F3} jitter std={jitterStandardDeviation:F3} => that is ~{multiples:F1} x sendInterval + {dynamicAdjustmentTolerance} => dynamic bufferTimeMultiplier={safezone}"); + return safezone; } - // get first & second buffer entries and delta between them. - // helper function because we use this several times. - // => assumes at least two entries in buffer. - public static void GetFirstSecondAndDelta(SortedList buffer, out T first, out T second, out double delta) + // call this for every received snapshot. + // adds / inserts it to the list & initializes local time if needed. + public static void Insert( + SortedList buffer, // snapshot buffer + T snapshot, // the newly received snapshot + ref double localTimeline, // local interpolation time based on server time + ref double localTimescale, // timeline multiplier to apply catchup / slowdown over time + float sendInterval, // for debugging + double bufferTime, // offset for buffering + double catchupSpeed, // in % [0,1] + double slowdownSpeed, // in % [0,1] + ref ExponentialMovingAverage driftEma, // for catchup / slowdown + float catchupNegativeThreshold, // in % of sendInteral (careful, we may run out of snapshots) + float catchupPositiveThreshold, // in % of sendInterval + ref ExponentialMovingAverage deliveryTimeEma) // for dynamic buffer time adjustment where T : Snapshot { - // get first & second - first = buffer.Values[0]; - second = buffer.Values[1]; - - // delta between first & second is needed a lot - delta = second.remoteTimestamp - first.remoteTimestamp; - } + // first snapshot? + // initialize local timeline. + // we want it to be behind by 'offset'. + // + // note that the first snapshot may be a lagging packet. + // so we would always be behind by that lag. + // this requires catchup later. + if (buffer.Count == 0) + localTimeline = snapshot.remoteTime - bufferTime; - // the core snapshot interpolation algorithm. - // for a given remoteTime, interpolationTime and buffer, - // we tick the snapshot simulation once. - // => it's the same one on server and client - // => should be called every Update() depending on authority - // - // time: LOCAL time since startup in seconds. like Unity's Time.time. - // deltaTime: Time.deltaTime from Unity. parameter for easier tests. - // interpolationTime: time in interpolation. moved along deltaTime. - // between [0, delta] where delta is snapshot - // B.timestamp - A.timestamp. - // IMPORTANT: - // => we use actual time instead of a relative - // t [0,1] because overshoot is easier to handle. - // if relative t overshoots but next snapshots are - // further apart than the current ones, it's not - // obvious how to calculate it. - // => for example, if t = 3 every time we skip we would have to - // make sure to adjust the subtracted value relative to the - // skipped delta. way too complex. - // => actual time can overshoot without problems. - // we know it's always by actual time. - // bufferTime: time in seconds that we buffer snapshots. - // buffer: our buffer of snapshots. - // Compute() assumes full integrity of the snapshots. - // for example, when interpolating between A=0 and C=2, - // make sure that you don't add B=1 between A and C if that - // snapshot arrived after we already started interpolating. - // => InsertIfNewEnough needs to protect against the 'ACB' problem - // catchupThreshold: amount of buffer entries after which we start to - // accelerate to catch up. - // if 'bufferTime' is 'sendInterval * 3', then try - // a value > 3 like 6. - // catchupMultiplier: catchup by % per additional excess buffer entry - // over the amount of 'catchupThreshold'. - // Interpolate: interpolates one snapshot to another, returns the result - // T Interpolate(T from, T to, double t); - // => needs to be Func instead of a function in the Snapshot - // interface because that would require boxing. - // => make sure to only allocate that function once. - // out catchup: useful for debugging only. - // - // returns - // 'true' if it spit out a snapshot to apply. - // 'false' means computation moved along, but nothing to apply. - public static bool Compute( - double time, - double deltaTime, - ref double interpolationTime, - double bufferTime, - SortedList buffer, - int catchupThreshold, - float catchupMultiplier, - Func Interpolate, - out T computed, - out double catchup) - where T : Snapshot - { - // we buffer snapshots for 'bufferTime' - // for example: - // * we buffer for 3 x sendInterval = 300ms - // * the idea is to wait long enough so we at least have a few - // snapshots to interpolate between - // * we process anything older 100ms immediately + // insert into the buffer. // - // IMPORTANT: snapshot timestamps are _remote_ time - // we need to interpolate and calculate buffer lifetimes based on it. - // -> we don't know remote's current time - // -> NetworkTime.time fluctuates too much, that's no good - // -> we _could_ calculate an offset when the first snapshot arrives, - // but if there was high latency then we'll always calculate time - // with high latency - // -> at any given time, we are interpolating from snapshot A to B - // => seems like A.timestamp += deltaTime is a good way to do it - catchup = 0; - computed = default; - //Debug.Log($"{name} snapshotbuffer={buffer.Count}"); + // note that we might insert it between our current interpolation + // which is fine, it adds another data point for accuracy. + // + // note that insert may be called twice for the same key. + // by default, this would throw. + // need to handle it silently. + if (!buffer.ContainsKey(snapshot.remoteTime)) + { + buffer.Add(snapshot.remoteTime, snapshot); - // do we have enough buffered to start interpolating? - if (!HasEnough(buffer, time, bufferTime)) - return false; + // dynamic buffer adjustment needs delivery interval jitter + if (buffer.Count >= 2) + { + // note that this is not entirely accurate for scrambled inserts. + // + // we always use the last two, not what we just inserted + // even if we were to use the diff for what we just inserted, + // a scrambled insert would still not be 100% accurate: + // => assume a buffer of AC, with delivery time C-A + // => we then insert B, with delivery time B-A + // => but then technically the first C-A wasn't correct, + // as it would have to be C-B + // + // in practice, scramble is rare and won't make much difference + double previousLocalTime = buffer.Values[buffer.Count - 2].localTime; + double lastestLocalTime = buffer.Values[buffer.Count - 1].localTime; - // multiply deltaTime by catchup. - // for example, assuming a catch up of 50%: - // - deltaTime = 1s => 1.5s - // - deltaTime = 0.1s => 0.15s - // in other words, variations in deltaTime don't matter. - // simply multiply. that's just how time works. - // (50% catch up means 0.5, so we multiply by 1.5) - // - // if '0' catchup then we multiply by '1', which changes nothing. - // (faster branch prediction) - catchup = CalculateCatchup(buffer, catchupThreshold, catchupMultiplier); - deltaTime *= (1 + catchup); + // this is the delivery time since last snapshot + double localDeliveryTime = lastestLocalTime - previousLocalTime; - // interpolationTime starts at 0 and we add deltaTime to move - // along the interpolation. - // - // ONLY while we have snapshots to interpolate. - // otherwise we might increase it to infinity which would lead - // to skipping the next snapshots entirely. - // - // IMPORTANT: interpolationTime as actual time instead of - // t [0,1] allows us to overshoot and subtract easily. - // if t was [0,1], and we overshoot by 0.1, that's a - // RELATIVE overshoot for the delta between B.time - A.time. - // => if the next C.time - B.time is not the same delta, - // then the relative overshoot would speed up or slow - // down the interpolation! CAREFUL. - // - // IMPORTANT: we NEVER add deltaTime to 'time'. - // 'time' is already NOW. that's how Unity works. - interpolationTime += deltaTime; + // feed the local delivery time to the EMA. + // this is what the original stream did too. + // our final dynamic buffer adjustment is different though. + // we use standard deviation instead of average. + deliveryTimeEma.Add(localDeliveryTime); + } - // get first & second & delta - GetFirstSecondAndDelta(buffer, out T first, out T second, out double delta); + // adjust timescale to catch up / slow down after each insertion + // because that is when we add new values to our EMA. - // reached goal and have more old enough snapshots in buffer? - // then skip and move to next. - // for example, if we have snapshots at t=1,2,3 - // and we are at interpolationTime = 2.5, then - // we should skip the first one, subtract delta and interpolate - // between 2,3 instead. - // - // IMPORTANT: we only ever use old enough snapshots. - // if we wouldn't check for old enough, then we would - // move to the next one, interpolate a little bit, - // and then in next compute() wait again because it - // wasn't old enough yet. - while (interpolationTime >= delta && - HasEnoughWithoutFirst(buffer, time, bufferTime)) - { - // subtract exactly delta from interpolation time - // instead of setting to '0', where we would lose the - // overshoot part and see jitter again. + // we want localTimeline to be about 'bufferTime' behind. + // for that, we need the delivery time EMA. + // snapshots may arrive out of order, we can not use last-timeline. + // we need to use the inserted snapshot's time - timeline. + double latestRemoteTime = snapshot.remoteTime; + double timeDiff = latestRemoteTime - localTimeline; + + // next, calculate average of a few seconds worth of timediffs. + // this gives smoother results. + // + // to calculate the average, we could simply loop through the + // last 'n' seconds worth of timediffs, but: + // - our buffer may only store a few snapshots (bufferTime) + // - looping through seconds worth of snapshots every time is + // expensive // - // IMPORTANT: subtracting delta TIME works perfectly. - // subtracting '1' from a ratio of t [0,1] would - // leave the overshoot as relative between the - // next delta. if next delta is different, then - // overshoot would be bigger than planned and - // speed up the interpolation. - interpolationTime -= delta; - //Debug.LogWarning($"{name} overshot and is now at: {interpolationTime}"); + // to solve this, we use an exponential moving average. + // https://en.wikipedia.org/wiki/Moving_average#Exponential_moving_average + // which is basically fancy math to do the same but faster. + // additionally, it allows us to look at more timeDiff values + // than we sould have access to in our buffer :) + driftEma.Add(timeDiff); + + // next up, calculate how far we are currently away from bufferTime + double drift = driftEma.Value - bufferTime; + + // convert relative thresholds to absolute values based on sendInterval + double absoluteNegativeThreshold = sendInterval * catchupNegativeThreshold; + double absolutePositiveThreshold = sendInterval * catchupPositiveThreshold; - // remove first, get first, second & delta again after change. - buffer.RemoveAt(0); - GetFirstSecondAndDelta(buffer, out first, out second, out delta); + // next, set localTimescale to catchup consistently in Update(). + // we quantize between default/catchup/slowdown, + // this way we have 'default' speed most of the time(!). + // and only catch up / slow down for a little bit occasionally. + // a consistent multiplier would never be exactly 1.0. + localTimescale = Timescale(drift, catchupSpeed, slowdownSpeed, absoluteNegativeThreshold, absolutePositiveThreshold); - // NOTE: it's worth consider spitting out all snapshots - // that we skipped, in case someone still wants to move - // along them to avoid physics collisions. - // * for NetworkTransform it's unnecessary as we always - // set transform.position, which can go anywhere. - // * for CharacterController it's worth considering + // debug logging + // UnityEngine.Debug.Log($"sendInterval={sendInterval:F3} bufferTime={bufferTime:F3} drift={drift:F3} driftEma={driftEma.Value:F3} timescale={localTimescale:F3} deliveryIntervalEma={deliveryTimeEma.Value:F3}"); } + } - // interpolationTime is actual time, NOT a 't' ratio [0,1]. - // we need 't' between [0,1] relative. - // InverseLerp calculates just that. - // InverseLerp CLAMPS between [0,1] and DOES NOT extrapolate! - // => we already skipped ahead as many as possible above. - // => we do NOT extrapolate for the reasons below. - // - // IMPORTANT: - // we should NOT extrapolate & predict while waiting for more - // snapshots as this would introduce a whole range of issues: - // * player might be extrapolated WAY out if we wait for long - // * player might be extrapolated behind walls - // * once we receive a new snapshot, we would interpolate - // not from the last valid position, but from the - // extrapolated position. this could be ANYWHERE. the - // player might get stuck in walls, etc. - // => we are NOT doing client side prediction & rollback here - // => we are simply interpolating with known, valid positions - // - // SEE TEST: Compute_Step5_OvershootWithoutEnoughSnapshots_NeverExtrapolates() - double t = Mathd.InverseLerp(first.remoteTimestamp, second.remoteTimestamp, first.remoteTimestamp + interpolationTime); - //Debug.Log($"InverseLerp({first.remoteTimestamp:F2}, {second.remoteTimestamp:F2}, {first.remoteTimestamp} + {interpolationTime:F2}) = {t:F2} snapshotbuffer={buffer.Count}"); + // sample snapshot buffer to find the pair around the given time. + // returns indices so we can use it with RemoveRange to clear old snaps. + // make sure to use use buffer.Values[from/to], not buffer[from/to]. + // make sure to only call this is we have > 0 snapshots. + public static void Sample( + SortedList buffer, // snapshot buffer + double localTimeline, // local interpolation time based on server time + out int from, // the snapshot <= time + out int to, // the snapshot >= time + out double t) // interpolation factor + where T : Snapshot + { + from = -1; + to = -1; + t = 0; - // interpolate snapshot, return true to indicate we computed one - computed = Interpolate(first, second, t); + // sample from [0,count-1] so we always have two at 'i' and 'i+1'. + for (int i = 0; i < buffer.Count - 1; ++i) + { + // is local time between these two? + T first = buffer.Values[i]; + T second = buffer.Values[i + 1]; + if (localTimeline >= first.remoteTime && + localTimeline <= second.remoteTime) + { + // use these two snapshots + from = i; + to = i + 1; + t = Mathd.InverseLerp(first.remoteTime, second.remoteTime, localTimeline); + return; + } + } - // interpolationTime: - // overshooting is ONLY allowed for smooth transitions when - // immediately moving to the NEXT snapshot afterwards. - // - // if there is ANY break, for example: - // * reached second snapshot and waiting for more - // * reached second snapshot and next one isn't old enough yet - // - // then we SHOULD NOT overshoot because: - // * increasing interpolationTime by deltaTime while waiting - // would make it grow HUGE to 100+. - // * once we have more snapshots, we would skip most of them - // instantly instead of actually interpolating through them. - // - // in other words: cap time if we WOULDN'T have enough after removing - if (!HasEnoughWithoutFirst(buffer, time, bufferTime)) + // didn't find two snapshots around local time. + // so pick either the first or last, depending on which is closer. + + // oldest snapshot ahead of local time? + if (buffer.Values[0].remoteTime > localTimeline) { - // interpolationTime is always from 0..delta. - // so we cap it at delta. - // DO NOT cap it at second.remoteTimestamp. - // (that's why when interpolating the third parameter is - // first.time + interpolationTime) - // => covered with test: - // Compute_Step5_OvershootWithEnoughSnapshots_NextIsntOldEnough() - interpolationTime = Math.Min(interpolationTime, delta); + from = to = 0; + t = 0; } + // otherwise initialize both to the last one + else + { + from = to = buffer.Count - 1; + t = 0; + } + } + + // update time, sample, clear old. + // call this every update. + // returns true if there is anything to apply (requires at least 1 snap) + public static bool Step( + SortedList buffer, // snapshot buffer + double deltaTime, // engine delta time (unscaled) + ref double localTimeline, // local interpolation time based on server time + double localTimescale, // catchup / slowdown is applied to time every update + Func Interpolate, // interpolates snapshot between two snapshots + out T computed) + where T : Snapshot + { + computed = default; + + // nothing to do if there are no snapshots at all yet + if (buffer.Count == 0) + return false; + + // move local forward in time, scaled with catchup / slowdown applied + localTimeline += deltaTime * localTimescale; + + // sample snapshot buffer at local interpolation time + Sample(buffer, localTimeline, out int from, out int to, out double t); + + // now interpolate between from & to (clamped) + T fromSnap = buffer.Values[from]; + T toSnap = buffer.Values[to]; + computed = Interpolate(fromSnap, toSnap, t); + // UnityEngine.Debug.Log($"step from: {from} to {to}"); + + // remove older snapshots that we definitely don't need anymore. + // after(!) using the indices. + // + // if we have 3 snapshots and we are between 2nd and 3rd: + // from = 1, to = 2 + // then we need to remove the first one, which is exactly 'from'. + // because 'from-1' = 0 would remove none. + buffer.RemoveRange(from); + // return the interpolated snapshot return true; } } diff --git a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs similarity index 57% rename from Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs rename to Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs index 3b55264c46a..592b8010dc7 100644 --- a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs +++ b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs @@ -1,4 +1,6 @@ +// tests from Mirror using NUnit.Framework; + namespace Mirror.Tests { [TestFixture] @@ -12,7 +14,7 @@ public void TestInitial() ema.Add(3); Assert.That(ema.Value, Is.EqualTo(3)); - Assert.That(ema.Var, Is.EqualTo(0)); + Assert.That(ema.Variance, Is.EqualTo(0)); } [Test] @@ -24,7 +26,7 @@ public void TestMovingAverage() ema.Add(6); Assert.That(ema.Value, Is.EqualTo(5.1818).Within(0.0001f)); - Assert.That(ema.Var, Is.EqualTo(0.1487).Within(0.0001f)); + Assert.That(ema.Variance, Is.EqualTo(0.1487).Within(0.0001f)); } [Test] @@ -36,7 +38,20 @@ public void TestVar() ema.Add(6); ema.Add(7); - Assert.That(ema.Var, Is.EqualTo(0.6134).Within(0.0001f)); + Assert.That(ema.Variance, Is.EqualTo(0.6134).Within(0.0001f)); + } + + [Test] + public void TestStd() + { + ExponentialMovingAverage ema = new ExponentialMovingAverage(10); + + // large numbers to show that standard deviation is an absolute value + ema.Add(5); + ema.Add(600); + ema.Add(70); + + Assert.That(ema.StandardDeviation, Is.EqualTo(208.2470).Within(0.0001f)); } } } diff --git a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs.meta similarity index 83% rename from Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta rename to Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs.meta index 535f33d7797..26cd2c8b692 100644 --- a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta +++ b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTests.cs.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 8e3f2ecadd13149f29cd3e83ef6a4bff +guid: 477170930507547dbabd25c50df055de MonoImporter: externalObjects: {} serializedVersion: 2 diff --git a/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs index 748ae3fb974..d2140eac8d3 100644 --- a/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs +++ b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs @@ -10,8 +10,8 @@ namespace Mirror.Tests.NetworkTransform2k public class NetworkTransformExposed : NetworkTransform { public new NTSnapshot ConstructSnapshot() => base.ConstructSnapshot(); - public new void ApplySnapshot(NTSnapshot start, NTSnapshot goal, NTSnapshot interpolated) => - base.ApplySnapshot(start, goal, interpolated); + public new void ApplySnapshot(NTSnapshot interpolated) => + base.ApplySnapshot(interpolated); public new void OnClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale) => base.OnClientToServerSync(position, rotation, scale); public new void OnServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) => @@ -108,14 +108,14 @@ public void ConstructSnapshot() // construct snapshot double time = NetworkTime.localTime; NTSnapshot snapshot = component.ConstructSnapshot(); - Assert.That(snapshot.remoteTimestamp, Is.EqualTo(time).Within(0.01)); + Assert.That(snapshot.remoteTime, Is.EqualTo(time).Within(0.01)); Assert.That(snapshot.position, Is.EqualTo(new Vector3(1, 2, 3))); Assert.That(snapshot.rotation, Is.EqualTo(Quaternion.identity)); Assert.That(snapshot.scale, Is.EqualTo(new Vector3(4, 5, 6))); } [Test] - public void ApplySnapshot_Interpolated() + public void ApplySnapshot() { // construct snapshot with unique position/rotation/scale Vector3 position = new Vector3(1, 2, 3); @@ -126,33 +126,7 @@ public void ApplySnapshot_Interpolated() component.syncPosition = true; component.syncRotation = true; component.syncScale = true; - component.interpolatePosition = true; - component.interpolateRotation = true; - component.interpolateScale = true; - component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); - - // was it applied? - Assert.That(transform.position, Is.EqualTo(position)); - Assert.That(Quaternion.Angle(transform.rotation, rotation), Is.EqualTo(0).Within(Mathf.Epsilon)); - Assert.That(transform.localScale, Is.EqualTo(scale)); - } - - [Test] - public void ApplySnapshot_Direct() - { - // construct snapshot with unique position/rotation/scale - Vector3 position = new Vector3(1, 2, 3); - Quaternion rotation = Quaternion.Euler(45, 90, 45); - Vector3 scale = new Vector3(4, 5, 6); - - // apply snapshot without interpolation - component.syncPosition = true; - component.syncRotation = true; - component.syncScale = true; - component.interpolatePosition = false; - component.interpolateRotation = false; - component.interpolateScale = false; - component.ApplySnapshot(default, new NTSnapshot(0, 0, position, rotation, scale), default); + component.ApplySnapshot(new NTSnapshot(0, 0, position, rotation, scale)); // was it applied? Assert.That(transform.position, Is.EqualTo(position)); @@ -172,10 +146,7 @@ public void ApplySnapshot_DontSyncPosition() component.syncPosition = false; component.syncRotation = true; component.syncScale = true; - component.interpolatePosition = false; - component.interpolateRotation = true; - component.interpolateScale = true; - component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + component.ApplySnapshot(new NTSnapshot(0, 0, position, rotation, scale)); // was it applied? Assert.That(transform.position, Is.EqualTo(Vector3.zero)); @@ -195,10 +166,7 @@ public void ApplySnapshot_DontSyncRotation() component.syncPosition = true; component.syncRotation = false; component.syncScale = true; - component.interpolatePosition = true; - component.interpolateRotation = false; - component.interpolateScale = true; - component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + component.ApplySnapshot(new NTSnapshot(0, 0, position, rotation, scale)); // was it applied? Assert.That(transform.position, Is.EqualTo(position)); @@ -218,10 +186,7 @@ public void ApplySnapshot_DontSyncScale() component.syncPosition = true; component.syncRotation = true; component.syncScale = false; - component.interpolatePosition = true; - component.interpolateRotation = true; - component.interpolateScale = false; - component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + component.ApplySnapshot(new NTSnapshot(0, 0, position, rotation, scale)); // was it applied? Assert.That(transform.position, Is.EqualTo(position)); @@ -235,7 +200,7 @@ public void OnClientToServerSync_WithoutClientAuthority() // call OnClientToServerSync without authority component.clientAuthority = false; component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.serverBuffer.Count, Is.EqualTo(0)); + Assert.That(component.serverSnapshots.Count, Is.EqualTo(0)); } [Test] @@ -244,7 +209,7 @@ public void OnClientToServerSync_WithClientAuthority() // call OnClientToServerSync with authority component.clientAuthority = true; component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); } [Test] @@ -257,11 +222,11 @@ public void OnClientToServerSync_WithClientAuthority_BufferSizeLimit() // add first should work component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); // add second should be too much component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); } [Test] @@ -276,8 +241,8 @@ public void OnClientToServerSync_WithClientAuthority_Nullables_Uses_Last() // to make sure it uses the last valid position then. component.clientAuthority = true; component.OnClientToServerSync(new Vector3?(), new Quaternion?(), new Vector3?()); - Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); - NTSnapshot first = component.serverBuffer.Values[0]; + Assert.That(component.serverSnapshots.Count, Is.EqualTo(1)); + NTSnapshot first = component.serverSnapshots.Values[0]; Assert.That(first.position, Is.EqualTo(Vector3.left)); Assert.That(first.rotation, Is.EqualTo(Quaternion.identity)); Assert.That(first.scale, Is.EqualTo(Vector3.right)); @@ -295,7 +260,7 @@ public void OnServerToClientSync_WithoutClientAuthority() // call OnServerToClientSync without authority component.clientAuthority = false; component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); } // server->client sync shouldn't work if client has authority @@ -314,11 +279,11 @@ public void OnServerToClientSync_WithoutClientAuthority_bufferSizeLimit() // add first should work component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); // add second should be too much component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); } // server->client sync shouldn't work if client has authority @@ -333,7 +298,7 @@ public void OnServerToClientSync_WithClientAuthority() // call OnServerToClientSync with authority component.clientAuthority = true; component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); - Assert.That(component.clientBuffer.Count, Is.EqualTo(0)); + Assert.That(component.clientSnapshots.Count, Is.EqualTo(0)); } [Test] @@ -352,8 +317,8 @@ public void OnServerToClientSync_WithClientAuthority_Nullables_Uses_Last() // call OnClientToServerSync with authority and nullable types // to make sure it uses the last valid position then. component.OnServerToClientSync(new Vector3?(), new Quaternion?(), new Vector3?()); - Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); - NTSnapshot first = component.clientBuffer.Values[0]; + Assert.That(component.clientSnapshots.Count, Is.EqualTo(1)); + NTSnapshot first = component.clientSnapshots.Values[0]; Assert.That(first.position, Is.EqualTo(Vector3.left)); Assert.That(first.rotation, Is.EqualTo(Quaternion.identity)); Assert.That(first.scale, Is.EqualTo(Vector3.right)); diff --git a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs index b08776e37a5..a1ff95f54f3 100644 --- a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs +++ b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs @@ -1,22 +1,19 @@ - -using System; using NUnit.Framework; using System.Collections.Generic; -using UnityEngine; namespace Mirror.Tests { // a simple snapshot with timestamp & interpolation struct SimpleSnapshot : Snapshot { - public double remoteTimestamp { get; set; } - public double localTimestamp { get; set; } + public double remoteTime { get; set; } + public double localTime { get; set; } public double value; - public SimpleSnapshot(double remoteTimestamp, double localTimestamp, double value) + public SimpleSnapshot(double remoteTime, double localTime, double value) { - this.remoteTimestamp = remoteTimestamp; - this.localTimestamp = localTimestamp; + this.remoteTime = remoteTime; + this.localTime = localTime; this.value = value; } @@ -34,6 +31,12 @@ public class SnapshotInterpolationTests // buffer for convenience so we don't have to create it manually each time SortedList buffer; + // some defaults + const double catchupSpeed = 0.02; + const double slowdownSpeed = 0.04; + const double negativeThresh = -0.10; + const double positiveThresh = 0.10; + [SetUp] public void SetUp() { @@ -41,684 +44,351 @@ public void SetUp() } [Test] - public void InsertIfNewEnough() + public void RemoveRange() { - // inserting a first value should always work - SimpleSnapshot first = new SimpleSnapshot(1, 1, 0); - SnapshotInterpolation.InsertIfNewEnough(first, buffer); - Assert.That(buffer.Count, Is.EqualTo(1)); - - // insert before first should not work - SimpleSnapshot before = new SimpleSnapshot(0.5, 0.5, 0); - SnapshotInterpolation.InsertIfNewEnough(before, buffer); - Assert.That(buffer.Count, Is.EqualTo(1)); - - // insert after first should work - SimpleSnapshot second = new SimpleSnapshot(2, 2, 0); - SnapshotInterpolation.InsertIfNewEnough(second, buffer); - Assert.That(buffer.Count, Is.EqualTo(2)); - Assert.That(buffer.Values[0], Is.EqualTo(first)); - Assert.That(buffer.Values[1], Is.EqualTo(second)); + buffer.Add(1, default); + buffer.Add(2, default); + buffer.Add(3, default); - // insert after second should work - SimpleSnapshot after = new SimpleSnapshot(2.5, 2.5, 0); - SnapshotInterpolation.InsertIfNewEnough(after, buffer); + // remove negative + buffer.RemoveRange(-1); Assert.That(buffer.Count, Is.EqualTo(3)); - Assert.That(buffer.Values[0], Is.EqualTo(first)); - Assert.That(buffer.Values[1], Is.EqualTo(second)); - Assert.That(buffer.Values[2], Is.EqualTo(after)); - } - // the 'ACB' problem: - // if we have a snapshot A at t=0 and C at t=2, - // we start interpolating between them. - // if suddenly B at t=1 comes in unexpectely, - // we should NOT suddenly steer towards B. - // => inserting between the first two snapshot should never be allowed - // in order to avoid all kinds of edge cases. - [Test] - public void InsertIfNewEnough_ACB_Problem() - { - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); - SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + // remove none + buffer.RemoveRange(0); + Assert.That(buffer.Count, Is.EqualTo(3)); - // insert A and C - SnapshotInterpolation.InsertIfNewEnough(a, buffer); - SnapshotInterpolation.InsertIfNewEnough(c, buffer); + // remove multiple + buffer.RemoveRange(2); + Assert.That(buffer.Count, Is.EqualTo(1)); + Assert.That(buffer.ContainsKey(3), Is.True); - // trying to insert B between the first two snapshots should fail - SnapshotInterpolation.InsertIfNewEnough(b, buffer); - Assert.That(buffer.Count, Is.EqualTo(2)); - Assert.That(buffer.Values[0], Is.EqualTo(a)); - Assert.That(buffer.Values[1], Is.EqualTo(c)); + // remove more than it has + buffer.RemoveRange(2); + Assert.That(buffer.Count, Is.EqualTo(0)); } - // the 'first is lagging' problem: - // server sends A, B. - // A is lagging behind by 2000ms for whatever reason. - // we get B first. - // B should remain the first snapshot, the lagging A should be dropped [Test] - public void InsertIfNewEnough_FirstIsLagging_Problem() + public void Timescale() { - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + // no drift: linear time + Assert.That(SnapshotInterpolation.Timescale(0, catchupSpeed, slowdownSpeed, negativeThresh, positiveThresh), Is.EqualTo(1.0)); - // insert B. A is still delayed. - SnapshotInterpolation.InsertIfNewEnough(b, buffer); + // near negative thresh but not under it: linear time + Assert.That(SnapshotInterpolation.Timescale(-0.09, catchupSpeed, slowdownSpeed, negativeThresh, positiveThresh), Is.EqualTo(1.0)); - // now the delayed A comes in. - // timestamp is before B though. - // but it should still be dropped. - SnapshotInterpolation.InsertIfNewEnough(a, buffer); - Assert.That(buffer.Count, Is.EqualTo(1)); - Assert.That(buffer.Values[0], Is.EqualTo(b)); - } + // near positive thresh but not above it: linear time + Assert.That(SnapshotInterpolation.Timescale(0.09, catchupSpeed, slowdownSpeed, negativeThresh, positiveThresh), Is.EqualTo(1.0)); - [Test] - public void HasAmountOlderThan_NotEnough() - { - // only add two - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); - buffer.Add(a.remoteTimestamp, a); - buffer.Add(b.remoteTimestamp, b); - - // shouldn't have more old enough than two - // because we don't have more than two - Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 0, 3), Is.False); - } + // below negative thresh: catchup + Assert.That(SnapshotInterpolation.Timescale(-0.11, catchupSpeed, slowdownSpeed, negativeThresh, positiveThresh), Is.EqualTo(0.96)); - [Test] - public void HasAmountOlderThan_EnoughButNotOldEnough() - { - // add three - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); - SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); - buffer.Add(a.remoteTimestamp, a); - buffer.Add(b.remoteTimestamp, b); - buffer.Add(c.remoteTimestamp, c); - - // check at time = 1.9, where third one would not be old enough. - Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 1.9, 3), Is.False); + // above positive thresh: slowdown + Assert.That(SnapshotInterpolation.Timescale(0.11, catchupSpeed, slowdownSpeed, negativeThresh, positiveThresh), Is.EqualTo(1.02)); } [Test] - public void HasAmountOlderThan_EnoughAndOldEnough() + public void DynamicAdjustment() { - // add three - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); - SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); - buffer.Add(a.remoteTimestamp, a); - buffer.Add(b.remoteTimestamp, b); - buffer.Add(c.remoteTimestamp, c); - - // check at time = 2.1, where third one would be old enough. - Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 2.1, 3), Is.True); + // 100ms send interval, 0ms std jitter, 0.5 (50%) tolerance + // -> sendInterval+jitter = 100ms + // -> that's 1x sendInterval + // -> add 0.5x tolerance + // => 1.5x buffer multiplier + Assert.That(SnapshotInterpolation.DynamicAdjustment(0.100, 0.000, 0.5), Is.EqualTo(1.5).Within(0.0001)); + + // 100ms send interval, 10ms std jitter, 0.5 (50%) tolerance + // -> sendInterval+jitter = 110ms + // -> that's 1.1x sendInterval + // -> add 0.5x tolerance + // => 1.6x buffer multiplier + Assert.That(SnapshotInterpolation.DynamicAdjustment(0.100, 0.010, 0.5), Is.EqualTo(1.6).Within(0.0001)); } - // UDP messages might arrive twice sometimes. - // make sure InsertIfNewEnough can handle it. + // UDP packets may arrive twice with the same snapshot. + // inserting twice needs to be handled without throwing exceptions. [Test] - public void InsertIfNewEnough_Duplicate() + public void InsertTwice() { - SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); - SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; + SimpleSnapshot snap = default; - // add two valid snapshots first. - // we can't add 'duplicates' before 3rd and 4th anyway. - SnapshotInterpolation.InsertIfNewEnough(a, buffer); - SnapshotInterpolation.InsertIfNewEnough(b, buffer); + double localTimeline = 0; + double localTimescale = 0; - // insert C which is newer than B. - // then insert it again because it arrive twice. - SnapshotInterpolation.InsertIfNewEnough(c, buffer); - SnapshotInterpolation.InsertIfNewEnough(c, buffer); + // insert twice + SnapshotInterpolation.Insert(buffer, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, snap, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); - // count should still be 3. - Assert.That(buffer.Count, Is.EqualTo(3)); + // should only be inserted once + Assert.That(buffer.Count, Is.EqualTo(1)); } [Test] - public void CalculateCatchup_Empty() + public void Insert_Sorts() { - // make sure nothing happens with buffer size = 0 - Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 0, 10), Is.EqualTo(0)); - } + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; - [Test] - public void CalculateCatchup_None() - { - // add one - buffer.Add(0, default); + double localTimeline = 0; + double localTimescale = 0; - // catch-up starts at threshold = 1. so nothing. - Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 1, 10), Is.EqualTo(0)); - } + // example snaps + SimpleSnapshot a = new SimpleSnapshot(2, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 0, 43); - [Test] - public void GetFirstSecondAndDelta() - { - // add three - SimpleSnapshot a = new SimpleSnapshot(0, 1, 0); - SimpleSnapshot b = new SimpleSnapshot(2, 3, 0); - SimpleSnapshot c = new SimpleSnapshot(10, 20, 0); - buffer.Add(a.remoteTimestamp, a); - buffer.Add(b.remoteTimestamp, b); - buffer.Add(c.remoteTimestamp, c); - - SnapshotInterpolation.GetFirstSecondAndDelta(buffer, out SimpleSnapshot first, out SimpleSnapshot second, out double delta); - Assert.That(first, Is.EqualTo(a)); - Assert.That(second, Is.EqualTo(b)); - Assert.That(delta, Is.EqualTo(b.remoteTimestamp - a.remoteTimestamp)); + // insert in reverse order + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // should be in sorted order + Assert.That(buffer.Count, Is.EqualTo(2)); + Assert.That(buffer.Values[0], Is.EqualTo(a)); + Assert.That(buffer.Values[1], Is.EqualTo(b)); } [Test] - public void CalculateCatchup_Multiple() + public void Insert_InitializesLocalTimeline() { - // add three - buffer.Add(0, default); - buffer.Add(1, default); - buffer.Add(2, default); + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; - // catch-up starts at threshold = 1. so two are multiplied by 10. - Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 1, 10), Is.EqualTo(20)); - } + double localTimeline = 0; + double localTimescale = 0; - // first step: with empty buffer and defaults, nothing should happen - [Test] - public void Compute_Step1_DefaultDoesNothing() - { - // compute with defaults - double localTime = 0; - double deltaTime = 0; - double interpolationTime = 0; - float bufferTime = 0; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should not spit out any snapshot to apply - Assert.That(result, Is.False); - // no interpolation should have happened yet - Assert.That(interpolationTime, Is.EqualTo(0)); - // buffer should still be untouched - Assert.That(buffer.Count, Is.EqualTo(0)); - } + // example snaps + SimpleSnapshot a = new SimpleSnapshot(2, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 0, 43); - // third step: compute should always wait until the first two snapshots - // are older than the time we buffer ('bufferTime') - // => test for both snapshots not old enough - [Test] - public void Compute_Step3_WaitsUntilBufferTime() - { - // add two snapshots that are barely not old enough - // (localTime - bufferTime) - // IMPORTANT: use a 'definitely old enough' remoteTime to make sure - // that compute() actually checks LOCAL, not REMOTE time! - SimpleSnapshot first = new SimpleSnapshot(0.1, 0.1, 0); - SimpleSnapshot second = new SimpleSnapshot(0.9, 1.1, 0); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - double localTime = 3; - double deltaTime = 0.5; - double interpolationTime = 0; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should not spit out any snapshot to apply - Assert.That(result, Is.False); - // no interpolation should happen yet (not old enough) - Assert.That(interpolationTime, Is.EqualTo(0)); - // buffer should be untouched - Assert.That(buffer.Count, Is.EqualTo(2)); - } + // first insertion should initialize the local timeline to remote time + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + Assert.That(localTimeline, Is.EqualTo(2)); - // third step: compute should always wait until the first two snapshots - // are older than the time we buffer ('bufferTime') - // => test for only one snapshot which is old enough - [Test] - public void Compute_Step3_WaitsForSecondSnapshot() - { - // add a snapshot at t = 0 - SimpleSnapshot first = new SimpleSnapshot(0, 0, 0); - buffer.Add(first.remoteTimestamp, first); - - // compute at localTime = 2 with bufferTime = 1 - // so the threshold is anything < t=1 - double localTime = 2; - double deltaTime = 0; - double interpolationTime = 0; - float bufferTime = 1; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should not spit out any snapshot to apply - Assert.That(result, Is.False); - // no interpolation should happen yet (not enough snapshots) - Assert.That(interpolationTime, Is.EqualTo(0)); - // buffer should be untouched - Assert.That(buffer.Count, Is.EqualTo(1)); + // second insertion should not modify the timeline again + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + Assert.That(localTimeline, Is.EqualTo(2)); } - // fourth step: compute should begin if we have two old enough snapshots [Test] - public void Compute_Step4_InterpolateWithTwoOldEnoughSnapshots() + public void Insert_ComputesAverageDrift() { - // add two old enough snapshots - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - // IMPORTANT: second snapshot delta is != 1 so we can be sure that - // interpolationTime result is actual time, not 't' ratio. - // for a delta of 1, absolute and relative values would - // return the same results. - SimpleSnapshot second = new SimpleSnapshot(2, 2, 2); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - double localTime = 4; - double deltaTime = 1.5; - double interpolationTime = 0; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started just now, from 0. - // and deltaTime is 1.5, so we should be at 1.5 now. - Assert.That(interpolationTime, Is.EqualTo(1.5)); - // buffer should be untouched, we are still interpolating between the two - Assert.That(buffer.Count, Is.EqualTo(2)); - // interpolationTime is at 1.5, so 3/4 between first & second. - // computed snapshot should be interpolated at 3/4ths. - Assert.That(computed.value, Is.EqualTo(1.75).Within(Mathf.Epsilon)); + // defaults: drift ema with 3 values + ExponentialMovingAverage driftEma = new ExponentialMovingAverage(3); + ExponentialMovingAverage deliveryIntervalEma = default; + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps + SimpleSnapshot a = new SimpleSnapshot(2, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 0, 43); + SimpleSnapshot c = new SimpleSnapshot(5, 0, 43); + + // insert in order + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // first insertion initializes localTime to '2'. + // so the timediffs to '2' are: 0, 1, 3. + // which gives an ema of 1.75 + Assert.That(driftEma.Value, Is.EqualTo(1.75)); } - // fourth step: compute should begin if we have two old enough snapshots - // => test with 3 snapshots to make sure the third one - // isn't touched while t between [0,1] [Test] - public void Compute_Step4_InterpolateWithThreeOldEnoughSnapshots() + public void Insert_ComputesAverageDrift_Scrambled() { - // add three old enough snapshots. - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); - SimpleSnapshot third = new SimpleSnapshot(2, 2, 2); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - buffer.Add(third.remoteTimestamp, third); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - double localTime = 4; - double deltaTime = 0.5; - double interpolationTime = 0; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started just now, from 0. - // and deltaTime is 0.5, so we should be at 0.5 now. - Assert.That(interpolationTime, Is.EqualTo(0.5)); - // buffer should be untouched, we are still interpolating between - // the first two. third should still be there. - Assert.That(buffer.Count, Is.EqualTo(3)); - // computed snapshot should be interpolated in the middle - Assert.That(computed.value, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + // defaults: drift ema with 3 values + ExponentialMovingAverage driftEma = new ExponentialMovingAverage(3); + ExponentialMovingAverage deliveryIntervalEma = default; + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps + SimpleSnapshot a = new SimpleSnapshot(2, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 0, 43); + SimpleSnapshot c = new SimpleSnapshot(5, 0, 43); + + // insert scrambled (not in order) + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // first insertion initializes localTime to '2'. + // so the timediffs to '2' are: 0, 3, 1. + // which gives an ema of 1.25 + // + // originally timeDiff was always computed from buffer[count-1], + // which would be 0, 3, 3, which would give a (wrong) ema of 2.25. + Assert.That(driftEma.Value, Is.EqualTo(1.25)); } - // fourth step: simulate interpolation after a long time of no updates. - // for example, a mobile user might put the app in the - // background for a minute. [Test] - public void Compute_Step4_InterpolateAfterLongPause() + public void Insert_ComputesAverageDeliveryInterval() { - // add two immediate, and one that arrives 100s later - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 0); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 1); - SimpleSnapshot third = new SimpleSnapshot(101, 2, 101); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - buffer.Add(third.remoteTimestamp, third); - - // compute where we are half way between first and second, - // and now are updated 1 minute later. - double localTime = 103; // 1011+bufferTime so third snapshot is old enough - double deltaTime = 98.5; // 99s - interpolation time - double interpolationTime = 0.5; // half way between first and second - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started at 0.5, right between first & second. - // we received another snapshot at t=101. - // delta = 98.5 seconds - // => interpolationTime = 99 - // => overshoots second goal, so we move to third goal and subtract 1 - // => so we should be at 98 now - Assert.That(interpolationTime, Is.EqualTo(98)); - // we moved to the next snapshot. so only 2 should be in buffer now. - Assert.That(buffer.Count, Is.EqualTo(2)); - // delta between second and third is 100. - // interpolationTime is at 98 - // interpolationTime is relative to second.time - // => InverseLerp(1, 101, 1 + 98) = 0.98 - // which is at 98% of the value - // => Lerp(1, 101, 0.98): 101-1 is 100. 98% are 98. relative to '1' - // makes it 99. - Assert.That(computed.value, Is.EqualTo(99).Within(Mathf.Epsilon)); + // defaults: delivery ema with 2 values + // because delivery time ema is always between 2 snaps. + // so for 3 values, it's only computed twice. + ExponentialMovingAverage driftEma = new ExponentialMovingAverage(2); + ExponentialMovingAverage deliveryIntervalEma = new ExponentialMovingAverage(2); + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps with local arrival times + SimpleSnapshot a = new SimpleSnapshot(2, 3, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 4, 43); + SimpleSnapshot c = new SimpleSnapshot(5, 6, 43); + + // insert in order + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // first insertion doesn't compute delivery interval because we need 2 snaps. + // second insertion computes 4-3 = 1 + // third insertion computes 6-4 = 2 + // which gives an ema of: 2.2222 + // with a standard deviation of: 1.1331 + Assert.That(driftEma.Value, Is.EqualTo(2.2222).Within(0.0001)); + Assert.That(driftEma.StandardDeviation, Is.EqualTo(1.1331).Within(0.0001)); } - // fourth step: catchup should be considered if buffer gets too large - [Test] - public void Compute_Step4_InterpolateWithCatchup() + [Test, Ignore("Delivery Time EMA doesn't handle scrambled packages differently yet")] + public void Insert_ComputesAverageDeliveryInterval_Scrambled() { - // add two old enough snapshots - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - - // start applying 25% catchup per excess when > 2. - int catchupThreshold = 2; - float catchupMultiplier = 0.25f; - - // two excess snapshots to make sure that multiplier is accumulated - SimpleSnapshot excess1 = new SimpleSnapshot(2, 2, 3); - SimpleSnapshot excess2 = new SimpleSnapshot(3, 3, 4); - buffer.Add(excess1.remoteTimestamp, excess1); - buffer.Add(excess2.remoteTimestamp, excess2); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - double localTime = 3; - double deltaTime = 0.5; - double interpolationTime = 0; - float bufferTime = 2; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started just now, from 0. - // and deltaTime is 0.5 + 50% catchup, so we should be at 0.75 now - Assert.That(interpolationTime, Is.EqualTo(0.75)); - // buffer should be untouched, we are still interpolating between - // the first two. - Assert.That(buffer.Count, Is.EqualTo(4)); - // computed snapshot should be interpolated in 3/4 because - // interpolationTime is at 3/4 - Assert.That(computed.value, Is.EqualTo(1.75).Within(Mathf.Epsilon)); + // defaults: delivery ema with 2 values + // because delivery time ema is always between 2 snaps. + // so for 3 values, it's only computed twice. + ExponentialMovingAverage driftEma = new ExponentialMovingAverage(2); + ExponentialMovingAverage deliveryIntervalEma = new ExponentialMovingAverage(2); + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps with local arrival times + SimpleSnapshot a = new SimpleSnapshot(2, 3, 42); + SimpleSnapshot b = new SimpleSnapshot(3, 4, 43); + SimpleSnapshot c = new SimpleSnapshot(5, 6, 43); + + // insert in order + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // first insertion doesn't compute delivery interval because we need 2 snaps. + // second insertion computes 4-3 = 1 + // third insertion computes 6-4 = 2 + // which gives an ema of: 2.2222 + // with a standard deviation of: 1.1331 + Assert.That(driftEma.Value, Is.EqualTo(2.2222).Within(0.0001)); + Assert.That(driftEma.StandardDeviation, Is.EqualTo(1.1331).Within(0.0001)); } - // fifth step: interpolation time overshoots the end while waiting for - // more snapshots. - // - // IMPORTANT: we should NOT extrapolate & predict while waiting for more - // snapshots as this would introduce a whole range of issues: - // * player might be extrapolated WAY out if we wait for long - // * player might be extrapolated behind walls - // * once we receive a new snapshot, we would interpolate - // not from the last valid position, but from the - // extrapolated position. this could be ANYWHERE. the - // player might get stuck in walls, etc. - // => we are NOT doing client side prediction & rollback here - // => we are simply interpolating with known, valid positions - // - // NOTE: to reproduce the issue in a real example: - // * open mirror benchmark example - // * editor=host 1000+ monsters & deep profiling for LOW FPS - // * build=client - // * move around client - // * see it all over the place in editor because it extrapolates, - // ends up at the wrong start positions and gets worse from - // there. - // - // video: https://gyazo.com/8de68f0a821449d7b9a8424e2c9e3ff8 - // (or see Mirror/Docs/Screenshots/NT Snap. Interp./extrapolation issues) [Test] - public void Compute_Step5_OvershootWithoutEnoughSnapshots() + public void Sample() { - // add two old enough snapshots - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - // -> interpolation time is already at '1' at the end. - // -> compute will add 0.5 deltaTime - // -> so we should NOT overshoot aka extrapolate beyond second snap. - double localTime = 3; - double deltaTime = 0.5; - double interpolationTime = 1; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started at the end = 1 - // and deltaTime is 0.5, so it's at 1.5 internally. - // - // BUT there's NO reason to overshoot interpolationTime if there's - // no other snapshots to move to. - // interpolationTime overshoot is only for smooth transitions WHILE - // moving. - // for example, if we keep overshooting to 100, then we would - // instantly skip the next 20 snapshots. - // => so it should be capped at second.remoteTime - Assert.That(interpolationTime, Is.EqualTo(1)); - // buffer should be untouched, we are still interpolating between the two - Assert.That(buffer.Count, Is.EqualTo(2)); - // computed snapshot should NOT extrapolate beyond second snap. - Assert.That(computed.value, Is.EqualTo(2).Within(Mathf.Epsilon)); + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps + SimpleSnapshot a = new SimpleSnapshot(10, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(20, 0, 43); + SimpleSnapshot c = new SimpleSnapshot(30, 0, 44); + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // sample at a time before the first snapshot + SnapshotInterpolation.Sample(buffer, 9, out int from, out int to, out double t); + Assert.That(from, Is.EqualTo(0)); + Assert.That(to, Is.EqualTo(0)); + Assert.That(t, Is.EqualTo(0)); + + // sample inbetween 2nd and 3rd snapshots + SnapshotInterpolation.Sample(buffer, 22.5, out from, out to, out t); + Assert.That(from, Is.EqualTo(1)); // second + Assert.That(to, Is.EqualTo(2)); // third + Assert.That(t, Is.EqualTo(0.25)); // exactly in the middle + + // sample at a time after the third snapshot + SnapshotInterpolation.Sample(buffer, 31, out from, out to, out t); + Assert.That(from, Is.EqualTo(2)); // third + Assert.That(to, Is.EqualTo(2)); // third + Assert.That(t, Is.EqualTo(0)); } - // fifth step: interpolation time overshoots the end while having more - // snapshots available. - // BUT: the next snapshot isn't old enough yet. - // we shouldn't move there until old enough. - // for the same reason we don't move to first, second - // until they are old enough. - // => always need to be 'bufferTime' old. [Test] - public void Compute_Step5_OvershootWithEnoughSnapshots_NextIsntOldEnough() + public void Step_Empty() { - // add two old enough snapshots - // (localTime - bufferTime) - // - // IMPORTANT: second.time needs to be != second.time-first.time - // to guarantee that we cap interpolationTime (which is - // RELATIVE from 0..delta) at delta, not at second.time. - // this was a bug before. - SimpleSnapshot first = new SimpleSnapshot(1, 1, 1); - SimpleSnapshot second = new SimpleSnapshot(2, 2, 2); - // IMPORTANT: third snapshot needs to be: - // - a different time delta - // to test if overflow is correct if deltas are different. - // it's not obvious if we ever use t ratio between [0,1] where an - // overflow of 0.1 between A,B could speed up B,C interpolation if - // that's not the same delta, since t is a ratio. - // - a different value delta to check if it really _interpolates_, - // not just extrapolates further after A,B - SimpleSnapshot third = new SimpleSnapshot(4, 4, 4); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - buffer.Add(third.remoteTimestamp, third); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - // -> interpolation time is already at '1' at the end. - // -> compute will add 0.5 deltaTime - // -> so we overshoot beyond the second one and move to the next - // - // localTime is at 4 - // third snapshot localTime is at 4. - // bufferTime is 2, so it is NOT old enough and we should wait! - double localTime = 4; - double deltaTime = 0.5; - double interpolationTime = 1; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should still spit out a result between first & second. - Assert.That(result, Is.True); - // interpolation started at the end = 1 - // and deltaTime is 0.5, so we were at 1.5 internally. - // - // BUT there's NO reason to overshoot interpolationTime while we - // wait for the next snapshot which isn't old enough. - // we stopped movement anyway. - // interpolationTime overshoot is only for smooth transitions WHILE - // moving. - // for example, if we overshoot to 100 while waiting, then we would - // instantly skip the next 20 snapshots. - // => so it should be capped at max - // => which is always 0..delta, NOT first.time .. second.time!! - Assert.That(interpolationTime, Is.EqualTo(1)); - // buffer should be untouched. shouldn't have moved to third yet. - Assert.That(buffer.Count, Is.EqualTo(3)); - // computed snapshot should be all the way at second snapshot. - Assert.That(computed.value, Is.EqualTo(2).Within(Mathf.Epsilon)); + // defaults + double localTimeline = 0; + double localTimescale = 0; + + // step shouldn't do anything if buffer is still empty + Assert.False(SnapshotInterpolation.Step(buffer, 0, ref localTimeline, localTimescale, SimpleSnapshot.Interpolate, out SimpleSnapshot computed)); } - // fifth step: interpolation time overshoots the end while having more - // snapshots available. [Test] - public void Compute_Step5_OvershootWithEnoughSnapshots_MovesToNextSnapshotIfOldEnough() + public void Step() { - // add two old enough snapshots - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); - // IMPORTANT: third snapshot needs to be: - // - a different time delta - // to test if overflow is correct if deltas are different. - // it's not obvious if we ever use t ratio between [0,1] where an - // overflow of 0.1 between A,B could speed up B,C interpolation if - // that's not the same delta, since t is a ratio. - // - a different value delta to check if it really _interpolates_, - // not just extrapolates further after A,B - SimpleSnapshot third = new SimpleSnapshot(3, 3, 4); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - buffer.Add(third.remoteTimestamp, third); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - // -> interpolation time is already at '1' at the end. - // -> compute will add 0.5 deltaTime - // -> so we overshoot beyond the second one and move to the next - // - // localTime is 5. third snapshot localTime is at 3. - // bufferTime is 2. - // so third is exactly old enough and we should move there. - double localTime = 5; - double deltaTime = 0.5; - double interpolationTime = 1; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started at the end = 1 - // and deltaTime is 0.5, so we were at 1.5 internally. - // we have more snapshots, so we jump to the next and subtract '1' - // 1 + 0.5 = 1.5 => -1 => 0.5 - Assert.That(interpolationTime, Is.EqualTo(0.5)); - // buffer's first entry should have been removed - Assert.That(buffer.Count, Is.EqualTo(2)); - // computed snapshot should be 1/4 way between second and third - // because delta is 2 and interpolationTime is at 0.5 which is 1/4 - Assert.That(computed.value, Is.EqualTo(2.5).Within(Mathf.Epsilon)); + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps + SimpleSnapshot a = new SimpleSnapshot(10, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(20, 0, 43); + SimpleSnapshot c = new SimpleSnapshot(30, 0, 44); + + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // step half way to the next snapshot + Assert.True(SnapshotInterpolation.Step(buffer, 5, ref localTimeline, localTimescale, SimpleSnapshot.Interpolate, out SimpleSnapshot computed)); + Assert.That(computed.value, Is.EqualTo(42.5)); } - // fifth step: interpolation time overshoots 2x the end while having - // >= 2 more snapshots available. it should correctly jump - // ahead the first pending one to the second one. [Test] - public void Compute_Step5_OvershootWithEnoughSnapshots_2x_MovesToSecondNextSnapshot() + public void Step_RemovesOld() { - // add two old enough snapshots - // (localTime - bufferTime) - SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); - SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); - // IMPORTANT: third snapshot needs to be: - // - a different time delta - // to test if overflow is correct if deltas are different. - // it's not obvious if we ever use t ratio between [0,1] where an - // overflow of 0.1 between A,B could speed up B,C interpolation if - // that's not the same delta, since t is a ratio. - // - a different value delta to check if it really _interpolates_, - // not just extrapolates further after A,B - SimpleSnapshot third = new SimpleSnapshot(3, 3, 4); - SimpleSnapshot fourth = new SimpleSnapshot(5, 5, 6); - buffer.Add(first.remoteTimestamp, first); - buffer.Add(second.remoteTimestamp, second); - buffer.Add(third.remoteTimestamp, third); - buffer.Add(fourth.remoteTimestamp, fourth); - - // compute with initialized remoteTime and buffer time of 2 seconds - // and a delta time to be sure that we move along it no matter what. - // -> interpolation time is already at '1' at the end. - // -> compute will add 1.5 deltaTime - // -> so we should overshoot beyond second and third even - // - // localTime is 7. fourth snapshot localTime is at 5. - // bufferTime is 2. - // so fourth is exactly old enough and we should move there. - double localTime = 7; - double deltaTime = 2.5; - double interpolationTime = 1; - float bufferTime = 2; - int catchupThreshold = Int32.MaxValue; - float catchupMultiplier = 0; - bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed, out _); - - // should spit out the interpolated snapshot - Assert.That(result, Is.True); - // interpolation started at the end = 1 - // and deltaTime is 2.5, so we were at 4.5 internally. - // we have more snapshots, so we: - // * jump to third, subtract delta of 1-0 = 1 => 2.5 - // * jump to fourth, subtract delta of 3-1 = 2 => 0.5 - // * end up at 0.5 again, between third and fourth - Assert.That(interpolationTime, Is.EqualTo(0.5)); - // buffer's first entry should have been removed + // defaults + ExponentialMovingAverage driftEma = default; + ExponentialMovingAverage deliveryIntervalEma = default; + + double localTimeline = 0; + double localTimescale = 0; + + // example snaps + SimpleSnapshot a = new SimpleSnapshot(10, 0, 42); + SimpleSnapshot b = new SimpleSnapshot(20, 0, 43); + SimpleSnapshot c = new SimpleSnapshot(30, 0, 44); + + SnapshotInterpolation.Insert(buffer, a, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, b, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + SnapshotInterpolation.Insert(buffer, c, ref localTimeline, ref localTimescale, 0, 0, 0.01, 0.01, ref driftEma, 0, 0, ref deliveryIntervalEma); + + // step 1.5 snapshots worth, so way past the first one + Assert.True(SnapshotInterpolation.Step(buffer, 15, ref localTimeline, localTimescale, SimpleSnapshot.Interpolate, out SimpleSnapshot computed)); + Assert.That(computed.value, Is.EqualTo(43.5)); + + // first snapshot should've been removed since we stepped past it Assert.That(buffer.Count, Is.EqualTo(2)); - // computed snapshot should be 1/4 way between second and third - // because delta is 2 and interpolationTime is at 0.5 which is 1/4 - Assert.That(computed.value, Is.EqualTo(4.5).Within(Mathf.Epsilon)); + Assert.That(buffer.Values[0], Is.EqualTo(b)); + Assert.That(buffer.Values[1], Is.EqualTo(c)); } } } From ebd668aff9f3085850d350a37f2ee41ea3742542 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 10 Jul 2022 18:31:38 +0800 Subject: [PATCH 134/824] Readme: 2020 LTS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index eabcd519a02..893a756c1b4 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ Making multiplayer games this way is fun & easy. Instead of MonoBehaviour, Mirro * **[SyncVar]** / SyncList to automatically synchronize variables from Server->Client ## Getting Started -Get **Unity 2019 LTS**, download [Mirror on the Asset Store](https://assetstore.unity.com/packages/tools/network/mirror-129321), open one of the examples & press Play! +Get **Unity 2020 LTS**, download [Mirror on the Asset Store](https://assetstore.unity.com/packages/tools/network/mirror-129321), open one of the examples & press Play! Check out our [Documentation](https://mirror-networking.gitbook.io/) to learn how it all works. From 31d2b4510e59d9a450e480c78685a3bf97d275d8 Mon Sep 17 00:00:00 2001 From: vis2k Date: Sun, 10 Jul 2022 22:52:31 +0800 Subject: [PATCH 135/824] Readme: showcases updated; better mobile support --- README.md | 135 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 893a756c1b4..6f36d73441a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![mMirror Logo](https://user-images.githubusercontent.com/16416509/119120944-6db26780-ba5f-11eb-9cdd-fc8500207f4d.png) +![Mirror Logo](https://user-images.githubusercontent.com/16416509/119120944-6db26780-ba5f-11eb-9cdd-fc8500207f4d.png) [![Download](https://img.shields.io/badge/asset_store-brightgreen.svg)](https://assetstore.unity.com/packages/tools/network/mirror-129321) [![Documentation](https://img.shields.io/badge/docs-brightgreen.svg)](https://mirror-networking.gitbook.io/) @@ -26,6 +26,7 @@ We needed a networking library that allows us to **[launch our games](https://mi Mirror is **[stable](https://mirror-networking.gitbook.io/docs/general/tests)** & **[production](https://www.oculus.com/experiences/quest/2564158073609422/)** ready. +--- ## Free & Open Mirror is **free & open source**! * Code: MIT licensed. @@ -52,71 +53,75 @@ Check out our [Documentation](https://mirror-networking.gitbook.io/) to learn ho If you are migrating from UNET, then please check out our [Migration Guide](https://mirror-networking.gitbook.io/docs/general/migration-guide). +--- ## Made with Mirror - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Population: ONEZoobaSCP: Secret LaboratoryNaïca Online
Laurum OnlineSamuTaleNimoydThe Wall
NestablesA Glimpse of LunaOne More NightCubica
InfernaNightZRoze BludWawa United
MACE
- -And [many more](https://mirror-networking.com/showcase/)... +### [Population: ONE](https://www.populationonevr.com/) +![Population: ONE](https://user-images.githubusercontent.com/16416509/178141286-9494c3a8-a4a5-4b06-af2b-b05b66162201.png) +The [BigBoxVR](https://www.bigboxvr.com/) team started using Mirror in February 2019 for what eventually became one of the most popular Oculus Rift games. + +In addition to [24/7 support](https://github.com/sponsors/vis2k) from the Mirror team, BigBoxVR also hired one of our engineers. + +**Population: ONE** was recently [acquired by Facebook](https://uploadvr.com/population-one-facebook-bigbox-acquire/). + +### [Nimoyd](https://www.nimoyd.com/) +![nimoyd_smaller](https://user-images.githubusercontent.com/16416509/178142672-340bac2c-628a-4610-bbf1-8f718cb5b033.jpg) +Nudge Nudge Games' first title: the colorful, post-apocalyptic open world sandbox game [Nimoyd](https://store.steampowered.com/app/1313210/Nimoyd__Survival_Sandbox/) is being developed with Mirror. + +_Soon to be released for PC & mobile!_ + +### [A Glimpse of Luna](https://www.glimpse-luna.com/) +![a glimpse of luna](https://user-images.githubusercontent.com/16416509/178148229-5b619655-055a-4583-a1d3-18455bde631f.jpg) +[A Glimpse of Luna](https://www.glimpse-luna.com/) - a tactical multiplayer card battle game with the most beautiful concept art & soundtrack. + +Made with Mirror by two brothers with [no prior game development](https://www.youtube.com/watch?v=5J2wj8l4pFA&start=12) experience. + +### [Inferna](https://inferna.net/) +![Inferna MMORPG](https://user-images.githubusercontent.com/16416509/178148768-5ba9ea5b-bcf1-4ace-ad7e-591f2185cbd5.jpg) +One of the first MMORPGs made with Mirror, released in 2019. + +An open world experience with over 1000 CCU during its peak, spread across multiple server instances. + +### [Samutale](https://www.samutale.com/) +![samutale](https://user-images.githubusercontent.com/16416509/178149040-b54e0fa1-3c41-4925-8428-efd0526f8d44.jpg) +A sandbox survival samurai MMORPG, originally released in September 2016. + +Later on, the Netherlands based Maple Media switched their netcode to Mirror. + +### [Untamed Isles](https://store.steampowered.com/app/1823300/Untamed_Isles/) +![Untamed Isles](https://user-images.githubusercontent.com/16416509/178143679-1c325b54-0938-4e84-97b6-b59db62a51e7.jpg) +The turn based, monster taming **MMORPG** [Untamed Isles](https://store.steampowered.com/app/1823300/Untamed_Isles/) is currently being developed by [Phat Loot Studios](https://untamedisles.com/about/). + +After their successful [Kickstarter](https://www.kickstarter.com/projects/untamedisles/untamed-isles), the New Zealand based studio is aiming for a 2022 release date. + +### [Zooba](https://play.google.com/store/apps/details?id=com.wildlife.games.battle.royale.free.zooba&gl=US) +![Zooba](https://user-images.githubusercontent.com/16416509/178141846-60805ad5-5a6e-4840-8744-5194756c2a6d.jpg) +[Wildlife Studio's](https://wildlifestudios.com/) hit Zooba made it to rank #5 of the largest battle royal shooters in the U.S. mobile market. + +The game has over **50 million** downloads on [Google Play](https://play.google.com/store/apps/details?id=com.wildlife.games.battle.royale.free.zooba&gl=US), with Wildlife Studios as one of the top 10 largest mobile gaming companies in the world. + +### [SCP: Secret Laboratory](https://scpslgame.com/) +![scp - secret laboratory_smaller](https://user-images.githubusercontent.com/16416509/178142224-413b3455-cdff-472e-b918-4246631af12f.jpg) +[Northwood Studios'](https://store.steampowered.com/developer/NWStudios/about/) first title: the multiplayer horror game SCP: Secret Laboratory was one of Mirror's early adopters. + +Released in December 2017, today it has more than **140,000** reviews on [Steam](https://store.steampowered.com/app/700330/SCP_Secret_Laboratory/?curator_clanid=33782778). + +### [Naïca Online](https://naicaonline.com/) +![Naica Online](https://user-images.githubusercontent.com/16416509/178147710-8ed83bbd-1bce-4e14-8465-edfb40af7c7f.png) +[Naïca](https://naicaonline.com/) is a beautiful, free to play 2D pixel art MMORPG. + +The [France based team](https://naicaonline.com/en/news/view/1) was one of Mirror's early adopters, releasing their first public beta in November 2020. + +### [Laurum Online](https://laurum.online/) +![laurum online](https://user-images.githubusercontent.com/16416509/178149616-3852d198-6fc9-44d5-9f63-da4e52f5546a.jpg) +[Laurum Online](https://play.google.com/store/apps/details?id=com.project7.project7beta) - a 2D retro mobile MMORPG with over 500,000 downloads on Google Play. + +### And many more... + + + + + + ## Mirror LTS (Long Term Support) From 97fe2764bc80fdc50bfed2693f44c05e2cf2354a Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 11 Jul 2022 20:43:56 +0800 Subject: [PATCH 136/824] Tests: Grid2D proper setup --- Assets/Mirror/Tests/Editor/Grid2DTests.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Assets/Mirror/Tests/Editor/Grid2DTests.cs b/Assets/Mirror/Tests/Editor/Grid2DTests.cs index b3a6085426b..34c3b88b6c2 100644 --- a/Assets/Mirror/Tests/Editor/Grid2DTests.cs +++ b/Assets/Mirror/Tests/Editor/Grid2DTests.cs @@ -6,7 +6,13 @@ namespace Mirror.Tests { public class Grid2DTests { - Grid2D grid = new Grid2D(); + Grid2D grid; + + [SetUp] + public void SetUp() + { + grid = new Grid2D(); + } [Test] public void AddAndGetNeighbours() From 5f4a03109dbe664309080e9365d34ebafd269a2b Mon Sep 17 00:00:00 2001 From: vis2k Date: Mon, 11 Jul 2022 23:40:37 +0800 Subject: [PATCH 137/824] fix: breaking: Spatial hashing interest management resolution formula fixed. this is why spatial hashing visibility range always seemed a bit lower than with distance interest management. --- .../SpatialHashingInterestManagement.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs b/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs index eb4c2c562ac..2f78e7254b2 100644 --- a/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs +++ b/Assets/Mirror/Components/InterestManagement/SpatialHashing/SpatialHashingInterestManagement.cs @@ -12,8 +12,17 @@ public class SpatialHashingInterestManagement : InterestManagement [Tooltip("The maximum range that objects will be visible at.")] public int visRange = 30; - // if we see 8 neighbors then 1 entry is visRange/3 - public int resolution => visRange / 3; + // we use a 9 neighbour grid. + // so we always see in a distance of 2 grids. + // for example, our own grid and then one on top / below / left / right. + // + // this means that grid resolution needs to be distance / 2. + // so for example, for distance = 30 we see 2 cells = 15 * 2 distance. + // + // on first sight, it seems we need distance / 3 (we see left/us/right). + // but that's not the case. + // resolution would be 10, and we only see 1 cell far, so 10+10=20. + public int resolution => visRange / 2; [Tooltip("Rebuild all every 'rebuildInterval' seconds.")] public float rebuildInterval = 1; From 89e80a7fa2376cf4979e928f35e6bec0fe47046e Mon Sep 17 00:00:00 2001 From: vis2k Date: Tue, 12 Jul 2022 13:34:47 +0800 Subject: [PATCH 138/824] Doxygen configuration & setup --- Doxygen/Doxyfile | 2736 ++++++++++++++++++++++++++++++++++++++++++++ Doxygen/README.txt | 9 + 2 files changed, 2745 insertions(+) create mode 100644 Doxygen/Doxyfile create mode 100644 Doxygen/README.txt diff --git a/Doxygen/Doxyfile b/Doxygen/Doxyfile new file mode 100644 index 00000000000..b87e58ccde2 --- /dev/null +++ b/Doxygen/Doxyfile @@ -0,0 +1,2736 @@ +# Doxyfile 1.9.4 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). +# +# Note: +# +# Use doxygen to compare the used configuration file with the template +# configuration file: +# doxygen -x [configFile] +# Use doxygen to compare the used configuration file with the template +# configuration file without replacing the environment variables: +# doxygen -x_noenv [configFile] + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "Mirror Networking" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = /Users/qwerty/x/dev/project_Mirror/Repository/Doxygen + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create up to 4096 +# sub-directories (in 2 levels) under the output directory of each output format +# and will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. Adapt CREATE_SUBDIRS_LEVEL to +# control the number of sub-directories. +# The default value is: NO. + +CREATE_SUBDIRS = YES + +# Controls the number of sub-directories that will be created when +# CREATE_SUBDIRS tag is set to YES. Level 0 represents 16 directories, and every +# level increment doubles the number of directories, resulting in 4096 +# directories at level 8 which is the default and also the maximum value. The +# sub-directories are organized in 2 levels, the first level always has a fixed +# numer of 16 directories. +# Minimum value: 0, maximum value: 8, default value: 8. +# This tag requires that the tag CREATE_SUBDIRS is set to YES. + +CREATE_SUBDIRS_LEVEL = 8 + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Bulgarian, +# Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, English +# (United States), Esperanto, Farsi (Persian), Finnish, French, German, Greek, +# Hindi, Hungarian, Indonesian, Italian, Japanese, Japanese-en (Japanese with +# English messages), Korean, Korean-en (Korean with English messages), Latvian, +# Lithuanian, Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, +# Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, +# Swedish, Turkish, Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = ../Assets/Mirror/Runtime/Transports + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:^^" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". Note that you cannot put \n's in the value part of an alias +# to insert newlines (in the resulting output). You can put ^^ in the value part +# of an alias to insert a newline as if a physical newline was in the original +# file. When you need a literal { or } or , in the value part of an alias you +# have to escape them by means of a backslash (\), this can lead to conflicts +# with the commands \{ and \} for these it is advised to use the version @{ and +# @} or use a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = YES + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, Lex, D, PHP, md (Markdown), Objective-C, Python, Slice, +# VHDL, Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number of threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which effectively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = YES + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_HEADERFILE tag is set to YES then the documentation for a class +# will show which file needs to be included to use the class. +# The default value is: YES. + +SHOW_HEADERFILE = YES + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = NO + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= NO + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. See also section "Changing the +# layout of pages" for information. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = NO + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as documenting some parameters in +# a documented function twice, or documenting parameters that don't exist or +# using markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# If WARN_IF_INCOMPLETE_DOC is set to YES, doxygen will warn about incomplete +# function parameter documentation. If set to NO, doxygen will accept that some +# parameters have no documentation without warning. +# The default value is: YES. + +WARN_IF_INCOMPLETE_DOC = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong parameter +# documentation, but not about the absence of documentation. If EXTRACT_ALL is +# set to YES then this flag will automatically be disabled. See also +# WARN_IF_INCOMPLETE_DOC +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# See also: WARN_LINE_FORMAT +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# In the $text part of the WARN_FORMAT command it is possible that a reference +# to a more specific place is given. To make it easier to jump to this place +# (outside of doxygen) the user can define a custom "cut" / "paste" string. +# Example: +# WARN_LINE_FORMAT = "'vi $file +$line'" +# See also: WARN_FORMAT +# The default value is: at line $line of file $file. + +WARN_LINE_FORMAT = "at line $line of file $file" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). In case the file specified cannot be opened for writing the +# warning and error messages are written to standard error. When as file - is +# specified the warning and error messages are written to standard output +# (stdout). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = /Users/qwerty/x/dev/project_Mirror/Repository/Assets/Mirror/Runtime + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.l, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, +# *.inc, *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C +# comment), *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, +# *.vhdl, *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# ANamespace::AClass, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = YES + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = YES + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If the CLANG_ASSISTED_PARSING tag is set to YES and the CLANG_ADD_INC_PATHS +# tag is set to YES then doxygen will add the directory of each input to the +# include path. +# The default value is: YES. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a color-wheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use gray-scales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag determines the URL of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDURL = + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# on Windows. In the beginning of 2021 Microsoft took the original page, with +# a.o. the download links, offline the HTML help workshop was already many years +# in maintenance mode). You can download the HTML help workshop from the web +# archives at Installation executable (see: +# http://web.archive.org/web/20160201063255/http://download.microsoft.com/downlo +# ad/0/A/9/0A939EF6-E31C-430F-A3DF-DFAE7960D564/htmlhelp.exe). +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine tune the look of the index (see "Fine-tuning the output"). As an +# example, the default style sheet generated by doxygen has an example that +# shows how to put an image at the root of the tree instead of the PROJECT_NAME. +# Since the tree basically has the same information as the tab index, you could +# consider setting DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# When both GENERATE_TREEVIEW and DISABLE_INDEX are set to YES, then the +# FULL_SIDEBAR option determines if the side bar is limited to only the treeview +# area (value NO) or if it should extend to the full height of the window (value +# YES). Setting this to YES gives a layout similar to +# https://docs.readthedocs.io with more room for contents, but less room for the +# project logo, title, and description. If either GENERATE_TREEVIEW or +# DISABLE_INDEX is set to NO, this option has no effect. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FULL_SIDEBAR = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the OBFUSCATE_EMAILS tag is set to YES, doxygen will obfuscate email +# addresses. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +OBFUSCATE_EMAILS = YES + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANSPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# With MATHJAX_VERSION it is possible to specify the MathJax version to be used. +# Note that the different versions of MathJax have different requirements with +# regards to the different settings, so it is possible that also other MathJax +# settings have to be changed when switching between the different MathJax +# versions. +# Possible values are: MathJax_2 and MathJax_3. +# The default value is: MathJax_2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_VERSION = MathJax_2 + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. For more details about the output format see MathJax +# version 2 (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) and MathJax version 3 +# (see: +# http://docs.mathjax.org/en/latest/web/components/output.html). +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility. This is the name for Mathjax version 2, for MathJax version 3 +# this will be translated into chtml), NativeMML (i.e. MathML. Only supported +# for NathJax 2. For MathJax version 3 chtml will be used instead.), chtml (This +# is the name for Mathjax version 3, for MathJax version 2 this will be +# translated into HTML-CSS) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. The default value is: +# - in case of MathJax version 2: https://cdn.jsdelivr.net/npm/mathjax@2 +# - in case of MathJax version 3: https://cdn.jsdelivr.net/npm/mathjax@3 +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# for MathJax version 2 (see https://docs.mathjax.org/en/v2.7-latest/tex.html +# #tex-and-latex-extensions): +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# For example for MathJax version 3 (see +# http://docs.mathjax.org/en/latest/input/tex/extensions/index.html): +# MATHJAX_EXTENSIONS = ams +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /