Skip to content

Commit

Permalink
Refactoring and fixing of HeapExplorer
Browse files Browse the repository at this point in the history
* Introduced the `Option<A>` type to represent values that might not be there.
  * Replaced values representing failure (like `-1`) with `Option` to indicate the possibility of failure.

* Replaced possibly-invalid types with `Option`s. Now if you if have an instance - it's guaranteed to be valid.

* Replaced magic values (like using positive integers for managed object indexes and negative integers for static object indexes) with proper semantic types to help with code clarity.

* Replaced dangerous numeric casts that could under/overflow with typesafe alternatives (`PInt` positive 32bit integer type) that guarantee no such things can happen.

* Replaced instances of `List` which were being used as stacks with `Stack`.

* Replaced as many fields in objects as possible with readonly ones to indicate that we have no intent of changing them after the object is constructed. This is a part of 'objects should stay valid after construction' philosophy.

* Added logging for potentially dangerous operations (like int -> uint).

* Replaced primitive loop guards with cycle tracking that tracks seen objects. This eliminates the false positives and errors out sooner if we actually have an error.

* Replaced places where things got cancelled silently with emitting warnings / errors. Failures should never be silent.

* Fixed algorithm that checks if a type contains reference types: it didn't account for nested value-types which eventually have reference types in them and only looked for reference types in the first level.

* Fixed bad lookups in `PackedCoreTypes`. Also made `PackedCoreTypes` to be fully initialized, without any missing entries.

* Added a warning about the inability to crawl the `[ThreadStatic]` variables.

* Improved search speed by:
  * caching search results.
  * downcasing everything before the search and then using fast ordinal string comparisons instead of slower `OrdinalIgnoreCase` comparisons.

* Improved documentation by adding new docstrings and converting existing docstrings into proper XMLDocs. Now IDEs and tools can properly render the documentation.

* Rewrote a part of code using C# 7.3 (supported since Unity 2019) to enhance readability.

* bumped version to 4.1.2

* Added capability to copy the root path as text in "Find GC roots" window.

* Added information about source fields that are pointing to the object in "Find GC roots" window.

* Added progress reporting to Unity when a memory snapshot is being converted from the Unity format to our format.
  • Loading branch information
arturaz committed Jan 21, 2023
1 parent f7b5567 commit 5f59252
Show file tree
Hide file tree
Showing 77 changed files with 4,006 additions and 3,122 deletions.
69 changes: 55 additions & 14 deletions Editor/Scripts/AbstractTreeView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@

using System;
using System.Collections.Generic;
using HeapExplorer.Utilities;
using UnityEngine;
using UnityEditor.IMGUI.Controls;
using UnityEditor;
using static HeapExplorer.Utilities.Option;

