Skip to content

Commit

Permalink
perf: Remove reflection from NetworkBehaviour code to improve perform…
Browse files Browse the repository at this point in the history
…ance. [MTT-1968] (#2522)

* perf: Remove reflection from NetworkBehaviour code to improve performance.

* changelog

* - Added some comments to clarify some ILPP code
- Added an extra error handler to catch an edge case I missed before
- Added a bit more info to the changelog
- Fixed testproject-tools-integration tests

* Apply suggestions from code review

Co-authored-by: Fatih Mar <[email protected]>

---------

Co-authored-by: Fatih Mar <[email protected]>
  • Loading branch information
ShadauxCat and 0xFA11 authored Apr 25, 2023
1 parent 8df4e98 commit 8707582
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 46 deletions.
1 change: 1 addition & 0 deletions com.unity.netcode.gameobjects/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)

## Changed
- Improved performance of NetworkBehaviour initialization by replacing reflection when initializing NetworkVariables with compile-time code generation, which should help reduce hitching during additive scene loads. (#2522)


## [1.4.0] - 2023-04-10
Expand Down
55 changes: 55 additions & 0 deletions com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Unity.CompilationPipeline.Common.Diagnostics;
using Unity.CompilationPipeline.Common.ILPostProcessing;
using UnityEngine;
using Object = System.Object;

namespace Unity.Netcode.Editor.CodeGen
{
Expand Down Expand Up @@ -112,6 +113,60 @@ public static string FullNameWithGenericParameters(this TypeReference typeRefere

return name;
}
public static TypeReference MakeGenericType(this TypeReference self, params TypeReference[] arguments)
{
if (self.GenericParameters.Count != arguments.Length)
{
throw new ArgumentException();
}

var instance = new GenericInstanceType(self);
foreach (var argument in arguments)
{
instance.GenericArguments.Add(argument);
}

return instance;
}

public static MethodReference MakeGeneric(this MethodReference self, params TypeReference[] arguments)
{
var reference = new MethodReference(self.Name, self.ReturnType)
{
DeclaringType = self.DeclaringType.MakeGenericType(arguments),
HasThis = self.HasThis,
ExplicitThis = self.ExplicitThis,
CallingConvention = self.CallingConvention,
};

foreach (var parameter in self.Parameters)
{
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
}

foreach (var generic_parameter in self.GenericParameters)
{
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
}

return reference;
}

public static bool IsSubclassOf(this TypeReference typeReference, TypeReference baseClass)
{
var type = typeReference.Resolve();
if (type.BaseType == null || type.BaseType.Name == nameof(Object))
{
return false;
}

if (type.BaseType.Resolve() == baseClass.Resolve())
{
return true;
}

return type.BaseType.IsSubclassOf(baseClass);
}

public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
{
Expand Down
180 changes: 180 additions & 0 deletions com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)

foreach (var type in m_WrappedNetworkVariableTypes)
{
if (type.Resolve() == null)
{
continue;
}

if (IsSpecialCaseType(type))
{
continue;
Expand Down Expand Up @@ -251,11 +256,15 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
private FieldReference m_NetworkManager_rpc_name_table_FieldRef;
private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef;
private TypeReference m_NetworkBehaviour_TypeRef;
private TypeReference m_NetworkVariableBase_TypeRef;
private MethodReference m_NetworkVariableBase_Initialize_MethodRef;
private MethodReference m_NetworkBehaviour___nameNetworkVariable_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef;
private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef;
private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef;
private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef;
private FieldReference m_NetworkBehaviour_NetworkVariableFields_FieldRef;
private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef;
private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef;
private MethodReference m_NetworkHandlerDelegateCtor_MethodRef;
Expand All @@ -275,6 +284,9 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;

private MethodReference m_ExceptionCtorMethodReference;
private MethodReference m_List_NetworkVariableBase_Add;

private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
Expand Down Expand Up @@ -348,12 +360,17 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table);

private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage);
private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields);
private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc);
private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc);
private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc);
private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc);
private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables);
private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager);
private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId);
private const string k_NetworkBehaviour___nameNetworkVariable = nameof(NetworkBehaviour.__nameNetworkVariable);

private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize);

private const string k_RpcAttribute_Delivery = nameof(RpcAttribute.Delivery);
private const string k_ServerRpcAttribute_RequireOwnership = nameof(ServerRpcAttribute.RequireOwnership);
Expand All @@ -379,6 +396,7 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)

TypeDefinition networkManagerTypeDef = null;
TypeDefinition networkBehaviourTypeDef = null;
TypeDefinition networkVariableBaseTypeDef = null;
TypeDefinition networkHandlerDelegateTypeDef = null;
TypeDefinition rpcParamsTypeDef = null;
TypeDefinition serverRpcParamsTypeDef = null;
Expand All @@ -402,6 +420,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
continue;
}

if (networkVariableBaseTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableBase))
{
networkVariableBaseTypeDef = netcodeTypeDef;
continue;
}

if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
{
networkHandlerDelegateTypeDef = netcodeTypeDef;
Expand Down Expand Up @@ -548,6 +572,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
case k_NetworkBehaviour_endSendClientRpc:
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
case k_NetworkBehaviour___nameNetworkVariable:
m_NetworkBehaviour___nameNetworkVariable_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
}
}

Expand All @@ -558,6 +585,21 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
case k_NetworkBehaviour_rpc_exec_stage:
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
case k_NetworkBehaviour_NetworkVariableFields:
m_NetworkBehaviour_NetworkVariableFields_FieldRef = moduleDefinition.ImportReference(fieldDef);
break;
}
}


