Skip to content

Commit 8707582

Browse files
ShadauxCat0xFA11
andauthored
perf: Remove reflection from NetworkBehaviour code to improve performance. [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 <mfatihmar@gmail.com> --------- Co-authored-by: Fatih Mar <mfatihmar@gmail.com>
1 parent 8df4e98 commit 8707582

File tree

6 files changed

+264
-46
lines changed

6 files changed

+264
-46
lines changed

com.unity.netcode.gameobjects/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Additional documentation and release notes are available at [Multiplayer Documen
1919
- Fixed some errors that could occur if a connection is lost and the loss is detected when attempting to write to the socket. (#2495)
2020

2121
## Changed
22+
- 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)
2223

2324

2425
## [1.4.0] - 2023-04-10

com.unity.netcode.gameobjects/Editor/CodeGen/CodeGenHelpers.cs

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Unity.CompilationPipeline.Common.Diagnostics;
1111
using Unity.CompilationPipeline.Common.ILPostProcessing;
1212
using UnityEngine;
13+
using Object = System.Object;
1314

1415
namespace Unity.Netcode.Editor.CodeGen
1516
{
@@ -112,6 +113,60 @@ public static string FullNameWithGenericParameters(this TypeReference typeRefere
112113

113114
return name;
114115
}
116+
public static TypeReference MakeGenericType(this TypeReference self, params TypeReference[] arguments)
117+
{
118+
if (self.GenericParameters.Count != arguments.Length)
119+
{
120+
throw new ArgumentException();
121+
}
122+
123+
var instance = new GenericInstanceType(self);
124+
foreach (var argument in arguments)
125+
{
126+
instance.GenericArguments.Add(argument);
127+
}
128+
129+
return instance;
130+
}
131+
132+
public static MethodReference MakeGeneric(this MethodReference self, params TypeReference[] arguments)
133+
{
134+
var reference = new MethodReference(self.Name, self.ReturnType)
135+
{
136+
DeclaringType = self.DeclaringType.MakeGenericType(arguments),
137+
HasThis = self.HasThis,
138+
ExplicitThis = self.ExplicitThis,
139+
CallingConvention = self.CallingConvention,
140+
};
141+
142+
foreach (var parameter in self.Parameters)
143+
{
144+
reference.Parameters.Add(new ParameterDefinition(parameter.ParameterType));
145+
}
146+
147+
foreach (var generic_parameter in self.GenericParameters)
148+
{
149+
reference.GenericParameters.Add(new GenericParameter(generic_parameter.Name, reference));
150+
}
151+
152+
return reference;
153+
}
154+
155+
public static bool IsSubclassOf(this TypeReference typeReference, TypeReference baseClass)
156+
{
157+
var type = typeReference.Resolve();
158+
if (type.BaseType == null || type.BaseType.Name == nameof(Object))
159+
{
160+
return false;
161+
}
162+
163+
if (type.BaseType.Resolve() == baseClass.Resolve())
164+
{
165+
return true;
166+
}
167+
168+
return type.BaseType.IsSubclassOf(baseClass);
169+
}
115170

116171
public static bool HasInterface(this TypeReference typeReference, string interfaceTypeFullName)
117172
{

com.unity.netcode.gameobjects/Editor/CodeGen/NetworkBehaviourILPP.cs

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,11 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
166166

167167
foreach (var type in m_WrappedNetworkVariableTypes)
168168
{
169+
if (type.Resolve() == null)
170+
{
171+
continue;
172+
}
173+
169174
if (IsSpecialCaseType(type))
170175
{
171176
continue;
@@ -251,11 +256,15 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
251256
private FieldReference m_NetworkManager_rpc_name_table_FieldRef;
252257
private MethodReference m_NetworkManager_rpc_name_table_Add_MethodRef;
253258
private TypeReference m_NetworkBehaviour_TypeRef;
259+
private TypeReference m_NetworkVariableBase_TypeRef;
260+
private MethodReference m_NetworkVariableBase_Initialize_MethodRef;
261+
private MethodReference m_NetworkBehaviour___nameNetworkVariable_MethodRef;
254262
private MethodReference m_NetworkBehaviour_beginSendServerRpc_MethodRef;
255263
private MethodReference m_NetworkBehaviour_endSendServerRpc_MethodRef;
256264
private MethodReference m_NetworkBehaviour_beginSendClientRpc_MethodRef;
257265
private MethodReference m_NetworkBehaviour_endSendClientRpc_MethodRef;
258266
private FieldReference m_NetworkBehaviour_rpc_exec_stage_FieldRef;
267+
private FieldReference m_NetworkBehaviour_NetworkVariableFields_FieldRef;
259268
private MethodReference m_NetworkBehaviour_getNetworkManager_MethodRef;
260269
private MethodReference m_NetworkBehaviour_getOwnerClientId_MethodRef;
261270
private MethodReference m_NetworkHandlerDelegateCtor_MethodRef;
@@ -275,6 +284,9 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
275284
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_UnmanagedValueEquals_MethodRef;
276285
private MethodReference m_NetworkVariableSerializationTypes_InitializeEqualityChecker_ManagedClassEquals_MethodRef;
277286

287+
private MethodReference m_ExceptionCtorMethodReference;
288+
private MethodReference m_List_NetworkVariableBase_Add;
289+
278290
private MethodReference m_BytePacker_WriteValueBitPacked_Short_MethodRef;
279291
private MethodReference m_BytePacker_WriteValueBitPacked_UShort_MethodRef;
280292
private MethodReference m_BytePacker_WriteValueBitPacked_Int_MethodRef;
@@ -348,12 +360,17 @@ private void CreateNetworkVariableTypeInitializers(AssemblyDefinition assembly)
348360
private const string k_NetworkManager_rpc_name_table = nameof(NetworkManager.__rpc_name_table);
349361

350362
private const string k_NetworkBehaviour_rpc_exec_stage = nameof(NetworkBehaviour.__rpc_exec_stage);
363+
private const string k_NetworkBehaviour_NetworkVariableFields = nameof(NetworkBehaviour.NetworkVariableFields);
351364
private const string k_NetworkBehaviour_beginSendServerRpc = nameof(NetworkBehaviour.__beginSendServerRpc);
352365
private const string k_NetworkBehaviour_endSendServerRpc = nameof(NetworkBehaviour.__endSendServerRpc);
353366
private const string k_NetworkBehaviour_beginSendClientRpc = nameof(NetworkBehaviour.__beginSendClientRpc);
354367
private const string k_NetworkBehaviour_endSendClientRpc = nameof(NetworkBehaviour.__endSendClientRpc);
368+
private const string k_NetworkBehaviour___initializeVariables = nameof(NetworkBehaviour.__initializeVariables);
355369
private const string k_NetworkBehaviour_NetworkManager = nameof(NetworkBehaviour.NetworkManager);
356370
private const string k_NetworkBehaviour_OwnerClientId = nameof(NetworkBehaviour.OwnerClientId);
371+
private const string k_NetworkBehaviour___nameNetworkVariable = nameof(NetworkBehaviour.__nameNetworkVariable);
372+
373+
private const string k_NetworkVariableBase_Initialize = nameof(NetworkVariableBase.Initialize);
357374

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

380397
TypeDefinition networkManagerTypeDef = null;
381398
TypeDefinition networkBehaviourTypeDef = null;
399+
TypeDefinition networkVariableBaseTypeDef = null;
382400
TypeDefinition networkHandlerDelegateTypeDef = null;
383401
TypeDefinition rpcParamsTypeDef = null;
384402
TypeDefinition serverRpcParamsTypeDef = null;
@@ -402,6 +420,12 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
402420
continue;
403421
}
404422

423+
if (networkVariableBaseTypeDef == null && netcodeTypeDef.Name == nameof(NetworkVariableBase))
424+
{
425+
networkVariableBaseTypeDef = netcodeTypeDef;
426+
continue;
427+
}
428+
405429
if (networkHandlerDelegateTypeDef == null && netcodeTypeDef.Name == nameof(NetworkManager.RpcReceiveHandler))
406430
{
407431
networkHandlerDelegateTypeDef = netcodeTypeDef;
@@ -548,6 +572,9 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
548572
case k_NetworkBehaviour_endSendClientRpc:
549573
m_NetworkBehaviour_endSendClientRpc_MethodRef = moduleDefinition.ImportReference(methodDef);
550574
break;
575+
case k_NetworkBehaviour___nameNetworkVariable:
576+
m_NetworkBehaviour___nameNetworkVariable_MethodRef = moduleDefinition.ImportReference(methodDef);
577+
break;
551578
}
552579
}
553580

@@ -558,6 +585,21 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
558585
case k_NetworkBehaviour_rpc_exec_stage:
559586
m_NetworkBehaviour_rpc_exec_stage_FieldRef = moduleDefinition.ImportReference(fieldDef);
560587
break;
588+
case k_NetworkBehaviour_NetworkVariableFields:
589+
m_NetworkBehaviour_NetworkVariableFields_FieldRef = moduleDefinition.ImportReference(fieldDef);
590+
break;
591+
}
592+
}
593+
594+
595+
m_NetworkVariableBase_TypeRef = moduleDefinition.ImportReference(networkVariableBaseTypeDef);
596+
foreach (var methodDef in networkVariableBaseTypeDef.Methods)
597+
{
598+
switch (methodDef.Name)
599+
{
600+
case k_NetworkVariableBase_Initialize:
601+
m_NetworkVariableBase_Initialize_MethodRef = moduleDefinition.ImportReference(methodDef);
602+
break;
561603
}
562604
}
563605

@@ -785,6 +827,16 @@ private bool ImportReferences(ModuleDefinition moduleDefinition)
785827
}
786828
}
787829

