mirror of
https://github.com/Dadechin/Unity-WebSocket.git
synced 2025-07-03 11:54:35 +00:00
1092 lines
50 KiB
C#
1092 lines
50 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// TypeReference of replicate data.
|
|
/// </summary>
|
|
public TypeReference ReplicateDataTypeRef;
|
|
/// <summary>
|
|
/// Typereference of reconcile data.
|
|
/// </summary>
|
|
public TypeReference ReconcileDataTypeRef;
|
|
|
|
/// <summary>
|
|
/// Delegate for calling replicate user logic.
|
|
/// </summary>
|
|
public FieldDefinition ReplicateUserLogicDelegate;
|
|
/// <summary>
|
|
/// Delegate for calling replicate user logic.
|
|
/// </summary>
|
|
public FieldDefinition ReconcileUserLogicDelegate;
|
|
|
|
/// <summary>
|
|
/// Replicate data which has not run yet and is in queue to do so.
|
|
/// </summary>
|
|
public FieldDefinition ReplicatesQueue;
|
|
/// <summary>
|
|
/// Replicate data which has already run and is used to reconcile/replay.
|
|
/// </summary>
|
|
public FieldDefinition ReplicatesHistory;
|
|
/// <summary>
|
|
/// Reconcile data cached locally from the local client.
|
|
/// </summary>
|
|
public FieldDefinition LocalReconciles;
|
|
|
|
/// <summary>
|
|
/// Last replicate read. This is used for reading delta replicates.
|
|
/// </summary>
|
|
public FieldDefinition LastReadReplicate;
|
|
/// <summary>
|
|
/// Last reconcile read. This is used for reading delta reconciles.
|
|
/// </summary>
|
|
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.
|
|
/// <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>
|
|
/// Gets number of predictions by checking for prediction attributes in typeDef parents, excluding typerDef.
|
|
/// </summary>
|
|
/// <param name="typeDef"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <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;
|
|
|
|
string replicateAttributeFullName = base.GetClass<AttributeHelper>().ReplicateAttribute_FullName;
|
|
string reconcileAttributeFullName = base.GetClass<AttributeHelper>().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<RpcProcessor>();
|
|
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<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, 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ensures the tick field for GetTick is non-serializable.
|
|
/// </summary>
|
|
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;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Registers RPCs that prediction uses.
|
|
/// </summary>
|
|
private void RegisterPredictionRpcs(TypeDefinition typeDef, uint hash, PredictionReaders readers)
|
|
{
|
|
MethodDefinition injectionMethodDef = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
|
ILProcessor processor = injectionMethodDef.Body.GetILProcessor();
|
|
List<Instruction> 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<NetworkBehaviourHelper>().ReplicateRpcDelegate_Ctor_MethodRef;
|
|
callMr = base.GetClass<NetworkBehaviourHelper>().RegisterReplicateRpc_MethodRef;
|
|
}
|
|
else
|
|
{
|
|
ctorMr = base.GetClass<NetworkBehaviourHelper>().ReconcileRpcDelegate_Ctor_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, MethodDefinition reconcileMd, CreatedPredictionFields predictionFields)
|
|
{
|
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|
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<GeneralHelper>().RingBuffer_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session);
|
|
ctorMd = base.GetClass<GeneralHelper>().RingBuffer_TypeRef.CachedResolve(base.Session).GetConstructor(base.Session, 1);
|
|
else
|
|
ctorMd = base.GetClass<GeneralHelper>().List_TypeRef.CachedResolve(base.Session).GetDefaultConstructor(base.Session);
|
|
|
|
MethodReference ctorMr = ctorMd.MakeHostInstanceGeneric(base.Session, lGit);
|
|
|
|
List<Instruction> insts = new();
|
|
|
|
insts.Add(processor.Create(OpCodes.Ldarg_0));
|
|
if (isRingBuffer)
|
|
insts.Add(processor.Create(OpCodes.Ldc_I4, RingBuffer<int>.DEFAULT_CAPACITY));
|
|
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();
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <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 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,
|
|
};
|
|
}
|
|
|
|
/// <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, 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates all methods needed for a RPC.
|
|
/// </summary>
|
|
/// <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;
|
|
|
|
uint startingRpcCount = rpcCount;
|
|
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();
|
|
|
|
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<NetworkBehaviourHelper>().Replicate_Current_MethodRef.MakeGenericMethod(new TypeReference[] { replicateDataTr });
|
|
|
|
/* ReplicateUserLogicDelegate<T> del
|
|
* uint methodHash
|
|
* BasicQueue<ReplicateData<T>> replicatesQueue
|
|
* RingBuffer<ReplicateData<T>> replicatesHistory
|
|
* ReplicateData<T> 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<T>
|
|
//new ReplicateData<T>(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<T> del, List<T> 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.
|
|
/// <summary>
|
|
/// Creates an override for the method responsible for resetting replicates.
|
|
/// </summary>
|
|
/// <param name=""></param>
|
|
/// <param name=""></param>
|
|
private void CreateClearReplicateCacheMethod(TypeDefinition typeDef, TypeReference replicateDataTr, TypeReference reconcileDataTr, CreatedPredictionFields predictionFields)
|
|
{
|
|
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
|
NetworkBehaviourHelper nbh = base.GetClass<NetworkBehaviourHelper>();
|
|
|
|
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);
|
|
}
|
|
|
|
/// <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();
|
|
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();
|
|
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();
|
|
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 a reader for replicate data received from clients.
|
|
/// </summary>
|
|
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<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);
|
|
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;
|
|
}
|
|
|
|
/// <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[1];
|
|
ILProcessor processor = reconcileMd.Body.GetILProcessor();
|
|
|
|
NetworkBehaviourHelper nbh = base.GetClass<NetworkBehaviourHelper>();
|
|
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.
|
|
/// <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(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);
|
|
|
|
/* 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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Outputs generic RingBuffer for dataTr.
|
|
/// </summary>
|
|
public GenericInstanceType GetGenericReplicateDataContainer(TypeReference dataTr)
|
|
{
|
|
TypeReference typeTr = base.ImportReference(typeof(ReplicateDataContainer<>));
|
|
return typeTr.MakeGenericInstanceType(new TypeReference[] { dataTr });
|
|
}
|
|
#endregion
|
|
}
|
|
} |