Merge remote-tracking branch 'origin/master' into 1.6

# Conflicts:
#	Source/Tools/Flax.Build/Build/Builder.Projects.cs
#	Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs
This commit is contained in:
Wojtek Figat
2023-03-14 11:52:41 +01:00
25 changed files with 545 additions and 196 deletions

View File

@@ -47,12 +47,25 @@ namespace Flax.Build.Plugins
public MethodDefinition Execute;
}
private struct DotnetContext
{
public bool Modified;
public bool Failed;
public AssemblyDefinition Assembly;
public List<TypeSerializer> AddSerializers;
public List<MethodRPC> MethodRPCs;
public HashSet<TypeDefinition> GeneratedSerializers;
public TypeReference VoidType;
public TypeReference NetworkStreamType;
}
internal const string Network = "Network";
internal const string NetworkReplicated = "NetworkReplicated";
internal const string NetworkReplicatedAttribute = "FlaxEngine.NetworkReplicatedAttribute";
internal const string NetworkRpc = "NetworkRpc";
private const string Thunk1 = "INetworkSerializable_Serialize";
private const string Thunk2 = "INetworkSerializable_Deserialize";
private static readonly Dictionary<string, InBuildSerializer> _inBuildSerializers = new Dictionary<string, InBuildSerializer>()
{
{ "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") },
@@ -127,6 +140,7 @@ namespace Flax.Build.Plugins
{
fields = structInfo.Fields;
}
bool useReplication = false, useRpc = false;
if (fields != null)
{
@@ -139,6 +153,7 @@ namespace Flax.Build.Plugins
}
}
}
if (properties != null)
{
foreach (var propertyInfo in properties)
@@ -150,6 +165,7 @@ namespace Flax.Build.Plugins
}
}
}
if (functions != null)
{
foreach (var functionInfo in functions)
@@ -161,20 +177,22 @@ namespace Flax.Build.Plugins
}
}
}
if (useReplication)
{
typeInfo.SetTag(NetworkReplicated, string.Empty);
// Generate C++ wrapper functions to serialize/deserialize type
BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkReplicator.h");
BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkStream.h");
OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, properties, true);
OnGenerateCppTypeSerialize(buildData, typeInfo, contents, fields, properties, false);
}
if (useRpc)
{
typeInfo.SetTag(NetworkRpc, string.Empty);
// Generate C++ wrapper functions to invoke/execute RPC
BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkStream.h");
BindingsGenerator.CppIncludeFiles.Add("Engine/Networking/NetworkReplicator.h");
@@ -282,7 +300,7 @@ namespace Flax.Build.Plugins
// Replicate base type
OnGenerateCppWriteSerializer(contents, classStructInfo.BaseType.NativeName, "obj", serialize);
}
// Replicate all marked fields and properties
if (fields != null)
{
@@ -293,6 +311,7 @@ namespace Flax.Build.Plugins
OnGenerateCppTypeSerializeData(buildData, typeInfo, contents, fieldInfo.Type, $"obj.{fieldInfo.Name}", serialize);
}
}
if (properties != null)
{
foreach (var propertyInfo in properties)
@@ -308,6 +327,7 @@ namespace Flax.Build.Plugins
}
}
}
contents.AppendLine(" }");
contents.AppendLine();
}
@@ -326,9 +346,10 @@ namespace Flax.Build.Plugins
if (apiType != null)
return IsRawPOD(buildData, apiType);
}
return false;
}
private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize)
{
var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller);
@@ -365,7 +386,7 @@ namespace Flax.Build.Plugins
throw new Exception($"Invalid pointer type '{type}' that cannot be serialized for replication of {caller.Name}.");
if (type.IsRef)
throw new Exception($"Invalid reference type '{type}' that cannot be serialized for replication of {caller.Name}.");
// Structure serializer
OnGenerateCppWriteSerializer(contents, apiType.NativeName, name, serialize);
}
@@ -375,13 +396,13 @@ namespace Flax.Build.Plugins
OnGenerateCppWriteRaw(contents, name, serialize);
}
}
private void OnGenerateCppWriteRaw(StringBuilder contents, string data, bool serialize)
{
var method = serialize ? "Write" : "Read";
contents.AppendLine($" stream->{method}({data});");
}
private void OnGenerateCppWriteSerializer(StringBuilder contents, string type, string data, bool serialize)
{
if (type == "ScriptingObject" || type == "Script" || type == "Actor")
@@ -405,6 +426,7 @@ namespace Flax.Build.Plugins
// Register generated serializer functions
contents.AppendLine($" NetworkReplicator::AddSerializer(ScriptingTypeHandle({typeNameNative}::TypeInitializer), {typeNameInternal}Internal::INetworkSerializable_Serialize, {typeNameInternal}Internal::INetworkSerializable_Deserialize);");
}
if (rpcTag != null)
{
// Register generated RPCs
@@ -413,6 +435,7 @@ namespace Flax.Build.Plugins
{
functions = classInfo.Functions;
}
if (functions != null)
{
foreach (var functionInfo in functions)
@@ -430,16 +453,15 @@ namespace Flax.Build.Plugins
// Skip types that don't use networking
if (typeInfo.GetTag(NetworkReplicated) == null)
return;
if (typeInfo is ClassInfo)
return;
// Generate C# wrapper functions to serialize/deserialize type directly from managed code
OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, true);
OnGenerateCSharpTypeSerialize(buildData, typeInfo, contents, indent, false);
}
private void OnGenerateCSharpTypeSerialize(Builder.BuildData buildData, ApiTypeInfo typeInfo, StringBuilder contents, string indent, bool serialize)
{
var mode = serialize ? "true" : "false";
@@ -463,6 +485,7 @@ namespace Flax.Build.Plugins
contents.Append(indent).AppendLine($" throw new NotImplementedException(\"Not supported native structure with references used in managed code for replication.\");");
}
}
contents.Append(indent).AppendLine("}");
}
@@ -493,7 +516,7 @@ namespace Flax.Build.Plugins
private void OnPatchDotNetAssembly(Builder.BuildData buildData, NativeCpp.BuildOptions buildOptions, Task buildTask, string assemblyPath)
{
using (DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver())
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters{ ReadWrite = true, ReadSymbols = true, AssemblyResolver = assemblyResolver }))
using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyPath, new ReaderParameters { ReadWrite = true, ReadSymbols = true, AssemblyResolver = assemblyResolver }))
{
// Setup module search locations
var searchDirectories = new HashSet<string>();
@@ -503,107 +526,36 @@ namespace Flax.Build.Plugins
if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
searchDirectories.Add(Path.GetDirectoryName(file));
}
foreach (var e in searchDirectories)
assemblyResolver.AddSearchDirectory(e);
ModuleDefinition module = assembly.MainModule;
TypeReference voidType = module.ImportReference(typeof(void));
module.GetType("FlaxEngine.Networking.NetworkStream", out var networkStreamType);
// Process all types within a module
bool modified = false;
bool failed = false;
var addSerializers = new List<TypeSerializer>();
var methodRPCs = new List<MethodRPC>();
var context = new DotnetContext
{
Modified = false,
Failed = false,
Assembly = assembly,
AddSerializers = new List<TypeSerializer>(),
MethodRPCs = new List<MethodRPC>(),
GeneratedSerializers = new HashSet<TypeDefinition>(),
VoidType = module.ImportReference(typeof(void)),
};
module.GetType("FlaxEngine.Networking.NetworkStream", out context.NetworkStreamType);
foreach (TypeDefinition type in module.Types)
{
if (type.IsInterface || type.IsEnum)
continue;
var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute");
if (isNative)
continue;
// Generate RPCs
var methods = type.Methods;
var methodsCount = methods.Count; // methods list can be modified during RPCs generation
for (int i = 0; i < methodsCount; i++)
{
MethodDefinition method = methods[i];
var attribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "FlaxEngine.NetworkRpcAttribute");
if (attribute != null)
{
GenerateDotNetRPCBody(type, method, attribute, ref failed, networkStreamType, methodRPCs);
}
}
// Generate serializers
if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2))
continue;
var isINetworkSerializable = type.HasInterface("FlaxEngine.Networking.INetworkSerializable");
MethodDefinition serializeINetworkSerializable = null, deserializeINetworkSerializable = null;
if (isINetworkSerializable)
{
foreach (MethodDefinition m in type.Methods)
{
if (m.HasBody && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "FlaxEngine.Networking.NetworkStream")
{
if (m.Name == "Serialize")
serializeINetworkSerializable = m;
else if (m.Name == "Deserialize")
deserializeINetworkSerializable = m;
}
}
}
var isNetworkReplicated = false;
foreach (FieldDefinition f in type.Fields)
{
if (!f.HasAttribute(NetworkReplicatedAttribute))
continue;
isNetworkReplicated = true;
break;
}
foreach (PropertyDefinition p in type.Properties)
{
if (!p.HasAttribute(NetworkReplicatedAttribute))
continue;
isNetworkReplicated = true;
break;
}
if (type.IsValueType)
{
if (isINetworkSerializable)
{
// Generate INetworkSerializable interface method calls
GenerateCallINetworkSerializable(type, Thunk1, voidType, networkStreamType, serializeINetworkSerializable);
GenerateCallINetworkSerializable(type, Thunk2, voidType, networkStreamType, deserializeINetworkSerializable);
modified = true;
}
else if (isNetworkReplicated)
{
// Generate serialization methods
GenerateSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType);
GenerateSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType);
modified = true;
}
}
else if (!isINetworkSerializable && isNetworkReplicated)
{
// Generate serialization methods
var addSerializer = new TypeSerializer();
addSerializer.Type = type;
addSerializer.Serialize = GenerateNativeSerializer(type, true, ref failed, Thunk1, voidType, networkStreamType);
addSerializer.Deserialize = GenerateNativeSerializer(type, false, ref failed, Thunk2, voidType, networkStreamType);
addSerializers.Add(addSerializer);
modified = true;
}
GenerateTypeNetworking(ref context, type);
}
if (failed)
if (context.Failed)
throw new Exception($"Failed to generate network replication for assembly {assemblyPath}");
if (!modified)
if (!context.Modified)
return;
// Generate serializers initializer (invoked on module load)
if (addSerializers.Count != 0 || methodRPCs.Count != 0)
if (context.AddSerializers.Count != 0 || context.MethodRPCs.Count != 0)
{
// Create class
var name = "Initializer";
@@ -623,7 +575,7 @@ namespace Flax.Build.Plugins
c.CustomAttributes.Add(attribute);
// Add Init method
var m = new MethodDefinition("Init", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, voidType);
var m = new MethodDefinition("Init", MethodAttributes.Private | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType);
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
module.GetType("System.Type", out var typeType);
@@ -637,7 +589,7 @@ namespace Flax.Build.Plugins
module.ImportReference(addRPC);
var executeRPCFuncType = addRPC.Parameters[2].ParameterType;
var executeRPCFuncCtor = executeRPCFuncType.Resolve().GetMethod(".ctor");
foreach (var e in addSerializers)
foreach (var e in context.AddSerializers)
{
// NetworkReplicator.AddSerializer(typeof(<type>), <type>.INetworkSerializable_SerializeNative, <type>.INetworkSerializable_DeserializeNative);
il.Emit(OpCodes.Ldtoken, e.Type);
@@ -650,7 +602,8 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Newobj, module.ImportReference(serializeFuncCtor));
il.Emit(OpCodes.Call, module.ImportReference(addSerializer));
}
foreach (var e in methodRPCs)
foreach (var e in context.MethodRPCs)
{
// NetworkReplicator.AddRPC(typeof(<type>), "<name>", <name>_Execute, <isServer>, <isClient>, <channel>);
il.Emit(OpCodes.Ldtoken, e.Type);
@@ -664,20 +617,135 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Ldc_I4, e.Channel);
il.Emit(OpCodes.Call, module.ImportReference(addRPC));
}
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
c.Methods.Add(m);
}
// Serialize assembly back to the file
assembly.Write(new WriterParameters { WriteSymbols = true } );
assembly.Write(new WriterParameters { WriteSymbols = true });
}
}
private static void GenerateCallINetworkSerializable(TypeDefinition type, string name, TypeReference voidType, TypeReference networkStreamType, MethodDefinition method)
private static void GenerateTypeNetworking(ref DotnetContext context, TypeDefinition type)
{
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType);
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, networkStreamType));
if (type.IsInterface || type.IsEnum)
return;
// Process nested types
foreach (var nestedType in type.NestedTypes)
{
GenerateTypeNetworking(ref context, nestedType);
}
if (type.IsClass)
{
// Generate RPCs
var methods = type.Methods;
var methodsCount = methods.Count; // methods list can be modified during RPCs generation
for (int i = 0; i < methodsCount; i++)
{
MethodDefinition method = methods[i];
var attribute = method.CustomAttributes.FirstOrDefault(x => x.AttributeType.FullName == "FlaxEngine.NetworkRpcAttribute");
if (attribute != null)
{
GenerateDotNetRPCBody(ref context, type, method, attribute, context.NetworkStreamType);
context.Modified = true;
}
}
}
GenerateTypeSerialization(ref context, type);
}
private static void GenerateTypeSerialization(ref DotnetContext context, TypeDefinition type)
{
// Skip types outside from current assembly
if (context.Assembly.MainModule != type.Module)
return;
// Skip if already generated serialization for this type (eg. via referenced RPC in other type)
if (context.GeneratedSerializers.Contains(type))
return;
context.GeneratedSerializers.Add(type);
// Skip native types
var isNative = type.HasAttribute("FlaxEngine.UnmanagedAttribute");
if (isNative)
return;
// Skip if manually implemented serializers
if (type.HasMethod(Thunk1) || type.HasMethod(Thunk2))
return;
// Generate serializers
var isINetworkSerializable = type.HasInterface("FlaxEngine.Networking.INetworkSerializable");
MethodDefinition serializeINetworkSerializable = null, deserializeINetworkSerializable = null;
if (isINetworkSerializable)
{
foreach (MethodDefinition m in type.Methods)
{
if (m.HasBody && m.Parameters.Count == 1 && m.Parameters[0].ParameterType.FullName == "FlaxEngine.Networking.NetworkStream")
{
if (m.Name == "Serialize")
serializeINetworkSerializable = m;
else if (m.Name == "Deserialize")
deserializeINetworkSerializable = m;
}
}
}
var isNetworkReplicated = false;
foreach (FieldDefinition f in type.Fields)
{
if (!f.HasAttribute(NetworkReplicatedAttribute))
continue;
isNetworkReplicated = true;
break;
}
foreach (PropertyDefinition p in type.Properties)
{
if (!p.HasAttribute(NetworkReplicatedAttribute))
continue;
isNetworkReplicated = true;
break;
}
if (type.IsValueType)
{
if (isINetworkSerializable)
{
// Generate INetworkSerializable interface method calls
GenerateCallINetworkSerializable(ref context, type, Thunk1, serializeINetworkSerializable);
GenerateCallINetworkSerializable(ref context, type, Thunk2, deserializeINetworkSerializable);
context.Modified = true;
}
else if (isNetworkReplicated)
{
// Generate serialization methods
GenerateSerializer(ref context, type, true, Thunk1);
GenerateSerializer(ref context, type, false, Thunk2);
context.Modified = true;
}
}
else if (!isINetworkSerializable && isNetworkReplicated)
{
// Generate serialization methods
var addSerializer = new TypeSerializer();
addSerializer.Type = type;
addSerializer.Serialize = GenerateNativeSerializer(ref context, type, true, Thunk1);
addSerializer.Deserialize = GenerateNativeSerializer(ref context, type, false, Thunk2);
context.AddSerializers.Add(addSerializer);
context.Modified = true;
}
}
private static void GenerateCallINetworkSerializable(ref DotnetContext context, TypeDefinition type, string name, MethodDefinition method)
{
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType);
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, context.NetworkStreamType));
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
@@ -688,15 +756,14 @@ namespace Flax.Build.Plugins
type.Methods.Add(m);
}
private static MethodDefinition GenerateSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType)
private static MethodDefinition GenerateSerializer(ref DotnetContext context, TypeDefinition type, bool serialize, string name)
{
ModuleDefinition module = type.Module;
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, voidType);
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, module.ImportReference(networkStreamType)));
TypeDefinition networkStream = networkStreamType.Resolve();
var m = new MethodDefinition(name, MethodAttributes.Public | MethodAttributes.HideBySig, context.VoidType);
m.Parameters.Add(new ParameterDefinition("stream", ParameterAttributes.None, module.ImportReference(context.NetworkStreamType)));
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
// Serialize base type
if (type.BaseType != null && type.BaseType.FullName != "System.ValueType" && type.BaseType.FullName != "FlaxEngine.Object" && type.BaseType.CanBeResolved())
{
@@ -708,7 +775,7 @@ namespace Flax.Build.Plugins
{
if (!f.HasAttribute(NetworkReplicatedAttribute))
continue;
GenerateSerializerType(type, serialize, ref failed, f, null, f.FieldType, il, networkStream);
GenerateSerializerType(ref context, type, serialize, f, null, f.FieldType, il);
}
// Serialize all type properties marked with NetworkReplicated attribute
@@ -716,7 +783,7 @@ namespace Flax.Build.Plugins
{
if (!p.HasAttribute(NetworkReplicatedAttribute))
continue;
GenerateSerializerType(type, serialize, ref failed, null, p, p.PropertyType, il, networkStream);
GenerateSerializerType(ref context, type, serialize, null, p, p.PropertyType, il);
}
if (serialize)
@@ -726,17 +793,17 @@ namespace Flax.Build.Plugins
return m;
}
private static MethodDefinition GenerateNativeSerializer(TypeDefinition type, bool serialize, ref bool failed, string name, TypeReference voidType, TypeReference networkStreamType)
private static MethodDefinition GenerateNativeSerializer(ref DotnetContext context, TypeDefinition type, bool serialize, string name)
{
ModuleDefinition module = type.Module;
module.GetType("System.IntPtr", out var intPtrType);
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, voidType);
var m = new MethodDefinition(name + "Native", MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, context.VoidType);
m.Parameters.Add(new ParameterDefinition("instancePtr", ParameterAttributes.None, intPtrType));
m.Parameters.Add(new ParameterDefinition("streamPtr", ParameterAttributes.None, intPtrType));
TypeReference networkStream = module.ImportReference(networkStreamType);
TypeReference networkStream = module.ImportReference(context.NetworkStreamType);
ILProcessor il = m.Body.GetILProcessor();
il.Emit(OpCodes.Nop);
il.Body.InitLocals = true;
@@ -747,16 +814,16 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr));
il.Emit(OpCodes.Castclass, type);
il.Emit(OpCodes.Stloc_0);
// NetworkStream stream = (NetworkStream)FlaxEngine.Object.FromUnmanagedPtr(streamPtr)
il.Body.Variables.Add(new VariableDefinition(networkStream));
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr));
il.Emit(OpCodes.Castclass, module.ImportReference(networkStream));
il.Emit(OpCodes.Stloc_1);
// Generate normal serializer
var serializer = GenerateSerializer(type, serialize, ref failed, name, voidType, networkStreamType);
var serializer = GenerateSerializer(ref context, type, serialize, name);
// Call serializer
il.Emit(OpCodes.Ldloc_0);
@@ -796,10 +863,11 @@ namespace Flax.Build.Plugins
}
}
private static void GenerateSerializerType(TypeDefinition type, bool serialize, ref bool failed, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType)
private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il)
{
if (field == null && property == null)
throw new ArgumentException();
TypeDefinition networkStreamType = context.NetworkStreamType.Resolve();
var propertyGetOpCode = OpCodes.Call;
var propertySetOpCode = OpCodes.Call;
if (property != null)
@@ -807,22 +875,29 @@ namespace Flax.Build.Plugins
if (property.GetMethod == null)
{
MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property);
failed = true;
context.Failed = true;
return;
}
if (property.SetMethod == null)
{
MonoCecil.CompilationError($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property);
failed = true;
context.Failed = true;
return;
}
if (property.GetMethod.IsVirtual)
propertyGetOpCode = OpCodes.Callvirt;
if (property.SetMethod.IsVirtual)
propertySetOpCode = OpCodes.Callvirt;
}
ModuleDefinition module = type.Module;
TypeDefinition valueTypeDef = valueType.Resolve();
// Ensure to have valid serialization already generated for that value type (eg. when using custom structure field serialization)
GenerateTypeSerialization(ref context, valueTypeDef);
if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer))
{
// Call NetworkStream method to write/read data
@@ -843,6 +918,7 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Ldarg_1);
m = networkStreamType.GetMethod(serializer.ReadMethod);
}
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
if (!serialize)
{
@@ -966,7 +1042,7 @@ namespace Flax.Build.Plugins
else
il.Emit(propertyGetOpCode, property.GetMethod);
// int num2 = ((array2 != null) ? array2.Length : 0);
// int num2 = ((array2 != null) ? array2.Length : 0);
il.Emit(OpCodes.Dup);
Instruction jmp1 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Brtrue_S, jmp1);
@@ -979,8 +1055,8 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Conv_I4);
il.Append(jmp2);
il.Emit(OpCodes.Stloc, varStart + 0);
// stream.WriteInt32(num2);
// stream.WriteInt32(num2);
il.Emit(OpCodes.Ldarg_1);
il.Emit(OpCodes.Ldloc, varStart + 0);
var m = networkStreamType.GetMethod("WriteInt32");
@@ -1039,16 +1115,16 @@ namespace Flax.Build.Plugins
var m = networkStreamType.GetMethod("ReadInt32");
il.Emit(OpCodes.Callvirt, module.ImportReference(m));
il.Emit(OpCodes.Stloc, varStart + 0);
// System.Array.Resize(ref Array1, num);
// System.Array.Resize(ref Array1, num);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldflda, field);
il.Emit(OpCodes.Ldloc, varStart + 0);
module.TryGetTypeReference("System.Array", out var arrayType);
m = arrayType.Resolve().GetMethod("Resize", 2);
il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType)));
// fixed (int* buffer = Array1)
// fixed (int* buffer = Array1)
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, field);
@@ -1067,8 +1143,8 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Stloc, varStart + 1);
Instruction jmp3 = il.Create(OpCodes.Nop);
il.Emit(OpCodes.Br_S, jmp3);
// stream.ReadBytes((byte*)buffer, num * sizeof(<elementType>));
// stream.ReadBytes((byte*)buffer, num * sizeof(<elementType>));
il.Append(jmp2);
il.Emit(OpCodes.Ldloc, varStart + 2);
il.Emit(OpCodes.Ldc_I4_0);
@@ -1097,14 +1173,18 @@ namespace Flax.Build.Plugins
MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.", field.Resolve());
else
MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication.");
failed = true;
context.Failed = true;
}
}
private static void GenerateDotNetRPCSerializerType(TypeDefinition type, bool serialize, ref bool failed, int localIndex, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType, int streamLocalIndex, Instruction ilStart)
private static void GenerateDotNetRPCSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, int localIndex, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType, int streamLocalIndex, Instruction ilStart)
{
ModuleDefinition module = type.Module;
TypeDefinition valueTypeDef = valueType.Resolve();
// Ensure to have valid serialization already generated for that value type
GenerateTypeSerialization(ref context, valueTypeDef);
if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer))
{
// Call NetworkStream method to write/read data
@@ -1127,7 +1207,7 @@ namespace Flax.Build.Plugins
module.GetType("System.Guid", out var guidType);
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
if (serialize)
{
{
il.InsertBefore(ilStart, il.Create(OpCodes.Nop));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, streamLocalIndex));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldarg, localIndex));
@@ -1209,39 +1289,43 @@ namespace Flax.Build.Plugins
{
// Unknown type
Log.Error($"Not supported type '{valueType.FullName}' for RPC parameter in {type.FullName}.");
failed = true;
context.Failed = true;
}
}
private static void GenerateDotNetRPCBody(TypeDefinition type, MethodDefinition method, CustomAttribute attribute, ref bool failed, TypeReference networkStreamType, List<MethodRPC> methodRPCs)
private static void GenerateDotNetRPCBody(ref DotnetContext context, TypeDefinition type, MethodDefinition method, CustomAttribute attribute, TypeReference networkStreamType)
{
// Validate RPC usage
if (method.IsAbstract)
{
MonoCecil.CompilationError($"Not supported abstract RPC method '{method.FullName}'.", method);
failed = true;
context.Failed = true;
return;
}
if (method.IsVirtual)
{
MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method);
failed = true;
context.Failed = true;
return;
}
ModuleDefinition module = type.Module;
var voidType = module.TypeSystem.Void;
if (method.ReturnType != voidType)
{
MonoCecil.CompilationError($"Not supported non-void RPC method '{method.FullName}'.", method);
failed = true;
context.Failed = true;
return;
}
if (method.IsStatic)
{
MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method);
failed = true;
context.Failed = true;
return;
}
var methodRPC = new MethodRPC();
methodRPC.Type = type;
methodRPC.Method = method;
@@ -1252,21 +1336,24 @@ namespace Flax.Build.Plugins
methodRPC.IsClient = (bool)attribute.ConstructorArguments[1].Value;
methodRPC.Channel = (int)attribute.ConstructorArguments[2].Value;
}
methodRPC.IsServer = (bool)attribute.GetFieldValue("Server", methodRPC.IsServer);
methodRPC.IsClient = (bool)attribute.GetFieldValue("Client", methodRPC.IsClient);
methodRPC.Channel = (int)attribute.GetFieldValue("Channel", methodRPC.Channel);
if (methodRPC.IsServer && methodRPC.IsClient)
{
MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} cannot be both Server and Client.", method);
failed = true;
context.Failed = true;
return;
}
if (!methodRPC.IsServer && !methodRPC.IsClient)
{
MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} needs to have Server or Client specifier.", method);
failed = true;
context.Failed = true;
return;
}
module.GetType("System.IntPtr", out var intPtrType);
module.GetType("FlaxEngine.Object", out var scriptingObjectType);
var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr");
@@ -1294,7 +1381,7 @@ namespace Flax.Build.Plugins
il.Emit(OpCodes.Call, module.ImportReference(fromUnmanagedPtr));
il.Emit(OpCodes.Castclass, networkStream);
il.Emit(OpCodes.Stloc_1);
// Add locals for each RPC parameter
var argsStart = il.Body.Variables.Count;
for (int i = 0; i < method.Parameters.Count; i++)
@@ -1303,9 +1390,10 @@ namespace Flax.Build.Plugins
if (parameter.IsOut)
{
MonoCecil.CompilationError($"Network RPC {method.Name} in {type.FullName} parameter {parameter.Name} cannot be 'out'.", method);
failed = true;
context.Failed = true;
return;
}
var parameterType = parameter.ParameterType;
il.Body.Variables.Add(new VariableDefinition(parameterType));
}
@@ -1315,7 +1403,7 @@ namespace Flax.Build.Plugins
{
var parameter = method.Parameters[i];
var parameterType = parameter.ParameterType;
GenerateDotNetRPCSerializerType(type, false, ref failed, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null);
GenerateDotNetRPCSerializerType(ref context, type, false, argsStart + i, parameterType, il, networkStream.Resolve(), 1, null);
}
// Call RPC method body
@@ -1324,8 +1412,9 @@ namespace Flax.Build.Plugins
{
il.Emit(OpCodes.Ldloc, argsStart + i);
}
il.Emit(OpCodes.Callvirt, method);
il.Emit(OpCodes.Nop);
il.Emit(OpCodes.Ret);
type.Methods.Add(m);
@@ -1371,10 +1460,10 @@ namespace Flax.Build.Plugins
// ||
il.InsertBefore(ilStart, jumpIf2Start);
il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 1));
il.InsertBefore(ilStart, il.Create(OpCodes.Brfalse_S, jumpBodyStart));
il.InsertBefore(ilStart, il.Create(OpCodes.Brfalse, jumpBodyStart));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldloc, varsStart + 2));
il.InsertBefore(ilStart, il.Create(OpCodes.Ldc_I4_2));
il.InsertBefore(ilStart, il.Create(OpCodes.Beq_S, jumpBodyStart));
il.InsertBefore(ilStart, il.Create(OpCodes.Beq, jumpBodyStart));
// {
il.InsertBefore(ilStart, jumpIfBodyStart);
@@ -1391,7 +1480,7 @@ namespace Flax.Build.Plugins
{
var parameter = method.Parameters[i];
var parameterType = parameter.ParameterType;
GenerateDotNetRPCSerializerType(type, true, ref failed, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart);
GenerateDotNetRPCSerializerType(ref context, type, true, i + 1, parameterType, il, networkStream.Resolve(), streamLocalIndex, ilStart);
}
// NetworkReplicator.EndInvokeRPC(this, typeof(<type>), "<name>", stream);
@@ -1418,7 +1507,6 @@ namespace Flax.Build.Plugins
il.InsertBefore(ilStart, tmp);
//il.InsertBefore(ilStart, il.Create(OpCodes.Ret));
il.InsertBefore(ilStart, il.Create(OpCodes.Br, jumpBodyEnd));
}
// if (client && networkMode == NetworkManagerMode.Server) return;
@@ -1439,7 +1527,7 @@ namespace Flax.Build.Plugins
il.InsertBefore(ilStart, jumpBodyStart);
}
methodRPCs.Add(methodRPC);
context.MethodRPCs.Add(methodRPC);
}
}
}