diff --git a/Assets/FishNet/CodeGenerating/Helpers/GeneralHelper.cs b/Assets/FishNet/CodeGenerating/Helpers/GeneralHelper.cs index e8c0c23f..92ee6ff3 100644 --- a/Assets/FishNet/CodeGenerating/Helpers/GeneralHelper.cs +++ b/Assets/FishNet/CodeGenerating/Helpers/GeneralHelper.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices.ComTypes; +using GameKit.Dependencies.Utilities.Types; using UnityEngine; using SR = System.Reflection; @@ -28,6 +29,7 @@ internal class GeneralHelper : CodegenBase public MethodReference Extension_Attribute_Ctor_MethodRef; public MethodReference BasicQueue_Clear_MethodRef; public TypeReference List_TypeRef; + public TypeReference RingBuffer_TypeRef; public MethodReference List_Clear_MethodRef; public MethodReference List_get_Item_MethodRef; public MethodReference List_get_Count_MethodRef; @@ -160,6 +162,9 @@ public override bool ImportReferences() //Lists. tmpType = typeof(List<>); List_TypeRef = base.ImportReference(tmpType); + tmpType = typeof(RingBuffer<>); + RingBuffer_TypeRef = base.ImportReference(tmpType); + SR.MethodInfo lstMi; lstMi = tmpType.GetMethod("Add"); List_Add_MethodRef = base.ImportReference(lstMi); @@ -231,8 +236,6 @@ void GeneratedComparers() return true; } - - #region Resolves. /// /// Adds a typeRef to TypeReferenceResolves. @@ -288,7 +291,6 @@ public MethodDefinition GetMethodReferenceResolve(MethodReference methodRef) return result; } - /// /// Adds a fieldRef to FieldReferenceResolves. /// @@ -317,7 +319,6 @@ public FieldDefinition GetFieldReferenceResolve(FieldReference fieldRef) } #endregion - /// /// Makes a method an extension method. /// @@ -419,10 +420,10 @@ public bool HasNotSerializableAttribute(SR.MethodInfo methodInfo) if (item.AttributeType.FullName == NotSerializerAttribute_FullName) return true; } - + return false; } - + /// /// Returns if type uses CodegenExcludeAttribute. /// @@ -433,7 +434,7 @@ public bool HasNotSerializableAttribute(MethodDefinition methodDef) if (item.AttributeType.FullName == NotSerializerAttribute_FullName) return true; } - + return false; } #endregion @@ -450,7 +451,6 @@ public void CallCopiedMethod(MethodDefinition md, MethodDefinition copiedMd) MethodReference mr = copiedMd.GetMethodReference(base.Session); processor.Emit(OpCodes.Call, mr); - } /// @@ -461,15 +461,14 @@ public List ListRemoveRange(MethodDefinition methodDef, FieldDefini /* Remove entries which exceed maximum buffer. */ //Method references for uint/data list: //get_count, RemoveRange. */ - GenericInstanceType dataListGit; - GetGenericList(dataTr, out dataListGit); + GenericInstanceType dataListGit = GetGenericList(dataTr); MethodReference lstDataRemoveRangeMr = base.GetClass().List_RemoveRange_MethodRef.MakeHostInstanceGeneric(base.Session, dataListGit); List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); //Index 1 is the uint, 0 is the data. - insts.Add(processor.Create(OpCodes.Ldarg_0));//this. + insts.Add(processor.Create(OpCodes.Ldarg_0)); //this. insts.Add(processor.Create(OpCodes.Ldfld, dataFd)); insts.Add(processor.Create(OpCodes.Ldc_I4_0)); insts.Add(processor.Create(OpCodes.Ldloc, countVd)); @@ -477,21 +476,51 @@ public List ListRemoveRange(MethodDefinition methodDef, FieldDefini return insts; } + + /// + /// Outputs generic lists for dataTr. + /// + public GenericInstanceType GetGenericList(TypeReference dataTr) + { + TypeReference typeTr = base.ImportReference(typeof(List<>)); + return typeTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); + } + + + /// + /// Outputs generic Dictionary for keyTr and valueTr. + /// + public GenericInstanceType GetGenericDictionary(TypeReference keyTr, TypeReference valueTr) + { + TypeReference typeTr = base.ImportReference(typeof(Dictionary<,>)); + return typeTr.MakeGenericInstanceType(new TypeReference[] { keyTr, valueTr }); + } + /// - /// Outputs generic lists for dataTr and uint. + /// Outputs generic RingBuffer for dataTr. /// - public void GetGenericList(TypeReference dataTr, out GenericInstanceType lstData) + public GenericInstanceType GetGenericRingBuffer(TypeReference dataTr) { - TypeReference listDataTr = base.ImportReference(typeof(List<>)); - lstData = listDataTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); + TypeReference typeTr = base.ImportReference(typeof(RingBuffer<>)); + return typeTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); } + /// - /// Outputs generic lists for dataTr and uint. + /// Gets a generic instance of any type with optional arguments. /// - public void GetGenericBasicQueue(TypeReference dataTr, out GenericInstanceType queueData) + public GenericInstanceType GetGenericType(Type type, params TypeReference[] datasTr) { - TypeReference queueDataTr = base.ImportReference(typeof(BasicQueue<>)); - queueData = queueDataTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); + TypeReference typeTr = base.ImportReference(type); + return typeTr.MakeGenericInstanceType(datasTr); + } + + /// + /// Outputs generic BasicQueue for dataTr. + /// + public GenericInstanceType GetGenericBasicQueue(TypeReference dataTr) + { + TypeReference typeTr = base.ImportReference(typeof(BasicQueue<>)); + return typeTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); } /// @@ -536,7 +565,6 @@ public MethodDefinition CopyMethodSignature(MethodDefinition originalMd, string return md; } - /// /// Creates the RuntimeInitializeOnLoadMethod attribute for a method. /// @@ -609,7 +637,6 @@ public MethodDefinition GetOrCreateMethod(TypeDefinition typeDef, out bool creat return result; } - /// /// Gets a class within moduleDef or creates and returns the class if it does not already exist. /// @@ -629,8 +656,7 @@ public TypeDefinition GetOrCreateClass(out bool created, TypeAttributes typeAttr else { created = true; - type = new(namespaceName, className, - typeAttr, base.ImportReference(typeof(object))); + type = new(namespaceName, className, typeAttr, base.ImportReference(typeof(object))); //Add base class if specified. if (baseTypeRef != null) type.BaseType = base.ImportReference(baseTypeRef); @@ -657,6 +683,7 @@ public bool HasNonSerializableAttribute(FieldDefinition fieldDef) //Fall through, no matches. return false; } + /// /// Returns if typeDef has a NonSerialized attribute. /// @@ -731,16 +758,12 @@ public MethodDefinition GetOrCreateConstructor(TypeDefinition typeDef, out bool else { created = true; - MethodAttributes methodAttr = (MonoFN.Cecil.MethodAttributes.HideBySig | - MonoFN.Cecil.MethodAttributes.SpecialName | - MonoFN.Cecil.MethodAttributes.RTSpecialName); + MethodAttributes methodAttr = (MonoFN.Cecil.MethodAttributes.HideBySig | MonoFN.Cecil.MethodAttributes.SpecialName | MonoFN.Cecil.MethodAttributes.RTSpecialName); if (makeStatic) methodAttr |= MonoFN.Cecil.MethodAttributes.Static; //Create a constructor. - constructorMethodDef = new(".ctor", methodAttr, - typeDef.Module.TypeSystem.Void - ); + constructorMethodDef = new(".ctor", methodAttr, typeDef.Module.TypeSystem.Void); typeDef.Methods.Add(constructorMethodDef); @@ -875,6 +898,7 @@ public ParameterDefinition CreateParameter(MethodDefinition methodDef, TypeRefer methodDef.Parameters.Insert(index, parameterDef); return parameterDef; } + /// /// Creates a parameter within methodDef and returns it's ParameterDefinition. /// @@ -885,6 +909,7 @@ public ParameterDefinition CreateParameter(MethodDefinition methodDef, Type para { return CreateParameter(methodDef, GetTypeReference(parameterType), name, attributes, index); } + /// /// Creates a variable type within the body and returns it's VariableDef. /// @@ -897,6 +922,7 @@ public VariableDefinition CreateVariable(MethodDefinition methodDef, TypeReferen methodDef.Body.Variables.Add(variableDef); return variableDef; } + /// Creates a variable type within the body and returns it's VariableDef. /// /// @@ -974,6 +1000,7 @@ public void SetVariableDefinitionFromInt(ILProcessor processor, VariableDefiniti processor.Emit(OpCodes.Ldc_I4, value); processor.Emit(OpCodes.Stloc, variableDef); } + /// /// Assigns value to a VariableDef. /// @@ -1007,7 +1034,6 @@ public bool IsCallToMethod(Instruction instruction, out MethodDefinition calledM } } - /// /// Returns if a serializer and deserializer exist for typeRef. /// @@ -1060,11 +1086,10 @@ public MethodDefinition CreateEqualityComparer(TypeReference dataTr) MethodDefinition comparerMd; if (!_comparerDelegates.TryGetValue(dataTr.FullName, out comparerMd)) { - comparerMd = GetOrCreateMethod(GeneratedComparer_ClassTypeDef, out created, WriterProcessor.GENERATED_METHOD_ATTRIBUTES, - $"Comparer___{dataTr.FullName}", base.Module.TypeSystem.Boolean); + comparerMd = GetOrCreateMethod(GeneratedComparer_ClassTypeDef, out created, WriterProcessor.GENERATED_METHOD_ATTRIBUTES, $"Comparer___{dataTr.FullName}", base.Module.TypeSystem.Boolean); /* Nullables are not yet supported for automatic - * comparers. Let user know they must make their own. */ + * comparers. Let user know they must make their own. */ if (dataTr.IsGenericInstance) { base.LogError($"Equality comparers cannot be automatically generated for generic types. Create a custom comparer for {dataTr.FullName}."); @@ -1091,7 +1116,7 @@ void CreateComparerMethod() ILProcessor processor = comparerMd.Body.GetILProcessor(); comparerMd.Body.InitLocals = true; - /* If type is a Unity type do not try to + /* If type is a Unity type do not try to * create a comparer other than ref comparer, as Unity will have built in ones. */ if (dataTr.CachedResolve(base.Session).Module.Name.Contains("UnityEngine")) { @@ -1147,15 +1172,13 @@ void CreateGenericInstanceComparer() } - void CreateClassOrStructComparer() { //Class or struct. Instruction exitMethodInst = processor.Create(OpCodes.Ldc_I4_0); //Fields. - foreach (FieldDefinition fieldDef in dataTr.FindAllSerializableFields(base.Session - , null, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) + foreach (FieldDefinition fieldDef in dataTr.FindAllSerializableFields(base.Session, null, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { FieldReference fr = base.ImportReference(fieldDef); MethodDefinition recursiveMd = CreateEqualityComparer(fieldDef.FieldType); @@ -1169,8 +1192,7 @@ void CreateClassOrStructComparer() } //Properties. - foreach (PropertyDefinition propertyDef in dataTr.FindAllSerializableProperties(base.Session - , null, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) + foreach (PropertyDefinition propertyDef in dataTr.FindAllSerializableProperties(base.Session, null, WriterProcessor.EXCLUDED_ASSEMBLY_PREFIXES)) { MethodReference getMr = base.Module.ImportReference(propertyDef.GetMethod); MethodDefinition recursiveMd = CreateEqualityComparer(getMr.ReturnType); @@ -1229,7 +1251,6 @@ void FinishTypeReferenceCompare(TypeReference tr) { processor.Emit(OpCodes.Bne_Un, exitMethodInst); } - } } @@ -1241,9 +1262,7 @@ void CreateValueOrReferenceComparer() processor.Emit(OpCodes.Ceq); processor.Emit(OpCodes.Ret); } - } - } /// @@ -1255,6 +1274,7 @@ public void RegisterComparerDelegate(MethodDefinition methodDef, TypeReference d { _comparerDelegates.Add(dataTr.FullName, methodDef); } + /// /// Creates a delegate for GeneratedComparers. /// @@ -1280,8 +1300,6 @@ public void CreateComparerDelegate(MethodDefinition comparerMd, TypeReference da processor.InsertFirst(insts); } - - /// /// Returns an OpCode for loading a parameter. /// @@ -1308,8 +1326,7 @@ public void CreateIsDefaultComparer(TypeReference dataTr, MethodDefinition compa { GeneralHelper gh = base.GetClass(); - MethodDefinition isDefaultMd = gh.GetOrCreateMethod(GeneratedComparer_ClassTypeDef, out bool created, WriterProcessor.GENERATED_METHOD_ATTRIBUTES, - $"IsDefault___{dataTr.FullName}", base.Module.TypeSystem.Boolean); + MethodDefinition isDefaultMd = gh.GetOrCreateMethod(GeneratedComparer_ClassTypeDef, out bool created, WriterProcessor.GENERATED_METHOD_ATTRIBUTES, $"IsDefault___{dataTr.FullName}", base.Module.TypeSystem.Boolean); //Already done. This can happen if the same replicate data is used in multiple places. if (!created) return; @@ -1344,8 +1361,6 @@ void CreateIsDefaultMethod() processor.Emit(OpCodes.Call, compareMr); processor.Emit(OpCodes.Ret); - - } //Creates a delegate to compare two of replicateTr. @@ -1369,7 +1384,6 @@ void CreateIsDefaultDelegate() insts.Add(processor.Create(OpCodes.Call, isDefaultMr)); processor.InsertFirst(insts); } - } #endregion } diff --git a/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs b/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs index 6f0952ee..c1bc89c4 100644 --- a/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs +++ b/Assets/FishNet/CodeGenerating/Helpers/NetworkBehaviourHelper.cs @@ -33,6 +33,7 @@ internal class NetworkBehaviourHelper : CodegenBase public MethodReference Replicate_NonAuthoritative_MethodRef; public MethodReference Replicate_Authortative_MethodRef; public MethodReference Reconcile_Client_MethodRef; + public MethodReference Reconcile_Client_Local_MethodRef; public MethodReference Replicate_Replay_MethodRef; public MethodReference Reconcile_Reader_MethodRef; public MethodReference RegisterReplicateRpc_MethodRef; @@ -44,7 +45,6 @@ internal class NetworkBehaviourHelper : CodegenBase public MethodReference SendServerRpc_MethodRef; public MethodReference SendObserversRpc_MethodRef; public MethodReference SendTargetRpc_MethodRef; - public MethodReference DirtySyncType_MethodRef; public MethodReference RegisterServerRpc_MethodRef; public MethodReference RegisterObserversRpc_MethodRef; public MethodReference RegisterTargetRpc_MethodRef; @@ -117,11 +117,11 @@ public override bool ImportReferences() Reconcile_Server_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Client)) Reconcile_Client_MethodRef = base.ImportReference(mi); + else if (mi.Name == nameof(NetworkBehaviour.Reconcile_Client_Local)) + Reconcile_Client_Local_MethodRef = base.ImportReference(mi); //Misc. else if (mi.Name == nameof(NetworkBehaviour.OwnerMatches)) OwnerMatches_MethodRef = base.ImportReference(mi); - else if (mi.Name == nameof(NetworkBehaviour.DirtySyncType)) - DirtySyncType_MethodRef = base.ImportReference(mi); else if (mi.Name == nameof(NetworkBehaviour.NetworkInitializeIfDisabled)) NetworkInitializeIfDisabled_MethodRef = base.ImportReference(mi); //Prediction @@ -178,7 +178,6 @@ internal MethodDefinition GetAwakeMethodDefinition(TypeDefinition typeDef) return typeDef.GetMethod(AWAKE_METHOD_NAME); } - /// /// Creates a replicate delegate. /// @@ -205,8 +204,6 @@ internal void CreateReplicateDelegate(MethodDefinition originalMethodDef, Method processor.InsertLast(insts); } - - /// /// Creates a RPC delegate for rpcType. /// @@ -277,9 +274,7 @@ internal Instruction CreateLocalClientIsOwnerCheck(MethodDefinition methodDef, L if (loggingType != LoggingType.Off) { string disableLoggingText = (notifyMessageCanBeDisabled) ? DISABLE_LOGGING_TEXT : string.Empty; - string msg = (retIfOwner) ? - $"Cannot complete action because you are the owner of this object. {disableLoggingText}." : - $"Cannot complete action because you are not the owner of this object. {disableLoggingText}."; + string msg = (retIfOwner) ? $"Cannot complete action because you are the owner of this object. {disableLoggingText}." : $"Cannot complete action because you are not the owner of this object. {disableLoggingText}."; instructions.AddRange(base.GetClass().LogMessage(methodDef, msg, loggingType)); } @@ -387,8 +382,8 @@ internal void CreateIsClientCheck(MethodDefinition methodDef, LoggingType loggin internal void CreateIsServerCheck(MethodDefinition methodDef, LoggingType loggingType, bool useStatic, bool insertFirst, bool checkIsNetworked) { /* This is placed after the if check. - * Should the if check pass then code - * jumps to this instruction. */ + * Should the if check pass then code + * jumps to this instruction. */ ILProcessor processor = methodDef.Body.GetILProcessor(); Instruction endIf = processor.Create(OpCodes.Nop); @@ -445,7 +440,6 @@ private List CreateIsNetworkedCheck(MethodDefinition methodDef, Ins return insts; } - /// /// Creates a return using the ReturnType for methodDef. /// diff --git a/Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs b/Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs index 133b3a54..da7ff101 100644 --- a/Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs +++ b/Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs @@ -15,6 +15,7 @@ using MonoFN.Cecil.Rocks; using System.Collections.Generic; using System.Linq; +using GameKit.Dependencies.Utilities.Types; using UnityEngine; using SR = System.Reflection; @@ -23,7 +24,6 @@ namespace FishNet.CodeGenerating.Processing internal class PredictionProcessor : CodegenBase { #region Types. - private class PredictionAttributedMethods { public MethodDefinition ReplicateMethod; @@ -70,6 +70,10 @@ private class CreatedPredictionFields /// public readonly FieldDefinition ReconcileData; /// + /// Reconcile data cached locally from the local client. + /// + public readonly FieldDefinition ReconcileDatasHistory; + /// /// A buffer to read replicates into. /// public readonly FieldDefinition ServerReplicateReaderBuffer; @@ -78,8 +82,7 @@ private class CreatedPredictionFields /// public readonly FieldDefinition LastReadReplicate; - public CreatedPredictionFields(TypeReference replicateDataTypeRef, FieldDefinition replicateULDelegate, FieldDefinition reconcileULDelegate, FieldDefinition replicateDatasQueue, FieldDefinition replicateDatasHistory, FieldDefinition reconcileData, - FieldDefinition serverReplicateReaderBuffer, FieldDefinition lastReadReplicate) + public CreatedPredictionFields(TypeReference replicateDataTypeRef, FieldDefinition replicateULDelegate, FieldDefinition reconcileULDelegate, FieldDefinition replicateDatasQueue, FieldDefinition replicateDatasHistory, FieldDefinition reconcileData, FieldDefinition reconcileDatasHistory, FieldDefinition serverReplicateReaderBuffer, FieldDefinition lastReadReplicate) { ReplicateDataTypeRef = replicateDataTypeRef; ReplicateULDelegate = replicateULDelegate; @@ -87,6 +90,7 @@ public CreatedPredictionFields(TypeReference replicateDataTypeRef, FieldDefiniti ReplicateDatasQueue = replicateDatasQueue; ReplicateDatasHistory = replicateDatasHistory; ReconcileData = reconcileData; + ReconcileDatasHistory = reconcileDatasHistory; ServerReplicateReaderBuffer = serverReplicateReaderBuffer; LastReadReplicate = lastReadReplicate; } @@ -103,11 +107,9 @@ public PredictionReaders(MethodReference replicateReader, MethodReference reconc ReconcileReader = reconcileReader; } } - #endregion #region Public. - public string IReplicateData_FullName = typeof(IReplicateData).FullName; public string IReconcileData_FullName = typeof(IReconcileData).FullName; public TypeReference ReplicateULDelegate_TypeRef; @@ -124,6 +126,7 @@ public PredictionReaders(MethodReference replicateReader, MethodReference reconc public override bool ImportReferences() { System.Type locType; + SR.MethodInfo locMi; base.ImportReference(typeof(BasicQueue<>)); ReplicateULDelegate_TypeRef = base.ImportReference(typeof(ReplicateUserLogicDelegate<>)); @@ -154,7 +157,6 @@ public override bool ImportReferences() } #region Setup and checks. - /// /// Gets number of predictions by checking for prediction attributes. This does not perform error checking. /// @@ -301,7 +303,6 @@ bool AlreadyFound(MethodDefinition md) else return true; } - #endregion internal bool Process(TypeDefinition typeDef) @@ -320,7 +321,7 @@ internal bool Process(TypeDefinition typeDef) RpcProcessor rp = base.GetClass(); uint predictionRpcCount = GetPredictionCountInParents(typeDef) + rp.GetRpcCountInParents(typeDef); - + //If replication methods found but this hierarchy already has max. if (predictionRpcCount >= NetworkBehaviourHelper.MAX_RPC_ALLOWANCE) { @@ -374,7 +375,7 @@ internal bool Process(TypeDefinition typeDef) MethodDefinition replicateULMd; MethodDefinition reconcileULMd; CreatePredictionMethods(typeDef, replicateMd, reconcileMd, predictionFields, predictionRpcCount, out predictionReaders, out replicateULMd, out reconcileULMd); - InitializeCollections(typeDef, replicateMd, predictionFields); + InitializeCollections(typeDef, replicateMd, reconcileMd, predictionFields); InitializeULDelegates(typeDef, predictionFields, replicateMd, reconcileMd, replicateULMd, reconcileULMd); RegisterPredictionRpcs(typeDef, predictionRpcCount, predictionReaders); @@ -478,29 +479,37 @@ void Register(MethodDefinition readerMd, bool replicate) /// Initializes collection fields made during this process. /// /// - private void InitializeCollections(TypeDefinition typeDef, MethodDefinition replicateMd, CreatedPredictionFields predictionFields) + private void InitializeCollections(TypeDefinition typeDef, MethodDefinition replicateMd, MethodDefinition reconcileMd, CreatedPredictionFields predictionFields) { GeneralHelper gh = base.GetClass(); TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType; + TypeReference reconcileDataTr = reconcileMd.Parameters[0].ParameterType; MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME); ILProcessor processor = injectionMethodDef.Body.GetILProcessor(); - Generate(predictionFields.ReplicateDatasQueue, false); - Generate(predictionFields.ReplicateDatasHistory, true); + Generate(predictionFields.ReplicateDatasQueue, gh.GetGenericBasicQueue(replicateDataTr), isRingBuffer: false); + Generate(predictionFields.ReplicateDatasHistory, gh.GetGenericRingBuffer(replicateDataTr), isRingBuffer: true); + + GenericInstanceType localReconcileGit = gh.GetGenericType(typeof(LocalReconcile<>), reconcileDataTr); + GenericInstanceType reconcileRingBufferDataGit = gh.GetGenericRingBuffer(localReconcileGit); + Generate(predictionFields.ReconcileDatasHistory, gh.GetGenericRingBuffer(localReconcileGit), isRingBuffer: true); - void Generate(FieldReference fr, bool isList) + void Generate(FieldReference fr, GenericInstanceType git, bool isRingBuffer) { - MethodDefinition ctorMd = base.GetClass().List_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session); - GenericInstanceType collectionGit; - if (isList) - gh.GetGenericList(replicateDataTr, out collectionGit); + MethodDefinition ctorMd; + if (isRingBuffer) + //ctorMd = base.GetClass().RingBuffer_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session); + ctorMd = base.GetClass().RingBuffer_TypeRef.CachedResolve(base.Session).GetConstructor(base.Session, 1); else - gh.GetGenericBasicQueue(replicateDataTr, out collectionGit); - MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, collectionGit); + ctorMd = base.GetClass().List_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session); + + MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, git); List insts = new(); insts.Add(processor.Create(OpCodes.Ldarg_0)); + if (isRingBuffer) + insts.Add(processor.Create(OpCodes.Ldc_I4, RingBuffer.DEFAULT_CAPACITY)); insts.Add(processor.Create(OpCodes.Newobj, ctorMr)); insts.Add(processor.Create(OpCodes.Stfld, fr)); @@ -557,33 +566,39 @@ private void CreateFields(TypeDefinition typeDef, MethodDefinition replicateMd, GenericInstanceType replicateULDelegateGit; GenericInstanceType reconcileULDelegateGit; - GenericInstanceType lstDataGit; - GenericInstanceType queueDataGit; + GetGenericULDelegate(replicateDataTr, typeof(ReplicateUserLogicDelegate<>), out replicateULDelegateGit); GetGenericULDelegate(reconcileDataTr, typeof(ReconcileUserLogicDelegate<>), out reconcileULDelegateGit); - gh.GetGenericList(replicateDataTr, out lstDataGit); - gh.GetGenericBasicQueue(replicateDataTr, out queueDataGit); + GenericInstanceType replicateRingBufferDataGit = gh.GetGenericRingBuffer(replicateDataTr); + GenericInstanceType queueDataGit = gh.GetGenericBasicQueue(replicateDataTr); + + //Make generic LocalReconcile. + + //TypeReference localReconcileTr = base.ImportReference(typeof(LocalReconcile<>)); + //GenericInstanceType reconcileRingBufferDataGit = gh.GetGenericRingBuffer(localReconcileTr); + GenericInstanceType localReconcileGit = gh.GetGenericType(typeof(LocalReconcile<>), reconcileDataTr); + GenericInstanceType reconcileRingBufferDataGit = gh.GetGenericRingBuffer(localReconcileGit); - base.ImportReference(lstDataGit); /* Data buffer. */ FieldDefinition replicateULDelegateFd = new($"_replicateULDelegate___{replicateMd.Name}", FieldAttributes.Private, replicateULDelegateGit); FieldDefinition reconcileULDelegateFd = new($"_reconcileULDelegate___{reconcileMd.Name}", FieldAttributes.Private, reconcileULDelegateGit); FieldDefinition replicatesQueueFd = new($"_replicatesQueue___{replicateMd.Name}", FieldAttributes.Private, queueDataGit); - FieldDefinition replicatesListFd = new($"_replicatesHistory___{replicateMd.Name}", FieldAttributes.Private, lstDataGit); + FieldDefinition replicatesRingBufferFd = new($"_replicatesHistory___{replicateMd.Name}", FieldAttributes.Private, replicateRingBufferDataGit); FieldDefinition reconcileDataFd = new($"_reconcileData___{replicateMd.Name}", FieldAttributes.Private, reconcileDataTr); + FieldDefinition reconcilesRingBufferFd = new($"_reconcilesHistory___{reconcileMd.Name}", FieldAttributes.Private, reconcileRingBufferDataGit); FieldDefinition serverReplicatesReadBufferFd = new($"_serverReplicateReadBuffer___{replicateMd.Name}", FieldAttributes.Private, replicateDataArrTr); FieldDefinition lastReadReplicateFd = new($"_lastReadReplicate___{replicateMd.Name}", FieldAttributes.Private, replicateDataTr); typeDef.Fields.Add(replicateULDelegateFd); typeDef.Fields.Add(reconcileULDelegateFd); typeDef.Fields.Add(replicatesQueueFd); - typeDef.Fields.Add(replicatesListFd); + typeDef.Fields.Add(replicatesRingBufferFd); typeDef.Fields.Add(reconcileDataFd); + typeDef.Fields.Add(reconcilesRingBufferFd); typeDef.Fields.Add(serverReplicatesReadBufferFd); typeDef.Fields.Add(lastReadReplicateFd); - predictionFields = new(replicateDataTr, replicateULDelegateFd, reconcileULDelegateFd, replicatesQueueFd, replicatesListFd, reconcileDataFd, - serverReplicatesReadBufferFd, lastReadReplicateFd); + predictionFields = new(replicateDataTr, replicateULDelegateFd, reconcileULDelegateFd, replicatesQueueFd, replicatesRingBufferFd, reconcileDataFd, reconcilesRingBufferFd, serverReplicatesReadBufferFd, lastReadReplicateFd); } /// @@ -670,7 +685,10 @@ private bool CreatePredictionMethods(TypeDefinition typeDef, MethodDefinition re if (!CreateReplicateReplayStart()) return false; - CreateClearReplicateCacheMethod(typeDef, replicateMd.Parameters[0].ParameterType, predictionFields); + TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType; + TypeReference reconcileDataTr = reconcileMd.Parameters[0].ParameterType; + + CreateClearReplicateCacheMethod(typeDef, replicateDataTr, reconcileDataTr, predictionFields); CreateReplicateReader(typeDef, startingRpcCount, replicateMd, predictionFields, out replicateReader); CreateReconcileReader(typeDef, reconcileMd, predictionFields, out reconcileReader); predictionReaders = new(replicateReader, reconcileReader); @@ -704,14 +722,12 @@ bool CreateReconcile() bool CreateEmptyReplicatesQueueIntoHistoryStart() { - MethodDefinition newMethodDef = nbh.EmptyReplicatesQueueIntoHistory_Start_MethodRef.CachedResolve(base.Session).CreateCopy( - base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); + MethodDefinition newMethodDef = nbh.EmptyReplicatesQueueIntoHistory_Start_MethodRef.CachedResolve(base.Session).CreateCopy(base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); typeDef.Methods.Add(newMethodDef); ILProcessor processor = newMethodDef.Body.GetILProcessor(); - MethodReference baseMethodGim = nbh.EmptyReplicatesQueueIntoHistory_MethodRef.GetMethodReference( - base.Session, new TypeReference[] { predictionFields.ReplicateDataTypeRef }); + MethodReference baseMethodGim = nbh.EmptyReplicatesQueueIntoHistory_MethodRef.GetMethodReference(base.Session, new TypeReference[] { predictionFields.ReplicateDataTypeRef }); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldarg_0); @@ -727,39 +743,42 @@ bool CreateEmptyReplicatesQueueIntoHistoryStart() //Overrides reconcile start to call reconcile_client_internal. bool CreateReconcileStart() { - MethodDefinition newMethodDef = nbh.Reconcile_Client_Start_MethodRef.CachedResolve(base.Session).CreateCopy( - base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); + MethodDefinition newMethodDef = nbh.Reconcile_Client_Start_MethodRef.CachedResolve(base.Session).CreateCopy(base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); typeDef.Methods.Add(newMethodDef); ILProcessor processor = newMethodDef.Body.GetILProcessor(); - MethodReference baseMethodGim = nbh.Reconcile_Client_MethodRef.GetMethodReference( - base.Session, new TypeReference[] { predictionFields.ReconcileData.FieldType, predictionFields.ReplicateDataTypeRef }); + Call_Reconcile_Client(); - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileULDelegate); - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateDatasHistory); - processor.Emit(OpCodes.Ldarg_0); - processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileData); - processor.Emit(OpCodes.Call, baseMethodGim); - processor.Emit(OpCodes.Ret); + void Call_Reconcile_Client() + { + MethodReference baseMethodGim = nbh.Reconcile_Client_MethodRef.GetMethodReference(base.Session, new TypeReference[] { predictionFields.ReconcileData.FieldType, predictionFields.ReplicateDataTypeRef }); + + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileULDelegate); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateDatasHistory); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileDatasHistory); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileData); + processor.Emit(OpCodes.Call, baseMethodGim); + processor.Emit(OpCodes.Ret); + } return true; } bool CreateReplicateReplayStart() { - MethodDefinition newMethodDef = nbh.Replicate_Replay_Start_MethodRef.CachedResolve(base.Session).CreateCopy( - base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); + MethodDefinition newMethodDef = nbh.Replicate_Replay_Start_MethodRef.CachedResolve(base.Session).CreateCopy(base.Session, null, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES); typeDef.Methods.Add(newMethodDef); ParameterDefinition replayTickPd = newMethodDef.Parameters[0]; ILProcessor processor = newMethodDef.Body.GetILProcessor(); - MethodReference baseMethodGim = nbh.Replicate_Replay_MethodRef.GetMethodReference( - base.Session, new TypeReference[] { predictionFields.ReplicateDataTypeRef }); + MethodReference baseMethodGim = nbh.Replicate_Replay_MethodRef.GetMethodReference(base.Session, new TypeReference[] { predictionFields.ReplicateDataTypeRef }); //uint replicateTick, ReplicateUserLogicDelegate del, List replicates, Channel channel) where T : IReplicateData processor.Emit(OpCodes.Ldarg_0); @@ -779,13 +798,12 @@ bool CreateReplicateReplayStart() } #region Universal prediction. - /// /// Creates an override for the method responsible for resetting replicates. /// /// /// - private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReference dataTr, CreatedPredictionFields predictionFields) + private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReference replicateDataTr, TypeReference reconcileDataTr, CreatedPredictionFields predictionFields) { GeneralHelper gh = base.GetClass(); NetworkBehaviourHelper nbh = base.GetClass(); @@ -810,7 +828,7 @@ private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReferen //Call the actual clear method. TypeDefinition nbTypeDef = typeDef.GetTypeDefinitionInBase(base.Session, typeof(NetworkBehaviour).FullName, false); MethodReference internalClearMr = base.Session.ImportReference(nbTypeDef.GetMethod(nameof(NetworkBehaviour.ClearReplicateCache_Internal))); - GenericInstanceMethod internalClearGim = internalClearMr.MakeGenericMethod(new TypeReference[] { dataTr }); + GenericInstanceMethod internalClearGim = internalClearMr.MakeGenericMethod(new TypeReference[] { replicateDataTr, reconcileDataTr }); processor.Emit(OpCodes.Ldarg_0); //Base. processor.Emit(OpCodes.Ldarg_0); @@ -818,6 +836,8 @@ private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReferen processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateDatasHistory); processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileDatasHistory); + processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReplicate); processor.Emit(OpCodes.Call, internalClearGim); processor.Emit(OpCodes.Ret); @@ -884,11 +904,9 @@ private List SubtractOneVariableFromAnother(MethodDefinition method return insts; } - #endregion #region Server side. - /// /// Creates replicate code for client. /// @@ -919,9 +937,7 @@ private void CallNonAuthoritativeReplicate(MethodDefinition replicateMd, Created private bool CreateReplicateReader(TypeDefinition typeDef, uint hash, MethodDefinition replicateMd, CreatedPredictionFields predictionFields, out MethodDefinition result) { string methodName = $"{REPLICATE_READER_PREFIX}{replicateMd.Name}"; - MethodDefinition createdMd = new(methodName, - MethodAttributes.Private, - replicateMd.Module.TypeSystem.Void); + MethodDefinition createdMd = new(methodName, MethodAttributes.Private, replicateMd.Module.TypeSystem.Void); typeDef.Methods.Add(createdMd); createdMd.Body.InitLocals = true; @@ -975,7 +991,20 @@ private void ServerCreateReconcile(MethodDefinition reconcileMd, CreatedPredicti ParameterDefinition channelPd = reconcileMd.Parameters[1]; ILProcessor processor = reconcileMd.Body.GetILProcessor(); - GenericInstanceMethod methodGim = base.GetClass().Reconcile_Server_MethodRef.MakeGenericMethod(new TypeReference[] { reconcileDataPd.ParameterType }); + NetworkBehaviourHelper nbh = base.GetClass(); + GenericInstanceMethod methodGim; + + /* Reconcile_Client_Local. */ + methodGim = nbh.Reconcile_Client_Local_MethodRef.MakeGenericMethod(new TypeReference[] { reconcileDataPd.ParameterType }); + + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldarg_0); + processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileDatasHistory); + processor.Emit(OpCodes.Ldarg, reconcileDataPd); + processor.Emit(OpCodes.Call, methodGim); + + /* Reconcile_Server. */ + methodGim = nbh.Reconcile_Server_MethodRef.MakeGenericMethod(new TypeReference[] { reconcileDataPd.ParameterType }); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldc_I4, (int)rpcCount); @@ -987,11 +1016,9 @@ private void ServerCreateReconcile(MethodDefinition reconcileMd, CreatedPredicti rpcCount++; } - #endregion #region Client side. - /// /// Creates replicate code for client. /// @@ -1026,9 +1053,7 @@ private void CallAuthoritativeReplicate(MethodDefinition replicateMd, CreatedPre private bool CreateReconcileReader(TypeDefinition typeDef, MethodDefinition reconcileMd, CreatedPredictionFields predictionFields, out MethodDefinition result) { string methodName = $"{RECONCILE_READER_PREFIX}{reconcileMd.Name}"; - MethodDefinition createdMd = new(methodName, - MethodAttributes.Private, - reconcileMd.Module.TypeSystem.Void); + MethodDefinition createdMd = new(methodName, MethodAttributes.Private, reconcileMd.Module.TypeSystem.Void); typeDef.Methods.Add(createdMd); createdMd.Body.InitLocals = true; @@ -1059,7 +1084,6 @@ private bool CreateReconcileReader(TypeDefinition typeDef, MethodDefinition reco result = createdMd; return true; } - #endregion } } \ No newline at end of file diff --git a/Assets/FishNet/Demos/Benchmarks/NetworkTransform/Prefabs/NetworkTransform Benchmark.prefab b/Assets/FishNet/Demos/Benchmarks/NetworkTransform/Prefabs/NetworkTransform Benchmark.prefab index 27114730..887033bd 100644 --- a/Assets/FishNet/Demos/Benchmarks/NetworkTransform/Prefabs/NetworkTransform Benchmark.prefab +++ b/Assets/FishNet/Demos/Benchmarks/NetworkTransform/Prefabs/NetworkTransform Benchmark.prefab @@ -51,11 +51,9 @@ MonoBehaviour: k__BackingField: 0 k__BackingField: {fileID: 0} k__BackingField: {fileID: 0} - _networkBehaviours: - - {fileID: 0} - - {fileID: -5271135124957689192} - k__BackingField: {fileID: 0} - k__BackingField: [] + NetworkBehaviours: [] + k__BackingField: {fileID: 0} + k__BackingField: [] _isNetworked: 1 _isSpawnable: 1 _isGlobal: 0 @@ -75,14 +73,14 @@ MonoBehaviour: _spectatorInterpolation: 2 _enableTeleport: 0 _teleportThreshold: 1 - k__BackingField: 26 + k__BackingField: 65535 k__BackingField: 0 k__BackingField: 14364260540862342890 k__BackingField: 0 SerializedTransformProperties: Position: {x: 0, y: 0, z: 0} - Rotation: {x: 0, y: 0, z: 0, w: 1} - LocalScale: {x: 1, y: 1, z: 1} + Rotation: {x: 0, y: 0, z: 0, w: 0} + LocalScale: {x: 0, y: 0, z: 0} --- !u!114 &6667641716399555817 MonoBehaviour: m_ObjectHideFlags: 0 @@ -116,9 +114,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a2836e36774ca1c4bbbee976e17b649c, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 1 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 4512293259955182956} - _networkObjectCache: {fileID: 4512293259955182956} + _networkObjectCache: {fileID: 0} _componentConfiguration: 0 _synchronizeParent: 0 _packing: diff --git a/Assets/FishNet/Demos/ColliderRollback/Prefabs/Player.prefab b/Assets/FishNet/Demos/ColliderRollback/Prefabs/Player.prefab index 3c599368..38c9802f 100644 --- a/Assets/FishNet/Demos/ColliderRollback/Prefabs/Player.prefab +++ b/Assets/FishNet/Demos/ColliderRollback/Prefabs/Player.prefab @@ -31,6 +31,7 @@ Transform: m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalPosition: {x: 0, y: 2, z: -2} m_LocalScale: {x: 1, y: 1, z: 1} + m_ConstrainProportionsScale: 0 m_Children: [] m_Father: {fileID: 0} m_RootOrder: 0 @@ -47,50 +48,40 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 26b716c41e9b56b4baafaf13a523ba2e, type: 3} m_Name: m_EditorClassIdentifier: - NetworkObserver: {fileID: 0} - k__BackingField: 27 - k__BackingField: 0 - _scenePathHash: 0 - k__BackingField: 0 - k__BackingField: 7473726319608011331 - AdaptiveInterpolationValue: 4 k__BackingField: 0 + k__BackingField: 0 + k__BackingField: {fileID: 0} + k__BackingField: {fileID: 0} + NetworkBehaviours: [] + k__BackingField: {fileID: 0} + k__BackingField: [] + _isNetworked: 1 + _isSpawnable: 1 + _isGlobal: 0 + _initializeOrder: 0 + _defaultDespawnType: 0 + NetworkObserver: {fileID: 0} _enablePrediction: 0 _predictionType: 0 _graphicalObject: {fileID: 0} + _detachGraphicalObject: 0 _enableStateForwarding: 1 + _networkTransform: {fileID: 0} _ownerInterpolation: 1 + _ownerSmoothedProperties: 255 + _adaptiveInterpolation: 3 + _spectatorSmoothedProperties: 255 + _spectatorInterpolation: 2 _enableTeleport: 0 - _ownerTeleportThreshold: 1 - _spectatorAdaptiveInterpolation: 1 - _spectatorInterpolation: 1 - _adaptiveSmoothingType: 0 - _customSmoothingData: - InterpolationPercent: 1 - CollisionInterpolationPercent: 0.1 - InterpolationDecreaseStep: 1 - InterpolationIncreaseStep: 3 - _preconfiguredSmoothingDataPreview: - InterpolationPercent: 0.5 - CollisionInterpolationPercent: 0.05 - InterpolationDecreaseStep: 1 - InterpolationIncreaseStep: 2 - k__BackingField: 0 - k__BackingField: {fileID: 0} - _networkBehaviours: - - {fileID: 2759061792589502182} - - {fileID: 1348621277} - - {fileID: 1348621278} - k__BackingField: {fileID: 0} - k__BackingField: [] + _teleportThreshold: 1 + k__BackingField: 65535 + k__BackingField: 0 + k__BackingField: 7473726319608011331 + k__BackingField: 0 SerializedTransformProperties: Position: {x: 0, y: 0, z: 0} Rotation: {x: 0, y: 0, z: 0, w: 0} LocalScale: {x: 0, y: 0, z: 0} - _isNetworked: 1 - _isGlobal: 0 - _initializeOrder: 0 - _defaultDespawnType: 0 --- !u!114 &2759061792589502182 MonoBehaviour: m_ObjectHideFlags: 0 @@ -103,15 +94,14 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 9d3558aad46c24549bea48d0e3938264, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 0 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 8475222101369129519} - _networkObjectCache: {fileID: 8475222101369129519} + _networkObjectCache: {fileID: 0} _hitboxLayer: serializedVersion: 2 - m_Bits: 32 - _audio: {fileID: 8300000, guid: 0330762d2b3c8d641bfe11ad89b7e196, type: 3} - _muzzleFlashPrefab: {fileID: 39148481766341303, guid: 4385a793e032d634bb912f84a23d6db1, - type: 3} + m_Bits: 0 + _audio: {fileID: 0} + _muzzleFlashPrefab: {fileID: 0} --- !u!114 &1348621277 MonoBehaviour: m_ObjectHideFlags: 0 @@ -124,9 +114,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: e6d656f377f37164d8d7431aa4e43cdb, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 1 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 8475222101369129519} - _networkObjectCache: {fileID: 8475222101369129519} + _networkObjectCache: {fileID: 0} --- !u!114 &1348621278 MonoBehaviour: m_ObjectHideFlags: 0 @@ -139,9 +129,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c718ab30626bbd648952910f74780a06, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 2 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 8475222101369129519} - _networkObjectCache: {fileID: 8475222101369129519} + _networkObjectCache: {fileID: 0} _moveRate: 3 --- !u!143 &5081159371976248031 CharacterController: diff --git a/Assets/FishNet/Demos/CustomSyncType/Component State Sync/ComponentStateSync.cs b/Assets/FishNet/Demos/CustomSyncType/Component State Sync/ComponentStateSync.cs index bec21995..8e84c811 100644 --- a/Assets/FishNet/Demos/CustomSyncType/Component State Sync/ComponentStateSync.cs +++ b/Assets/FishNet/Demos/CustomSyncType/Component State Sync/ComponentStateSync.cs @@ -8,8 +8,6 @@ namespace FishNet.Example.ComponentStateSync { - - /// /// It's very important to exclude this from codegen. /// However, whichever value you are synchronizing must not be excluded. This is why the value is outside the StructySync class. @@ -29,10 +27,12 @@ public bool Enabled /// Component to state sync. /// public T Component { get; private set; } + /// /// Delegate signature for when the component changes. /// public delegate void StateChanged(T component, bool prevState, bool nextState, bool asServer); + /// /// Called when the component state changes. /// @@ -99,6 +99,7 @@ private void AddOperation(T component, bool prev, bool next) bool asServer = true; OnChange?.Invoke(component, prev, next, asServer); } + /// /// Writes all changed values. /// @@ -128,21 +129,20 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - bool nextValue = reader.ReadBoolean(); - if (base.NetworkManager == null) - return; - + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool _, out bool canModifyValues); + bool prevValue = GetState(); + bool nextValue = reader.ReadBoolean(); /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); - if (!asClientAndHost) + * This is because changes would have already been made on + * the server side and doing so again would result in duplicates + * and potentially overwrite data not yet sent. */ + if (canModifyValues) Component.enabled = nextValue; - OnChange?.Invoke(Component, prevValue, nextValue, asServer); + if (newChangeId) + OnChange?.Invoke(Component, prevValue, nextValue, asServer); } /// @@ -151,4 +151,4 @@ internal protected override void Read(PooledReader reader, bool asServer) /// public object GetSerializedType() => null; } -} +} \ No newline at end of file diff --git a/Assets/FishNet/Demos/CustomSyncType/Custom Struct Sync/StructySync.cs b/Assets/FishNet/Demos/CustomSyncType/Custom Struct Sync/StructySync.cs index 85abe1f0..4e5afa2c 100644 --- a/Assets/FishNet/Demos/CustomSyncType/Custom Struct Sync/StructySync.cs +++ b/Assets/FishNet/Demos/CustomSyncType/Custom Struct Sync/StructySync.cs @@ -47,6 +47,7 @@ public ChangeData(CustomOperation operation, Structy data) Data = data; } } + /// /// Types of changes. This is related to ChangedData /// where you can specify what has changed. @@ -67,6 +68,7 @@ public enum CustomOperation : byte /// /// public delegate void CustomChanged(CustomOperation op, Structy oldItem, Structy newItem, bool asServer); + /// /// Called when the Structy changes. /// @@ -121,10 +123,10 @@ private void AddOperation(CustomOperation operation, Structy prev, Structy next) } /* Set as changed even if cannot dirty. - * Dirty is only set when there are observers, - * but even if there are not observers - * values must be marked as changed so when - * there are observers, new values are sent. */ + * Dirty is only set when there are observers, + * but even if there are not observers + * values must be marked as changed so when + * there are observers, new values are sent. */ _valuesChanged = true; base.Dirty(); @@ -190,11 +192,7 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool _, out bool canModifyValues); int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -213,12 +211,12 @@ internal protected override void Read(PooledReader reader, bool asServer) else if (operation == CustomOperation.Age) next.Age = reader.ReadUInt16(); - OnChange?.Invoke(operation, prev, next, asServer); - - if (!asClientAndHost) + if (canModifyValues) Value = next; + + if (newChangeId) + OnChange?.Invoke(operation, prev, next, asServer); } - } /// @@ -254,4 +252,4 @@ internal protected override void ResetState(bool asServer) /// public object GetSerializedType() => typeof(Structy); } -} +} \ No newline at end of file diff --git a/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Moving.prefab b/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Moving.prefab index 78b57b77..35d6222b 100644 --- a/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Moving.prefab +++ b/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Moving.prefab @@ -134,26 +134,37 @@ MonoBehaviour: k__BackingField: 0 k__BackingField: 0 k__BackingField: {fileID: 0} - _networkBehaviours: - - {fileID: 4670340455971777434} - - {fileID: 3019520109258855553} - k__BackingField: {fileID: 0} - k__BackingField: [] - SerializedTransformProperties: - Position: {x: 0, y: 0, z: 0} - Rotation: {x: 0, y: 0, z: 0, w: 0} - LocalScale: {x: 0, y: 0, z: 0} + k__BackingField: {fileID: 0} + NetworkBehaviours: [] + k__BackingField: {fileID: 0} + k__BackingField: [] _isNetworked: 1 _isSpawnable: 1 _isGlobal: 0 _initializeOrder: 0 _defaultDespawnType: 0 NetworkObserver: {fileID: 0} - k__BackingField: 0 + _enablePrediction: 0 + _predictionType: 0 + _graphicalObject: {fileID: 0} + _detachGraphicalObject: 0 + _enableStateForwarding: 1 + _networkTransform: {fileID: 0} + _ownerInterpolation: 1 + _ownerSmoothedProperties: 255 + _adaptiveInterpolation: 3 + _spectatorSmoothedProperties: 255 + _spectatorInterpolation: 2 + _enableTeleport: 0 + _teleportThreshold: 1 + k__BackingField: 65535 k__BackingField: 0 - _scenePathHash: 0 - k__BackingField: 0 k__BackingField: 11406911356865610645 + k__BackingField: 0 + SerializedTransformProperties: + Position: {x: 0, y: 0, z: 0} + Rotation: {x: 0, y: 0, z: 0, w: 0} + LocalScale: {x: 0, y: 0, z: 0} --- !u!114 &4670340455971777434 MonoBehaviour: m_ObjectHideFlags: 0 @@ -166,10 +177,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 10f399a5388d3b3459b7a8476ae13e6a, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 0 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 4512293259955182956} - _networkObjectCache: {fileID: 4512293259955182956} - _renderer: {fileID: 2529588038898116402} + _networkObjectCache: {fileID: 0} + _renderer: {fileID: 0} --- !u!114 &3019520109258855553 MonoBehaviour: m_ObjectHideFlags: 0 @@ -182,9 +193,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a2836e36774ca1c4bbbee976e17b649c, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 1 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 4512293259955182956} - _networkObjectCache: {fileID: 4512293259955182956} + _networkObjectCache: {fileID: 0} _componentConfiguration: 0 _synchronizeParent: 0 _packing: @@ -193,12 +204,10 @@ MonoBehaviour: Scale: 0 _interpolation: 2 _extrapolation: 2 - _enableTeleport: 1 - _teleportThreshold: 0.5 - _scaleThreshold: 1 + _enableTeleport: 0 + _teleportThreshold: 1 _clientAuthoritative: 1 _sendToOwner: 1 - _enableNetworkLod: 1 _interval: 1 _synchronizePosition: 1 _positionSnapping: diff --git a/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Static.prefab b/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Static.prefab index 58a18627..f828ee1b 100644 --- a/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Static.prefab +++ b/Assets/FishNet/Demos/HashGrid/Prefabs/HashGrid_Static.prefab @@ -153,17 +153,17 @@ MonoBehaviour: _ownerSmoothedProperties: 255 _adaptiveInterpolation: 3 _spectatorSmoothedProperties: 255 - _spectatorInterpolation: 1 + _spectatorInterpolation: 2 _enableTeleport: 0 _teleportThreshold: 1 - k__BackingField: 22 + k__BackingField: 65535 k__BackingField: 0 k__BackingField: 17472515426990886281 k__BackingField: 0 SerializedTransformProperties: Position: {x: 0, y: 0, z: 0} - Rotation: {x: 0, y: 0, z: 0, w: 1} - LocalScale: {x: 1, y: 1, z: 1} + Rotation: {x: 0, y: 0, z: 0, w: 0} + LocalScale: {x: 0, y: 0, z: 0} --- !u!114 &437322326027960749 MonoBehaviour: m_ObjectHideFlags: 0 @@ -176,6 +176,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 8a6a39c46bf52104ba8efe3100bce3f7, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 0 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 4512293259955182956} - _networkObjectCache: {fileID: 4512293259955182956} + _networkObjectCache: {fileID: 0} diff --git a/Assets/FishNet/Demos/SceneManager (Old Examples)/Prefabs/Player.prefab b/Assets/FishNet/Demos/SceneManager (Old Examples)/Prefabs/Player.prefab index 10dda46a..9e014cc5 100644 --- a/Assets/FishNet/Demos/SceneManager (Old Examples)/Prefabs/Player.prefab +++ b/Assets/FishNet/Demos/SceneManager (Old Examples)/Prefabs/Player.prefab @@ -218,10 +218,10 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 26e4f626a9ca9704f9befe7673a8dd15, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 0 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 611616139817875448} - _networkObjectCache: {fileID: 611616139817875448} - _camera: {fileID: 5090726669533971462} + _networkObjectCache: {fileID: 0} + _camera: {fileID: 0} _moveRate: 4 _clientAuth: 1 --- !u!54 &3514369712614123748 @@ -269,15 +269,10 @@ MonoBehaviour: k__BackingField: 0 k__BackingField: 0 k__BackingField: {fileID: 0} - _networkBehaviours: - - {fileID: 5090726670223187108} - - {fileID: 6654616088585099699} - k__BackingField: {fileID: 0} - k__BackingField: [] - SerializedTransformProperties: - Position: {x: 0, y: 0, z: 0} - Rotation: {x: 0, y: 0, z: 0, w: 0} - LocalScale: {x: 0, y: 0, z: 0} + k__BackingField: {fileID: 0} + NetworkBehaviours: [] + k__BackingField: {fileID: 0} + k__BackingField: [] _isNetworked: 1 _isSpawnable: 1 _isGlobal: 0 @@ -287,15 +282,24 @@ MonoBehaviour: _enablePrediction: 0 _predictionType: 0 _graphicalObject: {fileID: 0} + _detachGraphicalObject: 0 _enableStateForwarding: 1 + _networkTransform: {fileID: 0} _ownerInterpolation: 1 + _ownerSmoothedProperties: 255 + _adaptiveInterpolation: 3 + _spectatorSmoothedProperties: 255 + _spectatorInterpolation: 2 _enableTeleport: 0 _teleportThreshold: 1 - k__BackingField: 12 + k__BackingField: 65535 k__BackingField: 0 - _scenePathHash: 0 - k__BackingField: 0 k__BackingField: 12334122808499987737 + k__BackingField: 0 + SerializedTransformProperties: + Position: {x: 0, y: 0, z: 0} + Rotation: {x: 0, y: 0, z: 0, w: 0} + LocalScale: {x: 0, y: 0, z: 0} --- !u!114 &6420552185407096997 MonoBehaviour: m_ObjectHideFlags: 0 @@ -323,9 +327,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a2836e36774ca1c4bbbee976e17b649c, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 1 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 611616139817875448} - _networkObjectCache: {fileID: 611616139817875448} + _networkObjectCache: {fileID: 0} _componentConfiguration: 0 _synchronizeParent: 0 _packing: @@ -336,10 +340,8 @@ MonoBehaviour: _extrapolation: 2 _enableTeleport: 0 _teleportThreshold: 1 - _scaleThreshold: 1 _clientAuthoritative: 1 _sendToOwner: 1 - _enableNetworkLod: 1 _interval: 1 _synchronizePosition: 1 _positionSnapping: diff --git a/Assets/FishNet/Demos/SceneManager/Additive Scenes/Prefabs/Player.prefab b/Assets/FishNet/Demos/SceneManager/Additive Scenes/Prefabs/Player.prefab index 56293544..90552d40 100644 --- a/Assets/FishNet/Demos/SceneManager/Additive Scenes/Prefabs/Player.prefab +++ b/Assets/FishNet/Demos/SceneManager/Additive Scenes/Prefabs/Player.prefab @@ -328,15 +328,10 @@ MonoBehaviour: k__BackingField: 0 k__BackingField: 0 k__BackingField: {fileID: 0} - _networkBehaviours: - - {fileID: 6767263130208162619} - - {fileID: -7170926880071132319} - k__BackingField: {fileID: 0} - k__BackingField: [] - SerializedTransformProperties: - Position: {x: 0, y: 20, z: 0} - Rotation: {x: 0, y: 0, z: 0, w: 1} - LocalScale: {x: 1, y: 1, z: 1} + k__BackingField: {fileID: 0} + NetworkBehaviours: [] + k__BackingField: {fileID: 0} + k__BackingField: [] _isNetworked: 1 _isSpawnable: 1 _isGlobal: 0 @@ -346,15 +341,24 @@ MonoBehaviour: _enablePrediction: 0 _predictionType: 0 _graphicalObject: {fileID: 0} + _detachGraphicalObject: 0 _enableStateForwarding: 1 + _networkTransform: {fileID: 0} _ownerInterpolation: 1 + _ownerSmoothedProperties: 255 + _adaptiveInterpolation: 3 + _spectatorSmoothedProperties: 255 + _spectatorInterpolation: 2 _enableTeleport: 0 _teleportThreshold: 1 - k__BackingField: 20 + k__BackingField: 65535 k__BackingField: 0 - _scenePathHash: 0 - k__BackingField: 0 k__BackingField: 5491988111533709812 + k__BackingField: 0 + SerializedTransformProperties: + Position: {x: 0, y: 0, z: 0} + Rotation: {x: 0, y: 0, z: 0, w: 0} + LocalScale: {x: 0, y: 0, z: 0} --- !u!114 &6767263130208162619 MonoBehaviour: m_ObjectHideFlags: 0 @@ -367,11 +371,11 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: c8541d1cca4da7043b84a0d563939dea, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 0 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 8192566354860284824} - _networkObjectCache: {fileID: 8192566354860284824} - _ownerObjects: {fileID: 6508211851680439895} - _moveRate: 3 + _networkObjectCache: {fileID: 0} + _ownerObjects: {fileID: 0} + _moveRate: 2 --- !u!54 &-1062563901163859699 Rigidbody: m_ObjectHideFlags: 0 @@ -414,9 +418,9 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: a2836e36774ca1c4bbbee976e17b649c, type: 3} m_Name: m_EditorClassIdentifier: - _componentIndexCache: 1 + _componentIndexCache: 255 _addedNetworkObject: {fileID: 8192566354860284824} - _networkObjectCache: {fileID: 8192566354860284824} + _networkObjectCache: {fileID: 0} _componentConfiguration: 0 _synchronizeParent: 0 _packing: @@ -426,11 +430,9 @@ MonoBehaviour: _interpolation: 2 _extrapolation: 2 _enableTeleport: 0 - _teleportThreshold: 0.5 - _scaleThreshold: 1 - _clientAuthoritative: 0 + _teleportThreshold: 1 + _clientAuthoritative: 1 _sendToOwner: 1 - _enableNetworkLod: 1 _interval: 1 _synchronizePosition: 1 _positionSnapping: diff --git a/Assets/FishNet/Plugins.meta b/Assets/FishNet/Plugins.meta index a2186a1f..ab88963a 100644 --- a/Assets/FishNet/Plugins.meta +++ b/Assets/FishNet/Plugins.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: f8b8569be5872c245a621e319ceb25dd +guid: 1efd6a2198a30b44c98e7a6e48bc92b6 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/FishNet/Plugins/Edgegap.meta b/Assets/FishNet/Plugins/Edgegap.meta index 58e3cc0a..abd44447 100644 --- a/Assets/FishNet/Plugins/Edgegap.meta +++ b/Assets/FishNet/Plugins/Edgegap.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: eb02bdac67616b547818f5533ceaa410 +guid: 070891960b4815b44aa984d9791febd6 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes.meta b/Assets/FishNet/Runtime/Generated/SyncTypes.meta deleted file mode 100644 index 450b96ce..00000000 --- a/Assets/FishNet/Runtime/Generated/SyncTypes.meta +++ /dev/null @@ -1,8 +0,0 @@ -fileFormatVersion: 2 -guid: d2bcb6aac372c664091df2e19bc3e85a -folderAsset: yes -DefaultImporter: - externalObjects: {} - userData: - assetBundleName: - assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Managing/NetworkManager.cs b/Assets/FishNet/Runtime/Managing/NetworkManager.cs index 706658d6..a99513a7 100644 --- a/Assets/FishNet/Runtime/Managing/NetworkManager.cs +++ b/Assets/FishNet/Runtime/Managing/NetworkManager.cs @@ -212,7 +212,7 @@ public static IReadOnlyList Instances /// /// Version of this release. /// - public const string FISHNET_VERSION = "4.4.5"; + public const string FISHNET_VERSION = "4.4.6"; /// /// Maximum framerate allowed. /// diff --git a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs index 201d33f6..2f40df5c 100644 --- a/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs +++ b/Assets/FishNet/Runtime/Managing/Prediction/PredictionManager.cs @@ -13,7 +13,6 @@ namespace FishNet.Managing.Predicting { - /// /// Additional options for managing the observer system. /// @@ -21,6 +20,35 @@ namespace FishNet.Managing.Predicting [AddComponentMenu("FishNet/Manager/PredictionManager")] public sealed class PredictionManager : MonoBehaviour { + #region Types. + internal class StatePacketTick + { + public uint Client = TimeManager.UNSET_TICK; + public uint Server = TimeManager.UNSET_TICK; + + /// + /// Returns if ticks are unset. + /// Only client needs to be checked, as they both are set with non default at the same time. + /// + public bool IsUnset => Client == TimeManager.UNSET_TICK; + + public void Update(uint client, uint server) + { + Client = client; + Server = server; + } + + /// + /// Adds ticks onto each field. + /// + public void AddTick(uint quantity) + { + Client += quantity; + Server += quantity; + } + } + #endregion + #region Public. /// /// Called before performing a reconcile. Contains the client and server tick the reconcile is for. @@ -28,47 +56,61 @@ public sealed class PredictionManager : MonoBehaviour public event PreReconcileDel OnPreReconcile; public delegate void PreReconcileDel(uint clientTick, uint serverTick); + /// /// Called when performing a reconcile. /// This is used internally to reconcile objects and does not gaurantee your subscriptions to this event will process before or after internal components. /// public event ReconcileDel OnReconcile; + public delegate void ReconcileDel(uint clientTick, uint serverTick); + /// /// Called after performing a reconcile. Contains the client and server tick the reconcile is for. /// public event PostReconcileDel OnPostReconcile; + public delegate void PostReconcileDel(uint clientTick, uint serverTick); + /// /// Called before Physics SyncTransforms are run after a reconcile. /// This will only invoke if physics are set to TimeManager, within the TimeManager inspector. /// public event PrePhysicsSyncTransformDel OnPrePhysicsTransformSync; + public delegate void PrePhysicsSyncTransformDel(uint clientTick, uint serverTick); + /// /// Called after Physics SyncTransforms are run after a reconcile. /// This will only invoke if physics are set to TimeManager, within the TimeManager inspector. /// public event PostPhysicsSyncTransformDel OnPostPhysicsTransformSync; + public delegate void PostPhysicsSyncTransformDel(uint clientTick, uint serverTick); /// /// Called before physics is simulated when replaying a replicate method. /// public event PreReplicateReplayDel OnPreReplicateReplay; + public delegate void PreReplicateReplayDel(uint clientTick, uint serverTick); + /// /// Called when replaying a replication. /// This is called before physics are simulated. /// This is used internally to replay objects and does not gaurantee your subscriptions to this event will process before or after internal components. /// internal event ReplicateReplayDel OnReplicateReplay; + public delegate void ReplicateReplayDel(uint clientTick, uint serverTick); + /// /// Called after physics is simulated when replaying a replicate method. /// public event PostReplicateReplayDel OnPostReplicateReplay; + public delegate void PostReplicateReplayDel(uint clientTick, uint serverTick); + /// /// True if client timing needs to be reduced. This is fine-tuning of the prediction system. /// @@ -113,10 +155,12 @@ public sealed class PredictionManager : MonoBehaviour [Tooltip("Maximum number of replicates a server can queue per object. Higher values will put more load on the server and add replicate latency for the client.")] [SerializeField] private byte _maximumServerReplicates = 15; + /// /// Maximum number of replicates a server can queue per object. Higher values will put more load on the server and add replicate latency for the client. /// public byte GetMaximumServerReplicates() => _maximumServerReplicates; + /// /// Sets the maximum number of replicates a server can queue per object. /// @@ -125,6 +169,7 @@ public void SetMaximumServerReplicates(byte value) { _maximumServerReplicates = (byte)Mathf.Clamp(value, MINIMUM_REPLICATE_QUEUE_SIZE, MAXIMUM_REPLICATE_QUEUE_SIZE); } + /// /// No more than this value of replicates should be stored as a buffer. /// @@ -156,6 +201,7 @@ public void SetMaximumServerReplicates(byte value) /// True if StateOrder is set to future. /// internal bool IsAppendedStateOrder => (_stateOrder == ReplicateStateOrder.Appended); + /// /// Sets the current ReplicateStateOrder. This may be changed at runtime. /// Changing this value only affects the client which it is changed on. @@ -180,6 +226,7 @@ public void SetStateOrder(ReplicateStateOrder stateOrder) item.EmptyReplicatesQueueIntoHistory(); } } + /// /// Number of past inputs to send, which is also the number of times to resend final datas. /// @@ -204,6 +251,10 @@ public void SetStateOrder(ReplicateStateOrder stateOrder) /// private byte _droppedReconcilesCount; /// + /// Ticks for the last state packet to run. + /// + private StatePacketTick _lastStatePacketTick = new(); + /// /// Current reconcile state to use. /// //private StatePacket _reconcileState; @@ -292,6 +343,7 @@ public IncomingData(ArraySegment data, Channel channel) Channel = channel; } } + public List Datas; public uint ClientTick; public uint ServerTick; @@ -305,7 +357,8 @@ public void Update(ArraySegment data, uint clientTick, uint serverTick, Ch public void AddData(ArraySegment data, Channel channel) { - Datas.Add(new(data, channel)); + if (data.Array != null) + Datas.Add(new(data, channel)); } public void ResetState() @@ -322,14 +375,25 @@ public void InitializeState() } } + /// + /// Returns client or server state tick for the current reconcile. + /// + /// True to return client state tick, false for servers. + /// + public uint GetReconcileStateTick(bool clientTick) => (clientTick) ? ClientStateTick : ServerStateTick; + /// /// Reconciles to received states. /// internal void ReconcileToStates() - { + { if (!_networkManager.IsClientStarted) return; - //No states. + + //Creates a local state update if one is not available in reconcile states. + // CreateLocalStateUpdate(); + + //If there are no states then guestimate the next state. if (_reconcileStates.Count == 0) return; @@ -339,7 +403,7 @@ internal void ReconcileToStates() /* When there is an excessive amount of states try to consume * some.This only happens when the client gets really far behind - * and has to catch up, such as a latency increase then drop. + * and has to catch up, such as a latency increase then drop. * Limit the number of states consumed per tick so the clients * computer doesn't catch fire. */ int iterations = 0; @@ -375,7 +439,7 @@ bool ConditionsMet(StatePacket spChecked) * packets to arrive. This adds on varianceAllowance to replays but greatly * increases the chances of the state being received before skipping past it * in a replay. - * + * * When using Inserted (not AppendedStateOrder) there does not need to be any * additional allowance since there is no extra queue like appended, they rather just * go right into the past. */ @@ -384,6 +448,7 @@ bool ConditionsMet(StatePacket spChecked) bool serverPass = (spChecked.ServerTick < (estimatedLastRemoteTick - serverTickDifferenceRequirement)); bool clientPass = spChecked.ClientTick < (localTick - stateInterpolation); + return (serverPass && clientPass); } @@ -421,6 +486,8 @@ bool ConditionsMet(StatePacket spChecked) if (!dropReconcile) { IsReconciling = true; + _lastStatePacketTick.Update(clientTick, serverTick); + ClientStateTick = clientTick; /* This is the tick which the reconcile is for. * Since reconciles are performed after replicate, if @@ -431,6 +498,10 @@ bool ConditionsMet(StatePacket spChecked) //Have the reader get processed. foreach (StatePacket.IncomingData item in sp.Datas) { + // //If data isn't set skip it. This can be true if a locally generated state packet. + // if (item.Data.Array == null) + // continue; + PooledReader reader = ReaderPool.Retrieve(item.Data, _networkManager, Reader.DataSource.Server); _networkManager.ClientManager.ParseReader(reader, item.Channel); ReaderPool.Store(reader); @@ -451,9 +522,9 @@ bool ConditionsMet(StatePacket spChecked) } /* Set first replicate to be the 1 tick * after reconcile. This is because reconcile calcs - * should be performed after replicate has run. + * should be performed after replicate has run. * In result object will reconcile to data AFTER - * the replicate tick, and then run remaining replicates as replay. + * the replicate tick, and then run remaining replicates as replay. * * Replay up to localtick, excluding localtick. There will * be no input for localtick since reconcile runs before @@ -463,7 +534,7 @@ bool ConditionsMet(StatePacket spChecked) /* Only replay up to but excluding local tick. * This prevents client from running 1 local tick into the future - * since the OnTick has not run yet. + * since the OnTick has not run yet. * * EG: if localTick is 100 replay will run up to 99, then OnTick * will fire for 100. */ @@ -483,8 +554,8 @@ bool ConditionsMet(StatePacket spChecked) OnPostReconcile?.Invoke(ClientStateTick, ServerStateTick); - ClientStateTick = TimeManager.UNSET_TICK; - ServerStateTick = TimeManager.UNSET_TICK; + // ClientStateTick = TimeManager.UNSET_TICK; + // ServerStateTick = TimeManager.UNSET_TICK; ClientReplayTick = TimeManager.UNSET_TICK; ServerReplayTick = TimeManager.UNSET_TICK; IsReconciling = false; @@ -493,7 +564,32 @@ bool ConditionsMet(StatePacket spChecked) DisposeOfStatePacket(sp); } } - + + /// + /// Gets the reconcile tick to use when generating a local reconcile. + /// + /// + internal uint GetCreateReconcileTick(bool isOwner) + { + uint localTick = _networkManager.TimeManager.LocalTick; + + //Client uses current localTick if owner. + if (isOwner) + return localTick; + + //ClientStateTick has never been set, might happen when just connecting. Cannot get tick. + if (ClientStateTick == TimeManager.UNSET_TICK) + return TimeManager.UNSET_TICK; + + long tickDifference = (long)(localTick - ClientStateTick); + + //Should not be possible given state tick is always behind. + if (tickDifference < 0) + tickDifference = 0; + + return (ServerStateTick + (uint)tickDifference); + } + /// /// Sends written states for clients. /// @@ -555,7 +651,6 @@ internal void SendStateUpdate() } } - /// /// Parses a received state update. /// @@ -577,23 +672,7 @@ internal void ParseStateUpdate(PooledReader reader, Channel channel) { _lastOrderedReadReconcileTick = lastRemoteTick; - /* There should never really be more than queuedInputs so set - * a limit a little beyond to prevent reconciles from building up. - * This is more of a last result if something went terribly - * wrong with the network. */ - int adjustedStateInterpolation = (StateInterpolation * 4) + 2; - /* If appending allow an additional of stateInterpolation since - * entries arent added into the past until they are run on the appended - * queue for each networkObject. */ - if (IsAppendedStateOrder) - adjustedStateInterpolation += StateInterpolation; - int maxAllowedStates = Mathf.Max(adjustedStateInterpolation, 4); - - while (_reconcileStates.Count > maxAllowedStates) - { - StatePacket oldSp = _reconcileStates.Dequeue(); - DisposeOfStatePacket(oldSp); - } + RemoveExcessiveStates(); //LocalTick of this client the state is for. uint clientTick = reader.ReadTickUnpacked(); @@ -620,6 +699,56 @@ internal void ParseStateUpdate(PooledReader reader, Channel channel) } } } + // + // /// + // /// Creates a local statePacket with no data other than ticks. + // /// + // internal void CreateLocalStateUpdate() + // { + // //Only to be called when there are no reconcile states available. + // if (_reconcileStates.Count > 0) + // return; + // if (_networkManager.IsServerStarted) + // return; + // //Not yet received first state, cannot apply tick. + // if (_lastStatePacketTick.IsUnset) + // return; + // + // _lastStatePacketTick.AddTick(1); + // + // /* Update last read as well. If we've made it this far we won't be caring about states before this + // * even if they come in late. */ + // _lastOrderedReadReconcileTick = _lastStatePacketTick.Server; + // + // StatePacket sp = ResettableObjectCaches.Retrieve(); + // //Channel does not matter; it's only used to determine how data is parsed, data we don't have. + // sp.Update(default, _lastStatePacketTick.Client, _lastStatePacketTick.Server, Channel.Unreliable); + // _reconcileStates.Enqueue(sp); + // } + + /// + /// Removes excessively stored state packets. + /// + private void RemoveExcessiveStates() + { + /* There should never really be more than queuedInputs so set + * a limit a little beyond to prevent reconciles from building up. + * This is more of a last result if something went terribly + * wrong with the network. */ + int adjustedStateInterpolation = (StateInterpolation * 4) + 2; + /* If appending allow an additional of stateInterpolation since + * entries arent added into the past until they are run on the appended + * queue for each networkObject. */ + if (IsAppendedStateOrder) + adjustedStateInterpolation += StateInterpolation; + int maxAllowedStates = Mathf.Max(adjustedStateInterpolation, 4); + + while (_reconcileStates.Count > maxAllowedStates) + { + StatePacket oldSp = _reconcileStates.Dequeue(); + DisposeOfStatePacket(oldSp); + } + } /// /// Disposes of and cleans up everything related to a StatePacket. @@ -639,5 +768,4 @@ private void OnValidate() #endif } - -} +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs index 288b66b8..a96dd2ae 100644 --- a/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs +++ b/Assets/FishNet/Runtime/Managing/Scened/SceneManager.cs @@ -89,16 +89,19 @@ internal enum LightProbeUpdateType [Tooltip("Script to handle addressables loading and unloading. This field may be blank if addressables are not being used.")] [SerializeField] private SceneProcessorBase _sceneProcessor; + /// /// Script to handle addressables loading and unloading. This field may be blank if addressables are not being used. /// /// public SceneProcessorBase GetSceneProcessor() => _sceneProcessor; + /// /// Sets the SceneProcessor to use. /// /// public void SetSceneProcessor(SceneProcessorBase value) => _sceneProcessor = value; + /// /// NetworkManager for this script. /// @@ -255,8 +258,8 @@ private void ServerManager_OnServerConnectionState(ServerConnectionStateArgs obj //If no servers are started. if (!NetworkManager.ServerManager.AnyServerStarted()) ResetValues(); - } + /// /// Resets as if first use. /// @@ -368,6 +371,7 @@ private void OnClientEmptyStartScenes(EmptyStartScenesBroadcast msg, Channel cha TryInvokeLoadedStartScenes(_clientManager.Connection, false); _clientManager.Broadcast(msg); } + /// /// Received on server when client confirms there are no start scenes. /// @@ -405,9 +409,7 @@ private void ClientDisconnected(NetworkConnection conn) bool removed = hs.Remove(conn); /* If no more observers for scene, not a global scene, and not to be manually unloaded * then remove scene from SceneConnections and unload it. */ - if (removed && hs.Count == 0 && - !IsGlobalScene(scene) && !_manualUnloadScenes.Contains(scene) && - (scene != activeScene)) + if (removed && hs.Count == 0 && !IsGlobalScene(scene) && !_manualUnloadScenes.Contains(scene) && (scene != activeScene)) scenesToUnload.Add(scene); } @@ -476,6 +478,7 @@ private void TryInvokeOnQueueStart() IteratingQueue = true; OnQueueStart?.Invoke(); } + /// /// Checks if OnQueueEnd should invoke, and if so invokes. /// @@ -489,6 +492,7 @@ private void TryInvokeOnQueueEnd() QueueCompleteTime = Time.unscaledTime; OnQueueEnd?.Invoke(); } + /// /// Invokes that a scene load has started. Only called when valid scenes will be loaded. /// @@ -498,6 +502,7 @@ private void InvokeOnSceneLoadStart(LoadQueueData qd) TryInvokeOnQueueStart(); OnLoadStart?.Invoke(new(qd)); } + /// /// Invokes that a scene load has ended. Only called after a valid scene has loaded. /// @@ -513,6 +518,7 @@ private void InvokeOnSceneLoadEnd(LoadQueueData qd, List requestedLoadSc SceneLoadEndEventArgs args = new(qd, skippedScenes.ToArray(), loadedScenes.ToArray(), unloadedSceneNames); OnLoadEnd?.Invoke(args); } + /// /// Invokes that a scene unload has started. Only called when valid scenes will be unloaded. /// @@ -522,6 +528,7 @@ private void InvokeOnSceneUnloadStart(UnloadQueueData sqd) TryInvokeOnQueueStart(); OnUnloadStart?.Invoke(new(sqd)); } + /// /// Invokes that a scene unload has ended. Only called after a valid scene has unloaded. /// @@ -531,6 +538,7 @@ private void InvokeOnSceneUnloadEnd(UnloadQueueData sqd, List unloadedSce SceneUnloadEndEventArgs args = new(sqd, unloadedScenes, newUnloadedScenes); OnUnloadEnd?.Invoke(args); } + /// /// Invokes when completion percentage changes while unloading or unloading a scene. Value is between 0f and 1f, while 1f is 100% done. /// @@ -553,12 +561,13 @@ private void QueueOperation(object data) //Add to scene queue data. _queuedOperations.Add(data); /* If only one entry then scene operations are not currently in progress. - * Should there be more than one entry then scene operations are already + * Should there be more than one entry then scene operations are already * occuring. The coroutine will automatically load in order. */ if (_queuedOperations.Count == 1) StartCoroutine(__ProcessSceneQueue()); } + /// /// Processes queued scene operations. /// @@ -704,6 +713,7 @@ public void LoadGlobalScenes(SceneLoadData sceneLoadData) { LoadGlobalScenes_Internal(sceneLoadData, _globalScenes, true); } + private void LoadGlobalScenes_Internal(SceneLoadData sceneLoadData, string[] globalScenes, bool asServer) { if (!CanExecute(asServer, true)) @@ -730,6 +740,7 @@ public void LoadConnectionScenes(NetworkConnection conn, SceneLoadData sceneLoad //This cannot use cache because the array will persist for many frames after this method completion. LoadConnectionScenes(new NetworkConnection[] { conn }, sceneLoadData); } + /// /// Loads scenes on server and tells connections to load them as well. Other connections will not load this scene. /// @@ -739,6 +750,7 @@ public void LoadConnectionScenes(NetworkConnection[] conns, SceneLoadData sceneL { LoadConnectionScenes_Internal(conns, sceneLoadData, _globalScenes, true); } + /// /// Loads scenes on server without telling clients to load the scenes. /// @@ -747,6 +759,7 @@ public void LoadConnectionScenes(SceneLoadData sceneLoadData) { LoadConnectionScenes_Internal(Array.Empty(), sceneLoadData, _globalScenes, true); } + private void LoadConnectionScenes_Internal(NetworkConnection[] conns, SceneLoadData sceneLoadData, string[] globalScenes, bool asServer) { if (!CanExecute(asServer, true)) @@ -855,7 +868,6 @@ private IEnumerator __LoadScenes() int index = _globalScenes.Length; Array.Resize(ref _globalScenes, _globalScenes.Length + names.Length); Array.Copy(names, 0, _globalScenes, index, names.Length); - } CheckForDuplicateGlobalSceneNames(); data.GlobalScenes = _globalScenes; @@ -863,7 +875,7 @@ private IEnumerator __LoadScenes() /* Scene queue data scenes. - * All scenes in the scene queue data whether they will be loaded or not. */ + * All scenes in the scene queue data whether they will be loaded or not. */ List requestedLoadSceneNames = new(); List requestedLoadSceneHandles = new(); @@ -913,7 +925,7 @@ private IEnumerator __LoadScenes() } /* Move identities - * to holder scene to preserve them. + * to holder scene to preserve them. * Required if a single scene is specified. Cannot rely on * loadSingleScene since it is only true if the single scene * must be loaded, which may be false if it's already loaded on @@ -967,9 +979,9 @@ private IEnumerator __LoadScenes() /* Scene unloading if replacing scenes. - * - * Unload all scenes except MovedObjectsHolder. Also don't - * unload GlobalScenes if loading as connection. */ + * + * Unload all scenes except MovedObjectsHolder. Also don't + * unload GlobalScenes if loading as connection. */ List unloadableScenes = new(); //Do not run if running as client, and server is active. This would have already run as server. if ((replaceScenes != ReplaceOption.None) && !asHost) @@ -982,8 +994,8 @@ private IEnumerator __LoadScenes() if (s == GetMovedObjectsScene()) continue; /* Scene is in one of the scenes being loaded. - * This can occur when trying to load additional clients - * into an existing scene. */ + * This can occur when trying to load additional clients + * into an existing scene. */ if (requestedLoadSceneNames.Contains(s.name)) continue; //Same as above but using handles. @@ -1074,8 +1086,8 @@ private IEnumerator __LoadScenes() }; /* How much percentage each scene load can be worth - * at maximum completion. EG: if there are two scenes - * 1f / 2f is 0.5f. */ + * at maximum completion. EG: if there are two scenes + * 1f / 2f is 0.5f. */ float maximumIndexWorth = (1f / (float)loadableScenes.Count); _sceneProcessor.BeginLoadAsync(loadableScenes[i].Name, loadSceneParameters); @@ -1090,8 +1102,8 @@ private IEnumerator __LoadScenes() void InvokePercentageChange(int index, float maximumWorth, float currentScenePercent) { /* Total percent will be how much percentage is complete - * in total. Initialize it with a value based on how many - * scenes are already fully loaded. */ + * in total. Initialize it with a value based on how many + * scenes are already fully loaded. */ float totalPercent = (index * maximumWorth); //Add this scenes progress onto total percent. totalPercent += Mathf.Lerp(0f, maximumWorth, currentScenePercent); @@ -1140,7 +1152,7 @@ void InvokePercentageChange(int index, float maximumWorth, float currentScenePer lastSameSceneName = s; } - /* Shouldn't be possible since the scene will always exist either by + /* Shouldn't be possible since the scene will always exist either by * just being loaded or already loaded. */ if (string.IsNullOrEmpty(lastSameSceneName.name)) NetworkManager.LogError($"Scene {sceneLoadData.SceneLookupDatas[0].Name} could not be found in loaded scenes."); @@ -1207,6 +1219,7 @@ Scene GetFirstLoadedScene() } while (!allScenesLoaded); SetActiveScene_Local(); + void SetActiveScene_Local() { bool byUser; @@ -1214,15 +1227,20 @@ void SetActiveScene_Local() //If preferred still is not set then try to figure it out. if (!preferredActiveScene.IsValid()) { - /* Populate preferred scene to first loaded if replacing - * scenes for connection. Does not need to be set for - * global because when a global exist it's always set - * as the active scene. - * - * Do not set preferred scene if server as this could cause - * problems when stacking or connection specific scenes. Let the - * user make those changes. */ - if (sceneLoadData.ReplaceScenes != ReplaceOption.None && data.ScopeType == SceneScopeType.Connections && !NetworkManager.IsServerStarted) + bool setToFirstLookup = false; + //If any scenes are being replaced see if active needs to be updated. + if (sceneLoadData.ReplaceScenes != ReplaceOption.None) + { + //If load is for a connection and server isnt started. + setToFirstLookup |= (data.ScopeType == SceneScopeType.Connections && !NetworkManager.IsServerStarted); + /* If current active is the movedObjectsHolder, such as moved objects. + * This can happen when replacing a scene that was active and the next in line is + * set by unity as one of the temp scenes. */ + Scene activeScene = UnitySceneManager.GetActiveScene(); + setToFirstLookup |= (activeScene == GetMovedObjectsScene()); + } + + if (setToFirstLookup) preferredActiveScene = sceneLoadData.GetFirstLookupScene(); } @@ -1355,13 +1373,13 @@ public void UnloadGlobalScenes(SceneUnloadData sceneUnloadData) UnloadGlobalScenes_Internal(sceneUnloadData, _globalScenes, true); } + private void UnloadGlobalScenes_Internal(SceneUnloadData sceneUnloadData, string[] globalScenes, bool asServer) { UnloadQueueData uqd = new(SceneScopeType.Global, Array.Empty(), sceneUnloadData, globalScenes, asServer); QueueOperation(uqd); } - /// /// Unloads scenes on server and tells a connection to unload them as well. Other connections will not unload this scene. /// @@ -1372,6 +1390,7 @@ public void UnloadConnectionScenes(NetworkConnection connection, SceneUnloadData //This cannot use cache because the array will persist for many frames after this method completion. UnloadConnectionScenes(new NetworkConnection[] { connection }, sceneUnloadData); } + /// /// Unloads scenes on server and tells connections to unload them as well. Other connections will not unload this scene. /// @@ -1390,6 +1409,7 @@ public void UnloadConnectionScenes(SceneUnloadData sceneUnloadData) { UnloadConnectionScenes_Internal(Array.Empty(), sceneUnloadData, _globalScenes, true); } + private void UnloadConnectionScenes_Internal(NetworkConnection[] connections, SceneUnloadData sceneUnloadData, string[] globalScenes, bool asServer) { if (!CanExecute(asServer, true)) @@ -1400,6 +1420,7 @@ private void UnloadConnectionScenes_Internal(NetworkConnection[] connections, Sc UnloadQueueData uqd = new(SceneScopeType.Connections, connections, sceneUnloadData, globalScenes, asServer); QueueOperation(uqd); } + /// /// Loads scenes within QueuedSceneLoads. /// @@ -1414,7 +1435,7 @@ private IEnumerator __UnloadScenes() yield break; /* Some actions should not run as client if server is also active. - * This is to keep things from running twice. */ + * This is to keep things from running twice. */ bool asClientHost = (!data.AsServer && NetworkManager.IsServerStarted); ///True if running asServer. bool asServer = data.AsServer; @@ -1432,10 +1453,10 @@ private IEnumerator __UnloadScenes() } /* Remove from global scenes - * if server and scope is global. - * All passed in scenes should be removed from global - * regardless of if they're valid or not. If they are invalid, - * then they shouldn't be in global to begin with. */ + * if server and scope is global. + * All passed in scenes should be removed from global + * regardless of if they're valid or not. If they are invalid, + * then they shouldn't be in global to begin with. */ if (asServer && data.ScopeType == SceneScopeType.Global) { RemoveFromGlobalScenes(sceneUnloadData.SceneLookupDatas); @@ -1458,7 +1479,6 @@ private IEnumerator __UnloadScenes() } - /* This will contain all scenes which can be unloaded. * The collection will be modified through various checks. */ List unloadableScenes = scenes.ToList(); @@ -1503,7 +1523,7 @@ private IEnumerator __UnloadScenes() /* Remove from manualUnloadedScenes. * Scene may not be in this collection * but removing is one call vs checking - * then removing. */ + * then removing. */ _manualUnloadScenes.Remove(s); _sceneProcessor.BeginUnloadAsync(s); @@ -1515,11 +1535,11 @@ private IEnumerator __UnloadScenes() } /* Must yield after sceneProcessor handles things. - * This is a Unity bug of sorts. I'm not entirely sure what - * is happening, but without the yield it seems as though - * the processor logic doesn't complete. This doesn't make much - * sense given unity is supposed to be single threaded. Must be - * something to do with the coroutine. */ + * This is a Unity bug of sorts. I'm not entirely sure what + * is happening, but without the yield it seems as though + * the processor logic doesn't complete. This doesn't make much + * sense given unity is supposed to be single threaded. Must be + * something to do with the coroutine. */ yield return null; bool byUser; @@ -1570,7 +1590,6 @@ private IEnumerator __UnloadScenes() InvokeOnSceneUnloadEnd(data, unloadableScenes, unloadedScenes); } - /// /// Received on clients when networked scenes must be unloaded. /// @@ -1645,7 +1664,7 @@ private void MoveClientHostObjects(Scene scene, bool asServer) nob.ClearRuntimeSceneObject(); /* If the object is already being despawned then *just disable and move it. Otherwise despawn it - * on the server then move it. */ + * on the server then move it. */ //Not deinitializing, despawn it then. if (!nob.IsDeinitializing) nob.Despawn(); @@ -1722,13 +1741,12 @@ public void AddConnectionToScene(NetworkConnection conn, Scene scene) InvokeClientPresenceChange(scene, arrayConn, true, false); /* Also need to rebuild all networkobjects - * for connection so other players can - * see them. */ + * for connection so other players can + * see them. */ RebuildObservers(conn.Objects.ToArray()); } } - /// /// Removes connections from any scene which is not global. /// Exposed for power users, use caution. @@ -1783,7 +1801,6 @@ public void RemoveConnectionsFromNonGlobalScenes(NetworkConnection[] conns) RebuildObservers(c.Objects.ToArray()); } - /// /// Removes connections from specified scenes. /// Exposed for power users, use caution. @@ -1825,8 +1842,8 @@ public void RemoveConnectionsFromScene(NetworkConnection[] conns, Scene scene) } /* Also rebuild observers for objects owned by connection. - * This ensures other connections will lose visibility if - * they no longer share a scene. */ + * This ensures other connections will lose visibility if + * they no longer share a scene. */ foreach (NetworkConnection c in conns) RebuildObservers(c.Objects.ToArray()); } @@ -1915,6 +1932,7 @@ private void RebuildObservers(IList networkObjects) _serverManager.Objects.RebuildObservers(nob); } } + /// /// Rebuilds all NetworkObjects for connection. /// @@ -1924,6 +1942,7 @@ internal void RebuildObservers(NetworkConnection connection) RebuildObservers(connCache); CollectionCaches.Store(connCache); } + /// /// Rebuilds all NetworkObjects for connections. /// @@ -1933,6 +1952,7 @@ internal void RebuildObservers(IList connections) for (int i = 0; i < count; i++) _serverManager.Objects.RebuildObservers(connections[i]); } + /// /// Invokes OnClientPresenceChange start or end. /// @@ -2012,6 +2032,7 @@ public static Scene GetScene(string sceneName, NetworkManager nm = null, bool wa return result; } + /// /// Returns a scene by handle. /// @@ -2047,7 +2068,6 @@ private bool IsGlobalScene(Scene scene) return false; } - /// /// Warns if any scene names in GlobalScenes are unsupported. /// This only applies to FishNet version 3. @@ -2071,7 +2091,6 @@ private void CheckForDuplicateGlobalSceneNames() } } - /// /// Removes datas from GlobalScenes. /// @@ -2080,6 +2099,7 @@ private void RemoveFromGlobalScenes(Scene scene) { RemoveFromGlobalScenes(new SceneLookupData[] { SceneLookupData.CreateData(scene) }); } + /// /// Removes datas from GlobalScenes. /// @@ -2146,6 +2166,7 @@ private void AddPendingLoad(NetworkConnection conn) AddPendingLoad(conns, 1); CollectionCaches.Store(conns, 1); } + /// /// Adds a pending load for a connection. /// @@ -2155,8 +2176,8 @@ private void AddPendingLoad(NetworkConnection[] conns, int count) { /* Make sure connection is active. This should always be true * but perhaps disconnect happened as scene was loading on server - * therefor it cannot be sent to the client. - * Also only authenticated clients can load scenes. */ + * therefor it cannot be sent to the client. + * Also only authenticated clients can load scenes. */ if (!c.IsActive || !c.IsAuthenticated) continue; @@ -2166,6 +2187,7 @@ private void AddPendingLoad(NetworkConnection[] conns, int count) _pendingClientSceneChanges[c] = 1; } } + /// /// Sets the first global scene as the active scene. /// If a global scene is not available then FallbackActiveScene is used. @@ -2269,6 +2291,7 @@ internal bool IsIteratingQueue(float completionTimeRequirement = 0f) { return (IteratingQueue || (Time.unscaledTime - QueueCompleteTime) < completionTimeRequirement); } + /// /// Returns if a SceneLoadData is valid. /// @@ -2283,6 +2306,7 @@ private bool SceneDataInvalid(SceneLoadData data, bool error) return result; } + /// /// Returns if a SceneLoadData is valid. /// @@ -2298,6 +2322,7 @@ private bool SceneDataInvalid(SceneUnloadData data, bool error) return result; } + /// /// Returns if connection is active for server or client in association with AsServer. /// @@ -2307,6 +2332,7 @@ private bool ConnectionActive(bool asServer) { return (asServer) ? NetworkManager.IsServerStarted : NetworkManager.IsClientStarted; } + /// /// Returns if a method can execute. /// @@ -2332,6 +2358,5 @@ private bool CanExecute(bool asServer, bool warn) return result; } #endregion - } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs b/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs index 6804447b..5462cfda 100644 --- a/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs +++ b/Assets/FishNet/Runtime/Managing/Server/Object/ServerObjects.cs @@ -393,7 +393,6 @@ private void SetupSceneObjects(Scene s) CollectionCaches.Store(sceneNobs); } - /// /// Setup NetworkObjects in a scene. Should only be called when server is active. /// @@ -591,8 +590,12 @@ private void SpawnWithoutChecks(NetworkObject networkObject, NetworkConnection o if (NetworkManager.IsClientStarted) { int count = spawnCacheCopyCount; + NetworkConnection localConnection = NetworkManager.ClientManager.Connection; for (int i = 0; i < count; i++) - spawnCacheCopy[i].SetRenderersVisible(networkObject.Observers.Contains(NetworkManager.ClientManager.Connection)); + { + NetworkObject nob = spawnCacheCopy[i]; + nob.SetRenderersVisible(nob.Observers.Contains(localConnection)); + } } CollectionCaches.Store(spawnCacheCopy); @@ -685,7 +688,7 @@ internal void ReadSpawn(PooledReader reader, NetworkConnection conn) nob.Preinitialize_Internal(NetworkManager, objectId, owner, true); //Initialize for prediction. nob.InitializePredictedObject_Server(base.NetworkManager, conn); - + base.ReadPayload(conn, nob, reader); //Check user implementation of trySpawn. @@ -700,7 +703,7 @@ internal void ReadSpawn(PooledReader reader, NetworkConnection conn) /* This initializes the object again which cost only a small * amount of perf. Once the refactor is complete this will be changed. */ SpawnWithoutChecks(nob, owner, objectId); - + void SendFailedResponse(int objectId) { SkipRemainingSpawnLength(); @@ -740,7 +743,7 @@ void SendResponse(bool success, int objectId) rw.Initialize(writer, NetworkBehaviour.RPCLINK_RESERVED_BYTES); foreach (NetworkBehaviour nb in nob.NetworkBehaviours) nb.WriteRpcLinks(writer); - + rw.WriteLength(); } diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs index 820db919..3a24f5bd 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.Prediction.cs @@ -20,6 +20,7 @@ using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; +using GameKit.Dependencies.Utilities.Types; using UnityEngine; [assembly: InternalsVisibleTo(UtilityConstants.CODEGEN_ASSEMBLY_NAME)] @@ -57,7 +58,7 @@ public enum DataPlacementResult /// /// Gets the index in replicates where the tick matches. /// - public static int GetReplicateHistoryIndex(uint tick, List replicatesHistory, out DataPlacementResult findResult) where T : IReplicateData + public static int GetReplicateHistoryIndex(uint tick, RingBuffer replicatesHistory, out DataPlacementResult findResult) where T : IReplicateData { int replicatesCount = replicatesHistory.Count; if (replicatesCount == 0) @@ -214,9 +215,9 @@ public abstract partial class NetworkBehaviour : MonoBehaviour /// private List _readReplicateTicks; /// - /// Last tick read for a reconcile. + /// Last tick read for a reconcile. This is only set on the client. /// - private uint _lastReadReconcileTick = TimeManager.UNSET_TICK; + private uint _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK; /// /// Last tick this object reconciled on. /// @@ -238,7 +239,6 @@ public abstract partial class NetworkBehaviour : MonoBehaviour /// Last values when checking for transform changes since previous tick. /// private Vector3 _lastTransformScale; - #endregion #region Consts. @@ -342,7 +342,7 @@ internal void ResetState_Prediction(bool asServer) { if (_readReplicateTicks != null) _readReplicateTicks.Clear(); - _lastReadReconcileTick = TimeManager.UNSET_TICK; + _lastReadReconcileRemoteTick = TimeManager.UNSET_TICK; _lastReconcileTick = TimeManager.UNSET_TICK; } @@ -364,7 +364,7 @@ public virtual void ClearReplicateCache() { } /// [MakePublic] [APIExclude] - protected internal void ClearReplicateCache_Internal(BasicQueue replicatesQueue, List replicatesHistory, ref T lastFirstReadReplicate) where T : IReplicateData + protected internal void ClearReplicateCache_Internal(BasicQueue replicatesQueue, RingBuffer replicatesHistory, RingBuffer> reconcilesHistory, ref T lastFirstReadReplicate) where T : IReplicateData where T2 : IReconcileData { while (replicatesQueue.Count > 0) { @@ -378,6 +378,10 @@ protected internal void ClearReplicateCache_Internal(BasicQueue replicates for (int i = 0; i < replicatesHistory.Count; i++) replicatesHistory[i].Dispose(); replicatesHistory.Clear(); + + // for (int i = 0; i < reconcilesHistory.Count; i++) + // reconcilesHistory[i].Dispose(); + // reconcilesHistory.Clear(); } /// @@ -394,8 +398,8 @@ protected internal void Server_SendReconcileRpc(uint hash, ref T lastReconcil //No more redundancy left. Check if transform may have changed. if (_remainingReconcileResends == 0) return; - // else - // _remainingReconcileResends--; + else + _remainingReconcileResends--; //No owner and no state forwarding, nothing to do. bool stateForwarding = _networkObjectCache.EnableStateForwarding; @@ -501,7 +505,7 @@ internal virtual void Replicate_Replay_Start(uint replayTick) { } /// /// Replays inputs from replicates. /// - protected internal void Replicate_Replay(uint replayTick, ReplicateUserLogicDelegate del, List replicatesHistory, Channel channel) where T : IReplicateData + protected internal void Replicate_Replay(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { //Reconcile data was not received so cannot replay. if (!IsBehaviourReconciling) @@ -516,7 +520,7 @@ protected internal void Replicate_Replay(uint replayTick, ReplicateUserLogicD /// /// Replays an input for authoritative entity. /// - protected internal void Replicate_Replay_Authoritative(uint replayTick, ReplicateUserLogicDelegate del, List replicatesHistory, Channel channel) where T : IReplicateData + protected internal void Replicate_Replay_Authoritative(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { ReplicateTickFinder.DataPlacementResult findResult; int replicateIndex = ReplicateTickFinder.GetReplicateHistoryIndex(replayTick, replicatesHistory, out findResult); @@ -537,7 +541,7 @@ protected internal void Replicate_Replay_Authoritative(uint replayTick, Repli /// /// Replays an input for non authoritative entity. /// - protected internal void Replicate_Replay_NonAuthoritative(uint replayTick, ReplicateUserLogicDelegate del, List replicatesHistory, Channel channel) where T : IReplicateData + protected internal void Replicate_Replay_NonAuthoritative(uint replayTick, ReplicateUserLogicDelegate del, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { //NOTESSTART /* When inserting states only replay the first state after the reconcile. @@ -600,7 +604,7 @@ protected internal virtual void EmptyReplicatesQueueIntoHistory_Start() { } /// This should only be called when client only. /// [MakePublic] - protected internal void EmptyReplicatesQueueIntoHistory(BasicQueue replicatesQueue, List replicatesHistory) where T : IReplicateData + protected internal void EmptyReplicatesQueueIntoHistory(BasicQueue replicatesQueue, RingBuffer replicatesHistory) where T : IReplicateData { while (replicatesQueue.TryDequeue(out T data)) InsertIntoReplicateHistory(data.GetTick(), data, replicatesHistory); @@ -612,7 +616,7 @@ protected internal void EmptyReplicatesQueueIntoHistory(BasicQueue replica /// [MakePublic] [APIExclude] - protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate del, BasicQueue replicatesQueue, List replicatesHistory, Channel channel) where T : IReplicateData + protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate del, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { bool serverStarted = _networkObjectCache.IsServerStarted; bool ownerlessAndServer = (!Owner.IsValid && serverStarted); @@ -669,7 +673,9 @@ protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate _remainingReconcileResends = pm.RedundancyCount; ReplicateData(queueEntry, ReplicateState.CurrentCreated); - count--; + + //Update count since old entries were dropped and one replicate run. + count = replicatesQueue.Count; bool consumeExcess = (!pm.DropExcessiveReplicates || IsClientOnlyStarted); int leaveInBuffer = _networkObjectCache.PredictionManager.StateInterpolation; @@ -677,10 +683,10 @@ protected internal void Replicate_NonAuthoritative(ReplicateUserLogicDelegate //Only consume if the queue count is over leaveInBuffer. if (consumeExcess && count > leaveInBuffer) { - byte maximumAllowedConsumes = 1; + const byte maximumAllowedConsumes = 1; int maximumPossibleConsumes = (count - leaveInBuffer); int consumeAmount = Mathf.Min(maximumAllowedConsumes, maximumPossibleConsumes); - + for (int i = 0; i < consumeAmount; i++) ReplicateData(replicatesQueue.Dequeue(), ReplicateState.CurrentCreated); } @@ -722,7 +728,7 @@ void ReplicateData(T data, ReplicateState state) //Server always adds. if (isServer) { - replicatesHistory.Add(data); + AddReplicatesHistory(replicatesHistory, data); } //If client insert value into history. else @@ -753,7 +759,7 @@ uint GetDefaultedLastReplicateTick() /// True if data has changed.. [MakePublic] //internal [APIExclude] - protected internal void Replicate_Authoritative(ReplicateUserLogicDelegate del, uint methodHash, BasicQueue replicatesQueue, List replicatesHistory, T data, Channel channel) where T : IReplicateData + protected internal void Replicate_Authoritative(ReplicateUserLogicDelegate del, uint methodHash, BasicQueue replicatesQueue, RingBuffer replicatesHistory, T data, Channel channel) where T : IReplicateData { bool ownerlessAndServer = (!Owner.IsValid && IsServerStarted); if (!IsOwner && !ownerlessAndServer) @@ -792,12 +798,13 @@ protected internal void Replicate_Authoritative(ReplicateUserLogicDelegate replicatesHistory[i].Dispose(); //Then remove range. - replicatesHistory.RemoveRange(0, removeCount); + replicatesHistory.RemoveRange(true, removeCount); } } data.SetTick(dataTick); - replicatesHistory.Add(data); + AddReplicatesHistory(replicatesHistory, data); + //Check to reset resends. bool isDefault = isDefaultDel.Invoke(data); bool resetResends = (!isDefault || TransformChanged()); @@ -828,7 +835,7 @@ protected internal void Replicate_Authoritative(ReplicateUserLogicDelegate //Owner always replicates with new data. del.Invoke(data, ReplicateState.CurrentCreated, channel); } - + /// /// Returns the DeltaSerializeOption to use for the tick. /// @@ -858,7 +865,7 @@ internal DeltaSerializerOption GetDeltaSerializeOption() /// /// Sends a Replicate to server or clients. /// - private void Replicate_SendAuthoritative(bool toServer, uint hash, int pastInputs, List replicatesHistory, uint queuedTick, Channel channel, DeltaSerializerOption deltaOption) where T : IReplicateData + private void Replicate_SendAuthoritative(bool toServer, uint hash, int pastInputs, RingBuffer replicatesHistory, uint queuedTick, Channel channel, DeltaSerializerOption deltaOption) where T : IReplicateData { /* Do not use IsSpawnedWithWarning because the server * will still call this a tick or two as clientHost when @@ -933,7 +940,7 @@ private void Replicate_SendAuthoritative(bool toServer, uint hash, int pastIn /// Reads a replicate the client. /// [MakePublic] - internal void Replicate_Reader(uint hash, PooledReader reader, NetworkConnection sender, ref T lastReadReplicate, ref T[] arrBuffer, BasicQueue replicatesQueue, List replicatesHistory, Channel channel) where T : IReplicateData + internal void Replicate_Reader(uint hash, PooledReader reader, NetworkConnection sender, ref T lastReadReplicate, ref T[] arrBuffer, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { /* This will never be received on owner, except in the condition * the server is the owner and also a client. In such condition @@ -1063,7 +1070,7 @@ internal void Replicate_SendNonAuthoritative(uint hash, BasicQueue replica /// /// Handles a received replicate packet. /// - private void Replicate_EnqueueReceivedReplicate(int receivedReplicatesCount, T[] arrBuffer, BasicQueue replicatesQueue, List replicatesHistory, Channel channel) where T : IReplicateData + private void Replicate_EnqueueReceivedReplicate(int receivedReplicatesCount, T[] arrBuffer, BasicQueue replicatesQueue, RingBuffer replicatesHistory, Channel channel) where T : IReplicateData { int startQueueCount = replicatesQueue.Count; /* Owner never gets this for their own object so @@ -1137,7 +1144,7 @@ private void Replicate_EnqueueReceivedReplicate(int receivedReplicatesCount, /// Inserts data into the replicatesHistory collection. /// This should only be called when client only. /// - private void InsertIntoReplicateHistory(uint tick, T data, List replicatesHistory) where T : IReplicateData + private void InsertIntoReplicateHistory(uint tick, T data, RingBuffer replicatesHistory) where T : IReplicateData { /* See if replicate tick is in history. Keep in mind * this is the localTick from the server, not the localTick of @@ -1157,21 +1164,38 @@ private void InsertIntoReplicateHistory(uint tick, T data, List replicates } else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertMiddle) { - replicatesHistory.Insert(index, data); + InsertReplicatesHistory(replicatesHistory, data, index); } else if (findResult == ReplicateTickFinder.DataPlacementResult.InsertEnd) { - replicatesHistory.Add(data); + AddReplicatesHistory(replicatesHistory, data); } /* Insert beginning should not happen unless the data is REALLY old. * This would mean the network was in an unplayable state. Discard the * data. */ if (findResult == ReplicateTickFinder.DataPlacementResult.InsertBeginning) - { - //data.Dispose(); - replicatesHistory.Insert(0, data); - } + InsertReplicatesHistory(replicatesHistory, data, 0); + } + + /// + /// Adds to replicate history disposing of old entries if needed. + /// + private void AddReplicatesHistory(RingBuffer replicatesHistory, T value) where T : IReplicateData + { + T prev = replicatesHistory.Add(value); + if (prev != null) + prev.Dispose(); + } + + /// + /// Inserts to replicate history disposing of old entries if needed. + /// + private void InsertReplicatesHistory(RingBuffer replicatesHistory, T value, int index) where T : IReplicateData + { + T prev = replicatesHistory.Insert(index, value); + if (prev != null) + prev.Dispose(); } /// @@ -1201,16 +1225,137 @@ protected internal virtual void Reconcile_Client_Start() { } /// [APIExclude] [MakePublic] - protected internal void Reconcile_Client(ReconcileUserLogicDelegate reconcileDel, List replicatesHistory, T data) where T : IReconcileData where T2 : IReplicateData + protected internal void Reconcile_Client_Local(RingBuffer> reconcilesHistory, T data) where T : IReconcileData + { + // //Server does not need to store these locally. + // if (_networkObjectCache.IsServerStarted) + // return; + // + // /* This is called by the local client when creating + // * a local reconcile state. These states should always + // * be in order, so we will add data to the end + // * of the collection. */ + // + // /* These datas are used to fill missing reconciles + // * be it the packet dropped, server doesnt need to send, + // * or if the player is throttling reconciles. */ + // + // uint tick = _networkObjectCache.PredictionManager.GetCreateReconcileTick(_networkObjectCache.IsOwner); + // //Tick couldn't be retrieved. + // if (tick == TimeManager.UNSET_TICK) + // return; + // + // data.SetTick(tick); + // + // //Build LocalReconcile. + // LocalReconcile lr = new(); + // lr.Initialize(tick, data); + // + // reconcilesHistory.Add(lr); + } + + //private bool _skip = false; + /// + /// Processes a reconcile for client. + /// + [APIExclude] + [MakePublic] + protected internal void Reconcile_Client(ReconcileUserLogicDelegate reconcileDel, RingBuffer replicatesHistory, RingBuffer> reconcilesHistory, T data) where T : IReconcileData where T2 : IReplicateData { - //No data to reconcile to. + if (!IsBehaviourReconciling) + { + Debug.Log("Not reconciling."); return; + } + + // + // if (_skip) + // IsBehaviourReconciling = false; + // + // _skip = !_skip; + // /* If there is no data to reconcile from then + // * try to pull from the reconcilesHistory; this is the + // * collection the client had made locally.*/ + // if (!IsBehaviourReconciling) + // { + // //Should not ever happen, but cannot proceed without entries. + // if (reconcilesHistory.Count == 0) + // return; + // + // uint firstHistoryTick = reconcilesHistory[0].Tick; + // uint reconcileTick = _networkObjectCache.PredictionManager.GetReconcileStateTick(_networkObjectCache.IsOwner); //(_lastReconcileTick + 1); + // + // long historyIndex = ((long)reconcileTick - (long)firstHistoryTick); + // + // /* If difference is negative then negative then + // * the first history is beyond the tick being reconciled. + // * EG: if history index 0 is 100 and reconcile tick is 90 then + // * (90 - 100) = -10. + // * This should only happen when first connecting and data hasn't been made yet. */ + // if (historyIndex < 0) + // return; + // + // //Not enough histories to grab local reconcile form. + // if (reconcilesHistory.Count <= historyIndex) + // return; + // + // LocalReconcile localReconcile = reconcilesHistory[(int)historyIndex]; + // uint lrTick = localReconcile.Tick; + // /* Since we store reconcile data every tick moving ahead a set number of ticks + // * should usually match up to the reconcile tick. There are exceptions where the tick + // * used to locally create the reconcile was for non owner, so using the server tick, + // * and there is a slight misalignment in the server tick. This is not unusual as the + // * client corrects it's tick timing regularly, but such an alignment could make this not line up. */ + // if (lrTick != reconcileTick) + // { + // //When tick doesn't match still allow use of the entry if it's within 3 ticks. + // long mismatchDifference = Math.Abs((long)lrTick - (long)reconcileTick); + // Debug.LogError($"Match difference of {mismatchDifference}"); + // if (mismatchDifference > 3) + // return; + // } + // //Before disposing get the writer and call reconcile reader so it's parsed. + // PooledWriter reconcileWritten = reconcilesHistory[(int)historyIndex].Writer; + // /* Although this is actually from the local client the datasource is being set to server since server + // * is what typically sends reconciles. */ + // PooledReader reader = ReaderPool.Retrieve(reconcileWritten.GetArraySegment(), null, Reader.DataSource.Server); + // data = Reconcile_Reader(lrTick, reader); + // ReaderPool.Store(reader); + // + // //If here everything is good, remove up to used index. + // for (int i = 0; i < historyIndex; i++) + // reconcilesHistory[i].Dispose(); + // + // reconcilesHistory.RemoveRange(true, (int)historyIndex); + // + // //Uses iteration to try and find the tick. + // bool FindTickThroughIteration(uint tickToFind, out long index) + // { + // for (int i = 0; i < reconcilesHistory.Count; i++) + // { + // uint historyTick = reconcilesHistory[i].Tick; + // //Exact match. + // if (historyTick == tickToFind) + // { + // index = i; + // return true; + // } + // } + // + // //Failed to find. + // index = -1; + // return false; + // } + // } + + //Set on the networkObject that a reconcile can now occur. + _networkObjectCache.IsObjectReconciling = true; - //Remove up reconcile tick from received ticks. uint dataTick = data.GetTick(); _lastReconcileTick = dataTick; + //Remove up reconcile tick from received ticks. int readReplicatesRemovalCount = 0; for (int i = 0; i < _readReplicateTicks.Count; i++) { @@ -1228,7 +1373,7 @@ protected internal void Reconcile_Client(ReconcileUserLogicDelegate re * is the state after a replicate for it's tick we no longer * need any replicates prior. */ //Find the closest entry which can be removed. - int removalCount = 0; + int removeCount = 0; //A few quick tests. if (replicatesHistory.Count > 0) { @@ -1237,7 +1382,7 @@ protected internal void Reconcile_Client(ReconcileUserLogicDelegate re * as reconcile is beyond them. */ if (replicatesHistory[^1].GetTick() <= dataTick) { - removalCount = replicatesHistory.Count; + removeCount = replicatesHistory.Count; } //Somewhere in between. Find what to remove up to. else @@ -1249,16 +1394,16 @@ protected internal void Reconcile_Client(ReconcileUserLogicDelegate re * found remove up to that entry. */ if (entryTick > dataTick) { - removalCount = i; + removeCount = i; break; } } } } - for (int i = 0; i < removalCount; i++) + for (int i = 0; i < removeCount; i++) replicatesHistory[i].Dispose(); - replicatesHistory.RemoveRange(0, removalCount); + replicatesHistory.RemoveRange(true, removeCount); } //Call reconcile user logic. @@ -1271,7 +1416,7 @@ internal void Reconcile_Client_End() } /// - /// Reads a reconcile for the client. + /// Reads a reconcile from the server. /// public void Reconcile_Reader(PooledReader reader, ref T lastReconciledata, Channel channel) where T : IReconcileData { @@ -1283,18 +1428,30 @@ public void Reconcile_Reader(PooledReader reader, ref T lastReconciledata, Ch T newData = reader.ReadReconcile(); #endif //Do not process if an old state. - if (tick < _lastReadReconcileTick) + if (tick < _lastReadReconcileRemoteTick) return; lastReconciledata = newData; lastReconciledata.SetTick(tick); IsBehaviourReconciling = true; - //Also set on NetworkObject since at least one behaviour is reconciling. _networkObjectCache.IsObjectReconciling = true; - _lastReadReconcileTick = tick; + _lastReadReconcileRemoteTick = tick; } + // /// + // /// Reads a local reconcile from the client. + // /// + // public T Reconcile_Reader(uint tick, PooledReader reader) where T : IReconcileData + // { + // T newData = reader.ReadReconcile(); + // newData.SetTick(tick); + // + // IsBehaviourReconciling = true; + // + // return newData; + // } + /// /// Sets the last tick this NetworkBehaviour replicated with. /// diff --git a/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs b/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs index 3f69275e..56770899 100644 --- a/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs +++ b/Assets/FishNet/Runtime/Object/NetworkBehaviour.SyncTypes.cs @@ -113,12 +113,14 @@ internal bool DirtySyncType() { if (!IsServerStarted) return false; - // /* No reason to dirty if there are no observers. - // * This can happen even if a client is going to see - // * this object because the server side initializes - // * before observers are built. */ - // if (_networkObjectCache.Observers.Count == 0 && !_networkObjectCache.PredictedSpawner.IsValid) - // return false; + /* No reason to dirty if there are no observers. + * This can happen even if a client is going to see + * this object because the server side initializes + * before observers are built. Clients which become observers + * will get the latest values in the spawn message, which is separate + * from writing dirty syncTypes. */ + if (_networkObjectCache.Observers.Count == 0 && !_networkObjectCache.PredictedSpawner.IsValid) + return false; if (!SyncTypeDirty) _networkObjectCache.NetworkManager.ServerManager.Objects.SetDirtySyncType(this); @@ -265,7 +267,7 @@ internal bool WriteDirtySyncTypes(SyncTypeWriteFlag flags) dirtyCount++; //Interval not yet met. - if (!ignoreInterval && !sb.SyncTimeMet(tick)) + if (!ignoreInterval && !sb.IsNextSyncTimeMet(tick)) continue; //Unset that SyncType is dirty as it will be written now. @@ -505,7 +507,7 @@ internal void ReadSyncTypesForSpawn(PooledReader reader) byte syncTypeId = reader.ReadUInt8Unpacked(); if (_syncTypes.TryGetValueIL2CPP(syncTypeId, out SyncBase sb)) - sb.Read(reader, asServer: true); + sb.Read(reader, asServer: false); else NetworkManager.LogWarning($"SyncType not found for index {syncTypeId} on {transform.name}, component {GetType().FullName}. Remainder of packet may become corrupt."); } diff --git a/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs b/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs index fac02bcf..ec01529e 100644 --- a/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs +++ b/Assets/FishNet/Runtime/Object/NetworkObject.Prediction.cs @@ -1,10 +1,13 @@ -using FishNet.Component.Prediction; +using System; +using FishNet.Component.Prediction; using FishNet.Component.Transforming; using FishNet.Managing; using FishNet.Managing.Timing; using FishNet.Object.Prediction; using GameKit.Dependencies.Utilities; using System.Collections.Generic; +using FishNet.Connection; +using FishNet.Managing.Server; using UnityEngine; namespace FishNet.Object @@ -235,7 +238,6 @@ private void ChangePredictionSubscriptions(bool subscribe, NetworkManager manage } } - /// /// Initializes tick smoothing. /// @@ -287,7 +289,6 @@ private void DeinitializeSmoothers() } } - private void InvokeStartCallbacks_Prediction(bool asServer) { if (_predictionBehaviours.Count == 0) @@ -338,19 +339,25 @@ private void PredictionManager_OnPreReconcile(uint clientTick, uint serverTick) PredictionSmoother.OnPreReconcile(); } - private void PredictionManager_OnReconcile(uint clientReconcileTick, uint serverReconcileTick) { + /* If still not reconciling then pause rigidbody. + * This shouldn't happen unless the user is not calling + * reconcile at all. */ if (!IsObjectReconciling) { if (_rigidbodyPauser != null) _rigidbodyPauser.Pause(); + + return; } - else - { - for (int i = 0; i < _predictionBehaviours.Count; i++) - _predictionBehaviours[i].Reconcile_Client_Start(); - } + + /* Tell all prediction behaviours to set/validate their + * reconcile data now. This will use reconciles from the server + * whenever possible, and local reconciles if a server reconcile + * is not available. */ + for (int i = 0; i < _predictionBehaviours.Count; i++) + _predictionBehaviours[i].Reconcile_Client_Start(); } private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint serverReconcileTick) @@ -367,7 +374,6 @@ private void PredictionManager_OnPostReconcile(uint clientReconcileTick, uint se IsObjectReconciling = false; } - private void PredictionManager_OnReplicateReplay(uint clientTick, uint serverTick) { uint replayTick = (IsOwner) ? clientTick : serverTick; @@ -409,4 +415,47 @@ internal void SetReplicateTick(uint value, bool createdReplicate) /// private void ResetState_Prediction(bool asServer) { } } + + /// + /// Place this component on your NetworkManager object to remove ownership of objects for a disconnecting client. + /// This prevents any owned object from being despawned when the owner disconnects. + /// + public class GlobalPreserveOwnedObjects : MonoBehaviour + { + private void Awake() + { + ServerManager sm = GetComponent(); + sm.Objects.OnPreDestroyClientObjects += Objects_OnPreDestroyClientObjects; + } + + protected virtual void Objects_OnPreDestroyClientObjects(NetworkConnection conn) + { + foreach (NetworkObject networkObject in conn.Objects) + networkObject.RemoveOwnership(); + } + } + + /// + /// Place this component on NetworkObjects you wish to remove ownership on for a disconnecting owner. + /// This prevents the object from being despawned when the owner disconnects. + /// + public class NetworkPreserveOwnedObjects : NetworkBehaviour + { + public override void OnStartServer() + { + ServerManager.Objects.OnPreDestroyClientObjects += OnPreDestroyClientObjects; + } + + public override void OnStopServer() + { + if (ServerManager != null) + ServerManager.Objects.OnPreDestroyClientObjects -= OnPreDestroyClientObjects; + } + + private void OnPreDestroyClientObjects(NetworkConnection conn) + { + if (conn == Owner) + RemoveOwnership(); + } + } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs new file mode 100644 index 00000000..f48369b2 --- /dev/null +++ b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs @@ -0,0 +1,46 @@ +using FishNet.Documenting; +using FishNet.Serializing; + +namespace FishNet.Object.Prediction +{ + /// + /// Used to store reconciles locally. + /// + /// This is for internal use only. + [APIExclude] + public struct LocalReconcile where T : IReconcileData + { + /// + /// Tick for reconcile. + /// + public uint Tick; + /// + /// Writer reconcile was written to. + /// + public PooledWriter Writer; + /// + /// Data inside writer. + /// + public T Data; + + public void Initialize(uint tick, T data) + { + Tick = tick; + Data = data; + Writer = WriterPool.Retrieve(); + Writer.Write(data); + } + + /// + /// Disposes of used data. + /// + public void Dispose() + { + Data.Dispose(); + if (Writer != null) + WriterPool.Store(Writer); + } + } + + +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs.meta b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs.meta new file mode 100644 index 00000000..a4eaa212 --- /dev/null +++ b/Assets/FishNet/Runtime/Object/Prediction/LocalReconcile.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eb90cfdc07524be40a9d4d11ae7c35e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs index 23d4c423..24cf84c1 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncDictionary.cs @@ -239,8 +239,6 @@ internal protected override void OnStartCallback(bool asServer) [APIExclude] internal protected override void WriteDelta(PooledWriter writer, bool resetSyncTick = true) { - base.WriteDelta(writer, resetSyncTick); - //If sending all then clear changed and write full. if (_sendAll) { @@ -250,10 +248,11 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT } else { + base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); - base.WriteChangeId(writer, false); - + writer.WriteInt32(_changed.Count); for (int i = 0; i < _changed.Count; i++) @@ -276,7 +275,7 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT _changed.Clear(); } } - + /// /// Writers all values if not initial values. /// Internal use. @@ -290,9 +289,9 @@ internal protected override void WriteFull(PooledWriter writer) return; base.WriteHeader(writer, false); + //True for full write. writer.WriteBoolean(true); - base.WriteChangeId(writer, true); writer.WriteInt32(Collection.Count); foreach (KeyValuePair item in Collection) @@ -309,24 +308,19 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkBehaviour.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); IDictionary collection = Collection; bool fullWrite = reader.ReadBoolean(); - bool ignoreReadChanges = base.ReadChangeId(reader); - bool canModifyCollection = (!asClientAndHost && !ignoreReadChanges); //Clear collection since it's a full write. - if (canModifyCollection && fullWrite) + if (canModifyValues && fullWrite) collection.Clear(); int changes = reader.ReadInt32(); @@ -344,29 +338,31 @@ internal protected override void Read(PooledReader reader, bool asServer) { key = reader.Read(); value = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) collection[key] = value; } //Clear. else if (operation == SyncDictionaryOperation.Clear) { - if (canModifyCollection) + if (canModifyValues) collection.Clear(); } //Remove. else if (operation == SyncDictionaryOperation.Remove) { key = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) collection.Remove(key); } - if (!ignoreReadChanges) + if (newChangeId) InvokeOnChange(operation, key, value, false); } //If changes were made invoke complete after all have been read. - if (!ignoreReadChanges && changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncDictionaryOperation.Complete, default, default, false); } @@ -567,7 +563,7 @@ public void DirtyAll() { if (!base.IsInitialized) return; - if (!base.CanNetworkSetValues(true)) + if (!base.CanNetworkSetValues(log: true)) return; if (base.Dirty()) diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs index 47deea01..4c55f710 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncHashset.cs @@ -226,10 +226,10 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT else { base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); - base.WriteChangeId(writer, false); - // + writer.WriteInt32(_changed.Count); for (int i = 0; i < _changed.Count; i++) @@ -260,7 +260,6 @@ internal protected override void WriteFull(PooledWriter writer) base.WriteHeader(writer, false); //True for full write. writer.WriteBoolean(true); - base.WriteChangeId(writer, true); int count = Collection.Count; writer.WriteInt32(count); @@ -277,24 +276,19 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); ISet collection = Collection; bool fullWrite = reader.ReadBoolean(); - bool ignoreReadChanges = base.ReadChangeId(reader); - bool canModifyCollection = (!asClientAndHost && !ignoreReadChanges); //Clear collection since it's a full write. - if (canModifyCollection && fullWrite) + if (canModifyValues && fullWrite) collection.Clear(); int changes = reader.ReadInt32(); @@ -307,39 +301,42 @@ internal protected override void Read(PooledReader reader, bool asServer) if (operation == SyncHashSetOperation.Add) { next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) collection.Add(next); } //Clear. else if (operation == SyncHashSetOperation.Clear) { - if (canModifyCollection) + if (canModifyValues) collection.Clear(); } //Remove. else if (operation == SyncHashSetOperation.Remove) { next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) collection.Remove(next); } //Updated. else if (operation == SyncHashSetOperation.Update) { next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) { collection.Remove(next); collection.Add(next); } } - if (!ignoreReadChanges) + if (newChangeId) InvokeOnChange(operation, next, false); } //If changes were made invoke complete after all have been read. - if (!ignoreReadChanges && changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncHashSetOperation.Complete, default, false); } @@ -468,7 +465,7 @@ public void DirtyAll() { if (!base.IsInitialized) return; - if (!base.CanNetworkSetValues(true)) + if (!base.CanNetworkSetValues(log: true)) return; if (base.Dirty()) diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs index 30b3fa1c..90634f2b 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncList.cs @@ -255,9 +255,10 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT else { base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); - base.WriteChangeId(writer, false); + //Number of entries expected. writer.WriteInt32(_changed.Count); @@ -298,7 +299,6 @@ internal protected override void WriteFull(PooledWriter writer) base.WriteHeader(writer, false); //True for full write. writer.WriteBoolean(true); - base.WriteChangeId(writer, true); int count = Collection.Count; writer.WriteInt32(count); @@ -316,24 +316,18 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); List collection = Collection; bool fullWrite = reader.ReadBoolean(); - bool ignoreReadChanges = base.ReadChangeId(reader); - bool canModifyCollection = (!asClientAndHost && !ignoreReadChanges); - //Clear collection since it's a full write. - if (canModifyCollection && fullWrite) + if (canModifyValues && fullWrite) collection.Clear(); int changes = reader.ReadInt32(); @@ -349,7 +343,8 @@ internal protected override void Read(PooledReader reader, bool asServer) if (operation == SyncListOperation.Add) { next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) { index = collection.Count; collection.Add(next); @@ -358,7 +353,7 @@ internal protected override void Read(PooledReader reader, bool asServer) //Clear. else if (operation == SyncListOperation.Clear) { - if (canModifyCollection) + if (canModifyValues) collection.Clear(); } //Insert. @@ -366,14 +361,16 @@ internal protected override void Read(PooledReader reader, bool asServer) { index = reader.ReadInt32(); next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) collection.Insert(index, next); } //RemoveAt. else if (operation == SyncListOperation.RemoveAt) { index = reader.ReadInt32(); - if (canModifyCollection) + + if (canModifyValues) { prev = collection[index]; collection.RemoveAt(index); @@ -384,19 +381,20 @@ internal protected override void Read(PooledReader reader, bool asServer) { index = reader.ReadInt32(); next = reader.Read(); - if (canModifyCollection) + + if (canModifyValues) { prev = collection[index]; collection[index] = next; } } - if (!ignoreReadChanges) + if (newChangeId) InvokeOnChange(operation, index, prev, next, false); } //If changes were made invoke complete after all have been read. - if (!ignoreReadChanges && changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncListOperation.Complete, -1, default, default, false); } diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncStopwatch.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncStopwatch.cs index b5ff9a0a..cdf2f1c7 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncStopwatch.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncStopwatch.cs @@ -8,13 +8,11 @@ namespace FishNet.Object.Synchronizing { - /// /// A SyncObject to efficiently synchronize Stopwatchs over the network. /// public class SyncStopwatch : SyncBase, ICustomSync { - #region Type. /// /// Information about how the Stopwatch has changed. @@ -40,6 +38,7 @@ public ChangeData(SyncStopwatchOperation operation, float previous) /// Previous value of the Stopwatch. This will be -1f is the value is not available. /// True if occurring on server. public delegate void SyncTypeChanged(SyncStopwatchOperation op, float prev, bool asServer); + /// /// Called when a Stopwatch operation occurs. /// @@ -248,10 +247,11 @@ private void WriteStartStopwatch(Writer w, float elapsed, bool includeOperationB /// /// Reads and sets the current values for server or client. /// - [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -260,67 +260,71 @@ internal protected override void Read(PooledReader reader, bool asServer) if (op == SyncStopwatchOperation.Start) { float elapsed = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (canModifyValues) Elapsed = elapsed; - InvokeOnChange(op, elapsed, asServer); + + if (newChangeId) + InvokeOnChange(op, elapsed, asServer); } else if (op == SyncStopwatchOperation.Pause) { - if (CanSetValues(asServer)) + if (canModifyValues) Paused = true; - InvokeOnChange(op, -1f, asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, asServer); } else if (op == SyncStopwatchOperation.PauseUpdated) { float prev = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (canModifyValues) Paused = true; - InvokeOnChange(op, prev, asServer); + + if (newChangeId) + InvokeOnChange(op, prev, asServer); } else if (op == SyncStopwatchOperation.Unpause) { - if (CanSetValues(asServer)) + if (canModifyValues) Paused = false; - InvokeOnChange(op, -1f, asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, asServer); } else if (op == SyncStopwatchOperation.Stop) { - StopStopwatch_Internal(asServer); - InvokeOnChange(op, -1f, false); + if (canModifyValues) + StopStopwatch_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, false); } else if (op == SyncStopwatchOperation.StopUpdated) { float prev = reader.ReadSingle(); - StopStopwatch_Internal(asServer); - InvokeOnChange(op, prev, asServer); + if (canModifyValues) + StopStopwatch_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, asServer); } } - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncStopwatchOperation.Complete, -1f, asServer); } - /// - /// Returns if values can be updated. - /// - private bool CanSetValues(bool asServer) - { - return (asServer || !base.NetworkManager.IsServerStarted); - } - /// /// Stops the Stopwatch and resets. /// private void StopStopwatch_Internal(bool asServer) { - if (!CanSetValues(asServer)) - return; - Paused = false; Elapsed = -1f; } - /// /// Invokes OnChanged callback. /// @@ -342,7 +346,6 @@ private void InvokeOnChange(SyncStopwatchOperation operation, float prev, bool a } } - /// /// Called after OnStartXXXX has occurred. /// diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs index aad2ad63..f27bddf1 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncTimer.cs @@ -266,20 +266,14 @@ private void WriteStartTimer(Writer w, bool includeOperationByte) w.WriteSingle(Duration); } - /// - /// Returns if values can be updated. - /// - private bool CanSetValues(bool asServer) - { - return (asServer || !base.NetworkManager.IsServerStarted); - } - /// /// Reads and sets the current values for server or client. /// [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -289,32 +283,43 @@ internal protected override void Read(PooledReader reader, bool asServer) { float next = reader.ReadSingle(); float duration = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (canModifyValues) { Paused = false; Remaining = next; Duration = duration; } - - InvokeOnChange(op, -1f, next, asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, next, asServer); } else if (op == SyncTimerOperation.Pause || op == SyncTimerOperation.PauseUpdated || op == SyncTimerOperation.Unpause) { - UpdatePauseState(op); + if (canModifyValues) + UpdatePauseState(op); } else if (op == SyncTimerOperation.Stop) { float prev = Remaining; - StopTimer_Internal(asServer); - InvokeOnChange(op, prev, 0f, false); + + if (canModifyValues) + StopTimer_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, 0f, false); } // else if (op == SyncTimerOperation.StopUpdated) { float prev = Remaining; float next = reader.ReadSingle(); - StopTimer_Internal(asServer); - InvokeOnChange(op, prev, next, asServer); + + if (canModifyValues) + StopTimer_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, next, asServer); } } @@ -329,20 +334,19 @@ void UpdatePauseState(SyncTimerOperation op) if (op == SyncTimerOperation.PauseUpdated) { next = reader.ReadSingle(); - if (CanSetValues(asServer)) - Remaining = next; + Remaining = next; } else { next = Remaining; } - if (CanSetValues(asServer)) - Paused = newPauseState; - InvokeOnChange(op, prev, next, asServer); + Paused = newPauseState; + if (newChangeId) + InvokeOnChange(op, prev, next, asServer); } - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncTimerOperation.Complete, -1f, -1f, false); } @@ -351,9 +355,6 @@ void UpdatePauseState(SyncTimerOperation op) /// private void StopTimer_Internal(bool asServer) { - if (!CanSetValues(asServer)) - return; - Paused = false; Remaining = 0f; } diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncVar.cs b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncVar.cs index 4d69cef6..ec2a2f3d 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncVar.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/Beta/SyncVar.cs @@ -12,7 +12,6 @@ namespace FishNet.Object.Synchronizing { - internal interface ISyncVar { } [APIExclude] @@ -114,6 +113,7 @@ public T Value /// Called when the SyncDictionary changes. /// public event OnChanged OnChange; + public delegate void OnChanged(T prev, T next, bool asServer); #endregion @@ -178,6 +178,7 @@ public void SetInitialValues(T value) if (base.IsInitialized) _valueSetAfterInitialized = true; } + /// /// Sets current and previous values. /// @@ -190,11 +191,11 @@ private void UpdateValues(T next) _value = next; } + /// /// Sets current value and marks the SyncVar dirty when able to. Returns if able to set value. /// /// True if SetValue was called in response to user code. False if from automated code. - internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) { /* IsInitialized is only set after the script containing this SyncVar @@ -227,7 +228,7 @@ internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) if (!base.CanNetworkSetValues(true)) return; /* We will only be this far if the network is not active yet, - * server is active, or client has setting permissions. + * server is active, or client has setting permissions. * We only need to set asServerInvoke to false if the network * is initialized and the server is not active. */ bool asServerInvoke = CanInvokeCallbackAsServer(); @@ -240,7 +241,7 @@ internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) T prev = _value; UpdateValues(nextValue); //Still call invoke because change will be cached for when the network initializes. - InvokeOnChange(prev, _value, calledByUser); + InvokeOnChange(prev, _value, asServer: true); } else { @@ -257,13 +258,27 @@ internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) //Not called by user. else { - /* Previously clients were not allowed to set values - * but this has been changed because clients may want - * to update values locally while occasionally - * letting the syncvar adjust their side. */ - T prev = _value; - if (Comparers.EqualityCompare(prev, nextValue)) - return; + /* Only perform the equality checks when not host. + * + * In the previous SyncVar version it was okay to call + * this on host because a separate clientHost value was kept for + * the client side, and that was compared against. + * + * In newer SyncVar(this one) a client side copy is + * not kept so when compariing against the current vlaue + * as clientHost, it will always return as matched. + * + * But it's impossible for clientHost to send a value + * and it not have changed, so this check is not needed. */ + + // /* Previously clients were not allowed to set values + // * but this has been changed because clients may want + // * to update values locally while occasionally + // * letting the syncvar adjust their side. */ + // T prev = _value; + // if (Comparers.EqualityCompare(prev, nextValue)) + // return; + /* If also server do not update value. * Server side has say of the current value. */ /* Only update value if not server. We do not want @@ -272,7 +287,9 @@ internal void SetValue(T nextValue, bool calledByUser, bool sendRpc = false) if (!base.NetworkManager.IsServerStarted) UpdateValues(nextValue); - InvokeOnChange(prev, nextValue, calledByUser); + T prev = _value; + + InvokeOnChange(prev, nextValue, asServer: false); } @@ -353,7 +370,6 @@ private void InvokeOnChange(T prev, T next, bool asServer) /// Called after OnStartXXXX has occurred. /// /// True if OnStartServer was called, false if OnStartClient. - [MakePublic] internal protected override void OnStartCallback(bool asServer) { @@ -391,7 +407,7 @@ internal protected override void WriteFull(PooledWriter obj0) { /* If a class then skip comparer check. * InitialValue and Value will be the same reference. - * + * * If a value then compare field changes, since the references * will not be the same. */ //Compare if a value type. @@ -416,9 +432,22 @@ internal protected override void WriteFull(PooledWriter obj0) protected internal override void Read(PooledReader reader, bool asServer) { T value = reader.Read(); + + if (!ReadChangeId(reader)) + return; + SetValue(value, false); + //TODO this needs to separate invokes from setting values so that syncvar can be written like remainder of synctypes. } + //SyncVars do not use changeId. + [APIExclude] + protected override bool ReadChangeId(Reader reader) => true; + + //SyncVars do not use changeId. + [APIExclude] + protected override void WriteChangeId(PooledWriter writer) { } + /// /// Resets to initialized values. /// @@ -430,8 +459,7 @@ protected internal override void ResetState(bool asServer) * asServer is true. * Is not network initialized. * asServer is false, and server is not started. */ - if ((asServer && !base.NetworkManager.IsClientStarted) || - (!asServer && base.NetworkBehaviour.IsDeinitializing)) + if ((asServer && !base.NetworkManager.IsClientStarted) || (!asServer && base.NetworkBehaviour.IsDeinitializing)) { _value = _initialValue; _valueSetAfterInitialized = false; diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs index 5ab7a105..9c46cf51 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncBase.cs @@ -1,4 +1,5 @@ -using FishNet.CodeGenerating; +using System; +using FishNet.CodeGenerating; using FishNet.Managing; using FishNet.Managing.Timing; using FishNet.Serializing; @@ -41,13 +42,11 @@ public class SyncBase /// /// NetworkManager this uses. /// - [MakePublic] - internal NetworkManager NetworkManager = null; + public NetworkManager NetworkManager = null; /// /// NetworkBehaviour this SyncVar belongs to. /// - [MakePublic] - internal NetworkBehaviour NetworkBehaviour = null; + public NetworkBehaviour NetworkBehaviour = null; /// /// True if the server side has initialized this SyncType. /// @@ -70,13 +69,14 @@ public class SyncBase /// Channel to send on. /// internal Channel Channel => _currentChannel; + /// /// Sets a new currentChannel. /// /// internal void SetCurrentChannel(Channel channel) => _currentChannel = channel; #endregion - + #region Private. /// /// Sync interval converted to ticks. @@ -87,30 +87,29 @@ public class SyncBase /// private Channel _currentChannel; /// - /// Last localTick when full data was written. - /// - protected uint _lastWriteFullLocalTick; - /// - /// Id of the current change since the last full write. - /// This is used to prevent duplicates caused by deltas writing after full writes when clients already received the delta in the full write, such as a spawn message. + /// Last changerId read from sender. /// - protected uint _changeId; + private ushort _lastReadChangeId = UNSET_CHANGE_ID; /// - /// Last changeId read. + /// Last changeId that was sent to receivers. /// - private long _lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID; + private ushort _lastWrittenChangeId = UNSET_CHANGE_ID; #endregion - #region Const. + #region Consts. + /// + /// Value to use when readId is unset. + /// + private const ushort UNSET_CHANGE_ID = 0; /// - /// Default value for LastReadDirtyId. + /// Maximum value readId can be before resetting to the beginning. /// - private const long DEFAULT_LAST_READ_DIRTYID = -1; + private const ushort MAXIMUM_CHANGE_ID = ushort.MaxValue; #endregion - #region Constructors public SyncBase() : this(new()) { } + public SyncBase(SyncTypeSettings settings) { Settings = settings; @@ -125,32 +124,35 @@ public void UpdateSettings(SyncTypeSettings settings) Settings = settings; SetTimeToTicks(); } + /// /// Updates settings with new values. /// - public void UpdatePermissions(WritePermission writePermissions, ReadPermission readPermissions) { UpdatePermissions(writePermissions); UpdatePermissions(readPermissions); } + /// /// Updates settings with new values. /// public void UpdatePermissions(WritePermission writePermissions) => Settings.WritePermission = writePermissions; + /// /// Updates settings with new values. /// public void UpdatePermissions(ReadPermission readPermissions) => Settings.ReadPermission = readPermissions; + /// /// Updates settings with new values. /// - public void UpdateSendRate(float sendRate) { Settings.SendRate = sendRate; SetTimeToTicks(); } + /// /// Updates settings with new values. /// @@ -159,6 +161,7 @@ public void UpdateSettings(Channel channel) CheckChannel(ref channel); _currentChannel = channel; } + /// /// Updates settings with new values. /// @@ -187,7 +190,6 @@ private void CheckChannel(ref Channel c) /// /// Initializes this SyncBase before user Awake code. /// - [MakePublic] internal void InitializeEarly(NetworkBehaviour nb, uint syncIndex, bool isSyncObject) { @@ -201,7 +203,6 @@ internal void InitializeEarly(NetworkBehaviour nb, uint syncIndex, bool isSyncOb /// /// Called during InitializeLate in NetworkBehaviours to indicate user Awake code has executed. /// - [MakePublic] internal void InitializeLate() { @@ -255,6 +256,7 @@ internal protected virtual void OnStartCallback(bool asServer) else OnStartClientCalled = true; } + /// /// Called before OnStopXXXX has occurred for the NetworkBehaviour. /// @@ -271,9 +273,9 @@ internal protected virtual void OnStopCallback(bool asServer) /// /// True if can set values and send them over the network. /// - /// + /// /// - protected bool CanNetworkSetValues(bool warn = true) + protected bool CanNetworkSetValues(bool log = true) { /* If not registered then values can be set * since at this point the object is still being initialized @@ -294,7 +296,7 @@ protected bool CanNetworkSetValues(bool warn = true) /* If here then server is not active and additional * checks must be performed. */ bool result = (Settings.WritePermission == WritePermission.ClientUnsynchronized) || (Settings.ReadPermission == ReadPermission.ExcludeOwner && NetworkBehaviour.IsOwner); - if (!result && warn) + if (!result && log) LogServerNotActiveWarning(); return result; @@ -313,7 +315,7 @@ protected void LogServerNotActiveWarning() /// Dirties this Sync and the NetworkBehaviour. /// /// True to send current dirtied values immediately as a RPC. When this occurs values will arrive in the order they are sent and interval is ignored. - protected bool Dirty()//bool sendRpc = false) + protected bool Dirty() //bool sendRpc = false) { //if (sendRpc) // NextSyncTick = 0; @@ -326,9 +328,6 @@ protected bool Dirty()//bool sendRpc = false) * processed. This ensures that data * is flushed. */ bool canDirty = NetworkBehaviour.DirtySyncType(); - //If first time dirtying increase dirtyId. - if (IsDirty != canDirty) - _changeId++; IsDirty |= canDirty; return canDirty; @@ -342,33 +341,88 @@ protected bool Dirty()//bool sendRpc = false) protected bool CanInvokeCallbackAsServer() => (!IsNetworkInitialized || NetworkBehaviour.IsServerStarted); /// - /// Reads the change Id and returns if changes should be ignored. + /// Reads a change Id and returns true if the change is new. /// - /// - protected bool ReadChangeId(PooledReader reader) + /// This method is currently under evaluation and may change at any time. + protected virtual bool ReadChangeId(Reader reader) { - bool reset = reader.ReadBoolean(); - uint id = reader.ReadUInt32(); - bool ignoreResults = !reset && (id <= _lastReadDirtyId); + if (NetworkManager == null) + { + NetworkManager.LogWarning($"NetworkManager is unexpectedly null during a SyncType read."); + return false; + } + + bool rolledOver = reader.ReadBoolean(); + ushort id = reader.ReadUInt16(); + + //Only check lastReadId if its not unset. + if (_lastReadChangeId != UNSET_CHANGE_ID) + { + /* If not rolledOver then Id should always be larger + * than the last read. If it's not then the data is + * old. + * + * If Id is smaller then rolledOver should be normal, + * as rolling over means to restart the Id from the lowest + * value. */ + if (rolledOver) + { + if (id >= _lastReadChangeId) + return false; + } + else + { + if (id <= _lastReadChangeId) + return false; + } + } - _lastReadDirtyId = id; - return ignoreResults; + _lastReadChangeId = id; + return true; } + + + /// + /// Writes the readId for a change. + /// + /// This method is currently under evaluation and may change at any time. + protected virtual void WriteChangeId(PooledWriter writer) + { + bool rollOver; + if (_lastWrittenChangeId >= MAXIMUM_CHANGE_ID) + { + rollOver = true; + _lastWrittenChangeId = UNSET_CHANGE_ID; + } + else + { + rollOver = false; + } + + _lastWrittenChangeId++; + writer.WriteBoolean(rollOver); + writer.WriteUInt16(_lastWrittenChangeId); + } + + /// + /// Returns true if values are being read as clientHost. + /// + /// True if reading as server. + /// This method is currently under evaluation and may change at any time. + protected bool IsReadAsClientHost(bool asServer) => (!asServer && NetworkManager.IsServerStarted); /// - /// Writers the current ChangeId, and if it has been reset. + /// Outputs values which may be helpful on how to process a read operation. /// - protected void WriteChangeId(PooledWriter writer, bool fullWrite) + /// True if the changeId read is not old data. + /// True if being read as clientHost. + /// True if can modify values from the read, typically when asServer or not asServer and not clientHost. + /// This method is currently under evaluation and may change at any time. + protected void SetReadArguments(PooledReader reader, bool asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues) { - /* Fullwrites do not reset the Id, only - * delta changes do. */ - bool resetId = (!fullWrite && NetworkManager.TimeManager.LocalTick > _lastWriteFullLocalTick); - writer.WriteBoolean(resetId); - //If to reset Id then do so. - if (resetId) - _changeId = 0; - //Write Id. - writer.WriteUInt32(_changeId); + newChangeId = ReadChangeId(reader); + asClientHost = IsReadAsClientHost(asServer); + canModifyValues = (newChangeId && !asClientHost); } /// @@ -392,27 +446,27 @@ internal void ResetDirty() IsDirty = false; } } + /// /// True if dirty and enough time has passed to write changes. /// - /// - /// - internal bool SyncTimeMet(uint tick) - { - return (IsDirty && tick >= NextSyncTick); - } + internal bool IsNextSyncTimeMet(uint tick) => (IsDirty && tick >= NextSyncTick); + + [Obsolete("Use IsNextSyncTimeMet.")] //Remove on V5 + internal bool SyncTimeMet(uint tick) => IsNextSyncTimeMet(tick); + /// /// Writes current value. /// /// True to set the next time data may sync. - [MakePublic] internal protected virtual void WriteDelta(PooledWriter writer, bool resetSyncTick = true) { WriteHeader(writer, resetSyncTick); } + /// - /// Writers the header for this SyncType. + /// Writes the header for this SyncType. /// protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = true) { @@ -420,30 +474,28 @@ protected virtual void WriteHeader(PooledWriter writer, bool resetSyncTick = tru NextSyncTick = NetworkManager.TimeManager.LocalTick + _timeToTicks; writer.WriteUInt8Unpacked((byte)SyncIndex); + WriteChangeId(writer); } /// /// Indicates that a full write has occurred. /// This is called from WriteFull, or can be called manually. /// - protected void FullWritten() - { - _lastWriteFullLocalTick = NetworkManager.TimeManager.LocalTick; - } + [Obsolete("This method no longer functions. You may remove it from your code.")] //Remove on V5. + protected void FullWritten() { } + /// /// Writes all values for the SyncType. /// - [MakePublic] - internal protected virtual void WriteFull(PooledWriter writer) - { - FullWritten(); - } + internal protected virtual void WriteFull(PooledWriter writer) { } + /// /// Sets current value as server or client through deserialization. /// [MakePublic] internal protected virtual void Read(PooledReader reader, bool asServer) { } + /// /// Resets initialized values for server and client. /// @@ -461,8 +513,6 @@ internal protected virtual void ResetState(bool asServer) { if (asServer) { - _lastWriteFullLocalTick = 0; - _changeId = 0; NextSyncTick = 0; SetCurrentChannel(Settings.Channel); IsDirty = false; @@ -473,13 +523,11 @@ internal protected virtual void ResetState(bool asServer) * that means the object is deinitializing, and won't have any * client observers anyway. Because of this it's safe to reset * with asServer true, or false. - * + * * This change is made to resolve a bug where asServer:false * sometimes does not invoke when stopping clientHost while not * also stopping play mode. */ - _lastReadDirtyId = DEFAULT_LAST_READ_DIRTYID; + _lastReadChangeId = UNSET_CHANGE_ID; } - - } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs index 5ea3dd6a..bd3c76dc 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncDictionary.cs @@ -15,7 +15,6 @@ namespace FishNet.Object.Synchronizing [System.Serializable] public class SyncDictionary : SyncBase, IDictionary, IReadOnlyDictionary { - #region Types. /// /// Information needed to invoke a callback. @@ -58,6 +57,7 @@ public ChangeData(SyncDictionaryOperation operation, TKey key, TValue value) /// [APIExclude] public bool IsReadOnly => false; + /// /// Delegate signature for when SyncDictionary changes. /// @@ -67,6 +67,7 @@ public ChangeData(SyncDictionaryOperation operation, TKey key, TValue value) /// True if callback is on the server side. False is on the client side. [APIExclude] public delegate void SyncDictionaryChanged(SyncDictionaryOperation op, TKey key, TValue value, bool asServer); + /// /// Called when the SyncDictionary changes. /// @@ -128,6 +129,7 @@ public ChangeData(SyncDictionaryOperation operation, TKey key, TValue value) #region Constructors. public SyncDictionary(SyncTypeSettings settings = new()) : this(CollectionCaches.RetrieveDictionary(), settings) { } + public SyncDictionary(Dictionary objects, SyncTypeSettings settings = new()) : base(settings) { Collection = objects; @@ -195,18 +197,17 @@ protected override void Initialized() /// /// [APIExclude] - private void AddOperation(SyncDictionaryOperation operation, TKey key, TValue value) { if (!base.IsInitialized) return; /* asServer might be true if the client is setting the value - * through user code. Typically synctypes can only be set - * by the server, that's why it is assumed asServer via user code. - * However, when excluding owner for the synctype the client should - * have permission to update the value locally for use with - * prediction. */ + * through user code. Typically synctypes can only be set + * by the server, that's why it is assumed asServer via user code. + * However, when excluding owner for the synctype the client should + * have permission to update the value locally for use with + * prediction. */ bool asServerInvoke = (!base.IsNetworkInitialized || base.NetworkBehaviour.IsServerStarted); if (asServerInvoke) @@ -222,7 +223,6 @@ private void AddOperation(SyncDictionaryOperation operation, TKey key, TValue va InvokeOnChange(operation, key, value, asServerInvoke); } - /// /// Called after OnStartXXXX has occurred. /// @@ -241,7 +241,6 @@ internal protected override void OnStartCallback(bool asServer) collection.Clear(); } - /// /// Writes all changed values. /// Internal use. @@ -252,8 +251,6 @@ internal protected override void OnStartCallback(bool asServer) [APIExclude] internal protected override void WriteDelta(PooledWriter writer, bool resetSyncTick = true) { - base.WriteDelta(writer, resetSyncTick); - //If sending all then clear changed and write full. if (_sendAll) { @@ -263,6 +260,8 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT } else { + base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); writer.WriteInt32(_changed.Count); @@ -273,8 +272,7 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT writer.WriteUInt8Unpacked((byte)change.Operation); //Clear does not need to write anymore data so it is not included in checks. - if (change.Operation == SyncDictionaryOperation.Add || - change.Operation == SyncDictionaryOperation.Set) + if (change.Operation == SyncDictionaryOperation.Add || change.Operation == SyncDictionaryOperation.Set) { writer.Write(change.Key); writer.Write(change.Value); @@ -289,7 +287,6 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT } } - /// /// Writers all values if not initial values. /// Internal use. @@ -314,25 +311,20 @@ internal protected override void WriteFull(PooledWriter writer) } } - /// /// Reads and sets the current values for server or client. /// [APIExclude] - internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkBehaviour.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); - IDictionary collection = (asClientAndHost) ? ClientHostCollection : Collection; + IDictionary collection = (asClientHost) ? ClientHostCollection : Collection; //Clear collection since it's a full write. bool fullWrite = reader.ReadBoolean(); @@ -354,6 +346,7 @@ internal protected override void Read(PooledReader reader, bool asServer) { key = reader.Read(); value = reader.Read(); + if (!deinitialized) collection[key] = value; } @@ -367,19 +360,20 @@ internal protected override void Read(PooledReader reader, bool asServer) else if (operation == SyncDictionaryOperation.Remove) { key = reader.Read(); + if (!deinitialized) collection.Remove(key); } - InvokeOnChange(operation, key, value, false); + if (newChangeId) + InvokeOnChange(operation, key, value, false); } //If changes were made invoke complete after all have been read. - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncDictionaryOperation.Complete, default, default, false); } - /// /// Invokes OnChanged callback. /// @@ -401,7 +395,6 @@ private void InvokeOnChange(SyncDictionaryOperation operation, TKey key, TValue } } - /// /// Resets to initialized values. /// @@ -422,7 +415,6 @@ internal protected override void ResetState(bool asServer) } } - /// /// Adds item. /// @@ -431,6 +423,7 @@ public void Add(KeyValuePair item) { Add(item.Key, item.Value); } + /// /// Adds key and value. /// @@ -440,6 +433,7 @@ public void Add(TKey key, TValue value) { Add(key, value, true); } + private void Add(TKey key, TValue value, bool asServer) { if (!base.CanNetworkSetValues(true)) @@ -457,6 +451,7 @@ public void Clear() { Clear(true); } + private void Clear(bool asServer) { if (!base.CanNetworkSetValues(true)) @@ -467,7 +462,6 @@ private void Clear(bool asServer) AddOperation(SyncDictionaryOperation.Clear, default, default); } - /// /// Returns if key exist. /// @@ -477,12 +471,12 @@ public bool ContainsKey(TKey key) { return Collection.ContainsKey(key); } + /// /// Returns if item exist. /// /// Item to use. /// True if found. - public bool Contains(KeyValuePair item) { return TryGetValue(item.Key, out TValue value) && EqualityComparer.Default.Equals(value, item.Value); @@ -516,7 +510,6 @@ public void CopyTo([NotNull] KeyValuePair[] array, int offset) } } - /// /// Removes a key. /// @@ -536,7 +529,6 @@ public bool Remove(TKey key) return false; } - /// /// Removes an item. /// @@ -553,7 +545,6 @@ public bool Remove(KeyValuePair item) /// Key to use. /// Variable to output to. /// True if able to output value. - public bool TryGetValue(TKey key, out TValue value) { return Collection.TryGetValueIL2CPP(key, out value); @@ -640,12 +631,12 @@ public bool Dirty(TValue value, EqualityComparer comparer = null) /// /// public IEnumerator> GetEnumerator() => Collection.GetEnumerator(); + /// /// Gets the IEnumerator for the collection. /// /// IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator(); - } } #endif \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs index e28fede5..34087dcc 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncHashSet.cs @@ -53,6 +53,7 @@ public ChangeData(SyncHashSetOperation operation, T item) /// [APIExclude] public bool IsReadOnly => false; + /// /// Delegate signature for when SyncList changes. /// @@ -61,6 +62,7 @@ public ChangeData(SyncHashSetOperation operation, T item) /// True if callback is occuring on the server. [APIExclude] public delegate void SyncHashSetChanged(SyncHashSetOperation op, T item, bool asServer); + /// /// Called when the SyncList changes. /// @@ -80,7 +82,7 @@ public ChangeData(SyncHashSetOperation operation, T item) public int Count => Collection.Count; #endregion - #region Private. + #region Private. /// /// ListCache for comparing. /// @@ -120,6 +122,7 @@ public ChangeData(SyncHashSetOperation operation, T item) #region Constructors. public SyncHashSet(SyncTypeSettings settings = new()) : this(CollectionCaches.RetrieveHashSet(), EqualityComparer.Default, settings) { } public SyncHashSet(IEqualityComparer comparer, SyncTypeSettings settings = new()) : this(CollectionCaches.RetrieveHashSet(), (comparer == null) ? EqualityComparer.Default : comparer, settings) { } + public SyncHashSet(HashSet collection, IEqualityComparer comparer = null, SyncTypeSettings settings = new()) : base(settings) { _comparer = (comparer == null) ? EqualityComparer.Default : comparer; @@ -181,7 +184,6 @@ public HashSet GetCollection(bool asServer) /// /// Adds an operation and invokes locally. /// - private void AddOperation(SyncHashSetOperation operation, T item) { if (!base.IsInitialized) @@ -236,6 +238,7 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT else { base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); writer.WriteInt32(_changed.Count); @@ -280,21 +283,17 @@ internal protected override void WriteFull(PooledWriter writer) /// /// Reads and sets the current values for server or client. /// - [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); - ISet collection = (asClientAndHost) ? ClientHostCollection : Collection; + ISet collection = (asClientHost) ? ClientHostCollection : Collection; //Clear collection since it's a full write. bool fullWrite = reader.ReadBoolean(); @@ -338,11 +337,12 @@ internal protected override void Read(PooledReader reader, bool asServer) } } - InvokeOnChange(operation, next, false); + if (newChangeId) + InvokeOnChange(operation, next, false); } //If changes were made invoke complete after all have been read. - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncHashSetOperation.Complete, default, false); } @@ -393,6 +393,7 @@ public bool Add(T item) { return Add(item, true); } + private bool Add(T item, bool asServer) { if (!base.CanNetworkSetValues(true)) @@ -409,6 +410,7 @@ private bool Add(T item, bool asServer) return result; } + /// /// Adds a range of values. /// @@ -426,6 +428,7 @@ public void Clear() { Clear(true); } + private void Clear(bool asServer) { if (!base.CanNetworkSetValues(true)) @@ -459,6 +462,7 @@ public bool Remove(T item) { return Remove(item, true); } + private bool Remove(T item, bool asServer) { if (!base.CanNetworkSetValues(true)) @@ -520,8 +524,10 @@ public void Dirty(T obj) /// /// public IEnumerator GetEnumerator() => Collection.GetEnumerator(); + [APIExclude] IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator(); + [APIExclude] IEnumerator IEnumerable.GetEnumerator() => Collection.GetEnumerator(); diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs index 8d541854..dcd48ee5 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncList.cs @@ -268,9 +268,10 @@ internal protected override void WriteDelta(PooledWriter writer, bool resetSyncT else { base.WriteDelta(writer, resetSyncTick); + //False for not full write. writer.WriteBoolean(false); - WriteChangeId(writer, false); + //Number of entries expected. writer.WriteInt32(_changed.Count); @@ -311,7 +312,6 @@ internal protected override void WriteFull(PooledWriter writer) base.WriteHeader(writer, false); //True for full write. writer.WriteBoolean(true); - WriteChangeId(writer, true); int count = Collection.Count; writer.WriteInt32(count); @@ -329,24 +329,20 @@ internal protected override void WriteFull(PooledWriter writer) [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { - /* When !asServer don't make changes if server is running. - * This is because changes would have already been made on - * the server side and doing so again would result in duplicates - * and potentially overwrite data not yet sent. */ - bool asClientAndHost = (!asServer && base.NetworkManager.IsServerStarted); + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + //True to warn if this object was deinitialized on the server. - bool deinitialized = (asClientAndHost && !base.OnStartServerCalled); + bool deinitialized = (asClientHost && !base.OnStartServerCalled); if (deinitialized) base.NetworkManager.LogWarning($"SyncType {GetType().Name} received a Read but was deinitialized on the server. Client callback values may be incorrect. This is a ClientHost limitation."); - List collection = (asClientAndHost) ? ClientHostCollection : Collection; + List collection = (asClientHost) ? ClientHostCollection : Collection; //Clear collection since it's a full write. bool fullWrite = reader.ReadBoolean(); if (fullWrite) collection.Clear(); - bool ignoreReadChanges = base.ReadChangeId(reader); int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -360,7 +356,8 @@ internal protected override void Read(PooledReader reader, bool asServer) if (operation == SyncListOperation.Add) { next = reader.Read(); - if (!ignoreReadChanges) + + if (newChangeId) { index = collection.Count; collection.Add(next); @@ -369,7 +366,7 @@ internal protected override void Read(PooledReader reader, bool asServer) //Clear. else if (operation == SyncListOperation.Clear) { - if (!ignoreReadChanges) + if (newChangeId) collection.Clear(); } //Insert. @@ -377,14 +374,16 @@ internal protected override void Read(PooledReader reader, bool asServer) { index = reader.ReadInt32(); next = reader.Read(); - if (!ignoreReadChanges) + + if (newChangeId) collection.Insert(index, next); } //RemoveAt. else if (operation == SyncListOperation.RemoveAt) { index = reader.ReadInt32(); - if (!ignoreReadChanges) + + if (newChangeId) { prev = collection[index]; collection.RemoveAt(index); @@ -395,19 +394,20 @@ internal protected override void Read(PooledReader reader, bool asServer) { index = reader.ReadInt32(); next = reader.Read(); - if (!ignoreReadChanges) + + if (newChangeId) { prev = collection[index]; collection[index] = next; } } - if (!ignoreReadChanges) + if (newChangeId) InvokeOnChange(operation, index, prev, next, false); } //If changes were made invoke complete after all have been read. - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncListOperation.Complete, -1, default, default, false); } diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatch.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatch.cs similarity index 89% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatch.cs rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatch.cs index fedb1129..4c09be8c 100644 --- a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatch.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatch.cs @@ -8,13 +8,11 @@ namespace FishNet.Object.Synchronizing { - /// /// A SyncObject to efficiently synchronize Stopwatchs over the network. /// public class SyncStopwatch : SyncBase, ICustomSync { - #region Type. /// /// Information about how the Stopwatch has changed. @@ -40,6 +38,7 @@ public ChangeData(SyncStopwatchOperation operation, float previous) /// Previous value of the Stopwatch. This will be -1f is the value is not available. /// True if occurring on server. public delegate void SyncTypeChanged(SyncStopwatchOperation op, float prev, bool asServer); + /// /// Called when a Stopwatch operation occurs. /// @@ -248,10 +247,11 @@ private void WriteStartStopwatch(Writer w, float elapsed, bool includeOperationB /// /// Reads and sets the current values for server or client. /// - [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -260,67 +260,72 @@ internal protected override void Read(PooledReader reader, bool asServer) if (op == SyncStopwatchOperation.Start) { float elapsed = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (canModifyValues) Elapsed = elapsed; - InvokeOnChange(op, elapsed, asServer); + + if (newChangeId) + InvokeOnChange(op, elapsed, asServer); } else if (op == SyncStopwatchOperation.Pause) { - if (CanSetValues(asServer)) + if (canModifyValues) Paused = true; - InvokeOnChange(op, -1f, asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, asServer); } else if (op == SyncStopwatchOperation.PauseUpdated) { float prev = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (newChangeId) Paused = true; - InvokeOnChange(op, prev, asServer); + + if (newChangeId) + InvokeOnChange(op, prev, asServer); } else if (op == SyncStopwatchOperation.Unpause) { - if (CanSetValues(asServer)) + if (canModifyValues) Paused = false; - InvokeOnChange(op, -1f, asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, asServer); } else if (op == SyncStopwatchOperation.Stop) { - StopStopwatch_Internal(asServer); - InvokeOnChange(op, -1f, false); + if (canModifyValues) + StopStopwatch_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, -1f, false); } else if (op == SyncStopwatchOperation.StopUpdated) { float prev = reader.ReadSingle(); - StopStopwatch_Internal(asServer); - InvokeOnChange(op, prev, asServer); + + if (canModifyValues) + StopStopwatch_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, asServer); } } - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncStopwatchOperation.Complete, -1f, asServer); } - /// - /// Returns if values can be updated. - /// - private bool CanSetValues(bool asServer) - { - return (asServer || !base.NetworkManager.IsServerStarted); - } - /// /// Stops the Stopwatch and resets. /// private void StopStopwatch_Internal(bool asServer) { - if (!CanSetValues(asServer)) - return; - Paused = false; Elapsed = -1f; } - /// /// Invokes OnChanged callback. /// @@ -342,7 +347,6 @@ private void InvokeOnChange(SyncStopwatchOperation operation, float prev, bool a } } - /// /// Called after OnStartXXXX has occurred. /// diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatch.cs.meta b/Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatch.cs.meta similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatch.cs.meta rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatch.cs.meta diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatchOperation.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatchOperation.cs similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatchOperation.cs rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatchOperation.cs diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatchOperation.cs.meta b/Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatchOperation.cs.meta similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncStopwatchOperation.cs.meta rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncStopwatchOperation.cs.meta diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimer.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncTimer.cs similarity index 90% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimer.cs rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncTimer.cs index b3e88d5d..4c2f2bfa 100644 --- a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimer.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncTimer.cs @@ -16,7 +16,6 @@ namespace FishNet.Object.Synchronizing public class SyncTimer : SyncBase, ICustomSync { #region Type. - /// /// Information about how the timer has changed. /// @@ -33,11 +32,9 @@ public ChangeData(SyncTimerOperation operation, float previous, float next) Next = next; } } - #endregion #region Public. - /// /// Delegate signature for when the timer operation occurs. /// @@ -71,11 +68,9 @@ public ChangeData(SyncTimerOperation operation, float previous, float next) /// True if the SyncTimer is currently paused. Calls to Update(float) will be ignored when paused. /// public bool Paused { get; private set; } - #endregion #region Private. - /// /// Changed data which will be sent next tick. /// @@ -95,15 +90,10 @@ public ChangeData(SyncTimerOperation operation, float previous, float next) /// Last Time.unscaledTime the timer delta was updated. /// private float _updateTime; - #endregion #region Constructors - - public SyncTimer(SyncTypeSettings settings = new()) : base(settings) - { - } - + public SyncTimer(SyncTypeSettings settings = new()) : base(settings) { } #endregion /// @@ -183,11 +173,12 @@ public void StopTimer(bool sendRemaining = false) { if (Remaining <= 0f) return; - if (!base.CanNetworkSetValues(true)) + if (!base.CanNetworkSetValues(log: true)) return; bool asServer = true; float prev = Remaining; + StopTimer_Internal(asServer); SyncTimerOperation op = (sendRemaining) ? SyncTimerOperation.StopUpdated : SyncTimerOperation.Stop; AddOperation(op, prev, 0f); @@ -276,21 +267,14 @@ private void WriteStartTimer(Writer w, bool includeOperationByte) w.WriteSingle(Duration); } - /// - /// Returns if values can be updated. - /// - private bool CanSetValues(bool asServer) - { - return (asServer || !base.NetworkManager.IsServerStarted); - } - /// /// Reads and sets the current values for server or client. /// - [APIExclude] internal protected override void Read(PooledReader reader, bool asServer) { + base.SetReadArguments(reader, asServer, out bool newChangeId, out bool asClientHost, out bool canModifyValues); + int changes = reader.ReadInt32(); for (int i = 0; i < changes; i++) @@ -300,33 +284,43 @@ internal protected override void Read(PooledReader reader, bool asServer) { float next = reader.ReadSingle(); float duration = reader.ReadSingle(); - if (CanSetValues(asServer)) + + if (canModifyValues) { Paused = false; Remaining = next; Duration = duration; } - InvokeOnChange(op, -1f, next, asServer); + if (newChangeId) + InvokeOnChange(op, -1f, next, asServer); } - else if (op == SyncTimerOperation.Pause || op == SyncTimerOperation.PauseUpdated - || op == SyncTimerOperation.Unpause) + else if (op == SyncTimerOperation.Pause || op == SyncTimerOperation.PauseUpdated || op == SyncTimerOperation.Unpause) { - UpdatePauseState(op); + if (canModifyValues) + UpdatePauseState(op); } else if (op == SyncTimerOperation.Stop) { float prev = Remaining; - StopTimer_Internal(asServer); - InvokeOnChange(op, prev, 0f, false); + + if (canModifyValues) + StopTimer_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, 0f, false); } // else if (op == SyncTimerOperation.StopUpdated) { float prev = Remaining; float next = reader.ReadSingle(); - StopTimer_Internal(asServer); - InvokeOnChange(op, prev, next, asServer); + + if (canModifyValues) + StopTimer_Internal(asServer); + + if (newChangeId) + InvokeOnChange(op, prev, next, asServer); } } @@ -341,20 +335,18 @@ void UpdatePauseState(SyncTimerOperation op) if (op == SyncTimerOperation.PauseUpdated) { next = reader.ReadSingle(); - if (CanSetValues(asServer)) - Remaining = next; + Remaining = next; } else { next = Remaining; } - if (CanSetValues(asServer)) - Paused = newPauseState; + Paused = newPauseState; InvokeOnChange(op, prev, next, asServer); } - if (changes > 0) + if (newChangeId && changes > 0) InvokeOnChange(SyncTimerOperation.Complete, -1f, -1f, false); } @@ -363,14 +355,10 @@ void UpdatePauseState(SyncTimerOperation op) /// private void StopTimer_Internal(bool asServer) { - if (!CanSetValues(asServer)) - return; - Paused = false; Remaining = 0f; } - /// /// Invokes OnChanged callback. /// @@ -392,7 +380,6 @@ private void InvokeOnChange(SyncTimerOperation operation, float prev, float next } } - /// /// Called after OnStartXXXX has occurred. /// @@ -422,20 +409,17 @@ private void SetUpdateTime() /// /// Removes time passed from Remaining since the last unscaled time using this method. /// - public void Update() { float delta = (Time.unscaledTime - _updateTime); Update(delta); } - /// /// Removes delta from Remaining for server and client. /// This also resets unscaledTime delta for Update(). /// /// Value to remove from Remaining. - public void Update(float delta) { //Not enabled. diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimer.cs.meta b/Assets/FishNet/Runtime/Object/Synchronizing/SyncTimer.cs.meta similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimer.cs.meta rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncTimer.cs.meta diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimerOperation.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncTimerOperation.cs similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimerOperation.cs rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncTimerOperation.cs diff --git a/Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimerOperation.cs.meta b/Assets/FishNet/Runtime/Object/Synchronizing/SyncTimerOperation.cs.meta similarity index 100% rename from Assets/FishNet/Runtime/Generated/SyncTypes/SyncTimerOperation.cs.meta rename to Assets/FishNet/Runtime/Object/Synchronizing/SyncTimerOperation.cs.meta diff --git a/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs b/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs index ce67ce4a..819812a4 100644 --- a/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs +++ b/Assets/FishNet/Runtime/Object/Synchronizing/SyncVar.cs @@ -423,8 +423,21 @@ internal protected override void WriteFull(PooledWriter obj0) protected internal override void Read(PooledReader reader, bool asServer) { T value = reader.Read(); + + if (!ReadChangeId(reader)) + return; + SetValue(value, false); + //TODO this needs to separate invokes from setting values so that syncvar can be written like remainder of synctypes. } + + //SyncVars do not use changeId. + [APIExclude] + protected override bool ReadChangeId(Reader reader) => true; + + //SyncVars do not use changeId. + [APIExclude] + protected override void WriteChangeId(PooledWriter writer) { } /// /// Resets to initialized values. diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/BasicQueue.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/BasicQueue.cs index 65b36be6..439a33f7 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/BasicQueue.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/BasicQueue.cs @@ -1,4 +1,5 @@ using System; +using UnityEngine; namespace GameKit.Dependencies.Utilities { @@ -82,7 +83,10 @@ public bool TryDequeue(out T result, bool defaultArrayEntry = true) public T Dequeue(bool defaultArrayEntry = true) { if (_written == 0) - throw new($"Queue of type {typeof(T).Name} is empty."); + { + Debug.LogError($"Queue of type {typeof(T).Name} is empty."); + return default; + } T result = Collection[_read]; if (defaultArrayEntry) diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs index 620ff828..20eabaf3 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/ResettableRingBuffer.cs @@ -3,13 +3,15 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using UnityEngine; + namespace GameKit.Dependencies.Utilities.Types { /// /// Writes values to a collection of a set size, overwriting old values as needed. /// - public class ResettableRingBuffer : IResettable where T : IResettable + public class ResettableRingBuffer : IResettable, IEnumerable where T : IResettable { #region Types. /// @@ -30,7 +32,7 @@ public int ActualIndex get { int total = (_startIndex + (_read - 1)); - int capacity = _rollingCollection.Capacity; + int capacity = _enumeratedRingBuffer.Capacity; if (total >= capacity) total -= capacity; @@ -47,11 +49,15 @@ public int ActualIndex /// /// RollingCollection to use. /// - private ResettableRingBuffer _rollingCollection; + private ResettableRingBuffer _enumeratedRingBuffer; /// /// Collection to iterate. /// - private readonly T[] _collection; + private T[] _collection; + /// + /// Number of entries read during the enumeration. + /// + private int _entriesEnumerated; /// /// Number of entries read during the enumeration. /// @@ -60,78 +66,67 @@ public int ActualIndex /// Start index of enumerations. /// private int _startIndex; + /// + /// True if currently enumerating. + /// + private bool _enumerating => (_enumeratedRingBuffer != null); + /// + /// Count of the collection during initialization. + /// + private int _initializeCollectionCount; #endregion - public Enumerator(ResettableRingBuffer c) + public void Initialize(ResettableRingBuffer c) { - _read = 0; - _startIndex = 0; - _rollingCollection = c; + _entriesEnumerated = 0; + _startIndex = c.GetRealIndex(0); + _enumeratedRingBuffer = c; _collection = c.Collection; + _initializeCollectionCount = c.Count; Current = default; } public bool MoveNext() { - int written = _rollingCollection.Count; - if (_read >= written) + if (!_enumerating) + return false; + + int written = _enumeratedRingBuffer.Count; + + if (written != _initializeCollectionCount) + { + Debug.LogError($"{_enumeratedRingBuffer.GetType().Name} collection was modified during enumeration."); + //This will force a return/reset. + _entriesEnumerated = written; + } + + if (_entriesEnumerated >= written) { - ResetRead(); + Reset(); return false; } - int index = (_startIndex + _read); - int capacity = _rollingCollection.Capacity; + int index = (_startIndex + _entriesEnumerated); + int capacity = _enumeratedRingBuffer.Capacity; if (index >= capacity) index -= capacity; Current = _collection[index]; - _read++; + _entriesEnumerated++; return true; } - /// - /// Sets a new start index to begin reading at. - /// - public void SetStartIndex(int index) - { - _startIndex = index; - ResetRead(); - } - - - /// - /// Sets a new start index to begin reading at. - /// - public void AddStartIndex(int value) - { - _startIndex += value; - - int cap = _rollingCollection.Capacity; - if (_startIndex > cap) - _startIndex -= cap; - else if (_startIndex < 0) - _startIndex += cap; - - ResetRead(); - } - - /// - /// Resets number of entries read during the enumeration. - /// - public void ResetRead() - { - _read = 0; - } - /// /// Resets read count. /// public void Reset() { - _startIndex = 0; - ResetRead(); + /* Only need to reset value types. + * Numeric types change during initialization. */ + _enumeratedRingBuffer = default; + _collection = default; + Current = default; } object IEnumerator.Current => Current; @@ -178,10 +173,18 @@ public void Dispose() { } /// private bool _atCapacity => (_written == Capacity); #endregion + + + #region Consts. + /// + /// Default capacity when none is psecified. + /// + public const int DEFAULT_CAPACITY = 60; + #endregion - public ResettableRingBuffer() + public ResettableRingBuffer() { - _enumerator = new(this); + Initialize(DEFAULT_CAPACITY); } /// @@ -216,6 +219,20 @@ public void Initialize(int capacity) void GetNewCollection() => Collection = ArrayPool.Shared.Rent(capacity); } + + /// + /// Initializes with default capacity. + /// + /// True to log automatic initialization. + public void Initialize() + { + if (!Initialized) + { + UnityEngine.Debug.Log($"RingBuffer for type {typeof(T).FullName} is being initialized with a default capacity of {DEFAULT_CAPACITY}."); + Initialize(DEFAULT_CAPACITY); + } + } + /// /// Clears the collection to default values and resets indexing. @@ -345,13 +362,10 @@ private void IncreaseWritten() WriteIndex = 0; /* If written has exceeded capacity - * then the start index needs to be moved - * to adjust for overwritten values. */ + * then the start index needs to be moved + * to adjust for overwritten values. */ if (_written > capacity) - { _written = capacity; - _enumerator.SetStartIndex(WriteIndex); - } } @@ -388,20 +402,6 @@ int ReturnError() } } - /// - /// Returns Enumerator for the collection. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() - { - if (!IsInitializedWithError()) - return default; - - _enumerator.ResetRead(); - return _enumerator; - } - /// /// Removes values from the simulated start of the collection. /// @@ -426,11 +426,10 @@ public void RemoveRange(bool fromStart, int length) _written -= length; if (fromStart) { - _enumerator.AddStartIndex(length); + //No steps are needed from start other than reduce written, which is done above. } else { - WriteIndex -= length; if (WriteIndex < 0) WriteIndex += Capacity; @@ -467,6 +466,21 @@ public void ResetState() } public void InitializeState() { } + + + /// + /// Returns Enumerator for the collection. + /// + /// + public Enumerator GetEnumerator() + { + Initialize(); + _enumerator.Initialize(this); + return _enumerator; + } + + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // Collection.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // Collection.GetEnumerator(); } } \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs index fd6ad6d8..c734f0a6 100644 --- a/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs +++ b/Assets/FishNet/Runtime/Plugins/GameKit/Dependencies/Utilities/Types/RingBuffer.cs @@ -3,13 +3,14 @@ using System.Collections; using System.Collections.Generic; using System.Runtime.CompilerServices; +using UnityEngine; + namespace GameKit.Dependencies.Utilities.Types { - /// /// Writes values to a collection of a set size, overwriting old values as needed. /// - public class RingBuffer + public class RingBuffer : IEnumerable { #region Types. /// @@ -22,122 +23,91 @@ public struct Enumerator : IEnumerator /// Current entry in the enumerator. /// public T Current { get; private set; } - /// - /// Actual index of the last enumerated value. - /// - public int ActualIndex - { - get - { - int total = (_startIndex + (_read - 1)); - int capacity = _rollingCollection.Capacity; - if (total >= capacity) - total -= capacity; - - return total; - } - } - /// - /// Simulated index of the last enumerated value. - /// - public int SimulatedIndex => (_read - 1); #endregion #region Private. /// /// RollingCollection to use. /// - private RingBuffer _rollingCollection; + private RingBuffer _enumeratedRingBuffer; /// /// Collection to iterate. /// - private readonly T[] _collection; + private T[] _collection; /// /// Number of entries read during the enumeration. /// - private int _read; + private int _entriesEnumerated; /// /// Start index of enumerations. /// private int _startIndex; + /// + /// True if currently enumerating. + /// + private bool _enumerating => (_enumeratedRingBuffer != null); + /// + /// Count of the collection during initialization. + /// + private int _initializeCollectionCount; #endregion - public Enumerator(RingBuffer c) + public void Initialize(RingBuffer c) { - _read = 0; - _startIndex = 0; - _rollingCollection = c; + _entriesEnumerated = 0; + _startIndex = c.GetRealIndex(0); + _enumeratedRingBuffer = c; _collection = c.Collection; + _initializeCollectionCount = c.Count; Current = default; } public bool MoveNext() { - int written = _rollingCollection.Count; - if (_read >= written) + if (!_enumerating) + return false; + + int written = _enumeratedRingBuffer.Count; + + if (written != _initializeCollectionCount) + { + Debug.LogError($"{_enumeratedRingBuffer.GetType().Name} collection was modified during enumeration."); + //This will force a return/reset. + _entriesEnumerated = written; + } + + if (_entriesEnumerated >= written) { - ResetRead(); + Reset(); return false; } - int index = (_startIndex + _read); - int capacity = _rollingCollection.Capacity; + int index = (_startIndex + _entriesEnumerated); + int capacity = _enumeratedRingBuffer.Capacity; if (index >= capacity) index -= capacity; Current = _collection[index]; - _read++; + _entriesEnumerated++; return true; } - /// - /// Sets a new start index to begin reading at. - /// - public void SetStartIndex(int index) - { - _startIndex = index; - ResetRead(); - } - - - /// - /// Sets a new start index to begin reading at. - /// - public void AddStartIndex(int value) - { - _startIndex += value; - - int cap = _rollingCollection.Capacity; - if (_startIndex > cap) - _startIndex -= cap; - else if (_startIndex < 0) - _startIndex += cap; - - ResetRead(); - } - - /// - /// Resets number of entries read during the enumeration. - /// - public void ResetRead() - { - _read = 0; - } - /// /// Resets read count. /// public void Reset() { - _startIndex = 0; - ResetRead(); + /* Only need to reset value types. + * Numeric types change during initialization. */ + _enumeratedRingBuffer = default; + _collection = default; + Current = default; } object IEnumerator.Current => Current; public void Dispose() { } } - #endregion #region Public. @@ -174,6 +144,30 @@ public void Dispose() { } private Enumerator _enumerator; #endregion + #region Consts. + /// + /// Default capacity when none is psecified. + /// + public const int DEFAULT_CAPACITY = 60; + #endregion + + /// + /// Initializes with default capacity. + /// + public RingBuffer() + { + Initialize(DEFAULT_CAPACITY); + } + + /// + /// Initializes with a set capacity. + /// + /// Size to initialize the collection as. This cannot be changed after initialized. + public RingBuffer(int capacity) + { + Initialize(capacity); + } + /// /// Initializes the collection at length. /// @@ -192,15 +186,34 @@ public void Initialize(int capacity) } else if (Collection.Length < capacity) { + Clear(); ArrayPool.Shared.Return(Collection); GetNewCollection(); } + else + { + Clear(); + } Capacity = capacity; Initialized = true; void GetNewCollection() => Collection = ArrayPool.Shared.Rent(capacity); } + + /// + /// Initializes with default capacity. + /// + /// True to log automatic initialization. + public void Initialize() + { + if (!Initialized) + { + UnityEngine.Debug.Log($"RingBuffer for type {typeof(T).FullName} is being initialized with a default capacity of {DEFAULT_CAPACITY}."); + Initialize(DEFAULT_CAPACITY); + } + } + /// /// Clears the collection to default values and resets indexing. @@ -218,7 +231,7 @@ public void Clear() /// /// Resets the collection without clearing. /// - [Obsolete("This method no longer functions. Use Clear() instead.")] //Remove on 2024/06/01. + [Obsolete("This method no longer functions. Use Clear() instead.")] //Remove on V5 public void Reset() { } /// @@ -228,22 +241,23 @@ public void Reset() { } /// Simulated index to return. A value of 0 would return the first simulated index in the collection. /// Data to insert. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Insert(int simulatedIndex, T data) + public T Insert(int simulatedIndex, T data) { - if (!IsInitializedWithError()) - return; + Initialize(); + + int written = _written; + //If simulatedIndex is 0 and none are written then add. + if (simulatedIndex == 0 && written == 0) + return Add(data); int realIndex = GetRealIndex(simulatedIndex); if (realIndex == -1) - return; + return default; - int written = _written; - //If adding to the end. + + //If adding to the end or none written. if (simulatedIndex == (written - 1)) - { - Add(data); - return; - } + return Add(data); int lastSimulatedIndex = (written == Capacity) ? (written - 1) : written; @@ -255,10 +269,13 @@ public void Insert(int simulatedIndex, T data) lastSimulatedIndex--; } + T prev = Collection[realIndex]; Collection[realIndex] = data; //If written was not maxed out then increase it. if (written < Capacity) IncreaseWritten(); + + return prev; } /// @@ -269,8 +286,7 @@ public void Insert(int simulatedIndex, T data) [MethodImpl(MethodImplOptions.AggressiveInlining)] public T Add(T data) { - if (!IsInitializedWithError()) - return default; + Initialize(); T current = Collection[WriteIndex]; Collection[WriteIndex] = data; @@ -302,7 +318,6 @@ public T this[int simulatedIndex] } } - /// /// Increases written count and handles offset changes. /// @@ -317,16 +332,12 @@ private void IncreaseWritten() WriteIndex = 0; /* If written has exceeded capacity - * then the start index needs to be moved - * to adjust for overwritten values. */ + * then the start index needs to be moved + * to adjust for overwritten values. */ if (_written > capacity) - { _written = capacity; - _enumerator.SetStartIndex(WriteIndex); - } } - /// /// Returns the real index of the collection using a simulated index. /// @@ -355,25 +366,11 @@ private int GetRealIndex(int simulatedIndex, bool allowUnusedBuffer = false) int ReturnError() { - UnityEngine.Debug.LogError($"Index {simulatedIndex} is out of range. Collection count is {_written}, Capacity is {Capacity}"); + UnityEngine.Debug.LogError($"Index {simulatedIndex} is out of range. Written count is {_written}, Capacity is {Capacity}"); return -1; } } - /// - /// Returns Enumerator for the collection. - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Enumerator GetEnumerator() - { - if (!IsInitializedWithError()) - return default; - - _enumerator.ResetRead(); - return _enumerator; - } - /// /// Removes values from the simulated start of the collection. /// @@ -398,32 +395,28 @@ public void RemoveRange(bool fromStart, int length) _written -= length; if (fromStart) { - _enumerator.AddStartIndex(length); + //No steps are needed from start other than reduce written, which is done above. } else { - WriteIndex -= length; if (WriteIndex < 0) WriteIndex += Capacity; } } - + /// - /// Returns if initialized and errors if not. + /// Returns Enumerator for the collection. /// /// - private bool IsInitializedWithError() + public Enumerator GetEnumerator() { - if (!Initialized) - { - UnityEngine.Debug.LogError($"RingBuffer has not yet been initialized."); - return false; - } - - return true; + Initialize(); + _enumerator.Initialize(this); + return _enumerator; } + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // Collection.GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); // Collection.GetEnumerator(); } - -} +} \ No newline at end of file diff --git a/Assets/FishNet/Runtime/Serializing/Writer.cs b/Assets/FishNet/Runtime/Serializing/Writer.cs index d7cef4d3..45c2698e 100644 --- a/Assets/FishNet/Runtime/Serializing/Writer.cs +++ b/Assets/FishNet/Runtime/Serializing/Writer.cs @@ -10,6 +10,7 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using GameKit.Dependencies.Utilities.Types; using UnityEngine; [assembly: InternalsVisibleTo(UtilityConstants.GENERATED_ASSEMBLY_NAME)] @@ -1261,9 +1262,8 @@ internal void WriteReconcile(T data) /// /// Writes a replication to the server. /// - internal void WriteReplicate(List values, int offset) where T : IReplicateData + internal void WriteReplicate(RingBuffer values, int offset) where T : IReplicateData { - int startLength = Length; /* COUNT * * Each Entry: diff --git a/Assets/FishNet/Runtime/Utility/ChildTransformTickSmoother.cs b/Assets/FishNet/Runtime/Utility/ChildTransformTickSmoother.cs index e2536a6d..1eae853c 100644 --- a/Assets/FishNet/Runtime/Utility/ChildTransformTickSmoother.cs +++ b/Assets/FishNet/Runtime/Utility/ChildTransformTickSmoother.cs @@ -224,7 +224,7 @@ public ChildTransformTickSmoother() { } public void InitializeNetworked(NetworkObject nob, Transform graphicalObject, bool detach, float teleportDistance, float tickDelta, byte ownerInterpolation, TransformPropertiesFlag ownerSmoothedProperties, byte spectatorInterpolation, TransformPropertiesFlag specatorSmoothedProperties, AdaptiveInterpolationType adaptiveInterpolation) { ResetState(); - + _networkObject = nob; _spectatorInterpolation = spectatorInterpolation; _spectatorSmoothedProperties = specatorSmoothedProperties; @@ -579,7 +579,7 @@ private void SetMoveRates(in TransformProperties prevValues) _moveRates = MoveRates.GetMoveRates(prevValues, nextValues, duration, teleportT); _moveRates.TimeRemaining = duration; - + SetMovementMultiplier(); } diff --git a/Assets/FishNet/package.json b/Assets/FishNet/package.json index e6f7c780..11ead87f 100644 --- a/Assets/FishNet/package.json +++ b/Assets/FishNet/package.json @@ -1,6 +1,6 @@ { "name": "com.firstgeargames.fishnet", - "version": "4.4.5", + "version": "4.4.6", "displayName": "FishNet: Networking Evolved", "description": "A feature-rich Unity networking solution aimed towards reliability, ease of use, efficiency, and flexibility.", "unity": "2021.3",