diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index 236ac1113..fec2034d8 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -49,6 +49,7 @@ namespace Flax.Build.Plugins 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"; @@ -555,7 +556,14 @@ namespace Flax.Build.Plugins var isNetworkReplicated = false; foreach (FieldDefinition f in type.Fields) { - if (!f.HasAttribute("FlaxEngine.NetworkReplicatedAttribute")) + if (!f.HasAttribute(NetworkReplicatedAttribute)) + continue; + isNetworkReplicated = true; + break; + } + foreach (PropertyDefinition p in type.Properties) + { + if (!p.HasAttribute(NetworkReplicatedAttribute)) continue; isNetworkReplicated = true; break; @@ -697,9 +705,17 @@ namespace Flax.Build.Plugins // Serialize all type fields marked with NetworkReplicated attribute foreach (FieldDefinition f in type.Fields) { - if (!f.HasAttribute("FlaxEngine.NetworkReplicatedAttribute")) + if (!f.HasAttribute(NetworkReplicatedAttribute)) continue; - GenerateSerializerType(type, serialize, ref failed, f, f.FieldType, il, networkStream); + GenerateSerializerType(type, serialize, ref failed, f, null, f.FieldType, il, networkStream); + } + + // Serialize all type properties marked with NetworkReplicated attribute + foreach (PropertyDefinition p in type.Properties) + { + if (!p.HasAttribute(NetworkReplicatedAttribute)) + continue; + GenerateSerializerType(type, serialize, ref failed, null, p, p.PropertyType, il, networkStream); } if (serialize) @@ -779,8 +795,31 @@ namespace Flax.Build.Plugins } } - private static void GenerateSerializerType(TypeDefinition type, bool serialize, ref bool failed, FieldReference field, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType) + private static void GenerateSerializerType(TypeDefinition type, bool serialize, ref bool failed, FieldReference field, PropertyDefinition property, TypeReference valueType, ILProcessor il, TypeDefinition networkStreamType) { + if (field == null && property == null) + throw new ArgumentException(); + var propertyGetOpCode = OpCodes.Call; + var propertySetOpCode = OpCodes.Call; + if (property != null) + { + if (property.GetMethod == null) + { + Log.Error($"Missing getter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication."); + failed = true; + return; + } + if (property.SetMethod == null) + { + Log.Error($"Missing setter method for property '{property.Name}' of type {valueType.FullName} in {type.FullName} for automatic replication."); + 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(); if (_inBuildSerializers.TryGetValue(valueType.FullName, out var serializer)) @@ -791,7 +830,10 @@ namespace Flax.Build.Plugins { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + il.Emit(OpCodes.Ldfld, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); m = networkStreamType.GetMethod(serializer.WriteMethod); } else @@ -802,7 +844,12 @@ namespace Flax.Build.Plugins } il.Emit(OpCodes.Callvirt, module.ImportReference(m)); if (!serialize) - il.Emit(OpCodes.Stfld, field); + { + if (field != null) + il.Emit(OpCodes.Stfld, field); + else + il.Emit(propertySetOpCode, property.SetMethod); + } } else if (valueType.IsScriptingObject()) { @@ -813,7 +860,10 @@ namespace Flax.Build.Plugins { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + il.Emit(OpCodes.Ldfld, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); il.Emit(OpCodes.Dup); Instruction jmp1 = il.Create(OpCodes.Nop); il.Emit(OpCodes.Brtrue_S, jmp1); @@ -849,7 +899,10 @@ namespace Flax.Build.Plugins var tryFind = scriptingObjectType.Resolve().GetMethod("TryFind", 2); il.Emit(OpCodes.Call, module.ImportReference(tryFind)); il.Emit(OpCodes.Castclass, valueType); - il.Emit(OpCodes.Stfld, field); + if (field != null) + il.Emit(OpCodes.Stfld, field); + else + il.Emit(propertySetOpCode, property.SetMethod); } } else if (valueTypeDef.IsEnum) @@ -860,7 +913,10 @@ namespace Flax.Build.Plugins { il.Emit(OpCodes.Ldarg_1); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + il.Emit(OpCodes.Ldfld, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); var m = networkStreamType.GetMethod("WriteUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); } @@ -870,7 +926,10 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Ldarg_1); var m = networkStreamType.GetMethod("ReadUInt32"); il.Emit(OpCodes.Callvirt, module.ImportReference(m)); - il.Emit(OpCodes.Stfld, field); + if (field != null) + il.Emit(OpCodes.Stfld, field); + else + il.Emit(propertySetOpCode, property.SetMethod); } } else if (valueType.IsValueType) @@ -878,7 +937,10 @@ namespace Flax.Build.Plugins // Invoke structure generated serializer // TODO: check if this type has generated serialization code il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldflda, field); + if (field != null) + il.Emit(OpCodes.Ldflda, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); il.Emit(OpCodes.Ldarg_1); var m = valueTypeDef.GetMethod(serialize ? Thunk1 : Thunk2); il.Emit(OpCodes.Call, module.ImportReference(m)); @@ -898,7 +960,10 @@ namespace Flax.Build.Plugins // [] array2 = Array1; il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + il.Emit(OpCodes.Ldfld, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); // int num2 = ((array2 != null) ? array2.Length : 0); il.Emit(OpCodes.Dup); @@ -923,7 +988,10 @@ namespace Flax.Build.Plugins // fixed (* bytes2 = Array1) il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_0); - il.Emit(OpCodes.Ldfld, field); + if (field != null) + il.Emit(OpCodes.Ldfld, field); + else + il.Emit(propertyGetOpCode, property.GetMethod); il.Emit(OpCodes.Dup); il.Emit(OpCodes.Stloc, varStart + 2); Instruction jmp3 = il.Create(OpCodes.Nop); @@ -961,6 +1029,9 @@ namespace Flax.Build.Plugins } else { + if (field == null) + throw new NotImplementedException("TODO: add support for array property replication"); + // int num = stream.ReadInt32(); il.Emit(OpCodes.Nop); il.Emit(OpCodes.Ldarg_1); @@ -1019,7 +1090,7 @@ namespace Flax.Build.Plugins else { // Unknown type - Log.Error($"Not supported type '{valueType.FullName}' on {field.Name} in {type.FullName} for automatic replication."); + Log.Error($"Not supported type '{valueType.FullName}' on {(field?.Name ?? property.Name)} in {type.FullName} for automatic replication."); failed = true; } } diff --git a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs index 6bedab2e6..eb9ced342 100644 --- a/Source/Tools/Flax.Build/Utilities/MonoCecil.cs +++ b/Source/Tools/Flax.Build/Utilities/MonoCecil.cs @@ -11,12 +11,7 @@ namespace Flax.Build /// internal static class MonoCecil { - public static bool HasAttribute(this TypeDefinition type, string fullName) - { - return type.CustomAttributes.Any(x => x.AttributeType.FullName == fullName); - } - - public static bool HasAttribute(this FieldDefinition type, string fullName) + public static bool HasAttribute(this ICustomAttributeProvider type, string fullName) { return type.CustomAttributes.Any(x => x.AttributeType.FullName == fullName); }