Add support for C# properties automatic replication codegen

This commit is contained in:
Wojciech Figat
2022-11-30 16:19:32 +01:00
parent a819d657db
commit e5b4ce06b9
2 changed files with 86 additions and 20 deletions

View File

@@ -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
// <elementType>[] 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 (<elementType>* 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;
}
}