using FishNet.CodeGenerating.Extension; using FishNet.CodeGenerating.Helping; using FishNet.CodeGenerating.Helping.Extension; using FishNet.CodeGenerating.Processing.Rpc; using FishNet.Connection; using FishNet.Object; using FishNet.Object.Prediction; using FishNet.Object.Prediction.Delegating; using FishNet.Serializing; using FishNet.Transporting; using FishNet.Utility.Performance; using GameKit.Dependencies.Utilities; using MonoFN.Cecil; using MonoFN.Cecil.Cil; using MonoFN.Cecil.Rocks; using System.Collections.Generic; using System.Linq; using GameKit.Dependencies.Utilities.Types; using UnityEngine; using SR = System.Reflection; namespace FishNet.CodeGenerating.Processing { internal class PredictionProcessor : CodegenBase { #region Types. private class PredictionAttributedMethods { public MethodDefinition ReplicateMethod; public MethodDefinition ReconcileMethod; public PredictionAttributedMethods(MethodDefinition replicateMethod, MethodDefinition reconcileMethod) { ReplicateMethod = replicateMethod; ReconcileMethod = reconcileMethod; } } private enum InsertType { First, Last, Current } private class CreatedPredictionFields { /// /// TypeReference of replicate data. /// public TypeReference ReplicateDataTypeRef; /// /// Typereference of reconcile data. /// public TypeReference ReconcileDataTypeRef; /// /// Delegate for calling replicate user logic. /// public FieldDefinition ReplicateUserLogicDelegate; /// /// Delegate for calling replicate user logic. /// public FieldDefinition ReconcileUserLogicDelegate; /// /// Replicate data which has not run yet and is in queue to do so. /// public FieldDefinition ReplicatesQueue; /// /// Replicate data which has already run and is used to reconcile/replay. /// public FieldDefinition ReplicatesHistory; /// /// Reconcile data cached locally from the local client. /// public FieldDefinition LocalReconciles; /// /// Last replicate read. This is used for reading delta replicates. /// public FieldDefinition LastReadReplicate; /// /// Last reconcile read. This is used for reading delta reconciles. /// public FieldDefinition LastReadReconcile; } private class PredictionReaders { public readonly MethodReference ReplicateReader; public readonly MethodReference ReconcileReader; public PredictionReaders(MethodReference replicateReader, MethodReference reconcileReader) { ReplicateReader = replicateReader; ReconcileReader = reconcileReader; } } #endregion #region Public. public string IReplicateData_FullName = typeof(IReplicateData).FullName; public string IReconcileData_FullName = typeof(IReconcileData).FullName; public TypeReference ReplicateULDelegate_TypeRef; public TypeReference ReconcileULDelegate_TypeRef; public MethodReference IReplicateData_GetTick_MethodRef; public MethodReference IReconcileData_GetTick_MethodRef; public MethodReference ReplicateData_Ctor_MethodRef; #endregion #region Const. public const string REPLICATE_READER_PREFIX = "Reader_Replicate___"; public const string RECONCILE_READER_PREFIX = "Reader_Reconcile___"; #endregion public override bool ImportReferences() { System.Type locType; //SR.MethodInfo locMi; base.ImportReference(typeof(BasicQueue<>)); ReplicateULDelegate_TypeRef = base.ImportReference(typeof(ReplicateUserLogicDelegate<>)); ReconcileULDelegate_TypeRef = base.ImportReference(typeof(ReconcileUserLogicDelegate<>)); TypeDefinition replicateDataTd = base.ImportReference(typeof(ReplicateDataContainer<>)).CachedResolve(base.Session); ReplicateData_Ctor_MethodRef = base.ImportReference(replicateDataTd.GetConstructor(parameterCount: 2)); //Get/Set tick. locType = typeof(IReplicateData); foreach (SR.MethodInfo mi in locType.GetMethods()) { if (mi.Name == nameof(IReplicateData.GetTick)) { IReplicateData_GetTick_MethodRef = base.ImportReference(mi); break; } } locType = typeof(IReconcileData); foreach (SR.MethodInfo mi in locType.GetMethods()) { if (mi.Name == nameof(IReconcileData.GetTick)) { IReconcileData_GetTick_MethodRef = base.ImportReference(mi); break; } } return true; } #region Setup and checks. /// /// Gets number of predictions by checking for prediction attributes. This does not perform error checking. /// /// /// internal uint GetPredictionCount(TypeDefinition typeDef) { /* Currently only one prediction method is allowed per typeDef. * Return 1 soon as a method is found. */ foreach (MethodDefinition methodDef in typeDef.Methods) { foreach (CustomAttribute customAttribute in methodDef.CustomAttributes) { if (customAttribute.Is(base.GetClass().ReplicateAttribute_FullName)) return 1; } } return 0; } /// /// Gets number of predictions by checking for prediction attributes in typeDef parents, excluding typerDef. /// /// /// internal uint GetPredictionCountInParents(TypeDefinition typeDef) { uint count = 0; do { typeDef = typeDef.GetNextBaseClassToProcess(base.Session); if (typeDef != null) count += GetPredictionCount(typeDef); } while (typeDef != null); return count; } /// /// Ensures only one prediction and reconile method exist per typeDef, and outputs finding. /// /// True if there is only one set of prediction methods. False if none, or more than one set. internal bool GetPredictionMethods(TypeDefinition typeDef, out MethodDefinition replicateMd, out MethodDefinition reconcileMd) { replicateMd = null; reconcileMd = null; string replicateAttributeFullName = base.GetClass().ReplicateAttribute_FullName; string reconcileAttributeFullName = base.GetClass().ReconcileAttribute_FullName; bool error = false; foreach (MethodDefinition methodDef in typeDef.Methods) { foreach (CustomAttribute customAttribute in methodDef.CustomAttributes) { if (customAttribute.Is(replicateAttributeFullName)) { if (!IsMethodPrivate(methodDef) || IsPredictionMethodAlreadyFound(replicateMd)) error = true; else replicateMd = methodDef; } else if (customAttribute.Is(reconcileAttributeFullName)) { if (!IsMethodPrivate(methodDef) || IsPredictionMethodAlreadyFound(reconcileMd)) { error = true; } else { reconcileMd = methodDef; if (!CheckCreateReconcile(reconcileMd)) error = true; } } if (error) break; } if (error) break; } //Checks to make sure the CreateReconcile method exist and calls reconcile. bool CheckCreateReconcile(MethodDefinition reconcileMd) { string crName = nameof(NetworkBehaviour.CreateReconcile); MethodDefinition createReconcileMd = reconcileMd.DeclaringType.GetMethod(crName); //Does not exist. if (createReconcileMd == null) { base.LogError($"{reconcileMd.DeclaringType.Name} does not implement method {crName}. Override method {crName} and use it to create your reconcile data, and call your reconcile method {reconcileMd.Name}. Call "); return false; } //Exists, check for call. else { //Check for call instructions. foreach (Instruction inst in createReconcileMd.Body.Instructions) { if (inst.OpCode == OpCodes.Call || inst.OpCode == OpCodes.Callvirt) { if (inst.Operand is MethodReference mr) { if (mr.Name == reconcileMd.Name) return true; } } } base.LogError($"{reconcileMd.DeclaringType.Name} implements {crName} but does not call reconcile method {reconcileMd.Name}. If you are calling CreateReconcile from another type please make a new method to call in {reconcileMd.DeclaringType.Name}, which in return calls CreateReconcile."); //Fallthrough. return false; } } /* Forcing a method to private is not necessarily needed * but it adds a safe-guard against users calling base.Reconcile/Replicate * from another replicate. Doing this would cause the replicate to run twice * for the same script hierarchy, which would create unpredictable behavior. */ bool IsMethodPrivate(MethodDefinition md) { bool isPrivate = md.Attributes.HasFlag(MethodAttributes.Private); if (!isPrivate) base.LogError($"Method {md.Name} within {typeDef.Name} is a prediction method and must be private."); return isPrivate; } bool IsPredictionMethodAlreadyFound(MethodDefinition md) { bool alreadyFound = (md != null); if (alreadyFound) base.LogError($"{typeDef.Name} contains multiple prediction sets; currently only one set is allowed."); return alreadyFound; } if (!error && ((replicateMd == null) != (reconcileMd == null))) { base.LogError($"{typeDef.Name} must contain both a [Replicate] and [Reconcile] method when using prediction."); error = true; } if (error || (replicateMd == null) || (reconcileMd == null)) return false; else return true; } #endregion internal bool Process(TypeDefinition typeDef) { //Set prediction count in parents here. Increase count after each predictionAttributeMethods iteration. //Do a for each loop on predictionAttributedMethods. /* NOTES: get all prediction attributed methods up front and store them inside predictionAttributedMethods. * To find the proper reconciles for replicates add an attribute field allowing users to assign Ids. EG ReplicateV2.Id = 1. Default * value will be 0. */ MethodDefinition replicateMd; MethodDefinition reconcileMd; //Not using prediction methods. if (!GetPredictionMethods(typeDef, out replicateMd, out reconcileMd)) return false; 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) { base.LogError($"{typeDef.FullName} and inherited types exceed {NetworkBehaviourHelper.MAX_PREDICTION_ALLOWANCE} replicated methods. Only {NetworkBehaviourHelper.MAX_PREDICTION_ALLOWANCE} replicated methods are supported per inheritance hierarchy."); return false; } bool parameterError = false; parameterError |= HasParameterError(replicateMd, typeDef, true); parameterError |= HasParameterError(reconcileMd, typeDef, false); if (parameterError) return false; TypeDefinition replicateDataTd = replicateMd.Parameters[0].ParameterType.CachedResolve(base.Session); TypeDefinition reconcileDataTd = reconcileMd.Parameters[0].ParameterType.CachedResolve(base.Session); //Ensure datas implement interfaces. bool interfacesImplemented = true; DataImplementInterfaces(replicateMd, true, ref interfacesImplemented); DataImplementInterfaces(reconcileMd, false, ref interfacesImplemented); if (!interfacesImplemented) return false; if (!TickFieldIsNonSerializable(replicateDataTd, true)) return false; if (!TickFieldIsNonSerializable(reconcileDataTd, false)) return false; /* Make sure data can serialize. Use array type, this will * generate a serializer for element type as well. */ bool canSerialize; //Make sure replicate data can serialize. canSerialize = base.GetClass().HasSerializerAndDeserializer(replicateDataTd.MakeArrayType(), true); if (!canSerialize) { base.LogError($"Replicate data type {replicateDataTd.Name} does not support serialization. Use a supported type or create a custom serializer."); return false; } //Make sure reconcile data can serialize. canSerialize = base.GetClass().HasSerializerAndDeserializer(reconcileDataTd, true); if (!canSerialize) { base.LogError($"Reconcile data type {reconcileDataTd.Name} does not support serialization. Use a supported type or create a custom serializer."); return false; } //Creates fields for buffers. CreatedPredictionFields predictionFields; CreateFields(typeDef, replicateMd, reconcileMd, out predictionFields); PredictionReaders predictionReaders; MethodDefinition replicateULMd; MethodDefinition reconcileULMd; CreatePredictionMethods(typeDef, replicateMd, reconcileMd, predictionFields, predictionRpcCount, out predictionReaders, out replicateULMd, out reconcileULMd); InitializeCollections(typeDef, replicateMd, reconcileMd, predictionFields); InitializeULDelegates(typeDef, predictionFields, replicateMd, reconcileMd, replicateULMd, reconcileULMd); RegisterPredictionRpcs(typeDef, predictionRpcCount, predictionReaders); return true; } /// /// Ensures the tick field for GetTick is non-serializable. /// private bool TickFieldIsNonSerializable(TypeDefinition dataTd, bool replicate) { string methodName = (replicate) ? IReplicateData_GetTick_MethodRef.Name : IReconcileData_GetTick_MethodRef.Name; MethodDefinition getMd = dataTd.GetMethodDefinitionInAnyBase(base.Session, methodName); //Try to find ldFld. Instruction ldFldInst = null; foreach (Instruction item in getMd.Body.Instructions) { if (item.OpCode == OpCodes.Ldfld) { ldFldInst = item; break; } } //If ldFld not found. if (ldFldInst == null) { base.LogError($"{dataTd.FullName} method {getMd.Name} does not return a field type for the Tick. Make a new private or protected field of uint type and return it's value within {getMd.Name}."); return false; } //Make sure the field is correct accessibility FieldDefinition fd = (FieldDefinition)ldFldInst.Operand; if (!fd.Attributes.HasFlag(FieldAttributes.Private) && !fd.Attributes.HasFlag(FieldAttributes.Family)) { base.LogError($"{dataTd.FullName} method {getMd.Name} returns a tick field it does not have the correct accessibility. Make the field {fd.Name} private or protected."); return false; } //All checks pass. return true; } private void DataImplementInterfaces(MethodDefinition methodDef, bool isReplicate, ref bool interfacesImplemented) { TypeReference dataTr = methodDef.Parameters[0].ParameterType; string interfaceName = (isReplicate) ? IReplicateData_FullName : IReconcileData_FullName; //If does not implement. if (!dataTr.CachedResolve(base.Session).ImplementsInterfaceRecursive(base.Session, interfaceName)) { string name = (isReplicate) ? typeof(IReplicateData).Name : typeof(IReconcileData).Name; base.LogError($"Prediction data type {dataTr.Name} for method {methodDef.Name} in class {methodDef.DeclaringType.Name} must implement the {name} interface."); interfacesImplemented = false; } } /// /// Registers RPCs that prediction uses. /// private void RegisterPredictionRpcs(TypeDefinition typeDef, uint hash, PredictionReaders readers) { MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME); ILProcessor processor = injectionMethodDef.Body.GetILProcessor(); List insts = new(); Register(readers.ReplicateReader.CachedResolve(base.Session), true); Register(readers.ReconcileReader.CachedResolve(base.Session), false); void Register(MethodDefinition readerMd, bool replicate) { insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldc_I4, (int)hash)); /* Create delegate and call NetworkBehaviour method. */ insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldftn, readerMd)); MethodReference ctorMr; MethodReference callMr; if (replicate) { ctorMr = base.GetClass().ReplicateRpcDelegate_Ctor_MethodRef; callMr = base.GetClass().RegisterReplicateRpc_MethodRef; } else { ctorMr = base.GetClass().ReconcileRpcDelegate_Ctor_MethodRef; callMr = base.GetClass().RegisterReconcileRpc_MethodRef; } insts.Add(processor.Create(OpCodes.Newobj, ctorMr)); insts.Add(processor.Create(OpCodes.Call, callMr)); } processor.InsertLast(insts); } /// /// Initializes collection fields made during this process. /// /// 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(); GenericInstanceType git; //ReplicateQueue. git = gh.GetGenericType(typeof(ReplicateDataContainer<>), replicateDataTr); Generate(predictionFields.ReplicatesQueue, gh.GetGenericBasicQueue(git), isRingBuffer: false); //ReplicatesHistory buffer. git = gh.GetGenericType(typeof(ReplicateDataContainer<>), replicateDataTr); Generate(predictionFields.ReplicatesHistory, gh.GetGenericRingBuffer(git), isRingBuffer: true); //LocalReconcile buffer. git = gh.GetGenericType(typeof(LocalReconcile<>), reconcileDataTr); Generate(predictionFields.LocalReconciles, gh.GetGenericRingBuffer(git), isRingBuffer: true); void Generate(FieldReference fr, GenericInstanceType lGit, bool isRingBuffer) { 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 ctorMd = base.GetClass().List_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session); MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, lGit); 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)); processor.InsertFirst(insts); } } /// /// Initializes collection fields made during this process. /// /// private void InitializeULDelegates(TypeDefinition typeDef, CreatedPredictionFields predictionFields, MethodDefinition replicateMd, MethodDefinition reconcileMd, MethodDefinition replicateULMd, MethodDefinition reconcileULMd) { 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(); List insts = new(); Generate(replicateULMd, replicateDataTr, predictionFields.ReplicateUserLogicDelegate, typeof(ReplicateUserLogicDelegate<>), ReplicateULDelegate_TypeRef); Generate(reconcileULMd, reconcileDataTr, predictionFields.ReconcileUserLogicDelegate, typeof(ReconcileUserLogicDelegate<>), ReconcileULDelegate_TypeRef); void Generate(MethodDefinition ulMd, TypeReference dataTr, FieldReference fr, System.Type delegateType, TypeReference delegateTr) { insts.Clear(); MethodDefinition ctorMd = delegateTr.CachedResolve(base.Session).GetFirstConstructor(base.Session, true); GenericInstanceType collectionGit; GetGenericULDelegate(dataTr, delegateType, out collectionGit); MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, collectionGit); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldftn, ulMd)); insts.Add(processor.Create(OpCodes.Newobj, ctorMr)); insts.Add(processor.Create(OpCodes.Stfld, fr)); processor.InsertFirst(insts); } } /// /// Creates field buffers for replicate datas. /// /// /// /// /// private void CreateFields(TypeDefinition typeDef, MethodDefinition replicateMd, MethodDefinition reconcileMd, out CreatedPredictionFields predictionFields) { GeneralHelper gh = base.GetClass(); TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType; TypeReference reconcileDataTr = reconcileMd.Parameters[0].ParameterType; GenericInstanceType git; //User logic delegate for replicates. GetGenericULDelegate(replicateDataTr, typeof(ReplicateUserLogicDelegate<>), out git); FieldDefinition replicateUserLogicDelegateFd = new($"_replicateULDelegate___{replicateMd.Name}", FieldAttributes.Private, git); typeDef.Fields.Add(replicateUserLogicDelegateFd); //User logic delegate for reconciles. GetGenericULDelegate(reconcileDataTr, typeof(ReconcileUserLogicDelegate<>), out git); FieldDefinition reconcileUserLogicDelegateFd = new($"_reconcileULDelegate___{reconcileMd.Name}", FieldAttributes.Private, git); typeDef.Fields.Add(reconcileUserLogicDelegateFd); //Replicates history. git = gh.GetGenericType(typeof(ReplicateDataContainer<>), replicateDataTr); FieldDefinition replicatesHistoryFd = new($"_replicatesHistory___{replicateMd.Name}", FieldAttributes.Private, gh.GetGenericRingBuffer(git)); typeDef.Fields.Add(replicatesHistoryFd); //Replicates queue. git = gh.GetGenericType(typeof(ReplicateDataContainer<>), replicateDataTr); FieldDefinition replicatesQueueFd = new($"_replicatesQueue___{replicateMd.Name}", FieldAttributes.Private, gh.GetGenericBasicQueue(git)); typeDef.Fields.Add(replicatesQueueFd); //Local reconciles. git = gh.GetGenericType(typeof(LocalReconcile<>), reconcileDataTr); FieldDefinition localReconcilesFd = new($"_reconcilesHistory___{reconcileMd.Name}", FieldAttributes.Private, gh.GetGenericRingBuffer(git)); typeDef.Fields.Add(localReconcilesFd); //Used for delta reconcile. FieldDefinition lastReconcileDataFd = new($"_lastReadReconcile___{reconcileMd.Name}", FieldAttributes.Private, reconcileDataTr); typeDef.Fields.Add(lastReconcileDataFd); //Used for delta replicates. git = gh.GetGenericType(typeof(ReplicateDataContainer<>), replicateDataTr); FieldDefinition lastReadReplicateFd = new($"_lastReadReplicate___{replicateMd.Name}", FieldAttributes.Private, git); typeDef.Fields.Add(lastReadReplicateFd); predictionFields = new() { ReplicateDataTypeRef = replicateDataTr, ReconcileDataTypeRef = reconcileDataTr, ReplicateUserLogicDelegate = replicateUserLogicDelegateFd, ReconcileUserLogicDelegate = reconcileUserLogicDelegateFd, ReplicatesQueue = replicatesQueueFd, ReplicatesHistory = replicatesHistoryFd, LocalReconciles = localReconcilesFd, LastReadReplicate = lastReadReplicateFd, LastReadReconcile = lastReconcileDataFd, }; } /// /// Returns if there are any errors with the prediction methods parameters and will print if so. /// private bool HasParameterError(MethodDefinition methodDef, TypeDefinition typeDef, bool replicateMethod) { //Replicate: data, state, channel. //Reconcile: data, asServer, channel. int count = (replicateMethod) ? 3 : 2; //Check parameter count. if (methodDef.Parameters.Count != count) { PrintParameterExpectations(); return true; } string expectedName; //Data check. if (!methodDef.Parameters[0].ParameterType.IsClassOrStruct(base.Session)) { base.LogError($"Prediction methods must use a class or structure as the first parameter type. Structures are recommended to avoid allocations."); return true; } expectedName = (replicateMethod) ? typeof(ReplicateState).Name : typeof(Channel).Name; if (methodDef.Parameters[1].ParameterType.Name != expectedName) { PrintParameterExpectations(); return true; } //Only replicate uses more than 2 parameters. if (replicateMethod) { //Channel. if (methodDef.Parameters[2].ParameterType.Name != typeof(Channel).Name) { PrintParameterExpectations(); return true; } } void PrintParameterExpectations() { if (replicateMethod) base.LogError($"Replicate method {methodDef.Name} within {typeDef.Name} requires exactly {count} parameters. In order: replicate data, state = ReplicateState.Invalid, channel = Channel.Unreliable"); else base.LogError($"Reconcile method {methodDef.Name} within {typeDef.Name} requires exactly {count} parameters. In order: reconcile data, channel = Channel.Unreliable."); } //No errors with parameters. return false; } /// /// Creates all methods needed for a RPC. /// /// private bool CreatePredictionMethods(TypeDefinition typeDef, MethodDefinition replicateMd, MethodDefinition reconcileMd, CreatedPredictionFields predictionFields, uint rpcCount, out PredictionReaders predictionReaders, out MethodDefinition replicateULMd, out MethodDefinition reconcileULMd) { GeneralHelper gh = base.GetClass(); NetworkBehaviourHelper nbh = base.GetClass(); predictionReaders = null; uint startingRpcCount = rpcCount; string copySuffix = "___UL"; replicateULMd = base.GetClass().CopyIntoNewMethod(replicateMd, $"{replicateMd.Name}{copySuffix}", out _); reconcileULMd = base.GetClass().CopyIntoNewMethod(reconcileMd, $"{reconcileMd.Name}{copySuffix}", out _); replicateMd.Body.Instructions.Clear(); reconcileMd.Body.Instructions.Clear(); TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType; TypeReference reconcileDataTr = reconcileMd.Parameters[0].ParameterType; MethodDefinition replicateReader; MethodDefinition reconcileReader; if (!CreateReplicate()) return false; if (!CreateReconcile()) return false; if (!CreateEmptyReplicatesQueueIntoHistoryStart()) return false; if (!CreateReconcileStart()) return false; if (!CreateReplicateReplayStart()) return false; CreateClearReplicateCacheMethod(typeDef, replicateDataTr, reconcileDataTr, predictionFields); CreateReplicateReader(typeDef, startingRpcCount, replicateMd, predictionFields, out replicateReader); CreateReconcileReader(typeDef, reconcileMd, predictionFields, out reconcileReader); predictionReaders = new(replicateReader, reconcileReader); bool CreateReplicate() { ILProcessor processor = replicateMd.Body.GetILProcessor(); ParameterDefinition replicateDataPd = replicateMd.Parameters[0]; MethodDefinition comparerMd = gh.CreateEqualityComparer(replicateDataPd.ParameterType); gh.CreateIsDefaultComparer(replicateDataPd.ParameterType, comparerMd); ParameterDefinition channelPd = replicateMd.Parameters[2]; GenericInstanceMethod replicateGim = base.GetClass().Replicate_Current_MethodRef.MakeGenericMethod(new TypeReference[] { replicateDataTr }); /* ReplicateUserLogicDelegate del * uint methodHash * BasicQueue> replicatesQueue * RingBuffer> replicatesHistory * ReplicateData data) * where T : IReplicateData */ processor.Emit(OpCodes.Ldarg_0); //User logic delegate. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateUserLogicDelegate); //Rpc hash. processor.Emit(OpCodes.Ldc_I4, (int)rpcCount); //Replicates queue. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesQueue); //Replicates history. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); /* Data being called into the method. */ //Generate the ReplicateData //new ReplicateData(data, channel) processor.Emit(OpCodes.Ldarg, replicateDataPd); processor.Emit(OpCodes.Ldarg, channelPd); GenericInstanceType git = GetGenericReplicateDataContainer(replicateDataTr); MethodReference ctorMr = ReplicateData_Ctor_MethodRef.MakeHostInstanceGeneric(base.Session, git); processor.Emit(OpCodes.Newobj, ctorMr); //Call nb.Replicate_Current. processor.Emit(OpCodes.Call, replicateGim); processor.Emit(OpCodes.Ret); return true; } bool CreateReconcile() { ILProcessor processor = reconcileMd.Body.GetILProcessor(); ServerCreateReconcile(reconcileMd, predictionFields, ref rpcCount); processor.Emit(OpCodes.Ret); return true; } bool CreateEmptyReplicatesQueueIntoHistoryStart() { 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 }); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesQueue); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); processor.Emit(OpCodes.Call, baseMethodGim); processor.Emit(OpCodes.Ret); return true; } //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); typeDef.Methods.Add(newMethodDef); ILProcessor processor = newMethodDef.Body.GetILProcessor(); Call_Reconcile_Client(); void Call_Reconcile_Client() { MethodReference baseMethodGim = nbh.Reconcile_Client_MethodRef.GetMethodReference(base.Session, new TypeReference[] { predictionFields.LastReadReconcile.FieldType, predictionFields.ReplicateDataTypeRef }); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileUserLogicDelegate); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.LocalReconciles); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.LastReadReconcile); 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); 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 }); //uint replicateTick, ReplicateUserLogicDelegate del, List replicates, Channel channel) where T : IReplicateData processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldarg, replayTickPd); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateUserLogicDelegate); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); //processor.Emit(OpCodes.Ldc_I4, (int)Channel.Unreliable); //Channel does not really matter when replaying. At least not until someone needs it. processor.Emit(OpCodes.Call, baseMethodGim); processor.Emit(OpCodes.Ret); return true; } return true; } #region Universal prediction. /// /// Creates an override for the method responsible for resetting replicates. /// /// /// private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReference replicateDataTr, TypeReference reconcileDataTr, CreatedPredictionFields predictionFields) { GeneralHelper gh = base.GetClass(); NetworkBehaviourHelper nbh = base.GetClass(); string methodName = nameof(NetworkBehaviour.ClearReplicateCache); MethodDefinition baseClearMd = typeDef.GetMethodDefinitionInAnyBase(base.Session, methodName); MethodDefinition clearMd = typeDef.GetOrCreateMethodDefinition(base.Session, methodName, baseClearMd, true, out bool created); clearMd.Attributes = MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES; //This class already has the method created when it should not. if (baseClearMd.DeclaringType == typeDef) { base.LogError($"{typeDef.Name} overrides method {methodName} when it should not."); return; } ILProcessor processor = clearMd.Body.GetILProcessor(); //Call the base class first. processor.Emit(OpCodes.Ldarg_0); MethodReference baseClearMr = base.ImportReference(baseClearMd); processor.Emit(OpCodes.Call, baseClearMr); //Call the actual clear method. GenericInstanceMethod internalClearGim = nbh.ClearReplicateCache_Internal_MethodRef.MakeGenericMethod(new[] { replicateDataTr, reconcileDataTr }); processor.Emit(OpCodes.Ldarg_0); //Base. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesQueue); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.LocalReconciles); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReplicate); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReconcile); processor.Emit(OpCodes.Call, internalClearGim); processor.Emit(OpCodes.Ret); } /// /// Outputs generic ReplicateULDelegate for dataTr. /// private void GetGenericULDelegate(TypeReference dataTr, System.Type delegateType, out GenericInstanceType git) { TypeReference delDataTr = base.ImportReference(delegateType); git = delDataTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); } /// /// Subtracts 1 from a field. /// private List SubtractFromField(MethodDefinition methodDef, FieldDefinition fieldDef) { List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); // _field--; insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldarg_0)); insts.Add(processor.Create(OpCodes.Ldfld, fieldDef)); insts.Add(processor.Create(OpCodes.Ldc_I4_1)); insts.Add(processor.Create(OpCodes.Sub)); insts.Add(processor.Create(OpCodes.Stfld, fieldDef)); return insts; } /// /// Subtracts 1 from a variable. /// private List SubtractFromVariable(MethodDefinition methodDef, VariableDefinition variableDef) { List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); // variable--; insts.Add(processor.Create(OpCodes.Ldloc, variableDef)); insts.Add(processor.Create(OpCodes.Ldc_I4_1)); insts.Add(processor.Create(OpCodes.Sub)); insts.Add(processor.Create(OpCodes.Stloc, variableDef)); return insts; } /// /// Subtracts 1 from a variable. /// private List SubtractOneVariableFromAnother(MethodDefinition methodDef, VariableDefinition srcVd, VariableDefinition modifierVd) { List insts = new(); ILProcessor processor = methodDef.Body.GetILProcessor(); // variable -= v2; insts.Add(processor.Create(OpCodes.Ldloc, srcVd)); insts.Add(processor.Create(OpCodes.Ldloc, modifierVd)); insts.Add(processor.Create(OpCodes.Sub)); insts.Add(processor.Create(OpCodes.Stloc, srcVd)); return insts; } #endregion #region Server side. /// /// Creates a reader for replicate data received from clients. /// 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); typeDef.Methods.Add(createdMd); createdMd.Body.InitLocals = true; ILProcessor processor = createdMd.Body.GetILProcessor(); GeneralHelper gh = base.GetClass(); NetworkBehaviourHelper nbh = base.GetClass(); TypeReference dataTr = replicateMd.Parameters[0].ParameterType; //Create parameters. ParameterDefinition readerPd = gh.CreateParameter(createdMd, typeof(PooledReader)); ParameterDefinition networkConnectionPd = gh.CreateParameter(createdMd, typeof(NetworkConnection)); ParameterDefinition channelPd = gh.CreateParameter(createdMd, typeof(Channel)); MethodReference replicateReaderGim = nbh.Replicate_Reader_MethodRef.GetMethodReference(base.Session, dataTr); processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldc_I4, (int)hash); //Reader, NetworkConnection. processor.Emit(OpCodes.Ldarg, readerPd); processor.Emit(OpCodes.Ldarg, networkConnectionPd); //lastFirstReadReplicate. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReplicate); //Replicates queue. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesQueue); //Replicates history. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.ReplicatesHistory); //Channel. processor.Emit(OpCodes.Ldarg, channelPd); processor.Emit(OpCodes.Call, replicateReaderGim); processor.Emit(OpCodes.Ret); result = createdMd; return true; } /// /// Creates server side code for reconcileMd. /// /// /// private void ServerCreateReconcile(MethodDefinition reconcileMd, CreatedPredictionFields predictionFields, ref uint rpcCount) { ParameterDefinition reconcileDataPd = reconcileMd.Parameters[0]; ParameterDefinition channelPd = reconcileMd.Parameters[1]; ILProcessor processor = reconcileMd.Body.GetILProcessor(); NetworkBehaviourHelper nbh = base.GetClass(); GenericInstanceMethod methodGim; /* Reconcile_Current. */ methodGim = nbh.Reconcile_Current_MethodRef.MakeGenericMethod(new TypeReference[] { reconcileDataPd.ParameterType }); //Hash. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldc_I4, (int)rpcCount); //Last reconcile data. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReconcile); //Reconciles history (local reconciles). processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldfld, predictionFields.LocalReconciles); //Data. processor.Emit(OpCodes.Ldarg, reconcileDataPd); //Channel. processor.Emit(OpCodes.Ldarg, channelPd); processor.Emit(OpCodes.Call, methodGim); rpcCount++; } #endregion #region Client side. /// /// Creates a reader for replicate data received from clients. /// 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); typeDef.Methods.Add(createdMd); createdMd.Body.InitLocals = true; ILProcessor processor = createdMd.Body.GetILProcessor(); GeneralHelper gh = base.GetClass(); NetworkBehaviourHelper nbh = base.GetClass(); TypeReference dataTr = reconcileMd.Parameters[0].ParameterType; //Create parameters. ParameterDefinition readerPd = gh.CreateParameter(createdMd, typeof(PooledReader)); ParameterDefinition channelPd = gh.CreateParameter(createdMd, typeof(Channel)); MethodReference methodGim = nbh.Reconcile_Reader_MethodRef.GetMethodReference(base.Session, dataTr); processor.Emit(OpCodes.Ldarg_0); /* nb.Reconcile_Reader(readerPd, ref lastReadReconcile); */ processor.Emit(OpCodes.Ldarg, readerPd); //Data to assign read value to. processor.Emit(OpCodes.Ldarg_0); processor.Emit(OpCodes.Ldflda, predictionFields.LastReadReconcile); processor.Emit(OpCodes.Call, methodGim); //Add end of method. processor.Emit(OpCodes.Ret); result = createdMd; return true; } /// /// Outputs generic RingBuffer for dataTr. /// public GenericInstanceType GetGenericReplicateDataContainer(TypeReference dataTr) { TypeReference typeTr = base.ImportReference(typeof(ReplicateDataContainer<>)); return typeTr.MakeGenericInstanceType(new TypeReference[] { dataTr }); } #endregion } }