StationObscurum/Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs

960 lines
45 KiB
C#

using FishNet.CodeGenerating.Extension;
using FishNet.CodeGenerating.Helping;
using FishNet.CodeGenerating.Helping.Extension;
using FishNet.Connection;
using FishNet.Object;
using FishNet.Object.Prediction;
using FishNet.Object.Prediction.Delegating;
using FishNet.Serializing;
using FishNet.Serializing.Helping;
using FishNet.Transporting;
using MonoFN.Cecil;
using MonoFN.Cecil.Cil;
using MonoFN.Cecil.Rocks;
using System.Collections.Generic;
using UnityEngine;
using SR = System.Reflection;
namespace FishNet.CodeGenerating.Processing
{
internal class PredictionProcessor : CodegenBase
{
#region Types.
private enum InsertType
{
First,
Last,
Current
}
private class CreatedPredictionFields
{
/// <summary>
/// Delegate for calling replicate user logic.
/// </summary>
public readonly FieldReference ReplicateULDelegate;
/// <summary>
/// Delegate for calling replicate user logic.
/// </summary>
public readonly FieldReference ReconcileULDelegate;
/// <summary>
/// Replicate data buffered on the server.
/// </summary>
public readonly FieldReference ServerReplicateDatas;
/// <summary>
/// Replicate data buffered on the client.
/// </summary>
public readonly FieldReference ClientReplicateDatas;
/// <summary>
/// Last reconcile data received from the server.
/// </summary>
public readonly FieldReference ReconcileData;
/// <summary>
/// A buffer to read replicates into.
/// </summary>
public readonly FieldReference ServerReplicateReaderBuffer;
public CreatedPredictionFields(FieldReference replicateULDelegate, FieldReference reconcileULDelegate, FieldReference serverReplicateDatas, FieldReference clientReplicateDatas, FieldReference reconcileData,
FieldReference serverReplicateReaderBuffer)
{
ReplicateULDelegate = replicateULDelegate;
ReconcileULDelegate = reconcileULDelegate;
ServerReplicateDatas = serverReplicateDatas;
ClientReplicateDatas = clientReplicateDatas;
ReconcileData = reconcileData;
ServerReplicateReaderBuffer = serverReplicateReaderBuffer;
}
}
private class PredictionReaders
{
public MethodReference ReplicateReader;
public 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 IReplicateData_SetTick_MethodRef;
public MethodReference IReconcileData_GetTick_MethodRef;
public MethodReference IReconcileData_SetTick_MethodRef;
public MethodReference Unity_GetGameObject_MethodRef;
#endregion
#region Const.
public const string REPLICATE_LOGIC_PREFIX = "Logic_Replicate___";
public const string REPLICATE_READER_PREFIX = "Reader_Replicate___";
public const string RECONCILE_LOGIC_PREFIX = "Logic_Reconcile___";
public const string RECONCILE_READER_PREFIX = "Reader_Reconcile___";
#endregion
public override bool ImportReferences()
{
System.Type locType;
SR.MethodInfo locMi;
ReplicateULDelegate_TypeRef = base.ImportReference(typeof(ReplicateUserLogicDelegate<>));
ReconcileULDelegate_TypeRef = base.ImportReference(typeof(ReconcileUserLogicDelegate<>));
//GetGameObject.
locMi = typeof(UnityEngine.Component).GetMethod("get_gameObject");
Unity_GetGameObject_MethodRef = base.ImportReference(locMi);
//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);
else if (mi.Name == nameof(IReplicateData.SetTick))
IReplicateData_SetTick_MethodRef = base.ImportReference(mi);
}
locType = typeof(IReconcileData);
foreach (SR.MethodInfo mi in locType.GetMethods())
{
if (mi.Name == nameof(IReconcileData.GetTick))
IReconcileData_GetTick_MethodRef = base.ImportReference(mi);
else if (mi.Name == nameof(IReconcileData.SetTick))
IReconcileData_SetTick_MethodRef = base.ImportReference(mi);
}
return true;
}
internal bool Process(TypeDefinition typeDef, ref uint rpcCount)
{
bool modified = false;
modified |= ProcessLocal(typeDef, ref rpcCount);
return modified;
}
#region Setup and checks.
/// <summary>
/// Gets number of predictions by checking for prediction attributes. This does not perform error checking.
/// </summary>
/// <param name="typeDef"></param>
/// <returns></returns>
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<AttributeHelper>().ReplicateAttribute_FullName))
return 1;
}
}
return 0;
}
/// <summary>
/// Ensures only one prediction and reconile method exist per typeDef, and outputs finding.
/// </summary>
/// <returns>True if there is only one set of prediction methods. False if none, or more than one set.</returns>
internal bool GetPredictionMethods(TypeDefinition typeDef, out MethodDefinition replicateMd, out MethodDefinition reconcileMd)
{
replicateMd = null;
reconcileMd = null;
bool error = false;
foreach (MethodDefinition methodDef in typeDef.Methods)
{
foreach (CustomAttribute customAttribute in methodDef.CustomAttributes)
{
if (customAttribute.Is(base.GetClass<AttributeHelper>().ReplicateAttribute_FullName))
{
if (!MethodIsPrivate(methodDef) || AlreadyFound(replicateMd))
error = true;
else
replicateMd = methodDef;
}
else if (customAttribute.Is(base.GetClass<AttributeHelper>().ReconcileAttribute_FullName))
{
if (!MethodIsPrivate(methodDef) || AlreadyFound(reconcileMd))
error = true;
else
reconcileMd = methodDef;
}
if (error)
break;
}
if (error)
break;
}
bool MethodIsPrivate(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 AlreadyFound(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
private bool ProcessLocal(TypeDefinition typeDef, ref uint rpcCount)
{
MethodDefinition replicateMd;
MethodDefinition reconcileMd;
//Not using prediction methods.
if (!GetPredictionMethods(typeDef, out replicateMd, out reconcileMd))
return false;
//If replication methods found but this hierarchy already has max.
if (rpcCount >= NetworkBehaviourHelper.MAX_RPC_ALLOWANCE)
{
base.LogError($"{typeDef.FullName} and inherited types exceed {NetworkBehaviourHelper.MAX_RPC_ALLOWANCE} replicated methods. Only {NetworkBehaviourHelper.MAX_RPC_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<GeneralHelper>().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<GeneralHelper>().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, rpcCount, out predictionReaders, out replicateULMd, out reconcileULMd);
InitializeCollections(typeDef, replicateMd, predictionFields);
InitializeULDelegates(typeDef, predictionFields, replicateMd, reconcileMd, replicateULMd, reconcileULMd);
RegisterRpcs(typeDef, rpcCount, predictionReaders);
rpcCount++;
return true;
}
/// <summary>
/// Ensures the tick field for GetTick is non-serializable.
/// </summary>
/// <param name="dataTd"></param>
/// <returns></returns>
private bool TickFieldIsNonSerializable(TypeDefinition dataTd, bool replicate)
{
string methodName = (replicate) ? IReplicateData_GetTick_MethodRef.Name : IReconcileData_GetTick_MethodRef.Name;
MethodDefinition getMd = dataTd.GetMethod(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 field of uint type and return it's value within {getMd.Name}.");
return false;
}
//Make sure the field is private.
else
{
FieldDefinition fd = (FieldDefinition)ldFldInst.Operand;
if (!fd.Attributes.HasFlag(FieldAttributes.Private))
{
base.LogError($"{dataTd.FullName} method {getMd.Name} returns a tick field but it's not marked as private. Make the field {fd.Name} private.");
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;
}
}
/// <summary>
/// Registers RPCs that prediction uses.
/// </summary>
private void RegisterRpcs(TypeDefinition typeDef, uint hash, PredictionReaders readers)
{
MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
List<Instruction> insts = new List<Instruction>();
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<NetworkBehaviourHelper>().ReplicateRpcDelegateConstructor_MethodRef;
callMr = base.GetClass<NetworkBehaviourHelper>().RegisterReplicateRpc_MethodRef;
}
else
{
ctorMr = base.GetClass<NetworkBehaviourHelper>().ReconcileRpcDelegateConstructor_MethodRef;
callMr = base.GetClass<NetworkBehaviourHelper>().RegisterReconcileRpc_MethodRef;
}
insts.Add(processor.Create(OpCodes.Newobj, ctorMr));
insts.Add(processor.Create(OpCodes.Call, callMr));
}
processor.InsertLast(insts);
}
/// <summary>
/// Initializes collection fields made during this process.
/// </summary>
/// <param name="predictionFields"></param>
private void InitializeCollections(TypeDefinition typeDef, MethodDefinition replicateMd, CreatedPredictionFields predictionFields)
{
GeneralHelper gh = base.GetClass<GeneralHelper>();
TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType;
MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
Generate(predictionFields.ClientReplicateDatas, true);
Generate(predictionFields.ServerReplicateDatas, false);
void Generate(FieldReference fr, bool isList)
{
MethodDefinition ctorMd = base.GetClass<GeneralHelper>().List_TypeRef.CachedResolve(base.Session).GetConstructor();
GenericInstanceType collectionGit;
if (isList)
gh.GetGenericLists(replicateDataTr, out collectionGit);
else
gh.GetGenericQueues(replicateDataTr, out collectionGit);
MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, collectionGit);
List<Instruction> insts = new List<Instruction>();
insts.Add(processor.Create(OpCodes.Ldarg_0));
insts.Add(processor.Create(OpCodes.Newobj, ctorMr));
insts.Add(processor.Create(OpCodes.Stfld, fr));
processor.InsertFirst(insts);
}
}
/// <summary>
/// Initializes collection fields made during this process.
/// </summary>
/// <param name="predictionFields"></param>
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<Instruction> insts = new List<Instruction>();
Generate(replicateULMd, replicateDataTr, predictionFields.ReplicateULDelegate, typeof(ReplicateUserLogicDelegate<>), ReplicateULDelegate_TypeRef);
Generate(reconcileULMd, reconcileDataTr, predictionFields.ReconcileULDelegate, 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);
}
}
/// <summary>
/// Creates field buffers for replicate datas.
/// </summary>
/// <param name="typeDef"></param>
/// <param name="replicateMd"></param>
/// <param name=""></param>
/// <returns></returns>
private void CreateFields(TypeDefinition typeDef, MethodDefinition replicateMd, MethodDefinition reconcileMd, out CreatedPredictionFields predictionFields)
{
GeneralHelper gh = base.GetClass<GeneralHelper>();
TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType;
TypeReference replicateDataArrTr = replicateDataTr.MakeArrayType();
TypeReference reconcileDataTr = reconcileMd.Parameters[0].ParameterType;
GenericInstanceType replicateULDelegateGit;
GenericInstanceType reconcileULDelegateGit;
GenericInstanceType lstDataGit;
GenericInstanceType queueDataGit;
GetGenericULDelegate(replicateDataTr, typeof(ReplicateUserLogicDelegate<>), out replicateULDelegateGit);
GetGenericULDelegate(reconcileDataTr, typeof(ReconcileUserLogicDelegate<>), out reconcileULDelegateGit);
gh.GetGenericLists(replicateDataTr, out lstDataGit);
gh.GetGenericQueues(replicateDataTr, out queueDataGit);
/* Data buffer. */
FieldDefinition replicateULDelegateFd = new FieldDefinition($"_replicateULDelegate___{replicateMd.Name}", FieldAttributes.Private, replicateULDelegateGit);
FieldDefinition reconcileULDelegateFd = new FieldDefinition($"_reconcileULDelegate___{reconcileMd.Name}", FieldAttributes.Private, reconcileULDelegateGit);
FieldDefinition serverReplicatesFd = new FieldDefinition($"_serverReplicates___{replicateMd.Name}", FieldAttributes.Private, queueDataGit);
FieldDefinition clientReplicatesFd = new FieldDefinition($"_clientReplicates___{replicateMd.Name}", FieldAttributes.Private, lstDataGit);
FieldDefinition reconcileDataFd = new FieldDefinition($"_reconcileData___{replicateMd.Name}", FieldAttributes.Private, reconcileDataTr);
FieldDefinition serverReplicatesReadBufferFd = new FieldDefinition($"{replicateMd.Name}___serverReplicateReadBuffer", FieldAttributes.Private, replicateDataArrTr);
typeDef.Fields.Add(replicateULDelegateFd);
typeDef.Fields.Add(reconcileULDelegateFd);
typeDef.Fields.Add(serverReplicatesFd);
typeDef.Fields.Add(clientReplicatesFd);
typeDef.Fields.Add(reconcileDataFd);
typeDef.Fields.Add(serverReplicatesReadBufferFd);
predictionFields = new CreatedPredictionFields(replicateULDelegateFd, reconcileULDelegateFd, serverReplicatesFd, clientReplicatesFd, reconcileDataFd,
serverReplicatesReadBufferFd);
}
/// <summary>
/// Returns if there are any errors with the prediction methods parameters and will print if so.
/// </summary>
private bool HasParameterError(MethodDefinition methodDef, TypeDefinition typeDef, bool replicateMethod)
{
//Replicate: data, asServer, channel, replaying.
//Reconcile: data, asServer, channel.
int count = (replicateMethod) ? 4 : 3;
//Check parameter count.
if (methodDef.Parameters.Count != count)
{
PrintParameterExpectations();
return true;
}
//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;
}
//asServer
if (methodDef.Parameters[1].ParameterType.Name != typeof(bool).Name)
{
PrintParameterExpectations();
return true;
}
//Channel.
if (methodDef.Parameters[2].ParameterType.Name != typeof(Channel).Name)
{
PrintParameterExpectations();
return true;
}
if (replicateMethod)
{
//replaying
if (methodDef.Parameters[3].ParameterType.Name != typeof(bool).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, asServer boolean, channel = Channel.Unreliable, replaying boolean.");
else
base.LogError($"Reconcile method {methodDef.Name} within {typeDef.Name} requires exactly {count} parameters. In order: replicate data, asServer boolean, channel = Channel.Unreliable.");
}
//No errors with parameters.
return false;
}
/// <summary>
/// Creates all methods needed for a RPC.
/// </summary>
/// <param name="originalMethodDef"></param>
/// <param name="rpcAttribute"></param>
/// <returns></returns>
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<GeneralHelper>();
NetworkBehaviourHelper nbh = base.GetClass<NetworkBehaviourHelper>();
predictionReaders = null;
string copySuffix = "___UL";
replicateULMd = base.GetClass<GeneralHelper>().CopyIntoNewMethod(replicateMd, $"{replicateMd.Name}{copySuffix}", out _);
reconcileULMd = base.GetClass<GeneralHelper>().CopyIntoNewMethod(reconcileMd, $"{reconcileMd.Name}{copySuffix}", out _);
replicateMd.Body.Instructions.Clear();
reconcileMd.Body.Instructions.Clear();
MethodDefinition replicateReader;
MethodDefinition reconcileReader;
if (!CreateReplicate())
return false;
if (!CreateReconcile())
return false;
CreateClearReplicateCacheMethod(typeDef, replicateMd.Parameters[0].ParameterType, predictionFields);
CreateReplicateReader(typeDef, replicateMd, predictionFields, out replicateReader);
CreateReconcileReader(typeDef, reconcileMd, predictionFields, out reconcileReader);
predictionReaders = new PredictionReaders(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 asServerPd = replicateMd.Parameters[1];
ParameterDefinition replayingPd = replicateMd.Parameters[3];
Instruction exitMethodInst = processor.Create(OpCodes.Nop);
//Exit early conditions.
processor.Emit(OpCodes.Ldarg_0); //base.
processor.Emit(OpCodes.Ldarg, asServerPd);
processor.Emit(OpCodes.Ldarg, replayingPd);
processor.Emit(OpCodes.Call, base.GetClass<NetworkBehaviourHelper>().Replicate_ExitEarly_A_MethodRef);
processor.Emit(OpCodes.Brtrue, exitMethodInst);
//Wrap server content in an asServer if statement.
Instruction notAsServerInst = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg, asServerPd);
processor.Emit(OpCodes.Brfalse, notAsServerInst);
/***************************/
ServerCreateReplicate(replicateMd, predictionFields);
processor.Emit(OpCodes.Br, exitMethodInst);
/***************************/
//Wrap client content in an !asServer if statement.
processor.Append(notAsServerInst);
/***************************/
ClientCreateReplicate(replicateMd, predictionFields, rpcCount);
/***************************/
processor.Append(exitMethodInst);
processor.Emit(OpCodes.Ret);
return true;
}
bool CreateReconcile()
{
ILProcessor processor = reconcileMd.Body.GetILProcessor();
ParameterDefinition reconcileDataPd = reconcileMd.Parameters[0];
ParameterDefinition asServerPd = reconcileMd.Parameters[1];
ParameterDefinition channelPd = reconcileMd.Parameters[2];
TypeReference replicateDataTr = replicateMd.Parameters[0].ParameterType;
//ExitEarly A.
Instruction exitMethodInst = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldarg, asServerPd);
processor.Emit(OpCodes.Ldarga, channelPd);
processor.Emit(OpCodes.Call, base.GetClass<NetworkBehaviourHelper>().Reconcile_ExitEarly_A_MethodRef);
processor.Emit(OpCodes.Brtrue, exitMethodInst);
//Wrap server content in an asServer if statement.
Instruction notAsServerInst = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg, asServerPd);
processor.Emit(OpCodes.Brfalse, notAsServerInst);
/***************************/
ServerCreateReconcile(reconcileMd, predictionFields, ref rpcCount);
/***************************/
processor.Emit(OpCodes.Br, exitMethodInst);
processor.Append(notAsServerInst);
MethodReference reconcileClientGim = nbh.Reconcile_Client_MethodRef.GetMethodReference(
base.Session, new TypeReference[] { reconcileDataPd.ParameterType, replicateDataTr });
//<T>(ReplicateULDelegate<T> replicateDel, ReconcileULDelegate<T> reconcileDel, List<T> collection,
//T data, Channel channel) where T : IReconcileData
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.ReplicateULDelegate);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ClientReplicateDatas);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ReconcileData);
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, reconcileClientGim);
processor.Append(exitMethodInst);
processor.Emit(OpCodes.Ret);
return true;
}
return true;
}
#region Universal prediction.
/// <summary>
/// Creates an override for the method responsible for resetting replicates.
/// </summary>
/// <param name=""></param>
/// <param name=""></param>
private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReference dataTr, CreatedPredictionFields predictionFields)
{
GeneralHelper gh = base.GetClass<GeneralHelper>();
string clearDatasName = base.GetClass<NetworkBehaviourHelper>().ClearReplicateCache_Method_Name;
MethodDefinition md = typeDef.GetMethod(clearDatasName);
//Already exist when it shouldn't.
if (md != null)
{
base.LogWarning($"{typeDef.Name} overrides method {md.Name} when it should not. Logic within {md.Name} will be replaced by code generation.");
md.Body.Instructions.Clear();
}
else
{
md = new MethodDefinition(clearDatasName, (MethodAttributes.Public | MethodAttributes.Virtual), base.Module.TypeSystem.Void);
gh.CreateParameter(md, typeof(bool), "asServer");
typeDef.Methods.Add(md);
base.ImportReference(md);
}
ILProcessor processor = md.Body.GetILProcessor();
GenericInstanceType dataListGit;
gh.GetGenericLists(dataTr, out dataListGit);
//Get clear method.
MethodReference lstClearMr = gh.List_Clear_MethodRef.MakeHostInstanceGeneric(base.Session, dataListGit);
ParameterDefinition asServerPd = md.Parameters[0];
Instruction afterAsServerInst = processor.Create(OpCodes.Nop);
Instruction resetTicksInst = processor.Create(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg, asServerPd);
processor.Emit(OpCodes.Brfalse_S, afterAsServerInst);
//Clear on server replicates.
MethodReference clrQueueMr = base.ImportReference(typeof(NetworkBehaviour).GetMethod(nameof(NetworkBehaviour.ClearQueue_Server_Internal)));
GenericInstanceMethod clrQueueGim = clrQueueMr.MakeGenericMethod(new TypeReference[] { dataTr });
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ServerReplicateDatas);
processor.Emit(clrQueueMr.GetCallOpCode(base.Session), clrQueueGim);
processor.Emit(OpCodes.Br_S, resetTicksInst);
processor.Append(afterAsServerInst);
//Clear on client replicates.
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ClientReplicateDatas);
processor.Emit(lstClearMr.GetCallOpCode(base.Session), lstClearMr);
processor.Append(resetTicksInst);
processor.Emit(OpCodes.Ret);
}
/// <summary>
/// Outputs generic ReplicateULDelegate for dataTr.
/// </summary>
private void GetGenericULDelegate(TypeReference dataTr, System.Type delegateType, out GenericInstanceType git)
{
TypeReference delDataTr = base.ImportReference(delegateType);
git = delDataTr.MakeGenericInstanceType(new TypeReference[] { dataTr });
}
/// <summary>
/// Subtracts 1 from a field.
/// </summary>
private List<Instruction> SubtractFromField(MethodDefinition methodDef, FieldDefinition fieldDef)
{
List<Instruction> insts = new List<Instruction>();
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;
}
/// <summary>
/// Subtracts 1 from a variable.
/// </summary>
private List<Instruction> SubtractFromVariable(MethodDefinition methodDef, VariableDefinition variableDef)
{
List<Instruction> insts = new List<Instruction>();
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;
}
/// <summary>
/// Subtracts 1 from a variable.
/// </summary>
private List<Instruction> SubtractOneVariableFromAnother(MethodDefinition methodDef, VariableDefinition srcVd, VariableDefinition modifierVd)
{
List<Instruction> insts = new List<Instruction>();
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.
/// <summary>
/// Creates replicate code for client.
/// </summary>
private void ServerCreateReplicate(MethodDefinition replicateMd, CreatedPredictionFields predictionFields)
{
ILProcessor processor = replicateMd.Body.GetILProcessor();
ParameterDefinition replicateDataPd = replicateMd.Parameters[0];
ParameterDefinition channelPd = replicateMd.Parameters[2];
TypeReference replicateDataTr = replicateDataPd.ParameterType;
GenericInstanceMethod replicateGim = base.GetClass<NetworkBehaviourHelper>().Replicate_Server_MethodRef.MakeGenericMethod(new TypeReference[] { replicateDataTr });
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateULDelegate);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ServerReplicateDatas);
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, replicateGim);
}
/// <summary>
/// Creates a reader for replicate data received from clients.
/// </summary>
private bool CreateReplicateReader(TypeDefinition typeDef, MethodDefinition replicateMd, CreatedPredictionFields predictionFields, out MethodDefinition result)
{
string methodName = $"{REPLICATE_READER_PREFIX}{replicateMd.Name}";
MethodDefinition createdMd = new MethodDefinition(methodName,
MethodAttributes.Private,
replicateMd.Module.TypeSystem.Void);
typeDef.Methods.Add(createdMd);
createdMd.Body.InitLocals = true;
ILProcessor processor = createdMd.Body.GetILProcessor();
GeneralHelper gh = base.GetClass<GeneralHelper>();
NetworkBehaviourHelper nbh = base.GetClass<NetworkBehaviourHelper>();
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);
//Reader, NetworkConnection.
processor.Emit(OpCodes.Ldarg, readerPd);
processor.Emit(OpCodes.Ldarg, networkConnectionPd);
//arrBuffer.
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ServerReplicateReaderBuffer);
//replicates.
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ServerReplicateDatas);
//Channel.
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, replicateReaderGim);
//Add end of method.
processor.Emit(OpCodes.Ret);
result = createdMd;
return true;
}
/// <summary>
/// Creates server side code for reconcileMd.
/// </summary>
/// <param name="reconcileMd"></param>
/// <returns></returns>
private void ServerCreateReconcile(MethodDefinition reconcileMd, CreatedPredictionFields predictionFields, ref uint rpcCount)
{
ParameterDefinition reconcileDataPd = reconcileMd.Parameters[0];
ParameterDefinition channelPd = reconcileMd.Parameters[2];
ILProcessor processor = reconcileMd.Body.GetILProcessor();
GenericInstanceMethod methodGim = base.GetClass<NetworkBehaviourHelper>().Reconcile_Server_MethodRef.MakeGenericMethod(new TypeReference[] { reconcileDataPd.ParameterType });
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldc_I4, (int)rpcCount);
processor.Emit(OpCodes.Ldarg, reconcileDataPd);
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, methodGim);
rpcCount++;
}
#endregion
#region Client side.
/// <summary>
/// Creates replicate code for client.
/// </summary>
private void ClientCreateReplicate(MethodDefinition replicateMd, CreatedPredictionFields predictionFields, uint rpcCount)
{
ParameterDefinition dataPd = replicateMd.Parameters[0];
ParameterDefinition channelPd = replicateMd.Parameters[2];
TypeReference dataTr = dataPd.ParameterType;
ILProcessor processor = replicateMd.Body.GetILProcessor();
//Make method reference NB.SendReplicateRpc<dataTr>
GenericInstanceMethod replicateClientGim = base.GetClass<NetworkBehaviourHelper>().Replicate_Client_MethodRef.MakeGenericMethod(new TypeReference[] { dataTr });
processor.Emit(OpCodes.Ldarg_0);//base.
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, predictionFields.ReplicateULDelegate);
processor.Emit(OpCodes.Ldc_I4, (int)rpcCount);
processor.Emit(OpCodes.Ldarg_0);//this.
processor.Emit(OpCodes.Ldfld, predictionFields.ClientReplicateDatas.CachedResolve(base.Session));
processor.Emit(OpCodes.Ldarg, dataPd);
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, replicateClientGim);
}
/// <summary>
/// Creates a reader for replicate data received from clients.
/// </summary>
private bool CreateReconcileReader(TypeDefinition typeDef, MethodDefinition reconcileMd, CreatedPredictionFields predictionFields, out MethodDefinition result)
{
string methodName = $"{RECONCILE_READER_PREFIX}{reconcileMd.Name}";
MethodDefinition createdMd = new MethodDefinition(methodName,
MethodAttributes.Private,
reconcileMd.Module.TypeSystem.Void);
typeDef.Methods.Add(createdMd);
createdMd.Body.InitLocals = true;
ILProcessor processor = createdMd.Body.GetILProcessor();
GeneralHelper gh = base.GetClass<GeneralHelper>();
NetworkBehaviourHelper nbh = base.GetClass<NetworkBehaviourHelper>();
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);
//Reader, data, channel.
processor.Emit(OpCodes.Ldarg, readerPd);
//Data to assign read value to.
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldflda, predictionFields.ReconcileData);
//Channel.
processor.Emit(OpCodes.Ldarg, channelPd);
processor.Emit(OpCodes.Call, methodGim);
//Add end of method.
processor.Emit(OpCodes.Ret);
result = createdMd;
return true;
}
#endregion
}
}