fishnet installed
This commit is contained in:
61
Assets/FishNet/CodeGenerating/Processing/CodegenBase.cs
Normal file
61
Assets/FishNet/CodeGenerating/Processing/CodegenBase.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using MonoFN.Cecil;
|
||||
using SR = System.Reflection;
|
||||
|
||||
|
||||
namespace FishNet.CodeGenerating
|
||||
{
|
||||
internal abstract class CodegenBase
|
||||
{
|
||||
//Lazy debug checks.
|
||||
public bool IsIsolatedAsm => (Module.Name.Contains("IsolatedAsm"));
|
||||
public bool IsRuntimeAsm => (Module.Name.Contains("FishNet.Runtime"));
|
||||
|
||||
public CodegenSession Session { get; private set; }
|
||||
public ModuleDefinition Module { get; private set; }
|
||||
|
||||
public virtual bool ImportReferences() { return true; }
|
||||
|
||||
public void Initialize(CodegenSession session)
|
||||
{
|
||||
Session = session;
|
||||
Module = session.Module;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns class of type if found within Session.
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <returns></returns>
|
||||
internal T GetClass<T>() where T : CodegenBase => Session.GetClass<T>();
|
||||
|
||||
#region Logging.
|
||||
/// <summary>
|
||||
/// Logs a warning.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
internal void LogWarning(string msg) => Session.LogWarning(msg);
|
||||
/// <summary>
|
||||
/// Logs an error.
|
||||
/// </summary>
|
||||
/// <param name="msg"></param>
|
||||
internal void LogError(string msg) => Session.LogError(msg);
|
||||
#endregion
|
||||
|
||||
#region ImportReference.
|
||||
public MethodReference ImportReference(SR.MethodBase method) => Session.ImportReference(method);
|
||||
public MethodReference ImportReference(SR.MethodBase method, IGenericParameterProvider context) => Session.ImportReference(method, context);
|
||||
public TypeReference ImportReference(TypeReference type) => Session.ImportReference(type);
|
||||
public TypeReference ImportReference(TypeReference type, IGenericParameterProvider context) => Session.ImportReference(type, context);
|
||||
public FieldReference ImportReference(FieldReference field) => Session.ImportReference(field);
|
||||
public FieldReference ImportReference(FieldReference field, IGenericParameterProvider context) => Session.ImportReference(field, context);
|
||||
public FieldReference ImportReference(SR.FieldInfo field) => Session.ImportReference(field);
|
||||
public FieldReference ImportReference(SR.FieldInfo field, IGenericParameterProvider context) => Session.ImportReference(field, context);
|
||||
public MethodReference ImportReference(MethodReference method) => Session.ImportReference(method);
|
||||
public MethodReference ImportReference(MethodReference method, IGenericParameterProvider context) => Session.ImportReference(method, context);
|
||||
public TypeReference ImportReference(System.Type type) => Session.ImportReference(type, null);
|
||||
public TypeReference ImportReference(System.Type type, IGenericParameterProvider context) => Session.ImportReference(type, context);
|
||||
#endregion
|
||||
|
||||
}
|
||||
|
||||
}
|
11
Assets/FishNet/CodeGenerating/Processing/CodegenBase.cs.meta
Normal file
11
Assets/FishNet/CodeGenerating/Processing/CodegenBase.cs.meta
Normal file
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8462034e5255191499a018bd8fbbf751
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,359 @@
|
||||
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.CodeGenerating.Helping.Extension;
|
||||
using FishNet.Serializing;
|
||||
using FishNet.Serializing.Helping;
|
||||
using MonoFN.Cecil;
|
||||
using MonoFN.Cecil.Cil;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing
|
||||
{
|
||||
internal class CustomSerializerProcessor : CodegenBase
|
||||
{
|
||||
|
||||
#region Types.
|
||||
internal enum ExtensionType
|
||||
{
|
||||
None,
|
||||
Write,
|
||||
Read
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
internal bool CreateSerializerDelegates(TypeDefinition typeDef, bool replace)
|
||||
{
|
||||
bool modified = false;
|
||||
/* Find all declared methods and register delegates to them.
|
||||
* After they are all registered create any custom writers
|
||||
* needed to complete the declared methods. It's important to
|
||||
* make generated writers after so that a generated method
|
||||
* isn't made for a type when the user has already made a declared one. */
|
||||
foreach (MethodDefinition methodDef in typeDef.Methods)
|
||||
{
|
||||
ExtensionType extensionType = GetExtensionType(methodDef);
|
||||
if (extensionType == ExtensionType.None)
|
||||
continue;
|
||||
if (base.GetClass<GeneralHelper>().CodegenExclude(methodDef))
|
||||
continue;
|
||||
|
||||
MethodReference methodRef = base.ImportReference(methodDef);
|
||||
if (extensionType == ExtensionType.Write)
|
||||
{
|
||||
base.GetClass<WriterProcessor>().AddWriterMethod(methodRef.Parameters[1].ParameterType, methodRef, false, !replace);
|
||||
modified = true;
|
||||
}
|
||||
else if (extensionType == ExtensionType.Read)
|
||||
{
|
||||
base.GetClass<ReaderProcessor>().AddReaderMethod(methodRef.ReturnType, methodRef, false, !replace);
|
||||
modified = true;
|
||||
}
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates serializers for any custom types for declared methods.
|
||||
/// </summary>
|
||||
/// <param name="declaredMethods"></param>
|
||||
/// <param name="moduleDef"></param>
|
||||
internal bool CreateSerializers(TypeDefinition typeDef)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
List<(MethodDefinition, ExtensionType)> declaredMethods = new List<(MethodDefinition, ExtensionType)>();
|
||||
/* Go through all custom serializers again and see if
|
||||
* they use any types that the user didn't make a serializer for
|
||||
* and that there isn't a built-in type for. Create serializers
|
||||
* for these types. */
|
||||
foreach (MethodDefinition methodDef in typeDef.Methods)
|
||||
{
|
||||
ExtensionType extensionType = GetExtensionType(methodDef);
|
||||
if (extensionType == ExtensionType.None)
|
||||
continue;
|
||||
if (base.GetClass<GeneralHelper>().CodegenExclude(methodDef))
|
||||
continue;
|
||||
|
||||
declaredMethods.Add((methodDef, extensionType));
|
||||
modified = true;
|
||||
}
|
||||
//Now that all declared are loaded see if any of them need generated serializers.
|
||||
foreach ((MethodDefinition methodDef, ExtensionType extensionType) in declaredMethods)
|
||||
CreateSerializers(extensionType, methodDef);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a custom serializer for any types not handled within users declared.
|
||||
/// </summary>
|
||||
/// <param name="extensionType"></param>
|
||||
/// <param name="moduleDef"></param>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <param name="diagnostics"></param>
|
||||
private void CreateSerializers(ExtensionType extensionType, MethodDefinition methodDef)
|
||||
{
|
||||
for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
|
||||
CheckToModifyInstructions(extensionType, methodDef, ref i);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates delegates for custom comparers.
|
||||
/// </summary>
|
||||
internal bool CreateComparerDelegates(TypeDefinition typeDef)
|
||||
{
|
||||
bool modified = false;
|
||||
GeneralHelper gh = base.GetClass<GeneralHelper>();
|
||||
/* Find all declared methods and register delegates to them.
|
||||
* After they are all registered create any custom writers
|
||||
* needed to complete the declared methods. It's important to
|
||||
* make generated writers after so that a generated method
|
||||
* isn't made for a type when the user has already made a declared one. */
|
||||
foreach (MethodDefinition methodDef in typeDef.Methods)
|
||||
{
|
||||
if (gh.CodegenExclude(methodDef))
|
||||
continue;
|
||||
if (!methodDef.HasCustomAttribute<CustomComparerAttribute>())
|
||||
continue;
|
||||
//Validate return type.
|
||||
if (methodDef.ReturnType.FullName != gh.GetTypeReference(typeof(bool)).FullName)
|
||||
{
|
||||
base.LogError($"Comparer method {methodDef.Name} in type {typeDef.FullName} must return bool.");
|
||||
continue;
|
||||
}
|
||||
/* Make sure parameters are correct. */
|
||||
//Invalid count.
|
||||
if (methodDef.Parameters.Count != 2)
|
||||
{
|
||||
base.LogError($"Comparer method {methodDef.Name} in type {typeDef.FullName} must have exactly two parameters, each of the same type which is being compared.");
|
||||
continue;
|
||||
}
|
||||
TypeReference p0Tr = methodDef.Parameters[0].ParameterType;
|
||||
TypeReference p1Tr = methodDef.Parameters[0].ParameterType;
|
||||
//Not the same types.
|
||||
if (p0Tr != p1Tr)
|
||||
{
|
||||
base.LogError($"Both parameters must be the same type in comparer method {methodDef.Name} in type {typeDef.FullName}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
base.ImportReference(methodDef);
|
||||
base.ImportReference(p0Tr);
|
||||
gh.RegisterComparerDelegate(methodDef, p0Tr);
|
||||
gh.CreateComparerDelegate(methodDef, p0Tr);
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if instructions need to be modified and does so.
|
||||
/// </summary>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <param name="instructionIndex"></param>
|
||||
private void CheckToModifyInstructions(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
|
||||
{
|
||||
Instruction instruction = methodDef.Body.Instructions[instructionIndex];
|
||||
//Fields.
|
||||
if (instruction.OpCode == OpCodes.Ldsfld || instruction.OpCode == OpCodes.Ldfld)
|
||||
CheckFieldReferenceInstruction(extensionType, methodDef, ref instructionIndex);
|
||||
//Method calls.
|
||||
else if (instruction.OpCode == OpCodes.Call || instruction.OpCode == OpCodes.Callvirt)
|
||||
CheckCallInstruction(extensionType, methodDef, ref instructionIndex, (MethodReference)instruction.Operand);
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a reader or writer must be generated for a field type.
|
||||
/// </summary>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <param name="instructionIndex"></param>
|
||||
private void CheckFieldReferenceInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex)
|
||||
{
|
||||
Instruction instruction = methodDef.Body.Instructions[instructionIndex];
|
||||
FieldReference field = (FieldReference)instruction.Operand;
|
||||
TypeReference typeRef = field.DeclaringType;
|
||||
|
||||
if (typeRef.IsType(typeof(GenericWriter<>)) || typeRef.IsType(typeof(GenericReader<>)) && typeRef.IsGenericInstance)
|
||||
{
|
||||
GenericInstanceType typeGenericInst = (GenericInstanceType)typeRef;
|
||||
TypeReference parameterType = typeGenericInst.GenericArguments[0];
|
||||
CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a reader or writer must be generated for a call type.
|
||||
/// </summary>
|
||||
/// <param name="extensionType"></param>
|
||||
/// <param name="moduleDef"></param>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <param name="instructionIndex"></param>
|
||||
/// <param name="method"></param>
|
||||
private void CheckCallInstruction(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, MethodReference method)
|
||||
{
|
||||
if (!method.IsGenericInstance)
|
||||
return;
|
||||
|
||||
//True if call is to read/write.
|
||||
bool canCreate = (
|
||||
method.Is<Writer>(nameof(Writer.Write)) ||
|
||||
method.Is<Reader>(nameof(Reader.Read))
|
||||
);
|
||||
|
||||
if (canCreate)
|
||||
{
|
||||
GenericInstanceMethod instanceMethod = (GenericInstanceMethod)method;
|
||||
TypeReference parameterType = instanceMethod.GenericArguments[0];
|
||||
if (parameterType.IsGenericParameter)
|
||||
return;
|
||||
|
||||
CreateReaderOrWriter(extensionType, methodDef, ref instructionIndex, parameterType);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates a reader or writer for parameterType if needed. Otherwise calls existing reader.
|
||||
/// </summary>
|
||||
private void CreateReaderOrWriter(ExtensionType extensionType, MethodDefinition methodDef, ref int instructionIndex, TypeReference parameterType)
|
||||
{
|
||||
if (!parameterType.IsGenericParameter && parameterType.CanBeResolved(base.Session))
|
||||
{
|
||||
TypeDefinition typeDefinition = parameterType.CachedResolve(base.Session);
|
||||
//If class and not value type check for accessible constructor.
|
||||
if (typeDefinition.IsClass && !typeDefinition.IsValueType)
|
||||
{
|
||||
MethodDefinition constructor = typeDefinition.GetMethod(".ctor");
|
||||
//Constructor is inaccessible, cannot create serializer for type.
|
||||
if (!constructor.IsPublic)
|
||||
{
|
||||
base.LogError($"Unable to generator serializers for {typeDefinition.FullName} because it's constructor is not public.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ILProcessor processor = methodDef.Body.GetILProcessor();
|
||||
|
||||
//Find already existing read or write method.
|
||||
MethodReference createdMethodRef = (extensionType == ExtensionType.Write) ?
|
||||
base.GetClass<WriterProcessor>().GetWriteMethodReference(parameterType) :
|
||||
base.GetClass<ReaderProcessor>().GetReadMethodReference(parameterType);
|
||||
//If a created method already exist nothing further is required.
|
||||
if (createdMethodRef != null)
|
||||
{
|
||||
TryInsertAutoPack(ref instructionIndex);
|
||||
//Replace call to generic with already made serializer.
|
||||
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
|
||||
methodDef.Body.Instructions[instructionIndex] = newInstruction;
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
createdMethodRef = (extensionType == ExtensionType.Write) ?
|
||||
base.GetClass<WriterProcessor>().CreateWriter(parameterType) :
|
||||
base.GetClass<ReaderProcessor>().CreateReader(parameterType);
|
||||
}
|
||||
|
||||
//If method was created.
|
||||
if (createdMethodRef != null)
|
||||
{
|
||||
TryInsertAutoPack(ref instructionIndex);
|
||||
//Set new instruction.
|
||||
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
|
||||
methodDef.Body.Instructions[instructionIndex] = newInstruction;
|
||||
}
|
||||
}
|
||||
|
||||
void TryInsertAutoPack(ref int insertIndex)
|
||||
{
|
||||
if (IsAutoPackMethod(parameterType, extensionType))
|
||||
{
|
||||
ILProcessor processor = methodDef.Body.GetILProcessor();
|
||||
AutoPackType packType = base.GetClass<GeneralHelper>().GetDefaultAutoPackType(parameterType);
|
||||
Instruction autoPack = processor.Create(OpCodes.Ldc_I4, (int)packType);
|
||||
methodDef.Body.Instructions.Insert(insertIndex, autoPack);
|
||||
insertIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a typeRef serializer requires or uses autopacktype.
|
||||
/// </summary>
|
||||
private bool IsAutoPackMethod(TypeReference typeRef, ExtensionType extensionType)
|
||||
{
|
||||
if (extensionType == ExtensionType.Write)
|
||||
return base.GetClass<WriterProcessor>().IsAutoPackedType(typeRef);
|
||||
else if (extensionType == ExtensionType.Read)
|
||||
return base.GetClass<ReaderProcessor>().IsAutoPackedType(typeRef);
|
||||
else
|
||||
return false;
|
||||
|
||||
}
|
||||
/// <summary>
|
||||
/// Returns the RPC attribute on a method, if one exist. Otherwise returns null.
|
||||
/// </summary>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <returns></returns>
|
||||
private ExtensionType GetExtensionType(MethodDefinition methodDef)
|
||||
{
|
||||
bool hasExtensionAttribute = methodDef.HasCustomAttribute<System.Runtime.CompilerServices.ExtensionAttribute>();
|
||||
if (!hasExtensionAttribute)
|
||||
return ExtensionType.None;
|
||||
|
||||
bool write = (methodDef.ReturnType == methodDef.Module.TypeSystem.Void);
|
||||
|
||||
//Return None for Mirror types.
|
||||
#if MIRROR
|
||||
if (write)
|
||||
{
|
||||
if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkWriter")
|
||||
return ExtensionType.None;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (methodDef.Parameters.Count > 0 && methodDef.Parameters[0].ParameterType.FullName == "Mirror.NetworkReader")
|
||||
return ExtensionType.None;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
string prefix = (write) ? WriterProcessor.WRITE_PREFIX : ReaderProcessor.READ_PREFIX;
|
||||
|
||||
//Does not contain prefix.
|
||||
if (methodDef.Name.Length < prefix.Length || methodDef.Name.Substring(0, prefix.Length) != prefix)
|
||||
return ExtensionType.None;
|
||||
|
||||
//Make sure first parameter is right.
|
||||
if (methodDef.Parameters.Count >= 1)
|
||||
{
|
||||
TypeReference tr = methodDef.Parameters[0].ParameterType;
|
||||
if (tr.FullName != base.GetClass<WriterImports>().Writer_TypeRef.FullName &&
|
||||
tr.FullName != base.GetClass<ReaderImports>().Reader_TypeRef.FullName)
|
||||
return ExtensionType.None;
|
||||
}
|
||||
|
||||
if (write && methodDef.Parameters.Count < 2)
|
||||
{
|
||||
base.LogError($"{methodDef.FullName} must have at least two parameters, the first being PooledWriter, and second value to write.");
|
||||
return ExtensionType.None;
|
||||
}
|
||||
else if (!write && methodDef.Parameters.Count < 1)
|
||||
{
|
||||
base.LogError($"{methodDef.FullName} must have at least one parameters, the first being PooledReader.");
|
||||
return ExtensionType.None;
|
||||
}
|
||||
|
||||
return (write) ? ExtensionType.Write : ExtensionType.Read;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9269fd8a62199e24c965b4c99b641244
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,637 @@
|
||||
using FishNet.CodeGenerating.Extension;
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.CodeGenerating.Helping.Extension;
|
||||
using FishNet.CodeGenerating.Processing.Rpc;
|
||||
using FishNet.Configuring;
|
||||
using FishNet.Object;
|
||||
using MonoFN.Cecil;
|
||||
using MonoFN.Cecil.Cil;
|
||||
using MonoFN.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing
|
||||
{
|
||||
internal class NetworkBehaviourProcessor : CodegenBase
|
||||
{
|
||||
#region Types.
|
||||
private class NetworkInitializeMethodData
|
||||
{
|
||||
public MethodDefinition MethodDefinition;
|
||||
public FieldDefinition CalledFieldDef;
|
||||
public bool CalledFromAwake;
|
||||
|
||||
public NetworkInitializeMethodData(MethodDefinition methodDefinition, FieldDefinition calledFieldDef)
|
||||
{
|
||||
MethodDefinition = methodDefinition;
|
||||
CalledFieldDef = calledFieldDef;
|
||||
CalledFromAwake = false;
|
||||
}
|
||||
}
|
||||
private class AwakeMethodData
|
||||
{
|
||||
public MethodDefinition AwakeMethodDef;
|
||||
public MethodDefinition UserLogicMethodDef;
|
||||
public bool Created;
|
||||
|
||||
public AwakeMethodData(MethodDefinition awakeMd, MethodDefinition userLogicMd, bool created)
|
||||
{
|
||||
AwakeMethodDef = awakeMd;
|
||||
UserLogicMethodDef = userLogicMd;
|
||||
Created = created;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Misc.
|
||||
private Dictionary<TypeDefinition, NetworkInitializeMethodData> _earlyNetworkInitializeDatas = new Dictionary<TypeDefinition, NetworkInitializeMethodData>();
|
||||
private Dictionary<TypeDefinition, NetworkInitializeMethodData> _lateNetworkInitializeDatas = new Dictionary<TypeDefinition, NetworkInitializeMethodData>();
|
||||
/// <summary>
|
||||
/// Methods modified or iterated during weaving.
|
||||
/// </summary>
|
||||
internal List<MethodDefinition> ModifiedMethodDefinitions = new List<MethodDefinition>();
|
||||
/// <summary>
|
||||
/// Classes which have been processed for all NetworkBehaviour features.
|
||||
/// </summary>
|
||||
private HashSet<TypeDefinition> _processedClasses = new HashSet<TypeDefinition>();
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
internal const string EARLY_INITIALIZED_NAME = "NetworkInitializeEarly_";
|
||||
internal const string LATE_INITIALIZED_NAME = "NetworkInitializeLate_";
|
||||
internal const string NETWORKINITIALIZE_EARLY_INTERNAL_NAME = "NetworkInitialize___Early";
|
||||
internal const string NETWORKINITIALIZE_LATE_INTERNAL_NAME = "NetworkInitialize__Late";
|
||||
private MethodAttributes PUBLIC_VIRTUAL_ATTRIBUTES = (MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig);
|
||||
#pragma warning disable CS0414
|
||||
private MethodAttributes PROTECTED_VIRTUAL_ATTRIBUTES = (MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig);
|
||||
#pragma warning restore CS0414
|
||||
#endregion
|
||||
|
||||
internal bool Process(TypeDefinition typeDef, List<(SyncType, ProcessedSync)> allProcessedSyncs, Dictionary<TypeDefinition, uint> childSyncTypeCounts, Dictionary<TypeDefinition, uint> childRpcCounts)
|
||||
{
|
||||
bool modified = false;
|
||||
TypeDefinition copyTypeDef = typeDef;
|
||||
TypeDefinition firstTypeDef = typeDef;
|
||||
|
||||
//Make collection of NBs to processor.
|
||||
List<TypeDefinition> typeDefs = new List<TypeDefinition>();
|
||||
do
|
||||
{
|
||||
typeDefs.Add(copyTypeDef);
|
||||
copyTypeDef = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(copyTypeDef, base.Session);
|
||||
} while (copyTypeDef != null);
|
||||
|
||||
/* Iterate from child-most to parent first
|
||||
* while creating network initialize methods.
|
||||
* This is because the child-most must call the parents
|
||||
* base awake methods. */
|
||||
foreach (TypeDefinition td in typeDefs)
|
||||
{
|
||||
/* Class was already processed. Since child most is processed first
|
||||
* this can occur if a class is inherited by multiple types. If a class
|
||||
* has already been processed then there is no reason to scale up the hierarchy
|
||||
* because it would have already been done. */
|
||||
if (HasClassBeenProcessed(td))
|
||||
continue;
|
||||
|
||||
//Disallow nested network behaviours.
|
||||
ICollection<TypeDefinition> nestedTds = td.NestedTypes;
|
||||
foreach (TypeDefinition item in nestedTds)
|
||||
{
|
||||
if (item.InheritsNetworkBehaviour(base.Session))
|
||||
{
|
||||
base.LogError($"{td.FullName} contains nested NetworkBehaviours. These are not supported.");
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create NetworkInitialize before-hand so the other procesors
|
||||
* can use it. */
|
||||
MethodDefinition networkInitializeIfDisabledMd;
|
||||
CreateNetworkInitializeMethods(td, out networkInitializeIfDisabledMd);
|
||||
CallNetworkInitializeMethods(networkInitializeIfDisabledMd);
|
||||
}
|
||||
|
||||
/* Reverse and do RPCs/SyncTypes.
|
||||
* This counts up on children instead of the
|
||||
* parent, so we do not have to rewrite
|
||||
* parent numbers. */
|
||||
typeDefs.Reverse();
|
||||
|
||||
foreach (TypeDefinition td in typeDefs)
|
||||
{
|
||||
/* Class was already processed. Since child most is processed first
|
||||
* this can occur if a class is inherited by multiple types. If a class
|
||||
* has already been processed then there is no reason to scale up the hierarchy
|
||||
* because it would have already been done. */
|
||||
if (HasClassBeenProcessed(td))
|
||||
continue;
|
||||
|
||||
//No longer used...remove in rework.
|
||||
uint rpcCount = 0;
|
||||
childRpcCounts.TryGetValue(td, out rpcCount);
|
||||
/* Prediction. */
|
||||
/* Run prediction first since prediction will modify
|
||||
* user data passed into prediction methods. Because of this
|
||||
* other RPCs should use the modified version and reader/writers
|
||||
* made for prediction. */
|
||||
modified |= base.GetClass<PredictionProcessor>().Process(td, ref rpcCount);
|
||||
//25ms
|
||||
|
||||
/* RPCs. */
|
||||
modified |= base.GetClass<RpcProcessor>().ProcessLocal(td, ref rpcCount);
|
||||
//30ms
|
||||
/* //perf rpcCounts can be optimized by having different counts
|
||||
* for target, observers, server, replicate, and reoncile rpcs. Since
|
||||
* each registers to their own delegates this is possible. */
|
||||
|
||||
|
||||
|
||||
/* SyncTypes. */
|
||||
uint syncTypeStartCount;
|
||||
childSyncTypeCounts.TryGetValue(td, out syncTypeStartCount);
|
||||
modified |= base.GetClass<NetworkBehaviourSyncProcessor>().Process(td, allProcessedSyncs, ref syncTypeStartCount);
|
||||
//70ms
|
||||
_processedClasses.Add(td);
|
||||
}
|
||||
|
||||
int maxAllowSyncTypes = 256;
|
||||
if (allProcessedSyncs.Count > maxAllowSyncTypes)
|
||||
{
|
||||
base.LogError($"Found {allProcessedSyncs.Count} SyncTypes within {firstTypeDef.FullName}. The maximum number of allowed SyncTypes within type and inherited types is {maxAllowSyncTypes}. Remove SyncTypes or condense them using data containers, or a custom SyncObject.");
|
||||
return false;
|
||||
}
|
||||
|
||||
/* If here then all inerited classes for firstTypeDef have
|
||||
* been processed. */
|
||||
PrepareNetworkInitializeMethods(firstTypeDef);
|
||||
|
||||
/* Make awake methods for all inherited classes
|
||||
* public and virtual. This is so I can add logic
|
||||
* to the firstTypeDef awake and still execute
|
||||
* user awake methods. */
|
||||
List<AwakeMethodData> awakeDatas = new List<AwakeMethodData>();
|
||||
if (!CreateOrModifyAwakeMethods(firstTypeDef, ref awakeDatas))
|
||||
{
|
||||
base.LogError($"Was unable to make Awake methods public virtual starting on type {firstTypeDef.FullName}.");
|
||||
return modified;
|
||||
}
|
||||
|
||||
//NetworkInitializeEarly.
|
||||
CallNetworkInitializeFromAwake(awakeDatas, true);
|
||||
//Call base awake, then call user logic methods.
|
||||
CallBaseAwakeOnCreatedMethods(awakeDatas);
|
||||
CallAwakeUserLogic(awakeDatas);
|
||||
//NetworkInitializeLate
|
||||
CallNetworkInitializeFromAwake(awakeDatas, false);
|
||||
//Since awake methods are erased ret has to be added at the end.
|
||||
AddReturnsToAwake(awakeDatas);
|
||||
|
||||
base.GetClass<NetworkBehaviourSyncProcessor>().CallBaseReadSyncVar(firstTypeDef);
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a class has been processed.
|
||||
/// </summary>
|
||||
/// <param name="typeDef"></param>
|
||||
/// <returns></returns>
|
||||
private bool HasClassBeenProcessed(TypeDefinition typeDef)
|
||||
{
|
||||
return _processedClasses.Contains(typeDef);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if any typeDefs have attributes which are not allowed to be used outside NetworkBehaviour.
|
||||
/// </summary>
|
||||
/// <param name="typeDefs"></param>
|
||||
/// <returns></returns>
|
||||
internal bool NonNetworkBehaviourHasInvalidAttributes(Collection<TypeDefinition> typeDefs)
|
||||
{
|
||||
bool error = false;
|
||||
foreach (TypeDefinition typeDef in typeDefs)
|
||||
{
|
||||
//Inherits, don't need to check.
|
||||
if (typeDef.InheritsNetworkBehaviour(base.Session))
|
||||
continue;
|
||||
|
||||
//Check each method for attribute.
|
||||
foreach (MethodDefinition md in typeDef.Methods)
|
||||
{
|
||||
//Has RPC attribute but doesn't inherit from NB.
|
||||
if (base.GetClass<RpcProcessor>().Attributes.HasRpcAttributes(md))
|
||||
{
|
||||
base.LogError($"{typeDef.FullName} has one or more RPC attributes but does not inherit from NetworkBehaviour.");
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
//Check fields for attribute.
|
||||
foreach (FieldDefinition fd in typeDef.Fields)
|
||||
{
|
||||
if (base.GetClass<NetworkBehaviourSyncProcessor>().GetSyncType(fd, false, out _) != SyncType.Unset)
|
||||
{
|
||||
base.LogError($"{typeDef.FullName} has one or more SyncType attributes but does not inherit from NetworkBehaviour.");
|
||||
error = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calls the next awake method if the nested awake was created by codegen.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void CallBaseAwakeOnCreatedMethods(List<AwakeMethodData> datas)
|
||||
{
|
||||
/* Method definitions are added from child most
|
||||
* so they will always be going up the hierarchy. */
|
||||
for (int i = 0; i < datas.Count; i++)
|
||||
{
|
||||
AwakeMethodData amd = datas[i];
|
||||
/* If the awake already existed
|
||||
* then let the user code be the final say
|
||||
* if base is called. */
|
||||
if (!amd.Created)
|
||||
continue;
|
||||
|
||||
TypeDefinition typeDef = amd.AwakeMethodDef.DeclaringType;
|
||||
|
||||
/* Awake will always exist because it was added previously.
|
||||
* Get awake for the current declaring type. */
|
||||
MethodDefinition awakeMd = typeDef.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
|
||||
MethodReference baseAwakeMr = typeDef.GetMethodReferenceInBase(base.Session, NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
if (baseAwakeMr == null)
|
||||
return;
|
||||
MethodDefinition baseAwakeMd = baseAwakeMr.CachedResolve(base.Session);
|
||||
//MethodDefinition baseAwakeMd = typeDef.GetMethodDefinitionInBase(base.Session, NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
if (baseAwakeMd == null)
|
||||
return;
|
||||
|
||||
//Check if they already call base.
|
||||
ILProcessor processor = awakeMd.Body.GetILProcessor();
|
||||
bool alreadyHasBaseCall = false;
|
||||
//Check if already calls baseAwake.
|
||||
foreach (var item in awakeMd.Body.Instructions)
|
||||
{
|
||||
//If a call or call virt. Although, callvirt should never occur.
|
||||
if (item.OpCode == OpCodes.Call || item.OpCode == OpCodes.Callvirt)
|
||||
{
|
||||
if (item.Operand != null && item.Operand.GetType().Name == nameof(MethodDefinition))
|
||||
{
|
||||
MethodDefinition md = (MethodDefinition)item.Operand;
|
||||
if (md == baseAwakeMd)
|
||||
{
|
||||
alreadyHasBaseCall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyHasBaseCall)
|
||||
{
|
||||
//Create instructions for base call.
|
||||
processor.Emit(OpCodes.Ldarg_0); //base.
|
||||
processor.Emit(OpCodes.Call, baseAwakeMr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Calls the next awake method if the nested awake was created by codegen.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void CallAwakeUserLogic(List<AwakeMethodData> datas)
|
||||
{
|
||||
/* Method definitions are added from child most
|
||||
* so they will always be going up the hierarchy. */
|
||||
for (int i = 0; i < datas.Count; i++)
|
||||
{
|
||||
AwakeMethodData amd = datas[i];
|
||||
//If was created then there is no user logic.
|
||||
if (amd.Created)
|
||||
continue;
|
||||
//If logic method is null. Should never be the case.
|
||||
if (amd.UserLogicMethodDef == null)
|
||||
continue;
|
||||
|
||||
MethodDefinition awakeMd = amd.AwakeMethodDef;
|
||||
base.GetClass<GeneralHelper>().CallCopiedMethod(awakeMd, amd.UserLogicMethodDef);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Adds a check to NetworkInitialize to see if it has already run.
|
||||
/// </summary>
|
||||
/// <param name="typeDef"></param>
|
||||
private void AddNetworkInitializeExecutedCheck(TypeDefinition firstTypeDef, bool initializeEarly)
|
||||
{
|
||||
TypeDefinition copyTypeDef = firstTypeDef;
|
||||
AddCheck(copyTypeDef, initializeEarly);
|
||||
|
||||
void AddCheck(TypeDefinition td, bool early)
|
||||
{
|
||||
string methodName;
|
||||
string fieldName;
|
||||
if (early)
|
||||
{
|
||||
methodName = NETWORKINITIALIZE_EARLY_INTERNAL_NAME;
|
||||
fieldName = $"{EARLY_INITIALIZED_NAME}{td.FullName}_{td.Module.Name}";
|
||||
}
|
||||
else
|
||||
{
|
||||
methodName = NETWORKINITIALIZE_LATE_INTERNAL_NAME;
|
||||
fieldName = $"{LATE_INITIALIZED_NAME}{td.FullName}_{td.Module.Name}";
|
||||
}
|
||||
|
||||
MethodDefinition md = td.GetMethod(methodName);
|
||||
if (md == null)
|
||||
return;
|
||||
|
||||
TypeReference boolTr = base.GetClass<GeneralHelper>().GetTypeReference(typeof(bool));
|
||||
FieldReference fr = copyTypeDef.GetOrCreateFieldReference(base.Session, fieldName, FieldAttributes.Private, boolTr, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
List<Instruction> insts = new List<Instruction>();
|
||||
ILProcessor processor = md.Body.GetILProcessor();
|
||||
//Add check if already called.
|
||||
//if (alreadyInitialized) return;
|
||||
Instruction skipFirstRetInst = processor.Create(OpCodes.Nop);
|
||||
insts.Add(processor.Create(OpCodes.Ldarg_0));
|
||||
insts.Add(processor.Create(OpCodes.Ldfld, fr));
|
||||
insts.Add(processor.Create(OpCodes.Brfalse_S, skipFirstRetInst));
|
||||
insts.Add(processor.Create(OpCodes.Ret));
|
||||
insts.Add(skipFirstRetInst);
|
||||
//Set field to true.
|
||||
insts.Add(processor.Create(OpCodes.Ldarg_0));
|
||||
insts.Add(processor.Create(OpCodes.Ldc_I4_1));
|
||||
insts.Add(processor.Create(OpCodes.Stfld, fr));
|
||||
processor.InsertFirst(insts);
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Gets the top-most parent away method.
|
||||
/// </summary>
|
||||
/// <param name="typeDef"></param>
|
||||
/// <returns></returns>
|
||||
private void PrepareNetworkInitializeMethods(TypeDefinition firstTypeDef)
|
||||
{
|
||||
TypeDefinition thisTypeDef = firstTypeDef;
|
||||
|
||||
string[] initializeMethodNames = new string[] { NETWORKINITIALIZE_EARLY_INTERNAL_NAME, NETWORKINITIALIZE_LATE_INTERNAL_NAME };
|
||||
|
||||
do
|
||||
{
|
||||
bool canCallBase = thisTypeDef.CanProcessBaseType(base.Session);
|
||||
|
||||
foreach (string mdName in initializeMethodNames)
|
||||
{
|
||||
/* There are no more base calls to make but we still
|
||||
* need to check if the initialize methods have already ran, so do that
|
||||
* here. */
|
||||
if (canCallBase)
|
||||
{
|
||||
/* Awake will always exist because it was added previously.
|
||||
* Get awake for copy and base of copy. */
|
||||
MethodDefinition thisMd = thisTypeDef.GetMethod(mdName);
|
||||
MethodReference baseMr = thisTypeDef.GetMethodReferenceInBase(base.Session, mdName);
|
||||
MethodDefinition baseMd = baseMr.CachedResolve(base.Session);
|
||||
ILProcessor processor = thisMd.Body.GetILProcessor();
|
||||
|
||||
bool alreadyHasBaseCall = false;
|
||||
//Check if already calls baseAwake.
|
||||
foreach (Instruction item in thisMd.Body.Instructions)
|
||||
{
|
||||
//If a call or call virt. Although, callvirt should never occur.
|
||||
if (item.OpCode == OpCodes.Call || item.OpCode == OpCodes.Callvirt)
|
||||
{
|
||||
if (item.Operand != null && item.Operand.GetType().Name == nameof(MethodDefinition))
|
||||
{
|
||||
MethodDefinition md = (MethodDefinition)item.Operand;
|
||||
if (md == baseMd)
|
||||
{
|
||||
alreadyHasBaseCall = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!alreadyHasBaseCall)
|
||||
{
|
||||
//Create instructions for base call.
|
||||
List<Instruction> instructions = new List<Instruction>();
|
||||
instructions.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
||||
instructions.Add(processor.Create(OpCodes.Call, baseMr));
|
||||
processor.InsertFirst(instructions);
|
||||
}
|
||||
}
|
||||
|
||||
AddNetworkInitializeExecutedCheck(thisTypeDef, (mdName == NETWORKINITIALIZE_EARLY_INTERNAL_NAME));
|
||||
}
|
||||
|
||||
thisTypeDef = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(thisTypeDef, base.Session);
|
||||
} while (thisTypeDef != null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds returns awake method definitions within awakeDatas.
|
||||
/// </summary>
|
||||
private void AddReturnsToAwake(List<AwakeMethodData> awakeDatas)
|
||||
{
|
||||
foreach (AwakeMethodData amd in awakeDatas)
|
||||
{
|
||||
ILProcessor processor = amd.AwakeMethodDef.Body.GetILProcessor();
|
||||
//If no instructions or the last instruction isnt ret.
|
||||
if (processor.Body.Instructions.Count == 0
|
||||
|| processor.Body.Instructions[processor.Body.Instructions.Count - 1].OpCode != OpCodes.Ret)
|
||||
{
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls NetworKInitializeLate method on the typeDef.
|
||||
/// </summary>
|
||||
/// <param name="copyTypeDef"></param>
|
||||
private void CallNetworkInitializeFromAwake(List<AwakeMethodData> awakeDatas, bool callEarly)
|
||||
{
|
||||
/* InitializeLate should be called after the user runs
|
||||
* all their Awake logic. This is so the user can configure
|
||||
* sync types on Awake and it won't trigger those values
|
||||
* as needing to be sent over the network, since both
|
||||
* server and client will be assigning them on Awake. */
|
||||
foreach (AwakeMethodData amd in awakeDatas)
|
||||
{
|
||||
string methodName = (callEarly) ? NETWORKINITIALIZE_EARLY_INTERNAL_NAME :
|
||||
NETWORKINITIALIZE_LATE_INTERNAL_NAME;
|
||||
|
||||
TypeDefinition td = amd.AwakeMethodDef.DeclaringType;
|
||||
MethodReference networkInitMr = td.GetMethodReference(base.Session, methodName);
|
||||
|
||||
ILProcessor processor = amd.AwakeMethodDef.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(networkInitMr.GetCallOpCode(base.Session), networkInitMr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an 'NetworkInitialize' method which is called by the childmost class to initialize scripts on Awake.
|
||||
/// </summary>
|
||||
private void CreateNetworkInitializeMethods(TypeDefinition typeDef, out MethodDefinition networkInitializeIfDisabledMd)
|
||||
{
|
||||
CreateMethod(NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
CreateMethod(NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
|
||||
MethodDefinition baseInitIfDisabled = base.GetClass<NetworkBehaviourHelper>().NetworkInitializeIfDisabled_MethodRef.CachedResolve(base.Session);
|
||||
networkInitializeIfDisabledMd = CreateMethod(baseInitIfDisabled.Name, baseInitIfDisabled);
|
||||
|
||||
MethodDefinition CreateMethod(string name, MethodDefinition copied = null)
|
||||
{
|
||||
MethodDefinition md;
|
||||
bool created;
|
||||
if (copied == null)
|
||||
md = typeDef.GetOrCreateMethodDefinition(base.Session, name, PUBLIC_VIRTUAL_ATTRIBUTES, typeDef.Module.TypeSystem.Void, out created);
|
||||
else
|
||||
md = typeDef.GetOrCreateMethodDefinition(base.Session, name, copied, true, out created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
//Emit ret into new method.
|
||||
ILProcessor processor = md.Body.GetILProcessor();
|
||||
//End of method return.
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
return md;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates an 'NetworkInitialize' method which is called by the childmost class to initialize scripts on Awake.
|
||||
/// </summary>
|
||||
private void CallNetworkInitializeMethods(MethodDefinition networkInitializeIfDisabledMd)
|
||||
{
|
||||
ILProcessor processor = networkInitializeIfDisabledMd.Body.GetILProcessor();
|
||||
|
||||
networkInitializeIfDisabledMd.Body.Instructions.Clear();
|
||||
CallMethod(NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
CallMethod(NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
|
||||
void CallMethod(string name)
|
||||
{
|
||||
MethodReference initIfDisabledMr = networkInitializeIfDisabledMd.DeclaringType.GetMethodReference(base.Session, name);
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(initIfDisabledMr.GetCallOpCode(base.Session), initIfDisabledMr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Creates Awake method for and all parents of typeDef using the parentMostAwakeMethodDef as a template.
|
||||
/// </summary>
|
||||
/// <returns>True if successful.</returns>
|
||||
private bool CreateOrModifyAwakeMethods(TypeDefinition typeDef, ref List<AwakeMethodData> datas)
|
||||
{
|
||||
//Now update all scopes/create methods.
|
||||
TypeDefinition copyTypeDef = typeDef;
|
||||
do
|
||||
{
|
||||
bool created;
|
||||
MethodDefinition awakeMd = copyTypeDef.GetOrCreateMethodDefinition(base.Session, NetworkBehaviourHelper.AWAKE_METHOD_NAME, PUBLIC_VIRTUAL_ATTRIBUTES, copyTypeDef.Module.TypeSystem.Void, out created);
|
||||
|
||||
//Awake is found. Check for invalid return type.
|
||||
if (!created)
|
||||
{
|
||||
if (awakeMd.ReturnType != copyTypeDef.Module.TypeSystem.Void)
|
||||
{
|
||||
base.LogError($"IEnumerator Awake methods are not supported within NetworkBehaviours.");
|
||||
return false;
|
||||
}
|
||||
awakeMd.Attributes = PUBLIC_VIRTUAL_ATTRIBUTES;
|
||||
}
|
||||
//Aways was made.
|
||||
else
|
||||
{
|
||||
ILProcessor processor = awakeMd.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
MethodDefinition logicMd = base.GetClass<GeneralHelper>().CopyIntoNewMethod(awakeMd, $"{NetworkBehaviourHelper.AWAKE_METHOD_NAME}___UserLogic", out _);
|
||||
//Clear original awake.
|
||||
awakeMd.Body.Instructions.Clear();
|
||||
datas.Add(new AwakeMethodData(awakeMd, logicMd, created));
|
||||
|
||||
copyTypeDef = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(copyTypeDef, base.Session);
|
||||
|
||||
} while (copyTypeDef != null);
|
||||
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes all Awake methods within typeDef and base classes public and virtual.
|
||||
/// </summary>
|
||||
/// <param name="typeDef"></param>
|
||||
internal void CreateFirstNetworkInitializeCall(TypeDefinition typeDef, MethodDefinition firstUserAwakeMethodDef, MethodDefinition firstNetworkInitializeMethodDef)
|
||||
{
|
||||
ILProcessor processor;
|
||||
//Get awake for current method.
|
||||
MethodDefinition thisAwakeMethodDef = typeDef.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
bool created = false;
|
||||
|
||||
//If no awake then make one.
|
||||
if (thisAwakeMethodDef == null)
|
||||
{
|
||||
created = true;
|
||||
|
||||
thisAwakeMethodDef = new MethodDefinition(NetworkBehaviourHelper.AWAKE_METHOD_NAME, PUBLIC_VIRTUAL_ATTRIBUTES,
|
||||
typeDef.Module.TypeSystem.Void);
|
||||
thisAwakeMethodDef.Body.InitLocals = true;
|
||||
typeDef.Methods.Add(thisAwakeMethodDef);
|
||||
|
||||
processor = thisAwakeMethodDef.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
//MethodRefs for networkinitialize and awake.
|
||||
MethodReference networkInitializeMethodRef = typeDef.Module.ImportReference(firstNetworkInitializeMethodDef);
|
||||
|
||||
processor = thisAwakeMethodDef.Body.GetILProcessor();
|
||||
//Create instructions for base call.
|
||||
List<Instruction> instructions = new List<Instruction>();
|
||||
instructions.Add(processor.Create(OpCodes.Ldarg_0)); //this.
|
||||
instructions.Add(processor.Create(OpCodes.Call, networkInitializeMethodRef));
|
||||
|
||||
/* If awake was created then make a call to the users
|
||||
* first awake. There's no reason to do this if awake
|
||||
* already existed because the user would have control
|
||||
* over making that call. */
|
||||
if (created && firstUserAwakeMethodDef != null)
|
||||
{
|
||||
MethodReference baseAwakeMethodRef = typeDef.Module.ImportReference(firstUserAwakeMethodDef);
|
||||
instructions.Add(processor.Create(OpCodes.Ldarg_0));//this.
|
||||
instructions.Add(processor.Create(OpCodes.Call, baseAwakeMethodRef));
|
||||
}
|
||||
|
||||
processor.InsertFirst(instructions);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23866e4d620216745a837fa99e801164
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec95af37f78b9e340b5eaa199c1af94a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
12
Assets/FishNet/CodeGenerating/Processing/Prediction.meta
Normal file
12
Assets/FishNet/CodeGenerating/Processing/Prediction.meta
Normal file
@ -0,0 +1,12 @@
|
||||
fileFormatVersion: 2
|
||||
<<<<<<<< HEAD:Assets/FishNet/CodeGenerating/Processing/Prediction.meta
|
||||
guid: 07f594347080e6547b13253aa03402e3
|
||||
folderAsset: yes
|
||||
========
|
||||
guid: 89b00a3741f72ac4e861a08cf1202228
|
||||
>>>>>>>> origin/3-pre-3.1:Assets/Test/InitializeOrder/InitializeOrder_Test.unity.meta
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,960 @@
|
||||
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
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0663606e86b0b34bae85df164747e72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,164 @@
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.CodeGenerating.Helping.Extension;
|
||||
using FishNet.CodeGenerating.Processing.Rpc;
|
||||
using FishNet.Configuring;
|
||||
using FishNet.Managing.Logging;
|
||||
using MonoFN.Cecil;
|
||||
using MonoFN.Cecil.Cil;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing
|
||||
{
|
||||
internal class QolAttributeProcessor : CodegenBase
|
||||
{
|
||||
|
||||
internal bool Process(TypeDefinition typeDef, bool moveStrippedCalls)
|
||||
{
|
||||
bool modified = false;
|
||||
List<MethodDefinition> methods = typeDef.Methods.ToList();
|
||||
|
||||
|
||||
|
||||
foreach (MethodDefinition md in methods)
|
||||
{
|
||||
//Has RPC attribute, doesn't quality for a quality of life attribute.
|
||||
if (base.GetClass<RpcProcessor>().Attributes.HasRpcAttributes(md))
|
||||
continue;
|
||||
|
||||
QolAttributeType qolType;
|
||||
CustomAttribute qolAttribute = GetQOLAttribute(md, out qolType);
|
||||
if (qolAttribute == null)
|
||||
continue;
|
||||
|
||||
/* This is a one time check to make sure the qolType is
|
||||
* a supported value. Multiple methods beyond this rely on the
|
||||
* value being supported. Rather than check in each method a
|
||||
* single check is performed here. */
|
||||
if (qolType != QolAttributeType.Server && qolType != QolAttributeType.Client)
|
||||
{
|
||||
base.LogError($"QolAttributeType of {qolType.ToString()} is unhandled.");
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateAttributeMethod(md, qolAttribute, qolType);
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the RPC attribute on a method, if one exist. Otherwise returns null.
|
||||
/// </summary>
|
||||
/// <param name="methodDef"></param>
|
||||
/// <param name="rpcType"></param>
|
||||
/// <returns></returns>
|
||||
private CustomAttribute GetQOLAttribute(MethodDefinition methodDef, out QolAttributeType qolType)
|
||||
{
|
||||
CustomAttribute foundAttribute = null;
|
||||
qolType = QolAttributeType.None;
|
||||
//Becomes true if an error occurred during this process.
|
||||
bool error = false;
|
||||
//Nothing to check.
|
||||
if (methodDef == null || methodDef.CustomAttributes == null)
|
||||
return null;
|
||||
|
||||
foreach (CustomAttribute customAttribute in methodDef.CustomAttributes)
|
||||
{
|
||||
QolAttributeType thisQolType = base.GetClass<AttributeHelper>().GetQolAttributeType(customAttribute.AttributeType.FullName);
|
||||
if (thisQolType != QolAttributeType.None)
|
||||
{
|
||||
//A qol attribute already exist.
|
||||
if (foundAttribute != null)
|
||||
{
|
||||
base.LogError($"{methodDef.Name} {thisQolType.ToString()} method cannot have multiple quality of life attributes.");
|
||||
error = true;
|
||||
}
|
||||
////Static method.
|
||||
//if (methodDef.IsStatic)
|
||||
//{
|
||||
// CodegenSession.AddError($"{methodDef.Name} {thisQolType.ToString()} method cannot be static.");
|
||||
// error = true;
|
||||
//}
|
||||
//Abstract method.
|
||||
if (methodDef.IsAbstract)
|
||||
{
|
||||
base.LogError($"{methodDef.Name} {thisQolType.ToString()} method cannot be abstract.");
|
||||
error = true;
|
||||
}
|
||||
|
||||
//If all checks passed.
|
||||
if (!error)
|
||||
{
|
||||
foundAttribute = customAttribute;
|
||||
qolType = thisQolType;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//If an error occurred then reset results.
|
||||
if (error)
|
||||
{
|
||||
foundAttribute = null;
|
||||
qolType = QolAttributeType.None;
|
||||
}
|
||||
|
||||
return foundAttribute;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the specified method to use QolType.
|
||||
/// </summary>
|
||||
private void CreateAttributeMethod(MethodDefinition methodDef, CustomAttribute qolAttribute, QolAttributeType qolType)
|
||||
{
|
||||
bool inheritsNetworkBehaviour = methodDef.DeclaringType.InheritsNetworkBehaviour(base.Session);
|
||||
|
||||
//True to use InstanceFInder.
|
||||
bool useStatic = (methodDef.IsStatic || !inheritsNetworkBehaviour);
|
||||
|
||||
if (qolType == QolAttributeType.Client)
|
||||
{
|
||||
if (!StripMethod(methodDef))
|
||||
{
|
||||
LoggingType logging = qolAttribute.GetField("Logging", LoggingType.Warning);
|
||||
/* Since isClient also uses insert first
|
||||
* it will be put ahead of the IsOwner check, since the
|
||||
* codegen processes it after IsOwner. EG...
|
||||
* IsOwner will be added first, then IsClient will be added first over IsOwner. */
|
||||
bool requireOwnership = qolAttribute.GetField("RequireOwnership", false);
|
||||
if (requireOwnership && useStatic)
|
||||
{
|
||||
base.LogError($"Method {methodDef.Name} has a [Client] attribute which requires ownership but the method may not use this attribute. Either the method is static, or the script does not inherit from NetworkBehaviour.");
|
||||
return;
|
||||
}
|
||||
//If (!base.IsOwner);
|
||||
if (requireOwnership)
|
||||
base.GetClass<NetworkBehaviourHelper>().CreateLocalClientIsOwnerCheck(methodDef, logging, true, false, true);
|
||||
//Otherwise normal IsClient check.
|
||||
else
|
||||
base.GetClass<NetworkBehaviourHelper>().CreateIsClientCheck(methodDef, logging, useStatic, true);
|
||||
}
|
||||
}
|
||||
else if (qolType == QolAttributeType.Server)
|
||||
{
|
||||
if (!StripMethod(methodDef))
|
||||
{
|
||||
LoggingType logging = qolAttribute.GetField("Logging", LoggingType.Warning);
|
||||
base.GetClass<NetworkBehaviourHelper>().CreateIsServerCheck(methodDef, logging, useStatic, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool StripMethod(MethodDefinition md)
|
||||
{
|
||||
|
||||
|
||||
//Fall through.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5080d7597ffca904b9a9fd5926e4e5a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1114
Assets/FishNet/CodeGenerating/Processing/ReaderProcessor.cs
Normal file
1114
Assets/FishNet/CodeGenerating/Processing/ReaderProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f9d3654f5816c4409b88fa0602c6df4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/CodeGenerating/Processing/Rpc.meta
Normal file
8
Assets/FishNet/CodeGenerating/Processing/Rpc.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: de5f90c539e844445be428ff2a2fdf29
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,68 @@
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.Object.Helping;
|
||||
using MonoFN.Cecil;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing.Rpc
|
||||
{
|
||||
internal static class AttributeDataExtensions
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns RpcTypes in datas.
|
||||
/// </summary>
|
||||
public static List<RpcType> GetRpcTypes(this List<AttributeData> datas)
|
||||
{
|
||||
//RpcTypes for originalMd.
|
||||
List<RpcType> rpcTypes = new List<RpcType>();
|
||||
foreach (AttributeData ad in datas)
|
||||
rpcTypes.Add(ad.RpcType);
|
||||
|
||||
return rpcTypes;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets CustomAttribute for rpcType
|
||||
/// </summary>
|
||||
public static CustomAttribute GetAttribute(this List<AttributeData> datas, CodegenSession session, RpcType rpcType)
|
||||
{
|
||||
for (int i = 0; i < datas.Count; i++)
|
||||
{
|
||||
if (datas[i].RpcType == rpcType)
|
||||
return datas[i].Attribute;
|
||||
}
|
||||
|
||||
session.LogError($"RpcType {rpcType} not found in datas.");
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Returns RpcType as flag through combining datas.
|
||||
/// </summary>
|
||||
/// <param name="datas"></param>
|
||||
/// <returns></returns>
|
||||
public static RpcType GetCombinedRpcType(this List<AttributeData> datas)
|
||||
{
|
||||
RpcType result = RpcType.None;
|
||||
for (int i = 0; i < datas.Count; i++)
|
||||
result |= datas[i].RpcType;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
internal class AttributeData
|
||||
{
|
||||
public readonly CustomAttribute Attribute;
|
||||
public readonly RpcType RpcType;
|
||||
|
||||
public AttributeData(CustomAttribute attribute, RpcType rpcType)
|
||||
{
|
||||
Attribute = attribute;
|
||||
RpcType = rpcType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91f84e00db3d1ad448fb6a760afb6927
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
166
Assets/FishNet/CodeGenerating/Processing/Rpc/Attributes.cs
Normal file
166
Assets/FishNet/CodeGenerating/Processing/Rpc/Attributes.cs
Normal file
@ -0,0 +1,166 @@
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.CodeGenerating.Helping.Extension;
|
||||
using FishNet.Connection;
|
||||
using FishNet.Object.Helping;
|
||||
using MonoFN.Cecil;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing.Rpc
|
||||
{
|
||||
internal class Attributes : CodegenBase
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Returns if methodDef has any Rpc attribute.
|
||||
/// </summary>
|
||||
public bool HasRpcAttributes(MethodDefinition methodDef)
|
||||
{
|
||||
foreach (CustomAttribute customAttribute in methodDef.CustomAttributes)
|
||||
{
|
||||
RpcType rt = base.Session.GetClass<AttributeHelper>().GetRpcAttributeType(customAttribute);
|
||||
if (rt != RpcType.None)
|
||||
return true;
|
||||
}
|
||||
|
||||
//Fall through, nothing found.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a collection of RpcAttribute for methodDef.
|
||||
/// </summary>
|
||||
public List<AttributeData> GetRpcAttributes(MethodDefinition methodDef)
|
||||
{
|
||||
List<AttributeData> results = new List<AttributeData>();
|
||||
string asyncAttributeFullName = typeof(AsyncStateMachineAttribute).FullName;
|
||||
bool isAsync = false;
|
||||
|
||||
foreach (CustomAttribute customAttribute in methodDef.CustomAttributes)
|
||||
{
|
||||
RpcType rt = base.Session.GetClass<AttributeHelper>().GetRpcAttributeType(customAttribute);
|
||||
if (rt != RpcType.None)
|
||||
{
|
||||
results.Add(new AttributeData(customAttribute, rt));
|
||||
}
|
||||
//Not a rpc attribute.
|
||||
else
|
||||
{
|
||||
//Check if async.
|
||||
if (customAttribute.Is(asyncAttributeFullName))
|
||||
isAsync = true;
|
||||
}
|
||||
}
|
||||
|
||||
//Nothing found, exit early.
|
||||
if (results.Count == 0)
|
||||
{
|
||||
return results;
|
||||
}
|
||||
//If has at least one RPC attrivbute and is an async method.
|
||||
else if (isAsync)
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} is an async RPC. This feature is not currently supported. You may instead run an async method from this RPC.");
|
||||
return new List<AttributeData>();
|
||||
}
|
||||
//If more than one attribute make sure the combination is allowed.
|
||||
else if (results.Count >= 2)
|
||||
{
|
||||
RpcType allRpcTypes = results.GetCombinedRpcType();
|
||||
if (allRpcTypes != (RpcType.Observers | RpcType.Target))
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} contains multiple RPC attributes. Only ObserversRpc and TargetRpc attributes may be combined.");
|
||||
return new List<AttributeData>();
|
||||
}
|
||||
}
|
||||
|
||||
//Next validate that the method is setup properly for each rpcType.
|
||||
foreach (AttributeData ad in results)
|
||||
{
|
||||
//If not valid then return empty list.
|
||||
if (!IsRpcMethodValid(methodDef, ad.RpcType))
|
||||
return new List<AttributeData>();
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if a RpcMethod can be serialized and has a proper signature.
|
||||
/// </summary>
|
||||
private bool IsRpcMethodValid(MethodDefinition methodDef, RpcType rpcType)
|
||||
{
|
||||
//Static method.
|
||||
if (methodDef.IsStatic)
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} RPC method cannot be static.");
|
||||
return false;
|
||||
}
|
||||
//Is generic type.
|
||||
else if (methodDef.HasGenericParameters)
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} RPC method cannot contain generic parameters.");
|
||||
return false;
|
||||
}
|
||||
//Abstract method.
|
||||
else if (methodDef.IsAbstract)
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} RPC method cannot be abstract.");
|
||||
return false;
|
||||
}
|
||||
//Non void return.
|
||||
else if (methodDef.ReturnType != methodDef.Module.TypeSystem.Void)
|
||||
{
|
||||
base.Session.LogError($"{methodDef.Name} RPC method must return void.");
|
||||
return false;
|
||||
}
|
||||
//Misc failing conditions.
|
||||
else
|
||||
{
|
||||
//Check for async attribute.
|
||||
foreach (CustomAttribute ca in methodDef.CustomAttributes)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
//TargetRpc but missing correct parameters.
|
||||
if (rpcType == RpcType.Target)
|
||||
{
|
||||
if (methodDef.Parameters.Count == 0 || !methodDef.Parameters[0].Is(typeof(NetworkConnection)))
|
||||
{
|
||||
base.Session.LogError($"Target RPC {methodDef.Name} must have a NetworkConnection as the first parameter.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//Make sure all parameters can be serialized.
|
||||
for (int i = 0; i < methodDef.Parameters.Count; i++)
|
||||
{
|
||||
ParameterDefinition parameterDef = methodDef.Parameters[i];
|
||||
|
||||
//If NetworkConnection, TargetRpc, and first parameter.
|
||||
if ((i == 0) && (rpcType == RpcType.Target) && parameterDef.Is(typeof(NetworkConnection)))
|
||||
continue;
|
||||
|
||||
if (parameterDef.ParameterType.IsGenericParameter)
|
||||
{
|
||||
base.Session.LogError($"RPC method{methodDef.Name} contains a generic parameter. This is currently not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Can be serialized/deserialized.
|
||||
bool canSerialize = base.GetClass<GeneralHelper>().HasSerializerAndDeserializer(parameterDef.ParameterType, true);
|
||||
if (!canSerialize)
|
||||
{
|
||||
base.Session.LogError($"RPC method {methodDef.Name} parameter type {parameterDef.ParameterType.FullName} does not support serialization. Use a supported type or create a custom serializer.");
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//Fall through, success.
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 974ebf09757267941a86f92e5072258c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
58
Assets/FishNet/CodeGenerating/Processing/Rpc/CreatedRpc.cs
Normal file
58
Assets/FishNet/CodeGenerating/Processing/Rpc/CreatedRpc.cs
Normal file
@ -0,0 +1,58 @@
|
||||
using FishNet.Object.Helping;
|
||||
using MonoFN.Cecil;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing.Rpc
|
||||
{
|
||||
|
||||
internal class CreatedRpc
|
||||
{
|
||||
public MethodDefinition OriginalMethodDef;
|
||||
public uint MethodHash;
|
||||
public AttributeData AttributeData;
|
||||
public MethodDefinition WriterMethodDef;
|
||||
public MethodDefinition ReaderMethodDef;
|
||||
public MethodDefinition LogicMethodDef;
|
||||
public MethodDefinition RedirectMethodDef;
|
||||
public bool RunLocally;
|
||||
|
||||
public RpcType RpcType => AttributeData.RpcType;
|
||||
public CustomAttribute Attribute => AttributeData.Attribute;
|
||||
public TypeDefinition TypeDef => OriginalMethodDef.DeclaringType;
|
||||
public ModuleDefinition Module => OriginalMethodDef.Module;
|
||||
}
|
||||
|
||||
|
||||
internal static class CreatedRpcExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Returns CreatedRpc for rpcType.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static CreatedRpc GetCreatedRpc(this List<CreatedRpc> lst, RpcType rpcType)
|
||||
{
|
||||
for (int i = 0; i < lst.Count; i++)
|
||||
{
|
||||
if (lst[i].RpcType == rpcType)
|
||||
return lst[i];
|
||||
}
|
||||
//Fall through.
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns combined RpcType for all entries.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public static RpcType GetCombinedRpcType(this List<CreatedRpc> lst)
|
||||
{
|
||||
RpcType result = RpcType.None;
|
||||
for (int i = 0; i < lst.Count; i++)
|
||||
result |= lst[i].RpcType;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2176b6bfcc49934d8f36fba3df74d0c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1084
Assets/FishNet/CodeGenerating/Processing/Rpc/RpcProcessor.cs
Normal file
1084
Assets/FishNet/CodeGenerating/Processing/Rpc/RpcProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d4adb5891ee44f4397cd07ac2df0ce0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
8
Assets/FishNet/CodeGenerating/Processing/Typed.meta
Normal file
8
Assets/FishNet/CodeGenerating/Processing/Typed.meta
Normal file
@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1c24efb514a41fb41b4eb883a5f51fb5
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
@ -0,0 +1,24 @@
|
||||
using MonoFN.Cecil;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing
|
||||
{
|
||||
|
||||
public class ProcessedSync
|
||||
{
|
||||
public FieldReference OriginalFieldRef;
|
||||
public FieldReference GeneratedFieldRef;
|
||||
public MethodReference SetMethodRef;
|
||||
public MethodReference GetMethodRef;
|
||||
|
||||
public ProcessedSync(FieldReference originalFieldRef,FieldReference generatedFieldRef, MethodReference setMethodRef, MethodReference getMethodRef)
|
||||
{
|
||||
OriginalFieldRef = originalFieldRef;
|
||||
GeneratedFieldRef = generatedFieldRef;
|
||||
SetMethodRef = setMethodRef;
|
||||
GetMethodRef = getMethodRef;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c0dc2fea60bfe1341b04e7165251d36f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
1158
Assets/FishNet/CodeGenerating/Processing/WriterProcessor.cs
Normal file
1158
Assets/FishNet/CodeGenerating/Processing/WriterProcessor.cs
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a4021bd44dc40f47abb494e0a4326f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user