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
}
}