830+
// Standard types are really hard to reliably find using the Mono Cecil way, they resolve differently in Mono vs .NET Core
831+
// Importing with typeof() is less dangerous for standard framework types though, so we can just do it
832+
var exceptionType = typeof(Exception);
833+
var exceptionCtor = exceptionType.GetConstructor(new[] { typeof(string) });
834+
m_ExceptionCtorMethodReference = m_MainModule.ImportReference(exceptionCtor);
835+
836+
var listType = typeof(List<NetworkVariableBase>);
837+
var addMethod = listType.GetMethod(nameof(List<NetworkVariableBase>.Add), new[] { typeof(NetworkVariableBase) });
838+
m_List_NetworkVariableBase_Add = moduleDefinition.ImportReference(addMethod);
839+
788840
return true;
789841
}
790842

@@ -931,6 +983,8 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition, string[] ass
931983
}
932984
}
933985

986+
GenerateVariableInitialization(typeDefinition);
987+
934988
if (!typeDefinition.HasGenericParameters && !typeDefinition.IsGenericInstance)
935989
{
936990
var fieldTypes = new List<TypeReference>();
@@ -1889,6 +1943,132 @@ private void InjectWriteAndCallBlocks(MethodDefinition methodDefinition, CustomA
18891943
instructions.ForEach(instruction => processor.Body.Instructions.Insert(0, instruction));
18901944
}
18911945

1946+
private void GenerateVariableInitialization(TypeDefinition type)
1947+
{
1948+
foreach (var methodDefinition in type.Methods)
1949+
{
1950+
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
1951+
{
1952+
// If this hits, we've already generated the method for this class because a child class got processed first.
1953+
return;
1954+
}
1955+
}
1956+
1957+
var method = new MethodDefinition(
1958+
k_NetworkBehaviour___initializeVariables,
1959+
MethodAttributes.Family | MethodAttributes.Virtual | MethodAttributes.HideBySig,
1960+
m_MainModule.TypeSystem.Void);
1961+
1962+
var processor = method.Body.GetILProcessor();
1963+
1964+
method.Body.Variables.Add(new VariableDefinition(m_MainModule.TypeSystem.Boolean));
1965+
1966+
processor.Emit(OpCodes.Nop);
1967+
1968+
foreach (var fieldDefinition in type.Fields)
1969+
{
1970+
FieldReference field = fieldDefinition;
1971+
if (type.HasGenericParameters)
1972+
{
1973+
var genericType = new GenericInstanceType(fieldDefinition.DeclaringType);
1974+
foreach (var parameter in fieldDefinition.DeclaringType.GenericParameters)
1975+
{
1976+
genericType.GenericArguments.Add(parameter);
1977+
}
1978+
field = new FieldReference(fieldDefinition.Name, fieldDefinition.FieldType, genericType);
1979+
}
1980+
if (field.FieldType.IsSubclassOf(m_NetworkVariableBase_TypeRef))
1981+
{
1982+
// if({variable} != null) {
1983+
processor.Emit(OpCodes.Ldarg_0);
1984+
processor.Emit(OpCodes.Ldfld, field);
1985+
processor.Emit(OpCodes.Ldnull);
1986+
processor.Emit(OpCodes.Ceq);
1987+
processor.Emit(OpCodes.Stloc_0);
1988+
processor.Emit(OpCodes.Ldloc_0);
1989+
1990+
var afterThrowInstruction = processor.Create(OpCodes.Nop);
1991+
1992+
processor.Emit(OpCodes.Brfalse, afterThrowInstruction);
1993+
1994+
// throw new Exception("...");
1995+
processor.Emit(OpCodes.Nop);
1996+
processor.Emit(OpCodes.Ldstr, $"{type.Name}.{field.Name} cannot be null. All {nameof(NetworkVariableBase)} instances must be initialized.");
1997+
processor.Emit(OpCodes.Newobj, m_ExceptionCtorMethodReference);
1998+
processor.Emit(OpCodes.Throw);
1999+
2000+
// }
2001+
processor.Append(afterThrowInstruction);
2002+
2003+
// {variable}.Initialize(this);
2004+
processor.Emit(OpCodes.Ldarg_0);
2005+
processor.Emit(OpCodes.Ldfld, field);
2006+
processor.Emit(OpCodes.Ldarg_0);
2007+
processor.Emit(OpCodes.Callvirt, m_NetworkVariableBase_Initialize_MethodRef);
2008+
2009+
// __nameNetworkVariable({variable}, "{variable}");
2010+
processor.Emit(OpCodes.Nop);
2011+
processor.Emit(OpCodes.Ldarg_0);
2012+
processor.Emit(OpCodes.Ldarg_0);
2013+
processor.Emit(OpCodes.Ldfld, field);
2014+
processor.Emit(OpCodes.Ldstr, field.Name.Replace("<", string.Empty).Replace(">k__BackingField", string.Empty));
2015+
processor.Emit(OpCodes.Call, m_NetworkBehaviour___nameNetworkVariable_MethodRef);
2016+
2017+
// NetworkVariableFields.Add({variable});
2018+
processor.Emit(OpCodes.Nop);
2019+
processor.Emit(OpCodes.Ldarg_0);
2020+
processor.Emit(OpCodes.Ldfld, m_NetworkBehaviour_NetworkVariableFields_FieldRef);
2021+
processor.Emit(OpCodes.Ldarg_0);
2022+
processor.Emit(OpCodes.Ldfld, field);
2023+
processor.Emit(OpCodes.Callvirt, m_List_NetworkVariableBase_Add);
2024+
}
2025+
}
2026+
2027+
// Find the base method...
2028+
MethodReference initializeVariablesBaseReference = null;
2029+
foreach (var methodDefinition in type.BaseType.Resolve().Methods)
2030+
{
2031+
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
2032+
{
2033+
initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
2034+
break;
2035+
}
2036+
}
2037+
2038+
if (initializeVariablesBaseReference == null)
2039+
{
2040+
// If we couldn't find it, we have to go ahead and add it.
2041+
// The base class could be in another assembly... that's ok, this won't
2042+
// actually save but it'll generate the same method the same way later,
2043+
// so this at least allows us to reference it.
2044+
GenerateVariableInitialization(type.BaseType.Resolve());
2045+
foreach (var methodDefinition in type.BaseType.Resolve().Methods)
2046+
{
2047+
if (methodDefinition.Name == k_NetworkBehaviour___initializeVariables)
2048+
{
2049+
initializeVariablesBaseReference = m_MainModule.ImportReference(methodDefinition);
2050+
break;
2051+
}
2052+
}
2053+
}
2054+
2055+
if (type.BaseType.Resolve().HasGenericParameters)
2056+
{
2057+
var baseTypeInstance = (GenericInstanceType)type.BaseType;
2058+
initializeVariablesBaseReference = initializeVariablesBaseReference.MakeGeneric(baseTypeInstance.GenericArguments.ToArray());
2059+
}
2060+
2061+
// base.__initializeVariables();
2062+
processor.Emit(OpCodes.Nop);
2063+
processor.Emit(OpCodes.Ldarg_0);
2064+
processor.Emit(OpCodes.Call, initializeVariablesBaseReference);
2065+
processor.Emit(OpCodes.Nop);
2066+
2067+
processor.Emit(OpCodes.Ret);
2068+
2069+
type.Methods.Add(method);
2070+
}
2071+
18922072
private MethodDefinition GenerateStaticHandler(MethodDefinition methodDefinition, CustomAttribute rpcAttribute, uint rpcMethodId)
18932073
{
18942074
var typeSystem = methodDefinition.Module.TypeSystem;

com.unity.netcode.gameobjects/Editor/CodeGen/RuntimeAccessModifiersILPP.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
112112

113113
foreach (var fieldDefinition in typeDefinition.Fields)
114114
{
115-
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage))
115+
if (fieldDefinition.Name == nameof(NetworkBehaviour.__rpc_exec_stage) || fieldDefinition.Name == nameof(NetworkBehaviour.NetworkVariableFields))
116116
{
117117
fieldDefinition.IsFamily = true;
118118
}
@@ -123,7 +123,7 @@ private void ProcessNetworkBehaviour(TypeDefinition typeDefinition)
123123
if (methodDefinition.Name == nameof(NetworkBehaviour.__beginSendServerRpc) ||
124124
methodDefinition.Name == nameof(NetworkBehaviour.__endSendServerRpc) ||
125125
methodDefinition.Name == nameof(NetworkBehaviour.__beginSendClientRpc) ||
126-
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc))
126+
methodDefinition.Name == nameof(NetworkBehaviour.__endSendClientRpc) || methodDefinition.Name == nameof(NetworkBehaviour.__initializeVariables) || methodDefinition.Name == nameof(NetworkBehaviour.__nameNetworkVariable))
127127
{
128128
methodDefinition.IsFamily = true;
129129
}

0 commit comments

Comments
 (0)