namespace HeapExplorer
{
Expand Down Expand Up @@ -36,7 +38,6 @@ public HeapExplorerWindow window
IList<int> m_Expanded = new List<int>(32);
TreeViewItem m_Tree;
string[] m_SearchCache = new string[32];
System.Text.StringBuilder m_SearchBuilder = new System.Text.StringBuilder();

public AbstractTreeView(HeapExplorerWindow window, string editorPrefsKey, TreeViewState state)
: base(state)
Expand Down Expand Up @@ -301,23 +302,36 @@ protected override bool DoesItemMatchSearch(TreeViewItem item, string search)
var i = item as AbstractTreeViewItem;
if (i != null)
{
int searchCount;
string type;
string label;
i.GetItemSearchString(m_SearchCache, out searchCount, out type, out label);
if (!i.m_MaybeCachedItemSearchString.valueOut(out var searchString)) {
int searchCount;
string type;
string label;
i.GetItemSearchString(m_SearchCache, out searchCount, out type, out label);

var names = new List<string>(capacity: searchCount);
for (var n=0; n < searchCount; ++n)
{
var str = m_SearchCache[n];
if (!string.IsNullOrEmpty(str)) names.Add(str.ToLowerInvariant());
}

if (!m_Search.IsTypeMatch(type) || !m_Search.IsLabelMatch(label))
return false;
searchString = new AbstractTreeViewItem.Cache(
lowerCasedNames: names.ToArray(), type: type, label: label
);
i.m_MaybeCachedItemSearchString = Some(searchString);
}

m_SearchBuilder.Length = 0;
for (var n=0; n < searchCount; ++n)
{
m_SearchBuilder.Append(m_SearchCache[n]);
m_SearchBuilder.Append(" ");
if (!m_Search.IsTypeMatch(searchString.type) || !m_Search.IsLabelMatch(searchString.label)) {
return false;
}
m_SearchBuilder.Append("\0");
else {
// ReSharper disable once LoopCanBeConvertedToQuery
foreach (var lowerCasedName in searchString.lowerCasedNames) {
if (m_Search.IsNameMatch(lowerCasedName)) return true;
}

return m_Search.IsNameMatch(m_SearchBuilder.ToString());
return false;
}
}

return base.DoesItemMatchSearch(item, search);
Expand Down Expand Up @@ -461,7 +475,34 @@ public virtual void GetItemSearchString(string[] target, out int count, out stri
label = null;
}

/// <summary>
/// Results of <see cref="GetItemSearchString"/> are cached here to avoid re-computation. If this is `None`,
/// invoke the <see cref="GetItemSearchString"/> and store the result here.
/// </summary>
public Option<Cache> m_MaybeCachedItemSearchString;

public abstract void OnGUI(Rect position, int column);

public sealed class Cache {
/// <summary>
/// Parameters for <see cref="SearchTextParser.Result.IsNameMatch"/>.
/// <para/>
/// The search will match if any of these matches.
/// </summary>
public readonly string[] lowerCasedNames;

/// <summary>Parameter for <see cref="SearchTextParser.Result.IsTypeMatch"/>.</summary>
public readonly string type;

/// <summary>Parameter for <see cref="SearchTextParser.Result.IsLabelMatch"/>.</summary>
public readonly string label;

public Cache(string[] lowerCasedNames, string type, string label) {
this.lowerCasedNames = lowerCasedNames;
this.type = type;
this.label = label;
}
}
}

public static class TreeViewUtility
Expand Down
2 changes: 1 addition & 1 deletion Editor/Scripts/AbstractView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ protected string GetPrefsKey(Expression<Func<object>> exp)
body = ubody.Operand as MemberExpression;
}

return string.Format("HeapExplorer.{0}.{1}", editorPrefsKey, body.Member.Name);
return $"HeapExplorer.{editorPrefsKey}.{body.Member.Name}";
}

public HeapExplorerView()
Expand Down
15 changes: 9 additions & 6 deletions Editor/Scripts/CompareSnapshotsView/CompareSnapshotsControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
//
using System.Collections;
using System.Collections.Generic;
using HeapExplorer.Utilities;
using UnityEditor;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
Expand Down Expand Up @@ -139,7 +140,7 @@ void BuildMemoryTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeVie

foreach (var section in snapshot.managedHeapSections)
{
parent.size[k] += (long)section.size;
parent.size[k] += section.size;
}

parent.count[k] += snapshot.managedHeapSections.Length;
Expand All @@ -164,7 +165,9 @@ void BuildGCHandleTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeV

var snapshot = snapshots[k];

parent.size[k] += snapshot.gcHandles.Length * snapshot.virtualMachineInformation.pointerSize;
parent.size[k] += (
snapshot.gcHandles.Length * snapshot.virtualMachineInformation.pointerSize.sizeInBytes()
).ToUIntClamped();
parent.count[k] += snapshot.gcHandles.Length;
}
}
Expand Down Expand Up @@ -303,14 +306,14 @@ void BuildManagedTree(PackedMemorySnapshot[] snapshots, ref int uniqueId, TreeVi

class Item : AbstractTreeViewItem
{
public long[] size = new long[2];
public ulong[] size = new ulong[2];
public long[] count = new long[2];

public long sizeDiff
{
get
{
return size[1] - size[0];
return size[1].ToLongClamped() - size[0].ToLongClamped();
}
}

Expand Down Expand Up @@ -357,11 +360,11 @@ public override void OnGUI(Rect position, int column)
break;

case EColumn.SizeA:
HeEditorGUI.Size(position, size[0]);
HeEditorGUI.Size(position, size[0].ToLongClamped());
break;

case EColumn.SizeB:
HeEditorGUI.Size(position, size[1]);
HeEditorGUI.Size(position, size[1].ToLongClamped());
break;

case EColumn.SizeDiff:
Expand Down
Loading

0 comments on commit 5f59252

Please sign in to comment.