m_NetworkVariableBase_TypeRef = moduleDefinition.ImportReference(networkVariableBaseTypeDef);
foreach (var methodDef in networkVariableBaseTypeDef.Methods)
{
switch (methodDef.Name)
{
case k_NetworkVariableBase_Initialize:
m_NetworkVariableBase_Initialize_MethodRef = moduleDefinition.ImportReference(methodDef);
break;
}
}

Expand Down Expand Up @@ -785,6 +827,16 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
}
}

// Standard types are really hard to reliably find using the Mono Cecil way, they resolve differently in Mono vs .NET Core
// Importing with typeof() is less dangerous for standard framework types though, so we can just do it
var exceptionType = typeof(Exception);
var exceptionCtor = exceptionType.GetConstructor(new[] { typeof(string) });
m_ExceptionCtorMethodReference = m_MainModule.ImportReference(exceptionCtor);

var listType = typeof(List<NetworkVariableBase>);
var addMethod = listType.GetMethod(nameof(List<NetworkVariableBase>.Add), new[] { typeof(NetworkVariableBase) });
m_List_NetworkVariableBase_Add = moduleDefinition.ImportReference(addMethod);

return true;
}

Expand Down Expand Up @@ -931,6 +983,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
}
}

GenerateVariableInitialization(typeDefinition);

if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
{
var fieldTypes = new List<TypeReference>();
Expand Down Expand Up @@ -1889,6 +1943,132 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
}

private void GenerateVariableInitialization(TypeDefinition type)
{
foreach (var methodDefinition in type.Methods)
{
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
{
// If this hits, we've already generated the method for this class because a child class got processed first.
return;
}
}

var method = new MethodDefinition(
k_NetworkBehaviour___initializeVariables,
MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig,
m_MainModule.TypeSystem.Void);

var processor = method.Body.GetILProcessor();

method.Body.Variables.Add(new VariableDefinition(m_MainModule.TypeSystem.Boolean));

processor.Emit(OpCodes.Nop);

foreach (var fieldDefinition in type.Fields)
{
FieldReference field = fieldDefinition;
if (type.HasGenericParameters)
{
var genericType = new GenericInstanceType(fieldDefinition.DeclaringType);
foreach (var parameter in fieldDefinition.DeclaringType.GenericParameters)
{
genericType.GenericArguments.Add(parameter);
}
field = new FieldReference(fieldDefinition.Name, fieldDefinition.FieldType, genericType);
}
if (field.FieldType.IsSubclassOf(m_NetworkVariableBase_TypeRef))
{
// if({variable} != null) {
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, field);
processor.Emit(OpCodes.Ldnull);
processor.Emit(OpCodes.Ceq);
processor.Emit(OpCodes.Stloc_0);
processor.Emit(OpCodes.Ldloc_0);

var afterThrowInstruction = processor.Create(OpCodes.Nop);

processor.Emit(OpCodes.Brfalse, afterThrowInstruction);

// throw new Exception("...");
processor.Emit(OpCodes.Nop);
processor.Emit(OpCodes.Ldstr, $"{type.Name}.{field.Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
processor.Emit(OpCodes.Newobj, m_ExceptionCtorMethodReference);
processor.Emit(OpCodes.Throw);

// }
processor.Append(afterThrowInstruction);

// {variable}.Initialize(this);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, field);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Callvirt, m_NetworkVariableBase_Initialize_MethodRef);

// __nameNetworkVariable({variable}, "{variable}");
processor.Emit(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, field);
processor.Emit(OpCodes.Ldstr, field.Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty));
processor.Emit(OpCodes.Call, m_NetworkBehaviour___nameNetworkVariable_MethodRef);

// NetworkVariableFields.Add({variable});
processor.Emit(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, m_NetworkBehaviour_NetworkVariableFields_FieldRef);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Ldfld, field);
processor.Emit(OpCodes.Callvirt, m_List_NetworkVariableBase_Add);
}
}

// Find the base method...
MethodReference initializeVariablesBaseReference = null;
foreach (var methodDefinition in type.BaseType.Resolve().Methods)
{
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
{
initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
break;
}
}

if (initializeVariablesBaseReference == null)
{
// If we couldn't find it, we have to go ahead and add it.
// The base class could be in another assembly... that's ok, this won't
// actually save but it'll generate the same method the same way later,
// so this at least allows us to reference it.
GenerateVariableInitialization(type.BaseType.Resolve());
foreach (var methodDefinition in type.BaseType.Resolve().Methods)
{
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
{
initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
break;
}
}
}

if (type.BaseType.Resolve().HasGenericParameters)
{
var baseTypeInstance = (GenericInstanceType)type.BaseType;
initializeVariablesBaseReference = initializeVariablesBaseReference.MakeGeneric(baseTypeInstance.GenericArguments.ToArray());
}

// base.__initializeVariables();
processor.Emit(OpCodes.Nop);
processor.Emit(OpCodes.Ldarg_0);
processor.Emit(OpCodes.Call, initializeVariablesBaseReference);
processor.Emit(OpCodes.Nop);

processor.Emit(OpCodes.Ret);

type.Methods.Add(method);
}

private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId)
{
var typeSystem = methodDefinition.Module.TypeSystem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)

foreach (var fieldDefinition in typeDefinition.Fields)
{
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage))
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
{
fieldDefinition.IsFamily = true;
}
Expand All @@ -123,7 +123,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable))
{
methodDefinition.IsFamily = true;
}
Expand Down
Loading

0 comments on commit 8707582

Please sign in to comment.