selection | 通过 ProfilerTimeSampleSelection 构造函数创建或先前通过 ProfilerWindow.selection 检索的描述完整的选择。 |
bool 如果成功设置选择项,则返回 true;如果由于找不到合适的样本而拒绝选择,则返回 false。
在基于帧时间样本的 Profiler 模块(如 CPU 使用率模块 和 GPU 使用率 Profiler 模块)中设置当前选择。
如果您不知道要选择的样本的 rawSampleIndex,您可以使用 RawFrameDataView 或扩展方法 ProfilerEditorUtility.SetSelection 来查找它。此扩展方法按名称或 Profiler 标记 ID、帧和发生线程来搜索样本,并可以选择应找到样本的样本堆栈。
当 Profiler 窗口中没有可用的帧数据时,Unity 会引发异常。您可以检查 ProfilerWindow.firstAvailableFrameIndex 是否大于或等于 0 来验证帧数据是否可用。
当选择对象的帧索引超出 ProfilerWindow.firstAvailableFrameIndex 和 ProfilerWindow.lastAvailableFrameIndex 描述的范围时,Unity 会引发 ArgumentOutOfRangeException。
当找不到选择对象引用的线程时,Unity 会引发 ArgumentException。
其他资源:ProfilerEditorUtility.SetSelection 和 IProfilerFrameTimeViewSampleSelectionController.ClearSelection。
using System; using System.Collections.Generic; using UnityEditor; using UnityEditor.Profiling; using UnityEditorInternal; using UnityEngine;
// This example assumes the profiled scene contained a component of MyScript.cs: /* using UnityEngine;
public class MyScript : MonoBehaviour { void Update() { MethodWithABoxingAllocation(); }
object MethodWithABoxingAllocation() { return 1; } }*/
public class Example : EditorWindow { const string k_MainThreadGroupName = ""; const string k_MainThreadName = "Main Thread"; const string k_GCAllocSampleName = "GC.Alloc"; // Profiler samples that were instrumented by Unity's Message Invoking mechanism, // e.g. Update(), Start(), FixedUpdate() ... get an " [Invoke]" postfix const string k_InvokePostFix = " [Invoke]"; public enum UpdateNameMatchType { Short, Full, }
ProfilerWindow m_Profiler = null; UpdateNameMatchType m_UpdateNameMatchType = UpdateNameMatchType.Short; bool m_UseMarkerNames = true;
string GetUpdateSampleName(UpdateNameMatchType updateNameMatchType, bool deepProfiling) { switch (updateNameMatchType) { case UpdateNameMatchType.Short: if (deepProfiling) return k_UpdateSampleNameShort; return k_UpdateSampleNameShort + k_InvokePostFix; case UpdateNameMatchType.Full: if (deepProfiling) return k_UpdateSampleNameFull;
return k_UpdateSampleNameFull + k_InvokePostFix; default: throw new NotImplementedException(); } }
const string k_UpdateSampleNameFull = "Assembly-CSharp.dll!::MyScript.Update()"; // Invoked method samples or samples instrumented via Deep Profiling will by default be shown // without their fully qualifying type name as above // Instead the Profiler UI will strip out everything before the '!::' part of their name. // SetSelection will still find these samples, if the UI is set to not Show Full Scripting Method Names // Note that RawFrameDataView and HierarchyFrameDataView will not be able to identify the Marker IDs // for such samples from this shorter name. const string k_UpdateSampleNameShort = "MyScript.Update()";
static readonly List<string> k_SampleNames = new List<string> { "PlayerLoop", "Update.ScriptRunBehaviourUpdate", "BehaviourUpdate", };
[MenuItem("Window/Analysis/Profiler Extension")] public static void ShowExampleWindow() { var window = GetWindow<Example>(); window.m_Profiler = EditorWindow.GetWindow<ProfilerWindow>(); }
void OnGUI() { // First make sure there is an open Profiler Window if (m_Profiler == null) m_Profiler = EditorWindow.GetWindow<ProfilerWindow>();
// For demonstration purposes, let the user choose if the names or if Marker IDs should be used. m_UseMarkerNames = GUILayout.Toggle(m_UseMarkerNames, "Use Marker names instead of IDs"); if (!m_UseMarkerNames) m_UpdateNameMatchType = UpdateNameMatchType.Full;
// Marker IDs need to be gotten from the fully qualified type name, so the shorter name is not an option when using IDs using (new EditorGUI.DisabledScope(!m_UseMarkerNames)) { // For demonstration purposes, let the user choose if the short or the long name should be used. m_UpdateNameMatchType = (UpdateNameMatchType)EditorGUILayout.EnumPopup(m_UpdateNameMatchType); }
// If the currently selected Module is not the CPU Usage module, setting the selection will not be visible to the user immediately if (m_Profiler.selectedModuleIdentifier == ProfilerWindow.cpuModuleIdentifier) { // Get the CPU Usage Profiler module's selection controller interface to interact with the selection var cpuSampleSelectionController = m_Profiler.GetFrameTimeViewSampleSelectionController(ProfilerWindow.cpuModuleIdentifier); // If the current selection object is null, there is no selection to print out. using (new EditorGUI.DisabledScope(m_Profiler.lastAvailableFrameIndex < 0)) { if (GUILayout.Button("Check my Script for GC.Alloc")) { string samplePath = ""; for (int i = 0; i < k_SampleNames.Count; i++) { samplePath += $"{k_SampleNames[i]}/"; }
var samplePathDeepProfiling = samplePath + $"{GetUpdateSampleName(m_UpdateNameMatchType, true)}/"; samplePath = samplePath + $"{GetUpdateSampleName(m_UpdateNameMatchType, false)}/"; // the sample we are looking for, without a trailing '/' samplePath += k_GCAllocSampleName; samplePathDeepProfiling += k_GCAllocSampleName;
// This check will fail in Deep Profiling because "MethodWithABoxingAllocation()" will be instrumented // and sitting in the sample stack between "Update()" and the "GC.Alloc". if (cpuSampleSelectionController.SetSelection(samplePath) || cpuSampleSelectionController.SetSelection(samplePathDeepProfiling)) { Debug.LogWarning("MyScript allocates in its Update loop"); } else { if (m_UseMarkerNames) { samplePath = ""; for (int i = 0; i < k_SampleNames.Count; i++) { samplePath += $"{k_SampleNames[i]}{ (i < k_SampleNames.Count - 1 ?"/": "")}"; } // SetSelection calls that take sample names as strings will find shortened scripting sample names var mySctiprSamplePathDeepProfiling = $"{samplePath}/{GetUpdateSampleName(m_UpdateNameMatchType, true)}"; var myScriptSamplePath = $"{samplePath}/{GetUpdateSampleName(m_UpdateNameMatchType, false)}";
if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, myScriptSamplePath) || cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, mySctiprSamplePathDeepProfiling)) { Debug.LogWarning("MyScript allocates in its Update loop"); } // MyScript did not have a GC.Alloc sample underneath it, but maybe a different Update sample allocated // Search through all Update() samples else if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, k_GCAllocSampleName, samplePath)) { Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); } else { Debug.Log("No Script is allocating in its Update Loop"); FindAnyGCAllocSample(cpuSampleSelectionController); } } else { List<int> markerIdPath = new List<int>(k_SampleNames.Count + 1); List<int> deepProfilingmarkerIdPath = new List<int>(k_SampleNames.Count + 1); int gcAllocMarkerId = FrameDataView.invalidMarkerId; using (var frameData = ProfilerDriver.GetRawFrameDataView((int)m_Profiler.selectedFrameIndex, 0)) { for (int i = 0; i < k_SampleNames.Count; i++) { markerIdPath.Add(frameData.GetMarkerId(k_SampleNames[i])); } deepProfilingmarkerIdPath.AddRange(markerIdPath); // GetMarkerId needs the full length marker name to be able to identify this sample. markerIdPath.Add(frameData.GetMarkerId(GetUpdateSampleName(UpdateNameMatchType.Full, false))); deepProfilingmarkerIdPath.Add(frameData.GetMarkerId(GetUpdateSampleName(UpdateNameMatchType.Full, true)));
gcAllocMarkerId = frameData.GetMarkerId(k_GCAllocSampleName); }
if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, markerIdPath) || cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, deepProfilingmarkerIdPath)) { Debug.LogWarning("MyScript allocates in its Update loop"); } else { // MyScript did not have a GC.Alloc sample underneath it, but maybe a different Update sample allocated // Remove the MyScript sample id from the path and search through all Update() samples markerIdPath.Remove(markerIdPath.Count - 1); if (cpuSampleSelectionController.SetSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, gcAllocMarkerId, markerIdPath)) { Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); return; } Debug.Log("No Script is allocating in its Update Loop"); FindAnyGCAllocSample(cpuSampleSelectionController); } } } } } } }
void FindAnyGCAllocSample(IProfilerFrameTimeViewSampleSelectionController cpuSampleSelectionController) { using (var frameData = ProfilerDriver.GetRawFrameDataView((int)m_Profiler.selectedFrameIndex, 0)) { var gcAllocMarkerId = frameData.GetMarkerId(k_GCAllocSampleName); for (int i = 0; i < frameData.sampleCount; i++) { if (frameData.GetSampleMarkerId(i) == gcAllocMarkerId) { var selection = new ProfilerTimeSampleSelection(m_Profiler.selectedFrameIndex, k_MainThreadGroupName, k_MainThreadName, frameData.threadId, i); if (cpuSampleSelectionController.SetSelection(selection)) { // do not use the selection object here. The CPU Profiler Module created a new one. // Instead Get the selection object from the Profiler Debug.LogWarning($"MyScript does not allocate but {cpuSampleSelectionController.selection.markerNamePath[k_SampleNames.Count]} allocates in its Update loop"); return; } } } Debug.Log("No Script is allocating anything"); } } }
此示例展示了 SetSelection 的所有主要变化及其差异。