/
CacheComponentLookupRule.cs
86 lines (71 loc) · 3.3 KB
/
CacheComponentLookupRule.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
namespace Unity.Rules.Performance
{
using System;
using System.Globalization;
using System.Linq;
using Gendarme.Framework;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Unity.Rules.Utility;
/// <summary>
/// Better to cache the get component in start/awake
/// </summary>
[Problem( "Recurrent components calls in update can leads to performance loss." )]
[Solution( "Cache the component call in a local member, on Start() or Awake()." )]
public class CacheComponentLookupRule : Rule, IMethodRule
{
private static readonly string[] methodNames = {
"Update",
"LateUpdate",
"FixedUpdate",
};
/// <summary>
///
/// </summary>
/// <param name="method"></param>
/// <returns></returns>
public RuleResult CheckMethod( MethodDefinition method )
{
// Check if we're in a MonoBehaviour
if ( !Utilities.IsMonoBehaviour( method.DeclaringType ) ) return RuleResult.DoesNotApply;
// Check if we're in an heavily used method
if ( methodNames.Contains( method.Name ) )
{
// TODO : update the severity report with a severity threshold based of the number of occurrences of Component lookups
ComputeNumberOfComponentLookup( method, 0 );
}
return Runner.CurrentRuleResult;
}
int ComputeNumberOfComponentLookup( MethodDefinition method , int level )
{
int lookups = 0;
int currentLevel = level + 1;
if ( method == null || !method.HasBody || currentLevel > 8 ) return lookups;
// Check if there's an access to a component property
// check for GetComponent<>
foreach ( Instruction instruction in method.Body.Instructions )
{
Code code = instruction.OpCode.Code;
if ( code != Code.Callvirt && code != Code.Call ) continue;
MethodReference methodReference = instruction.Operand as MethodReference;
if ( methodReference == null ) continue;
// Debug.Log can lead to stackoverflow because of string building
if ( methodReference.DeclaringType.FullName.Equals( "UnityEngine.Debug" ) ) return lookups;
//
if ( methodReference.DeclaringType.FullName.Equals( "UnityEngine.Component" ) )
{
lookups++;
// Write report about that defect
string message = String.Format( CultureInfo.CurrentCulture, "{0} Component lookup could be cached.", methodReference.Name );
Runner.Report( method, instruction, Severity.High, Confidence.Normal, message );
}
else
{
MethodDefinition methodDefinition = methodReference.Resolve();
lookups += ComputeNumberOfComponentLookup( methodDefinition, currentLevel );
}
}
return lookups;
}
}
}