diff --git a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs index fcc1ca83b..5dccfccc4 100644 --- a/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs +++ b/Source/Tools/Flax.Build/Build/Plugins/NetworkingPlugin.cs @@ -53,6 +53,7 @@ namespace Flax.Build.Plugins internal const string NetworkRpc = "NetworkRpc"; private const string Thunk1 = "INetworkSerializable_Serialize"; private const string Thunk2 = "INetworkSerializable_Deserialize"; + private static readonly Dictionary _inBuildSerializers = new Dictionary() { { "System.Boolean", new InBuildSerializer("WriteBoolean", "ReadBoolean") }, @@ -127,6 +128,7 @@ namespace Flax.Build.Plugins { fields = structInfo.Fields; } + bool useReplication = false, useRpc = false; if (fields != null) { @@ -139,6 +141,7 @@ namespace Flax.Build.Plugins } } } + if (properties != null) { foreach (var propertyInfo in properties) @@ -150,6 +153,7 @@ namespace Flax.Build.Plugins } } } + if (functions != null) { foreach (var functionInfo in functions) @@ -161,20 +165,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 +288,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 +299,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 +315,7 @@ namespace Flax.Build.Plugins } } } + contents.AppendLine(" }"); contents.AppendLine(); } @@ -326,9 +334,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 +374,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 +384,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 +414,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 +423,7 @@ namespace Flax.Build.Plugins { functions = classInfo.Functions; } + if (functions != null) { foreach (var functionInfo in functions) @@ -430,16 +441,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 +473,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 +504,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(); @@ -503,6 +514,7 @@ namespace Flax.Build.Plugins if (file.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) searchDirectories.Add(Path.GetDirectoryName(file)); } + foreach (var e in searchDirectories) assemblyResolver.AddSearchDirectory(e); @@ -554,6 +566,7 @@ namespace Flax.Build.Plugins } } } + var isNetworkReplicated = false; foreach (FieldDefinition f in type.Fields) { @@ -562,6 +575,7 @@ namespace Flax.Build.Plugins isNetworkReplicated = true; break; } + foreach (PropertyDefinition p in type.Properties) { if (!p.HasAttribute(NetworkReplicatedAttribute)) @@ -569,6 +583,7 @@ namespace Flax.Build.Plugins isNetworkReplicated = true; break; } + if (type.IsValueType) { if (isINetworkSerializable) @@ -597,6 +612,7 @@ namespace Flax.Build.Plugins modified = true; } } + if (failed) throw new Exception($"Failed to generate network replication for assembly {assemblyPath}"); if (!modified) @@ -650,6 +666,7 @@ namespace Flax.Build.Plugins il.Emit(OpCodes.Newobj, module.ImportReference(serializeFuncCtor)); il.Emit(OpCodes.Call, module.ImportReference(addSerializer)); } + foreach (var e in methodRPCs) { // NetworkReplicator.AddRPC(typeof(), "", _Execute, , , ); @@ -664,13 +681,14 @@ 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 }); } } @@ -696,7 +714,7 @@ namespace Flax.Build.Plugins TypeDefinition networkStream = networkStreamType.Resolve(); 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()) { @@ -747,14 +765,14 @@ 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); @@ -810,17 +828,20 @@ namespace Flax.Build.Plugins 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; 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)) @@ -843,6 +864,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 +988,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 +1001,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 +1061,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 +1089,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()); + + // stream.ReadBytes((byte*)buffer, num * sizeof()); il.Append(jmp2); il.Emit(OpCodes.Ldloc, varStart + 2); il.Emit(OpCodes.Ldc_I4_0); @@ -1127,7 +1149,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)); @@ -1222,12 +1244,14 @@ namespace Flax.Build.Plugins failed = true; return; } + if (method.IsVirtual) { MonoCecil.CompilationError($"Not supported virtual RPC method '{method.FullName}'.", method); failed = true; return; } + ModuleDefinition module = type.Module; var voidType = module.TypeSystem.Void; if (method.ReturnType != voidType) @@ -1236,12 +1260,14 @@ namespace Flax.Build.Plugins failed = true; return; } + if (method.IsStatic) { MonoCecil.CompilationError($"Not supported static RPC method '{method.FullName}'.", method); failed = true; return; } + var methodRPC = new MethodRPC(); methodRPC.Type = type; methodRPC.Method = method; @@ -1252,6 +1278,7 @@ 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); @@ -1261,12 +1288,14 @@ namespace Flax.Build.Plugins 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; return; } + module.GetType("System.IntPtr", out var intPtrType); module.GetType("FlaxEngine.Object", out var scriptingObjectType); var fromUnmanagedPtr = scriptingObjectType.Resolve().GetMethod("FromUnmanagedPtr"); @@ -1294,7 +1323,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++) @@ -1306,6 +1335,7 @@ namespace Flax.Build.Plugins failed = true; return; } + var parameterType = parameter.ParameterType; il.Body.Variables.Add(new VariableDefinition(parameterType)); } @@ -1324,8 +1354,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); @@ -1418,7 +1449,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;