From 2e22abaff95822d97ed59b9026420ecc504ea4c5 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Sun, 16 Apr 2023 14:56:54 +0200 Subject: [PATCH] Refactor C# network replication codegen to be more extendable based on value context source --- .../Build/Plugins/NetworkingPlugin.cs | 578 +++++++++++------- 1 file changed, 342 insertions(+), 236 deletions(-) diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index f4f69571a..2b5058b0e 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -347,14 +347,14 @@ namespace Flax.Build.Plugins contents.AppendLine(); } - private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) + private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo type) { // TODO: what if type fields have custom replication settings (eg. compression)? type.EnsureInited(buildData); return type.IsPod; } - private bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) + private static bool IsRawPOD(Builder.BuildData buildData, ApiTypeInfo caller, ApiTypeInfo apiType, TypeInfo type) { if (type.IsPod(buildData, caller)) { @@ -365,6 +365,12 @@ namespace Flax.Build.Plugins return false; } + private static bool IsRawPOD(TypeReference type) + { + // TODO: + return type.IsValueType; + } + private void OnGenerateCppTypeSerializeData(Builder.BuildData buildData, ApiTypeInfo caller, StringBuilder contents, TypeInfo type, string name, bool serialize) { var apiType = BindingsGenerator.FindApiTypeInfo(buildData, type, caller); @@ -789,7 +795,7 @@ namespace Flax.Build.Plugins { if (!f.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(ref context, type, serialize, f, null, f.FieldType, il); + GenerateSerializerType(ref context, type, serialize, f.FieldType, il, new DotnetValueContext(f)); } // Serialize all type properties marked with NetworkReplicated attribute @@ -797,7 +803,7 @@ namespace Flax.Build.Plugins { if (!p.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(ref context, type, serialize, null, p, p.PropertyType, il); + GenerateSerializerType(ref context, type, serialize, p.PropertyType, il, new DotnetValueContext(p)); } if (serialize) @@ -877,53 +883,346 @@ namespace Flax.Build.Plugins } } - private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il) + private struct DotnetValueContext { - if (field == null && property == null) - throw new ArgumentException(); - TypeDefinition networkStreamType = context.NetworkStreamType.Resolve(); - var propertyGetOpCode = OpCodes.Call; - var propertySetOpCode = OpCodes.Call; - if (property != null) + public FieldReference Field; + public PropertyDefinition Property; + public int LocalVarIndex; + + public OpCode PropertyGetOpCode { - if (property.GetMethod == null) + get { - MonoCecil.CompilationError($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); + var propertyGetOpCode = OpCodes.Call; + if (Property != null && Property.GetMethod.IsVirtual) + propertyGetOpCode = OpCodes.Callvirt; + return propertyGetOpCode; + } + } + + public OpCode PropertySetOpCode + { + get + { + var propertyGetOpCode = OpCodes.Call; + if (Property != null && Property.GetMethod.IsVirtual) + propertyGetOpCode = OpCodes.Callvirt; + return propertyGetOpCode; + } + } + + public DotnetValueContext(FieldDefinition field) + { + Field = field; + Property = null; + LocalVarIndex = -1; + } + + public DotnetValueContext(PropertyDefinition property) + { + Field = null; + Property = property; + LocalVarIndex = -1; + } + + public void GetProperty(ILProcessor il, int propertyVar) + { + if (Property != null) + { + // [] array = ArrayProperty; + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + il.Emit(OpCodes.Stloc, propertyVar); + LocalVarIndex = propertyVar; + } + } + + public void SetProperty(ILProcessor il) + { + if (Property != null) + { + // ArrayProperty = array + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldloc, LocalVarIndex); + il.Emit(PropertySetOpCode, Property.SetMethod); + } + } + + public void Load(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldfld, Field); + } + else if (Property != null && LocalVarIndex == -1) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + } + else + { + il.Emit(OpCodes.Ldloc, LocalVarIndex); + } + } + + public void LoadAddress(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Ldflda, Field); + } + else if (Property != null && LocalVarIndex == -1) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(PropertyGetOpCode, Property.GetMethod); + } + else + { + il.Emit(OpCodes.Ldloca_S, (byte)LocalVarIndex); + } + } + + public void Store(ILProcessor il) + { + if (Field != null) + { + il.Emit(OpCodes.Stfld, Field); + } + else if (Property != null) + { + il.Emit(PropertySetOpCode, Property.SetMethod); + } + else + { + throw new NotImplementedException("TODO: storing local variable value"); + } + } + } + + private static void GenerateSerializerType(ref DotnetContext context, TypeDefinition type, bool serialize, TypeReference valueType, ILProcessor il, DotnetValueContext valueContext) + { + if (valueContext.Property != null) + { + if (valueContext.Property.GetMethod == null) + { + MonoCecil.CompilationError($"Missing getter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property); context.Failed = true; return; } - if (property.SetMethod == null) + if (valueContext.Property.SetMethod == null) { - MonoCecil.CompilationError($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", property); + MonoCecil.CompilationError($"Missing setter method for property '{valueContext.Property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication.", valueContext.Property); 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(); + TypeDefinition networkStreamType = context.NetworkStreamType.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)) + if (valueType.IsArray) + { + var elementType = valueType.GetElementType(); + var isRawPod = IsRawPOD(elementType); // Whether to use raw memory copy (eg. int, enum, Vector2) + var varStart = il.Body.Variables.Count; + module.GetType("System.Int32", out var intType); + il.Body.Variables.Add(new VariableDefinition(intType)); // [0] int length + if (isRawPod) + { + il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); // [1] * + il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); // [2] [] pinned + } + else + { + il.Body.Variables.Add(new VariableDefinition(intType)); // [1] int idx + il.Body.Variables.Add(new VariableDefinition(elementType)); // [2] + } + if (valueContext.Property != null) + il.Body.Variables.Add(new VariableDefinition(valueType)); // [3] [] + il.Body.InitLocals = true; + valueContext.GetProperty(il, varStart + 3); + if (serialize) + { + // [] array = Array; + il.Emit(OpCodes.Nop); + valueContext.Load(il); + + // int length = ((array != null) ? array.Length : 0); + il.Emit(OpCodes.Dup); + Instruction jmp1 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp1); + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldc_I4_0); + Instruction jmp2 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp2); + il.Append(jmp1); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + il.Append(jmp2); + il.Emit(OpCodes.Stloc, varStart + 0); + + // stream.WriteInt32(length); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 0); + var m = networkStreamType.GetMethod("WriteInt32"); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + + il.Emit(OpCodes.Nop); + if (isRawPod) + { + // fixed (* bytes2 = Array) + valueContext.Load(il); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, varStart + 2); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brfalse_S, jmp3); + il.Emit(OpCodes.Ldloc_2); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + Instruction jmp4 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp4); + il.Append(jmp3); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp5 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp5); + + // stream.WriteBytes((byte*)bytes, length * sizeof())); + il.Append(jmp4); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, elementType); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + il.Append(jmp5); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 1); + il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Sizeof, elementType); + il.Emit(OpCodes.Mul); + m = networkStreamType.GetMethod("WriteBytes", 2); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Stloc, varStart + 2); + } + else + { + // int idx = 0 + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp3); + + // element = array[idx]; + Instruction jmp4 = il.Create(OpCodes.Nop); + il.Append(jmp4); + valueContext.Load(il); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldelem_Ref); + il.Emit(OpCodes.Stloc, varStart + 2); // + + // TODO: serialize element type from [varStart + 2] + + il.Emit(OpCodes.Nop); + + // idx++ + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldc_I4_1); + il.Emit(OpCodes.Add); + il.Emit(OpCodes.Stloc, varStart + 1); // idx + + // idx < num + il.Append(jmp3); + il.Emit(OpCodes.Ldloc, varStart + 1); // idx + il.Emit(OpCodes.Ldloc, varStart + 0); // length + il.Emit(OpCodes.Clt); + il.Emit(OpCodes.Brtrue_S, jmp4); + } + } + else + { + // int length = stream.ReadInt32(); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldarg_1); + var m = networkStreamType.GetMethod("ReadInt32"); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Stloc, varStart + 0); + + // System.Array.Resize(ref Array, length); + valueContext.LoadAddress(il); + il.Emit(OpCodes.Ldloc, varStart + 0); + module.TryGetTypeReference("System.Array", out var arrayType); + if (arrayType == null) + module.GetType("System.Array", out arrayType); + m = arrayType.Resolve().GetMethod("Resize", 2); + il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); + + il.Emit(OpCodes.Nop); + if (isRawPod) + { + // fixed (* buffer = Array) + valueContext.Load(il); + il.Emit(OpCodes.Dup); + il.Emit(OpCodes.Stloc, varStart + 2); + Instruction jmp1 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brfalse_S, jmp1); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldlen); + il.Emit(OpCodes.Conv_I4); + Instruction jmp2 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Brtrue_S, jmp2); + il.Append(jmp1); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + Instruction jmp3 = il.Create(OpCodes.Nop); + il.Emit(OpCodes.Br_S, jmp3); + + // stream.ReadBytes((byte*)buffer, num * sizeof()); + il.Append(jmp2); + il.Emit(OpCodes.Ldloc, varStart + 2); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Ldelema, elementType); + il.Emit(OpCodes.Conv_U); + il.Emit(OpCodes.Stloc, varStart + 1); + il.Append(jmp3); + il.Emit(OpCodes.Ldarg_1); + il.Emit(OpCodes.Ldloc, varStart + 1); + il.Emit(OpCodes.Ldloc, varStart + 0); + il.Emit(OpCodes.Sizeof, elementType); + il.Emit(OpCodes.Mul); + m = networkStreamType.GetMethod("ReadBytes", 2); + il.Emit(OpCodes.Callvirt, module.ImportReference(m)); + il.Emit(OpCodes.Nop); + il.Emit(OpCodes.Ldnull); + il.Emit(OpCodes.Stloc, varStart + 2); + } + else + { + // TODO: deserialize item-by-item + } + + valueContext.SetProperty(il); + } + } + else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data MethodDefinition m; if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); m = networkStreamType.GetMethod(serializer.WriteMethod); } else @@ -936,10 +1235,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Callvirt, module.ImportReference(m)); if (!serialize) { - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueType.IsScriptingObject()) @@ -950,11 +1246,7 @@ namespace Flax.Build.Plugins if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); il.Emit(OpCodes.Dup); Instruction jmp1 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Brtrue_S, jmp1); @@ -990,10 +1282,7 @@ namespace Flax.Build.Plugins var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2); il.Emit(OpCodes.Call, module.ImportReference(tryFind)); il.Emit(OpCodes.Castclass, valueType); - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueTypeDef.IsEnum) @@ -1003,11 +1292,7 @@ namespace Flax.Build.Plugins if (serialize) { il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldfld, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.Load(il); var m = networkStreamType.GetMethod("WriteUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); } @@ -1017,209 +1302,24 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldarg_1); var m = networkStreamType.GetMethod("ReadUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - if (field != null) - il.Emit(OpCodes.Stfld, field); - else - il.Emit(propertySetOpCode, property.SetMethod); + valueContext.Store(il); } } else if (valueType.IsValueType) { // Invoke structure generated serializer - // TODO: check if this type has generated serialization code - il.Emit(OpCodes.Ldarg_0); - if (field != null) - il.Emit(OpCodes.Ldflda, field); - else - il.Emit(propertyGetOpCode, property.GetMethod); + valueContext.LoadAddress(il); il.Emit(OpCodes.Ldarg_1); var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2); il.Emit(OpCodes.Call, module.ImportReference(m)); } - else if (valueType.IsArray && valueType.GetElementType().IsValueType) - { - // TODO: support any array type by iterating over elements (separate serialize for each one) - var elementType = valueType.GetElementType(); - var varStart = il.Body.Variables.Count; - module.GetType("System.Int32", out var intType); - il.Body.Variables.Add(new VariableDefinition(intType)); - il.Body.Variables.Add(new VariableDefinition(new PointerType(elementType))); - il.Body.Variables.Add(new VariableDefinition(new PinnedType(valueType))); - if (property != null) - il.Body.Variables.Add(new VariableDefinition(valueType)); - il.Body.InitLocals = true; - if (serialize) - { - // [] array = Array; - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - { - il.Emit(OpCodes.Ldfld, field); - } - else - { - // [] array = ArrayProperty; - il.Emit(propertyGetOpCode, property.GetMethod); - il.Emit(OpCodes.Stloc, varStart + 3); - il.Emit(OpCodes.Ldloc, varStart + 3); - } - - // int num2 = ((array != null) ? array.Length : 0); - il.Emit(OpCodes.Dup); - Instruction jmp1 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp1); - il.Emit(OpCodes.Pop); - il.Emit(OpCodes.Ldc_I4_0); - Instruction jmp2 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp2); - il.Append(jmp1); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - il.Append(jmp2); - il.Emit(OpCodes.Stloc, varStart + 0); - - // stream.WriteInt32(num2); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 0); - var m = networkStreamType.GetMethod("WriteInt32"); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - - // fixed (* bytes2 = Array) - il.Emit(OpCodes.Nop); - if (field != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - } - else - { - il.Emit(OpCodes.Ldloc, varStart + 3); - } - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, varStart + 2); - Instruction jmp3 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brfalse_S, jmp3); - il.Emit(OpCodes.Ldloc_2); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - Instruction jmp4 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp4); - il.Append(jmp3); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - Instruction jmp5 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp5); - - // stream.WriteBytes((byte*)bytes, num * sizeof())); - il.Append(jmp4); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, elementType); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - il.Append(jmp5); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); - il.Emit(OpCodes.Ldloc, varStart + 0); - il.Emit(OpCodes.Sizeof, elementType); - il.Emit(OpCodes.Mul); - m = networkStreamType.GetMethod("WriteBytes", 2); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, varStart + 2); - } - else - { - // int num = stream.ReadInt32(); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldarg_1); - var m = networkStreamType.GetMethod("ReadInt32"); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Stloc, varStart + 0); - - // System.Array.Resize(ref ArrayField, num); - il.Emit(OpCodes.Ldarg_0); - if (field != null) - { - il.Emit(OpCodes.Ldflda, field); - } - else - { - // [] array = ArrayProperty; - il.Emit(propertyGetOpCode, property.GetMethod); - il.Emit(OpCodes.Stloc, varStart + 3); - il.Emit(OpCodes.Ldloca_S, (byte)(varStart + 3)); - } - il.Emit(OpCodes.Ldloc, varStart + 0); - module.TryGetTypeReference("System.Array", out var arrayType); - if (arrayType == null) - module.GetType("System.Array", out arrayType); - m = arrayType.Resolve().GetMethod("Resize", 2); - il.Emit(OpCodes.Call, module.ImportReference(m.InflateGeneric(elementType))); - - // fixed (* buffer = Array) - il.Emit(OpCodes.Nop); - if (field != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); - } - else - { - il.Emit(OpCodes.Ldloc, varStart + 3); - } - il.Emit(OpCodes.Dup); - il.Emit(OpCodes.Stloc, varStart + 2); - Instruction jmp1 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brfalse_S, jmp1); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldlen); - il.Emit(OpCodes.Conv_I4); - Instruction jmp2 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Brtrue_S, jmp2); - il.Append(jmp1); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - Instruction jmp3 = il.Create(OpCodes.Nop); - il.Emit(OpCodes.Br_S, jmp3); - - // stream.ReadBytes((byte*)buffer, num * sizeof()); - il.Append(jmp2); - il.Emit(OpCodes.Ldloc, varStart + 2); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Ldelema, elementType); - il.Emit(OpCodes.Conv_U); - il.Emit(OpCodes.Stloc, varStart + 1); - il.Append(jmp3); - il.Emit(OpCodes.Ldarg_1); - il.Emit(OpCodes.Ldloc, varStart + 1); - il.Emit(OpCodes.Ldloc, varStart + 0); - il.Emit(OpCodes.Sizeof, elementType); - il.Emit(OpCodes.Mul); - m = networkStreamType.GetMethod("ReadBytes", 2); - il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Nop); - il.Emit(OpCodes.Ldnull); - il.Emit(OpCodes.Stloc, varStart + 2); - if (property != null) - { - il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldloc, varStart + 3); - il.Emit(propertySetOpCode, property.SetMethod); - } - } - } else { // Unknown type - if (property != null) - MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {property.Name} in {type.FullName} for automatic replication.", property); - else if (field != null) - MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication.", field.Resolve()); + if (valueContext.Property != null) + MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Property.Name} in {type.FullName} for automatic replication.", valueContext.Property); + else if (valueContext.Field != null) + MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' on {valueContext.Field.Name} in {type.FullName} for automatic replication.", valueContext.Field.Resolve()); else MonoCecil.CompilationError($"Not supported type '{valueType.FullName}' for automatic replication."); context.Failed = true; @@ -1234,7 +1334,13 @@ namespace Flax.Build.Plugins // Ensure to have valid serialization already generated for that value type GenerateTypeSerialization(ref context, valueTypeDef); - if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) + if (type.IsArray) + { + // TODO: refactor network stream read/write to share code between replication and rpcs + Log.Error($"Not supported type '{valueType.FullName}' for RPC parameter in {type.FullName}."); + context.Failed = true; + } + else if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) { // Call NetworkStream method to write/read data if (serialize)