[Add] FishNet
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
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>();
|
||||
|
||||
public string MethodDefinitionTraceText(MethodDefinition methodDef) => Session.MethodDefinitionTraceText(methodDef);
|
||||
public string TypeDefinitionTraceText(TypeDefinition typeDef) => Session.TypeDefinitionTraceText(typeDef);
|
||||
public string TypeReferenceTraceText(TypeReference typeRef) => Session.TypeReferenceTraceText(typeRef);
|
||||
public string FieldDefinitionTraceText(FieldDefinition fieldDef) => Session.FieldDefinitionTraceText(fieldDef);
|
||||
public string PropertyDefinitionTraceText(PropertyDefinition propertyDef) => Session.PropertyDefinitionTraceText(propertyDef);
|
||||
|
||||
#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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8462034e5255191499a018bd8fbbf751
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/CodegenBase.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,322 @@
|
||||
using FishNet.CodeGenerating.Extension;
|
||||
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 (GetClass<GeneralHelper>().HasNotSerializableAttribute(methodDef))
|
||||
continue;
|
||||
|
||||
MethodReference methodRef = ImportReference(methodDef);
|
||||
if (extensionType == ExtensionType.Write)
|
||||
{
|
||||
GetClass<WriterProcessor>().AddWriterMethod(methodRef.Parameters[1].ParameterType, methodRef, false, !replace);
|
||||
modified = true;
|
||||
}
|
||||
else if (extensionType == ExtensionType.Read)
|
||||
{
|
||||
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();
|
||||
/* 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 (GetClass<GeneralHelper>().HasNotSerializableAttribute(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 = 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.HasNotSerializableAttribute(methodDef))
|
||||
continue;
|
||||
if (!methodDef.HasCustomAttribute<CustomComparerAttribute>())
|
||||
continue;
|
||||
// Validate return type.
|
||||
if (methodDef.ReturnType.FullName != gh.GetTypeReference(typeof(bool)).FullName)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
LogError($"Both parameters must be the same type in comparer method {methodDef.Name} in type {typeDef.FullName}.");
|
||||
continue;
|
||||
}
|
||||
|
||||
ImportReference(methodDef);
|
||||
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)
|
||||
{
|
||||
string traceText = MethodDefinitionTraceText(methodDef);
|
||||
|
||||
ReaderProcessor rp = GetClass<ReaderProcessor>();
|
||||
WriterProcessor wp = GetClass<WriterProcessor>();
|
||||
////If parameterType has user declared do nothing.
|
||||
// if (wp.IsGlobalSerializer(parameterType))
|
||||
// return;
|
||||
|
||||
if (!parameterType.IsGenericParameter && parameterType.CanBeResolved(Session))
|
||||
{
|
||||
TypeDefinition typeDefinition = parameterType.CachedResolve(Session);
|
||||
// If class and not value type check for accessible constructor.
|
||||
if (typeDefinition.IsClass && !typeDefinition.IsValueType)
|
||||
{
|
||||
MethodDefinition constructor = typeDefinition.GetDefaultConstructor(Session);
|
||||
// Constructor is inaccessible, cannot create serializer for type.
|
||||
if (constructor != null && !constructor.IsPublic)
|
||||
{
|
||||
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 ? wp.GetWriteMethodReference(parameterType) : rp.GetReadMethodReference(parameterType);
|
||||
|
||||
// If a created method already exist nothing further is required.
|
||||
if (createdMethodRef != null)
|
||||
{
|
||||
// Replace call to generic with already made serializer.
|
||||
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
|
||||
methodDef.Body.Instructions[instructionIndex] = newInstruction;
|
||||
return;
|
||||
}
|
||||
|
||||
createdMethodRef = extensionType == ExtensionType.Write ? wp.CreateWriter(parameterType, traceText) : rp.CreateReader(parameterType, traceText);
|
||||
|
||||
// If method was created.
|
||||
if (createdMethodRef != null)
|
||||
{
|
||||
// Set new instruction.
|
||||
Instruction newInstruction = processor.Create(OpCodes.Call, createdMethodRef);
|
||||
methodDef.Body.Instructions[instructionIndex] = newInstruction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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.CUSTOM_WRITER_PREFIX : ReaderProcessor.CUSTOM_READER_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 != GetClass<WriterImports>().Writer_TypeRef.FullName && tr.FullName != GetClass<ReaderImports>().Reader_TypeRef.FullName)
|
||||
return ExtensionType.None;
|
||||
}
|
||||
|
||||
if (write && methodDef.Parameters.Count < 2)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9269fd8a62199e24c965b4c99b641244
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/CustomSerializerProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,498 @@
|
||||
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 Private.
|
||||
/// <summary>
|
||||
/// Classes which have been processed for all NetworkBehaviour features.
|
||||
/// </summary>
|
||||
private HashSet<TypeDefinition> _processedClasses = new();
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
internal const string NETWORKINITIALIZE_EARLY_INTERNAL_NAME = "NetworkInitialize___Early";
|
||||
internal const string NETWORKINITIALIZE_LATE_INTERNAL_NAME = "NetworkInitialize___Late";
|
||||
private readonly string[] _networkInitializeMethodNames = new string[] { NETWORKINITIALIZE_EARLY_INTERNAL_NAME, NETWORKINITIALIZE_LATE_INTERNAL_NAME };
|
||||
#endregion
|
||||
|
||||
internal bool ProcessLocal(TypeDefinition typeDef)
|
||||
{
|
||||
bool modified = false;
|
||||
TypeDefinition copyTypeDef = typeDef;
|
||||
|
||||
// TypeDefs which are using prediction.
|
||||
List<TypeDefinition> _usesPredictionTypeDefs = new();
|
||||
|
||||
// Make collection of NBs to processor.
|
||||
List<TypeDefinition> typeDefs = new();
|
||||
do
|
||||
{
|
||||
if (!HasClassBeenProcessed(copyTypeDef))
|
||||
{
|
||||
// Disallow nested network behaviours.
|
||||
ICollection<TypeDefinition> nestedTds = copyTypeDef.NestedTypes;
|
||||
foreach (TypeDefinition item in nestedTds)
|
||||
{
|
||||
if (item.InheritsNetworkBehaviour(Session))
|
||||
{
|
||||
LogError($"{copyTypeDef.FullName} contains nested NetworkBehaviours. These are not supported.");
|
||||
return modified;
|
||||
}
|
||||
}
|
||||
|
||||
typeDefs.Add(copyTypeDef);
|
||||
}
|
||||
copyTypeDef = TypeDefinitionExtensionsOld.GetNextBaseClassToProcess(copyTypeDef, Session);
|
||||
} while (copyTypeDef != null);
|
||||
|
||||
/* Reverse type definitions so that the parent
|
||||
* is first. This counts indexes up as we go further
|
||||
* down the children. By doing so we do not have to
|
||||
* rebuild rpc or synctype indexes when a parent is inherited
|
||||
* multiple times. EG: with this solution if parent had 1 sync type
|
||||
* and childA had 2 the parent would be index 0, and childA would have 1 and 2.
|
||||
* But also, if childB inherited childA it would have 3+.
|
||||
*
|
||||
* Going in reverse also gaurantees the awake method will already be created
|
||||
* or modified in any class a child inherits. This lets us call it appropriately
|
||||
* as well error if the awake does not exist, such as could not be created. */
|
||||
typeDefs.Reverse();
|
||||
|
||||
foreach (TypeDefinition td in typeDefs)
|
||||
{
|
||||
/* Create NetworkInitialize before-hand so the other procesors
|
||||
* can use it. */
|
||||
MethodDefinition networkInitializeIfDisabledMd;
|
||||
CreateNetworkInitializeMethods(td, out networkInitializeIfDisabledMd);
|
||||
CallNetworkInitialize(networkInitializeIfDisabledMd);
|
||||
|
||||
/* 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. */
|
||||
if (GetClass<PredictionProcessor>().Process(td))
|
||||
{
|
||||
_usesPredictionTypeDefs.Add(td);
|
||||
modified = true;
|
||||
}
|
||||
// 25ms
|
||||
|
||||
/* RPCs. */
|
||||
modified |= GetClass<RpcProcessor>().ProcessLocal(td);
|
||||
// 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. */
|
||||
modified |= GetClass<SyncTypeProcessor>().ProcessLocal(td);
|
||||
|
||||
// Call base networkinitialize early/late.
|
||||
CallBaseOnNetworkInitializeMethods(td);
|
||||
// Add networkinitialize executed check to early/late.
|
||||
AddNetworkInitializeExecutedChecks(td);
|
||||
|
||||
// Copy user logic from awake into a new method.
|
||||
CopyAwakeUserLogic(td);
|
||||
/* Create awake method or if already exist make
|
||||
* it public virtual. */
|
||||
if (!ModifyAwakeMethod(td, out bool awakeCreated))
|
||||
{
|
||||
// This is a hard fail and will break the solution so throw here.
|
||||
LogError($"{td.FullName} has an Awake method which could not be modified, or could not be found. This often occurs when a child class is in an assembly different from the parent, and the parent does not implement Awake. To resolve this make an Awake in {td.Name} public virtual.");
|
||||
return modified;
|
||||
}
|
||||
|
||||
// Calls NetworkInitializeEarly from awake.
|
||||
CallMethodFromAwake(td, NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
// Only call base if awake was created. Otherwise let the users implementation handle base calling.
|
||||
if (awakeCreated)
|
||||
CallBaseAwake(td);
|
||||
// Call logic user may have put in awake.
|
||||
CallAwakeUserLogic(td);
|
||||
// NetworkInitializeLate from awake.
|
||||
CallMethodFromAwake(td, NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
// Since awake methods are erased ret has to be added at the end.
|
||||
AddReturnToAwake(td);
|
||||
|
||||
// 70ms
|
||||
_processedClasses.Add(td);
|
||||
}
|
||||
|
||||
/* If here then all inerited classes for firstTypeDef have
|
||||
* been processed. */
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the name to use for user awake logic method.
|
||||
/// </summary>
|
||||
internal string GetAwakeUserLogicMethodDefinition(TypeDefinition td) => $"Awake_UserLogic_{td.FullName}_{Module.Name}";
|
||||
|
||||
/// <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)
|
||||
{
|
||||
SyncTypeProcessor stProcessor = GetClass<SyncTypeProcessor>();
|
||||
RpcProcessor rpcProcessor = GetClass<RpcProcessor>();
|
||||
|
||||
foreach (TypeDefinition typeDef in typeDefs)
|
||||
{
|
||||
// Inherits, don't need to check.
|
||||
if (typeDef.InheritsNetworkBehaviour(Session))
|
||||
continue;
|
||||
|
||||
// Check each method for attribute.
|
||||
foreach (MethodDefinition md in typeDef.Methods)
|
||||
{
|
||||
// Has RPC attribute but doesn't inherit from NB.
|
||||
if (rpcProcessor.Attributes.HasRpcAttributes(md))
|
||||
{
|
||||
LogError($"{typeDef.FullName} has one or more RPC attributes but does not inherit from NetworkBehaviour.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Check fields for attribute.
|
||||
foreach (FieldDefinition fd in typeDef.Fields)
|
||||
{
|
||||
if (stProcessor.IsSyncType(fd))
|
||||
{
|
||||
LogError($"{typeDef.FullName} implements one or more SyncTypes but does not inherit from NetworkBehaviour.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallthrough / pass.
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Calls the next awake method if the nested awake was created by codegen.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private void CallBaseAwake(TypeDefinition td)
|
||||
{
|
||||
/* If base is not a class which can be processed then there
|
||||
* is no need to continue. */
|
||||
if (!td.CanProcessBaseType(Session))
|
||||
return;
|
||||
|
||||
// Base Awake.
|
||||
MethodReference baseAwakeMr = td.GetMethodReferenceInBase(Session, NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
// This Awake.
|
||||
MethodDefinition tdAwakeMd = td.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
|
||||
ILProcessor processor = tdAwakeMd.Body.GetILProcessor();
|
||||
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(TypeDefinition td)
|
||||
{
|
||||
// UserLogic.
|
||||
MethodDefinition userLogicMd = td.GetMethod(GetAwakeUserLogicMethodDefinition(td));
|
||||
/* Userlogic may be null if Awake was created.
|
||||
* If so, there's no need to proceed. */
|
||||
if (userLogicMd == null)
|
||||
return;
|
||||
|
||||
// This Awake.
|
||||
MethodDefinition awakeMd = td.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
// Call logic.
|
||||
GetClass<GeneralHelper>().CallCopiedMethod(awakeMd, userLogicMd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a check to NetworkInitialize to see if it has already run.
|
||||
/// </summary>
|
||||
/// <param name = "typeDef"></param>
|
||||
private void AddNetworkInitializeExecutedChecks(TypeDefinition typeDef)
|
||||
{
|
||||
AddCheck(NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
AddCheck(NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
|
||||
void AddCheck(string methodName)
|
||||
{
|
||||
string fieldName = $"{methodName}{typeDef.FullName}{typeDef.Module.Name}_Excuted";
|
||||
MethodDefinition md = typeDef.GetMethod(methodName);
|
||||
if (md == null)
|
||||
return;
|
||||
|
||||
TypeReference boolTr = GetClass<GeneralHelper>().GetTypeReference(typeof(bool));
|
||||
FieldReference fr = typeDef.GetOrCreateFieldReference(Session, fieldName, FieldAttributes.Private, boolTr, out bool created);
|
||||
|
||||
if (created)
|
||||
{
|
||||
List<Instruction> insts = new();
|
||||
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>
|
||||
/// Calls base for NetworkInitializeEarly/Late on a TypeDefinition.
|
||||
/// </summary>
|
||||
private void CallBaseOnNetworkInitializeMethods(TypeDefinition typeDef)
|
||||
{
|
||||
// If base is null cannot call base.
|
||||
if (typeDef.BaseType == null)
|
||||
return;
|
||||
|
||||
foreach (string mdName in _networkInitializeMethodNames)
|
||||
{
|
||||
MethodReference baseMr = typeDef.GetMethodReferenceInBase(Session, mdName);
|
||||
// Not found in base.
|
||||
if (baseMr == null)
|
||||
continue;
|
||||
|
||||
MethodDefinition baseMd = baseMr.CachedResolve(Session);
|
||||
|
||||
MethodDefinition thisMd = typeDef.GetMethod(mdName);
|
||||
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()
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0), // this.
|
||||
processor.Create(OpCodes.Call, baseMr)
|
||||
};
|
||||
processor.InsertFirst(instructions);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds returns awake method definitions within awakeDatas.
|
||||
/// </summary>
|
||||
private void AddReturnToAwake(TypeDefinition td)
|
||||
{
|
||||
// This Awake.
|
||||
MethodDefinition awakeMd = td.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
ILProcessor processor = awakeMd.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 a method by name from awake.
|
||||
/// </summary>
|
||||
private void CallMethodFromAwake(TypeDefinition typeDef, string methodName)
|
||||
{
|
||||
// Will never be null because we added it previously.
|
||||
MethodDefinition awakeMethodDef = typeDef.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
MethodReference networkInitMr = typeDef.GetMethodReference(Session, methodName);
|
||||
|
||||
ILProcessor processor = awakeMethodDef.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(networkInitMr.GetCallOpCode(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);
|
||||
networkInitializeIfDisabledMd = CreateMethod(nameof(NetworkBehaviour.NetworkInitializeIfDisabled));
|
||||
|
||||
MethodDefinition CreateMethod(string name, MethodDefinition copied = null)
|
||||
{
|
||||
bool created;
|
||||
MethodDefinition md = typeDef.GetOrCreateMethodDefinition(Session, name, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES, typeDef.Module.TypeSystem.Void, 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 CallNetworkInitialize(MethodDefinition networkIntializeMd)
|
||||
{
|
||||
ILProcessor processor = networkIntializeMd.Body.GetILProcessor();
|
||||
|
||||
networkIntializeMd.Body.Instructions.Clear();
|
||||
CallMethod(NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
CallMethod(NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
processor.Emit(OpCodes.Ret);
|
||||
|
||||
void CallMethod(string name)
|
||||
{
|
||||
MethodReference initIfDisabledMr = networkIntializeMd.DeclaringType.GetMethodReference(Session, name);
|
||||
processor.Emit(OpCodes.Ldarg_0);
|
||||
processor.Emit(initIfDisabledMr.GetCallOpCode(Session), initIfDisabledMr);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies logic from users Awake if present, to a new method.
|
||||
/// </summary>
|
||||
private void CopyAwakeUserLogic(TypeDefinition typeDef)
|
||||
{
|
||||
MethodDefinition awakeMd = typeDef.GetMethod(NetworkBehaviourHelper.AWAKE_METHOD_NAME);
|
||||
// If found copy.
|
||||
if (awakeMd != null)
|
||||
GetClass<GeneralHelper>().CopyIntoNewMethod(awakeMd, GetAwakeUserLogicMethodDefinition(typeDef), out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Erases content in awake if it already exist, otherwise makes a new Awake.
|
||||
/// Makes Awake public and virtual.
|
||||
/// </summary>
|
||||
/// <returns>True if successful.</returns>
|
||||
private bool ModifyAwakeMethod(TypeDefinition typeDef, out bool created)
|
||||
{
|
||||
MethodDefinition awakeMd = typeDef.GetOrCreateMethodDefinition(Session, NetworkBehaviourHelper.AWAKE_METHOD_NAME, MethodDefinitionExtensions.PUBLIC_VIRTUAL_ATTRIBUTES, typeDef.Module.TypeSystem.Void, out created);
|
||||
|
||||
// Awake is found. Check for invalid return type.
|
||||
if (!created)
|
||||
{
|
||||
if (awakeMd.ReturnType != typeDef.Module.TypeSystem.Void)
|
||||
{
|
||||
LogError($"IEnumerator Awake methods are not supported within NetworkBehaviours.");
|
||||
return false;
|
||||
}
|
||||
// Make public if good.
|
||||
awakeMd.SetPublicAttributes();
|
||||
}
|
||||
// Already was made.
|
||||
else
|
||||
{
|
||||
ILProcessor processor = awakeMd.Body.GetILProcessor();
|
||||
processor.Emit(OpCodes.Ret);
|
||||
}
|
||||
|
||||
// Clear original awake.
|
||||
awakeMd.Body.Instructions.Clear();
|
||||
|
||||
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(NetworkBehaviourHelper.AWAKE_METHOD_NAME, MethodDefinitionExtensions.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()
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0), // this.
|
||||
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,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 23866e4d620216745a837fa99e801164
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/NetworkBehaviourProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98f6937bb72a7254b92b4656f28b7333
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d0663606e86b0b34bae85df164747e72
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/Prediction/PredictionProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,161 @@
|
||||
using FishNet.CodeGenerating.Extension;
|
||||
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 (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)
|
||||
{
|
||||
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 = GetClass<AttributeHelper>().GetQolAttributeType(customAttribute.AttributeType.FullName);
|
||||
if (thisQolType != QolAttributeType.None)
|
||||
{
|
||||
// A qol attribute already exist.
|
||||
if (foundAttribute != null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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(Session);
|
||||
|
||||
// True to use InstanceFInder.
|
||||
bool useStatic = methodDef.IsStatic || !inheritsNetworkBehaviour;
|
||||
// Check to force using static.
|
||||
if (!useStatic && qolAttribute.GetField("UseIsStarted", false))
|
||||
useStatic = true;
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
GetClass<NetworkBehaviourHelper>().CreateLocalClientIsOwnerCheck(methodDef, logging, true, false, true);
|
||||
// Otherwise normal IsClient check.
|
||||
else
|
||||
GetClass<NetworkBehaviourHelper>().CreateIsClientCheck(methodDef, logging, useStatic, true, !useStatic);
|
||||
}
|
||||
}
|
||||
else if (qolType == QolAttributeType.Server)
|
||||
{
|
||||
if (!StripMethod(methodDef))
|
||||
{
|
||||
LoggingType logging = qolAttribute.GetField("Logging", LoggingType.Warning);
|
||||
GetClass<NetworkBehaviourHelper>().CreateIsServerCheck(methodDef, logging, useStatic, true, !useStatic);
|
||||
}
|
||||
}
|
||||
|
||||
bool StripMethod(MethodDefinition md)
|
||||
{
|
||||
// Fall through.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5080d7597ffca904b9a9fd5926e4e5a6
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/QOLAttributeProcessor.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f9d3654f5816c4409b88fa0602c6df4
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/ReaderProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 810757384532c3a4b9e4681f869acf1e
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,64 @@
|
||||
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();
|
||||
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,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91f84e00db3d1ad448fb6a760afb6927
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/Rpc/AttributeData.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,160 @@
|
||||
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 = 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();
|
||||
string asyncAttributeFullName = typeof(AsyncStateMachineAttribute).FullName;
|
||||
bool isAsync = false;
|
||||
|
||||
foreach (CustomAttribute customAttribute in methodDef.CustomAttributes)
|
||||
{
|
||||
RpcType rt = Session.GetClass<AttributeHelper>().GetRpcAttributeType(customAttribute);
|
||||
if (rt != RpcType.None)
|
||||
{
|
||||
results.Add(new(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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
//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))
|
||||
{
|
||||
Session.LogError($"{methodDef.Name} contains multiple RPC attributes. Only ObserversRpc and TargetRpc attributes may be combined.");
|
||||
return new();
|
||||
}
|
||||
}
|
||||
|
||||
//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();
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
Session.LogError($"{methodDef.Name} RPC method cannot be static.");
|
||||
return false;
|
||||
}
|
||||
//Is generic type.
|
||||
else if (methodDef.HasGenericParameters)
|
||||
{
|
||||
Session.LogError($"{methodDef.Name} RPC method cannot contain generic parameters.");
|
||||
return false;
|
||||
}
|
||||
//Abstract method.
|
||||
else if (methodDef.IsAbstract)
|
||||
{
|
||||
Session.LogError($"{methodDef.Name} RPC method cannot be abstract.");
|
||||
return false;
|
||||
}
|
||||
//Non void return.
|
||||
else if (methodDef.ReturnType != methodDef.Module.TypeSystem.Void)
|
||||
{
|
||||
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)))
|
||||
{
|
||||
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)
|
||||
{
|
||||
Session.LogError($"RPC method{methodDef.Name} contains a generic parameter. This is currently not supported.");
|
||||
return false;
|
||||
}
|
||||
|
||||
//Can be serialized/deserialized.
|
||||
bool canSerialize = GetClass<GeneralHelper>().HasSerializerAndDeserializer(parameterDef.ParameterType, true);
|
||||
if (!canSerialize)
|
||||
{
|
||||
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,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 974ebf09757267941a86f92e5072258c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/Rpc/Attributes.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,53 @@
|
||||
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,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c2176b6bfcc49934d8f36fba3df74d0c
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/Rpc/CreatedRpc.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4d4adb5891ee44f4397cd07ac2df0ce0
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/Rpc/RpcProcessor.cs
|
||||
uploadId: 866910
|
||||
@@ -0,0 +1,555 @@
|
||||
using FishNet.CodeGenerating.Extension;
|
||||
using FishNet.CodeGenerating.Helping;
|
||||
using FishNet.CodeGenerating.Helping.Extension;
|
||||
using FishNet.Object.Delegating;
|
||||
using FishNet.Object.Synchronizing;
|
||||
using FishNet.Object.Synchronizing.Internal;
|
||||
using GameKit.Dependencies.Utilities;
|
||||
using MonoFN.Cecil;
|
||||
using MonoFN.Cecil.Cil;
|
||||
using MonoFN.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace FishNet.CodeGenerating.Processing
|
||||
{
|
||||
internal class SyncTypeProcessor : CodegenBase
|
||||
{
|
||||
#region Reflection references.
|
||||
private TypeReference _syncBase_TypeRef;
|
||||
private TypeReference _syncVar_TypeRef;
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
private string _syncVarAttribute_FullName = typeof(SyncVarAttribute).FullName;
|
||||
private string _syncObjectAttribute_FullName = typeof(SyncObjectAttribute).FullName;
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
#endregion
|
||||
|
||||
#region Const.
|
||||
private const string INITIALIZELATE_METHOD_NAME = nameof(SyncBase.InitializeLate);
|
||||
private const string INITIALIZEEARLY_METHOD_NAME = nameof(SyncBase.InitializeEarly);
|
||||
private const string GETSERIALIZEDTYPE_METHOD_NAME = nameof(ICustomSync.GetSerializedType);
|
||||
#endregion
|
||||
|
||||
public override bool ImportReferences()
|
||||
{
|
||||
System.Type svType = typeof(SyncVar<>);
|
||||
_syncVar_TypeRef = ImportReference(svType);
|
||||
|
||||
System.Type syncBaseType = typeof(SyncBase);
|
||||
_syncBase_TypeRef = ImportReference(syncBaseType).Resolve();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Processes SyncVars and Objects.
|
||||
/// </summary>
|
||||
/// <param name = "syncTypeHash">Number of SyncTypes implemented in this typeDef and those inherited of.</param>
|
||||
internal bool ProcessLocal(TypeDefinition typeDef)
|
||||
{
|
||||
bool modified = false;
|
||||
|
||||
ValidateVersion3ToVersion4SyncVars(typeDef);
|
||||
|
||||
uint startingHash = GetSyncTypeCountInParents(typeDef);
|
||||
uint totalSyncTypes = startingHash + GetSyncTypeCount(typeDef);
|
||||
if (totalSyncTypes > NetworkBehaviourHelper.MAX_SYNCTYPE_ALLOWANCE)
|
||||
{
|
||||
LogError($"Found {totalSyncTypes} SyncTypes within {typeDef.FullName} and inherited classes. The maximum number of allowed SyncTypes within type and inherited types is {NetworkBehaviourHelper.MAX_SYNCTYPE_ALLOWANCE}. Remove SyncTypes or condense them using data containers, or a custom SyncObject.");
|
||||
return false;
|
||||
}
|
||||
|
||||
FieldDefinition[] fieldDefs = typeDef.Fields.ToArray();
|
||||
foreach (FieldDefinition fd in fieldDefs)
|
||||
{
|
||||
// Check if uses old attributes first.
|
||||
if (HasSyncTypeAttributeUnchecked(fd))
|
||||
{
|
||||
LogError($"SyncType {fd.Name} on type {fd.DeclaringType.FullName} implements [SyncVar] or [SyncObject]. These attributes are no longer supported as of version 4. Please see Break Solutions within the documentation to resolve these errors.");
|
||||
continue;
|
||||
}
|
||||
SyncType st = GetSyncType(fd);
|
||||
// Not a sync type field.
|
||||
if (st == SyncType.Unset)
|
||||
continue;
|
||||
// Needs to be addressed if this ever occurs.
|
||||
if (st == SyncType.Unhandled)
|
||||
{
|
||||
LogError($"Field {fd.Name} in type {fd.DeclaringType.FullName} is unhandled.");
|
||||
return false;
|
||||
}
|
||||
// Errors occurred while checking the synctype field.
|
||||
if (!IsSyncTypeFieldValid(fd, true))
|
||||
return false;
|
||||
|
||||
bool isSyncObject = st != SyncType.Variable;
|
||||
bool isGeneric = fd.FieldType.IsGenericInstance;
|
||||
if (isGeneric)
|
||||
{
|
||||
if (TryCreateGenericSyncType(startingHash, fd, isSyncObject))
|
||||
startingHash++;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryCreateNonGenericSyncType(startingHash, fd, isSyncObject))
|
||||
startingHash++;
|
||||
}
|
||||
|
||||
modified = true;
|
||||
}
|
||||
|
||||
return modified;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets number of SyncTypes by checking for SyncVar/Object attributes. This does not perform error checking.
|
||||
/// </summary>
|
||||
/// <param name = "typeDef"></param>
|
||||
/// <returns></returns>
|
||||
internal uint GetSyncTypeCount(TypeDefinition typeDef)
|
||||
{
|
||||
uint count = 0;
|
||||
foreach (FieldDefinition fd in typeDef.Fields)
|
||||
{
|
||||
if (IsSyncType(fd))
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets SyncType count in all of typeDefs parents, excluding typeDef itself.
|
||||
/// </summary>
|
||||
internal uint GetSyncTypeCountInParents(TypeDefinition typeDef)
|
||||
{
|
||||
uint count = 0;
|
||||
while (true)
|
||||
{
|
||||
typeDef = typeDef.GetNextBaseClassToProcess(Session);
|
||||
if (typeDef != null)
|
||||
count += GetSyncTypeCount(typeDef);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if fieldDef is a syncType.
|
||||
/// </summary>
|
||||
internal bool IsSyncType(FieldReference fieldRef)
|
||||
{
|
||||
FieldDefinition fd = fieldRef.CachedResolve(Session);
|
||||
return IsSyncType(fd);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if fieldDef is a syncType.
|
||||
/// </summary>
|
||||
internal bool IsSyncType(FieldDefinition fieldDef)
|
||||
{
|
||||
TypeDefinition ftTypeDef = fieldDef.FieldType.CachedResolve(Session);
|
||||
/* TypeDef may be null for certain generated types,
|
||||
* as well for some normal types such as queue because Unity broke
|
||||
* them in 2022+ and decided the issue was not worth resolving. */
|
||||
if (ftTypeDef == null)
|
||||
return false;
|
||||
|
||||
return ftTypeDef.InheritsFrom<SyncBase>(Session);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Throws an error on any SyncVars which are comparing null on the SyncVar field, or trying to set the field null.
|
||||
/// </summary>
|
||||
private void ValidateVersion3ToVersion4SyncVars(TypeDefinition td)
|
||||
{
|
||||
#if !FISHNET_DISABLE_V3TOV4_HELPERS
|
||||
/* In version3 since the user could reference the field directly like a value these actions were allowed. Doing so now however would cause unintended behavior.
|
||||
* For example...
|
||||
* [SyncVar]
|
||||
* private Player _myPlayer;
|
||||
*
|
||||
* void SomeMethod()
|
||||
* {
|
||||
* if (_myPlayer == null) doStuff();
|
||||
* }
|
||||
* The above context would be valid in version 3.
|
||||
*
|
||||
* But if the user converted without paying close attention, which is reasonable to miss, this would not work as intended.
|
||||
* For example...
|
||||
* private readonly SyncVar<Player> _myPlayer = new();
|
||||
*
|
||||
* void SomeMethod()
|
||||
* {
|
||||
* if (_myPlayer == null) doStuff();
|
||||
* }
|
||||
* The above is not the same behavior as the field _myPlayer would never be null. Instead the code should look like this...
|
||||
*
|
||||
* void SomeMethod()
|
||||
* {
|
||||
* if (_myPlayer.Value == null) doStuff();
|
||||
* }
|
||||
* The checks below will catch this scenarios.
|
||||
*/
|
||||
|
||||
foreach (MethodDefinition methodDef in td.Methods)
|
||||
{
|
||||
// Ignore constructors.
|
||||
if (methodDef.IsConstructor)
|
||||
continue;
|
||||
if (methodDef.IsAbstract)
|
||||
continue;
|
||||
for (int i = 0; i < methodDef.Body.Instructions.Count; i++)
|
||||
{
|
||||
Instruction inst = methodDef.Body.Instructions[i];
|
||||
|
||||
/* Loading a field. (Getter) */
|
||||
if ((inst.OpCode == OpCodes.Ldfld || inst.OpCode == OpCodes.Ldflda) && inst.Operand is FieldReference opFieldld)
|
||||
{
|
||||
FieldReference resolvedOpField = opFieldld.CachedResolve(Session);
|
||||
if (resolvedOpField == null)
|
||||
resolvedOpField = opFieldld.DeclaringType.CachedResolve(Session).GetFieldReference(opFieldld.Name, Session);
|
||||
|
||||
if (IsSyncType(resolvedOpField))
|
||||
{
|
||||
// Check next opcode for a brfalse/true.
|
||||
// If there are more instructions to check, which there should always be.
|
||||
if (i < methodDef.Body.Instructions.Count - 1)
|
||||
{
|
||||
Instruction nextInst = methodDef.Body.Instructions[i + 1];
|
||||
if (nextInst.OpCode == OpCodes.Brfalse || nextInst.OpCode == OpCodes.Brfalse_S || nextInst.OpCode == OpCodes.Brtrue || nextInst.OpCode == OpCodes.Brtrue_S)
|
||||
LogError($"Method {methodDef.Name} in class {td.Name} is comparing null for the SyncType field {resolvedOpField.Name}. SyncType fields should never be null; did you intend to compare the SyncType.Value instead?");
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Setting a field. (Setter) */
|
||||
else if (inst.OpCode == OpCodes.Stfld && inst.Operand is FieldReference opFieldst)
|
||||
{
|
||||
FieldReference resolvedOpField = opFieldst.CachedResolve(Session);
|
||||
if (resolvedOpField == null)
|
||||
resolvedOpField = opFieldst.DeclaringType.CachedResolve(Session).GetFieldReference(opFieldst.Name, Session);
|
||||
|
||||
if (IsSyncType(resolvedOpField))
|
||||
LogError($"Method {methodDef.Name} in class {td.Name} is setting value to the SyncType field {resolvedOpField.Name}. This will result in the SyncType not functioning.");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Error checks a SyncType field. This assumes the field is a SyncType.
|
||||
/// </summary>
|
||||
private bool IsSyncTypeFieldValid(FieldDefinition fieldDef, bool outputError)
|
||||
{
|
||||
// Static.
|
||||
if (fieldDef.IsStatic)
|
||||
{
|
||||
if (outputError)
|
||||
LogError($"{fieldDef.Name} SyncType in type {fieldDef.DeclaringType.FullName} cannot be static.");
|
||||
return false;
|
||||
}
|
||||
// Generic.
|
||||
if (fieldDef.FieldType.IsGenericParameter)
|
||||
{
|
||||
if (outputError)
|
||||
LogError($"{fieldDef.Name} SyncType in type {fieldDef.DeclaringType.FullName} cannot be be generic.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// todo checking if field is initialized would be good.
|
||||
|
||||
/* Forces readonly unless user allows for serialization.
|
||||
* If within this logic then the field is readonly. */
|
||||
if (!fieldDef.Attributes.HasFlag(FieldAttributes.InitOnly))
|
||||
{
|
||||
bool ignoreRequirement = false;
|
||||
string attributeFullName = typeof(AllowMutableSyncTypeAttribute).FullName;
|
||||
// Check if has attribute to bypass readonly check.
|
||||
foreach (CustomAttribute item in fieldDef.CustomAttributes)
|
||||
{
|
||||
if (item.AttributeType.FullName == attributeFullName)
|
||||
{
|
||||
ignoreRequirement = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ignoreRequirement)
|
||||
{
|
||||
if (outputError)
|
||||
LogError($"{fieldDef.Name} SyncType in type {fieldDef.DeclaringType.FullName} must be readonly or decorated with the {nameof(AllowMutableSyncTypeAttribute)} attribute. If allowing muteable do not ever re-assign the field at runtime.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through/pass.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns SyncType on a field while error checking.
|
||||
/// </summary>
|
||||
internal SyncType GetSyncType(FieldDefinition fieldDef)
|
||||
{
|
||||
if (!IsSyncType(fieldDef))
|
||||
return SyncType.Unset;
|
||||
|
||||
TypeDefinition fieldTypeDef = fieldDef.FieldType.CachedResolve(Session);
|
||||
|
||||
ObjectHelper oh = GetClass<ObjectHelper>();
|
||||
string fdName = fieldDef.FieldType.Name;
|
||||
if (fdName == oh.SyncVar_Name || fieldTypeDef.ImplementsInterfaceRecursive<ISyncVar>(Session))
|
||||
return SyncType.Variable;
|
||||
else if (fdName == oh.SyncList_Name)
|
||||
return SyncType.List;
|
||||
else if (fdName == oh.SyncDictionary_Name)
|
||||
return SyncType.Dictionary;
|
||||
else if (fdName == oh.SyncHashSet_Name)
|
||||
return SyncType.HashSet;
|
||||
// Custom types must also implement ICustomSync.
|
||||
else if (fieldTypeDef.ImplementsInterfaceRecursive<ICustomSync>(Session))
|
||||
return SyncType.Custom;
|
||||
else
|
||||
return SyncType.Unhandled;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create a SyncType that does not use generics.
|
||||
/// </summary>
|
||||
private bool TryCreateNonGenericSyncType(uint hash, FieldDefinition originalFieldDef, bool isSyncObject)
|
||||
{
|
||||
TypeDefinition fieldTypeTd = originalFieldDef.FieldType.CachedResolve(Session);
|
||||
if (!fieldTypeTd.ImplementsInterface<ICustomSync>())
|
||||
{
|
||||
LogError($"SyncType field {originalFieldDef.Name} in type {originalFieldDef.DeclaringType.FullName} does not implement {nameof(ICustomSync)}. Non-generic SyncTypes must implement {nameof(ICustomSync)}. See documentation on Custom SyncTypes for more information.");
|
||||
return false;
|
||||
}
|
||||
// Get the serialized type.
|
||||
MethodDefinition getSerialziedTypeMd = originalFieldDef.FieldType.CachedResolve(Session).GetMethod(GETSERIALIZEDTYPE_METHOD_NAME);
|
||||
MethodReference getSerialziedTypeMr = ImportReference(getSerialziedTypeMd);
|
||||
Collection<Instruction> instructions = getSerialziedTypeMr.CachedResolve(Session).Body.Instructions;
|
||||
|
||||
bool checkForSerializer = true;
|
||||
TypeReference serializedDataTypeRef = null;
|
||||
/* If the user is returning null then
|
||||
* they are indicating a built-in serializer
|
||||
* already exists. */
|
||||
if (instructions.Count == 2 && instructions[0].OpCode == OpCodes.Ldnull && instructions[1].OpCode == OpCodes.Ret)
|
||||
{
|
||||
checkForSerializer = false;
|
||||
}
|
||||
// If not returning null then make a serializer for return type.
|
||||
else
|
||||
{
|
||||
foreach (Instruction item in instructions)
|
||||
{
|
||||
if (item.OpCode == OpCodes.Ldnull)
|
||||
{
|
||||
checkForSerializer = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// This token references the type.
|
||||
if (item.OpCode == OpCodes.Ldtoken)
|
||||
{
|
||||
TypeReference importedTr = null;
|
||||
if (item.Operand is TypeDefinition td)
|
||||
importedTr = ImportReference(td);
|
||||
else if (item.Operand is TypeReference tr)
|
||||
importedTr = ImportReference(tr);
|
||||
|
||||
if (importedTr != null)
|
||||
serializedDataTypeRef = importedTr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TypeReference[] typeRefs;
|
||||
// If need to check for serialization.
|
||||
if (checkForSerializer)
|
||||
{
|
||||
if (serializedDataTypeRef == null)
|
||||
{
|
||||
LogError($"SyncType field {originalFieldDef.Name} in type {originalFieldDef.DeclaringType.FullName} does not indicate which data type it needs to serialize. Review documentation for custom SyncTypes to view how to implement this feature.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// If here then check.
|
||||
typeRefs = new TypeReference[]
|
||||
{
|
||||
serializedDataTypeRef
|
||||
};
|
||||
if (!CanSerialize(originalFieldDef, typeRefs))
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
typeRefs = null;
|
||||
}
|
||||
|
||||
if (!InitializeSyncType(hash, originalFieldDef, typeRefs, isSyncObject))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tries to create a SyncType that uses generics.
|
||||
/// </summary>
|
||||
private bool TryCreateGenericSyncType(uint hash, FieldDefinition originalFieldDef, bool isSyncObject)
|
||||
{
|
||||
GenericInstanceType tmpGenerinstanceType = originalFieldDef.FieldType as GenericInstanceType;
|
||||
TypeReference[] typeRefs = new TypeReference[tmpGenerinstanceType.GenericArguments.Count];
|
||||
for (int i = 0; i < typeRefs.Length; i++)
|
||||
typeRefs[i] = ImportReference(tmpGenerinstanceType.GenericArguments[i]);
|
||||
if (!CanSerialize(originalFieldDef, typeRefs))
|
||||
return false;
|
||||
|
||||
if (!InitializeSyncType(hash, originalFieldDef, typeRefs, isSyncObject))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if type references can be serialized.
|
||||
/// </summary>
|
||||
/// <param name = "fd">Field definition specifying types. This is only used for debug output.</param>
|
||||
private bool CanSerialize(FieldDefinition fd, TypeReference[] typeRefs)
|
||||
{
|
||||
if (typeRefs == null)
|
||||
return true;
|
||||
|
||||
GeneralHelper gh = GetClass<GeneralHelper>();
|
||||
foreach (TypeReference item in typeRefs)
|
||||
{
|
||||
bool canSerialize = gh.HasSerializerAndDeserializer(item, true);
|
||||
if (!canSerialize)
|
||||
{
|
||||
LogError($"SyncType name {fd.Name} in type {fd.DeclaringType.FullName} data type {item.FullName} does not support serialization and one could not be created automatically. Use a supported type or create a custom serializer.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Fall through/pass.
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if attribute if a SyncVarAttribute.
|
||||
/// </summary>
|
||||
/// <param name = "attributeFullName"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsSyncVariableAttribute(string attributeFullName)
|
||||
{
|
||||
return attributeFullName == _syncVarAttribute_FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if attribute if a SyncObjectAttribute.
|
||||
/// </summary>
|
||||
/// <param name = "attributeFullName"></param>
|
||||
/// <returns></returns>
|
||||
private bool IsSyncObjectAttribute(string attributeFullName)
|
||||
{
|
||||
return attributeFullName == _syncObjectAttribute_FullName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns if fieldDef has a SyncType attribute. No error checking is performed.
|
||||
/// </summary>
|
||||
/// <param name = "fieldDef"></param>
|
||||
/// <returns></returns>
|
||||
private bool HasSyncTypeAttributeUnchecked(FieldDefinition fieldDef)
|
||||
{
|
||||
foreach (CustomAttribute customAttribute in fieldDef.CustomAttributes)
|
||||
{
|
||||
if (IsSyncObjectAttribute(customAttribute.AttributeType.FullName) || IsSyncVariableAttribute(customAttribute.AttributeType.FullName))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets methods used from SyncBase for typeDef.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
internal bool SetSyncBaseInitializeMethods(FieldDefinition syncTypeFieldDef, TypeReference[] variableTypeRefs, out MethodReference initializeEarlyMr, out MethodReference initializeLateMr)
|
||||
{
|
||||
initializeEarlyMr = null;
|
||||
initializeLateMr = null;
|
||||
// Find the SyncBase class.
|
||||
TypeDefinition fieldTd = syncTypeFieldDef.FieldType.CachedResolve(Session);
|
||||
TypeDefinition syncBaseTd = fieldTd.GetClassInInheritance(Session, typeof(SyncBase).FullName);
|
||||
// If SyncBase isn't found.
|
||||
if (syncBaseTd == null)
|
||||
{
|
||||
LogError($"Could not find SyncBase within type {fieldTd.FullName}.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Early
|
||||
initializeEarlyMr = syncBaseTd.GetMethodReference(Session, INITIALIZEEARLY_METHOD_NAME);
|
||||
// if (variableTypeRefs != null)
|
||||
// initializeEarlyMr = initializeEarlyMr.MakeGenericMethod(variableTypeRefs);
|
||||
// Late
|
||||
initializeLateMr = syncBaseTd.GetMethodReference(Session, INITIALIZELATE_METHOD_NAME);
|
||||
// if (variableTypeRefs != null)
|
||||
// initializeLateMr = initializeLateMr.MakeGenericMethod(variableTypeRefs);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a SyncType using default settings.
|
||||
/// </summary>
|
||||
private bool InitializeSyncType(uint hash, FieldDefinition originalFieldDef, TypeReference[] variableTypeRefs, bool isSyncObject)
|
||||
{
|
||||
// Set needed methods from syncbase.
|
||||
MethodReference initializeLateMr;
|
||||
MethodReference initializeEarlyMr;
|
||||
if (!SetSyncBaseInitializeMethods(originalFieldDef, variableTypeRefs, out initializeEarlyMr, out initializeLateMr))
|
||||
return false;
|
||||
|
||||
// Make user field public.
|
||||
originalFieldDef.Attributes &= ~FieldAttributes.Private;
|
||||
originalFieldDef.Attributes |= FieldAttributes.Public;
|
||||
|
||||
TypeDefinition typeDef = originalFieldDef.DeclaringType;
|
||||
List<Instruction> insts;
|
||||
ILProcessor processor;
|
||||
MethodDefinition injectionMd;
|
||||
|
||||
// InitializeEarly.
|
||||
injectionMd = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_EARLY_INTERNAL_NAME);
|
||||
processor = injectionMd.Body.GetILProcessor();
|
||||
insts = new()
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0), // this.
|
||||
processor.Create(OpCodes.Ldfld, originalFieldDef),
|
||||
processor.Create(OpCodes.Ldarg_0), // this again for NetworkBehaviour.
|
||||
processor.Create(OpCodes.Ldc_I4, (int)hash),
|
||||
processor.Create(OpCodes.Ldc_I4, isSyncObject.ToInt()),
|
||||
processor.Create(OpCodes.Call, initializeEarlyMr)
|
||||
};
|
||||
processor.InsertFirst(insts);
|
||||
|
||||
// InitializeLate.
|
||||
injectionMd = typeDef.GetMethod(NetworkBehaviourProcessor.NETWORKINITIALIZE_LATE_INTERNAL_NAME);
|
||||
processor = injectionMd.Body.GetILProcessor();
|
||||
insts = new()
|
||||
{
|
||||
processor.Create(OpCodes.Ldarg_0), // this.
|
||||
processor.Create(OpCodes.Ldfld, originalFieldDef),
|
||||
processor.Create(initializeLateMr.GetCallOpCode(Session), initializeLateMr)
|
||||
};
|
||||
processor.InsertFirst(insts);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ec95af37f78b9e340b5eaa199c1af94a
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/SyncTypeProcessor.cs
|
||||
uploadId: 866910
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,18 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2a4021bd44dc40f47abb494e0a4326f9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
AssetOrigin:
|
||||
serializedVersion: 1
|
||||
productId: 207815
|
||||
packageName: 'FishNet: Networking Evolved'
|
||||
packageVersion: 4.6.22R
|
||||
assetPath: Assets/FishNet/CodeGenerating/Processing/WriterProcessor.cs
|
||||
uploadId: 866910
|
||||
Reference in New Issue
Block a user