Yuxiao Mao 8 месяцев назад
Родитель
Сommit
099c2eafa0
6 измененных файлов с 777 добавлено и 717 удалено
  1. 3 0
      bin/defaultProps.json
  2. 65 57
      bin/style.css
  3. 72 67
      bin/style.less
  4. 2 2
      hide/Ide.hx
  5. 635 0
      hide/view/MemProfiler.hx
  6. 0 591
      hide/view/Profiler.hx

+ 3 - 0
bin/defaultProps.json

@@ -95,6 +95,9 @@
 	"key.shadergraph.comment" : "C",
 	"key.graph.openAddMenu" : "Space",
 
+	// Memory profiler config
+	"key.memprof.inspectPrev" : "Alt-Left",
+	"key.memprof.inspectNext" : "Alt-Right",
 
 
 	// cdb config

+ 65 - 57
bin/style.css

@@ -3253,6 +3253,16 @@ div.gradient-box {
   height: 100%;
   display: flex;
 }
+.profiler .ico {
+  padding-left: 5px;
+  padding-right: 5px;
+  cursor: pointer;
+  color: lightgray;
+}
+.profiler .disable {
+  cursor: default;
+  color: gray;
+}
 .profiler .left-panel {
   height: 100%;
   width: 80%;
@@ -3260,13 +3270,33 @@ div.gradient-box {
   background-color: #303030;
   flex-direction: column;
 }
-.profiler .left-panel .tree-map {
-  background-color: #4c00ff;
+.profiler .left-panel .hide-tabs {
+  height: 100%;
   width: 100%;
-  flex-grow: 1;
+}
+.profiler .left-panel .hide-tabs #search-bar {
+  display: inline;
+  float: right;
+}
+.profiler .left-panel .hide-tabs .tab-content {
+  height: 100%;
+  width: 100%;
+  display: flex;
+  flex-direction: column;
+  padding-bottom: 25px;
+  box-sizing: border-box;
+  margin: 2px;
+}
+.profiler .left-panel .hide-tabs .tab-content .hide-scroll {
+  overflow-x: hidden;
 }
 .profiler .left-panel table {
   overflow-y: scroll;
+  margin-top: 10px;
+  margin-bottom: 10px;
+}
+.profiler .left-panel table caption {
+  text-align: left;
 }
 .profiler .left-panel table thead {
   position: sticky;
@@ -3276,9 +3306,13 @@ div.gradient-box {
 .profiler .left-panel table thead td {
   font-weight: bold;
 }
-.profiler .left-panel table thead .sort-count,
-.profiler .left-panel table thead .sort-size {
+.profiler .left-panel table #actions {
+  padding-right: 3px;
+}
+.profiler .left-panel table #actions .ico {
   cursor: pointer;
+  pointer-events: auto;
+  color: lightgray;
 }
 .profiler .left-panel table tr:hover {
   background-color: #272727;
@@ -3289,94 +3323,77 @@ div.gradient-box {
 .profiler .left-panel table thead tr:hover {
   background-color: #151515;
 }
-.profiler .left-panel table td:last-child {
+.profiler .left-panel table td:nth-last-child(1) {
   width: 100%;
-}
-.profiler .left-panel table td:nth-child(3) {
-  width: 70%;
   max-width: 700px;
+  text-wrap: auto;
 }
 .profiler .left-panel table td {
   padding: 3px 30px 3px 1px;
   overflow: hidden;
   white-space: nowrap;
 }
-.profiler .left-panel table td .folder {
-  cursor: pointer;
-  margin-right: 10px;
-  margin-left: 5px;
-  font-size: 12pt;
-  text-align: center;
-  vertical-align: text-top;
-}
-.profiler .left-panel table td .outer-gauge {
-  background-color: #535353;
-  width: 100%;
-  height: 15px;
-}
-.profiler .left-panel table td .inner-gauge {
-  background-color: #d6d6d6;
-  height: 15px;
+.profiler .left-panel table .arrow {
+  color: #4169E1;
 }
-.profiler .left-panel table td .icon {
-  padding-left: 10px;
+.profiler .left-panel table .tid {
+  color: #C71585;
 }
-.profiler .left-panel table .inspect-row td {
-  width: 100px;
+.profiler .left-panel table .roots {
+  color: #DAA520;
 }
 .profiler .right-panel {
   height: 100%;
   width: 20%;
   padding: 10px;
-}
-.profiler .right-panel dl {
+  box-sizing: border-box;
   overflow-x: hidden;
-  margin: 0px;
 }
 .profiler .right-panel dt {
   width: 80px;
-  text-align: right;
   font-size: 11px;
   color: #aaa;
   user-select: none;
   text-transform: capitalize;
-  cursor: pointer;
   font-weight: normal;
-  vertical-align: middle;
+  vertical-align: top;
   display: inline-block;
-  text-wrap: wrap;
-  word-break: break-word;
   margin-top: 4px;
+  max-width: 100%;
 }
 .profiler .right-panel dd {
   position: relative;
-  width: 200px;
-  margin-left: 10px;
-  vertical-align: middle;
+  vertical-align: top;
   display: inline-block;
   text-wrap: wrap;
   word-break: break-word;
-  margin-top: 4px;
+  margin-left: 0px;
+  padding-left: 10px;
+  padding-top: 4px;
+  width: 200px;
+  max-width: 100%;
+  box-sizing: border-box;
+}
+.profiler .right-panel dd input {
+  max-width: 100%;
+  box-sizing: border-box;
 }
 .profiler .right-panel .outer-gauge {
   background-color: #535353;
   width: 100%;
-  height: 20px;
+  height: 15px;
 }
 .profiler .right-panel .inner-gauge {
   background-color: #d6d6d6;
-  height: 20px;
+  height: 15px;
 }
 .profiler .right-panel h4,
 .profiler .right-panel h5 {
+  margin-top: 2px;
   margin-bottom: 2px;
 }
 .profiler .right-panel h5 {
-  margin-top: 2px;
-}
-.profiler .right-panel hr {
-  margin-top: 25px;
-  border-top: 1px, solid, #666666;
+  word-wrap: break-word;
 }
 .profiler .right-panel .title {
   text-align: center;
@@ -3393,13 +3410,6 @@ div.gradient-box {
   height: 100px;
   padding-top: 20px;
 }
-.profiler .right-panel .drop-zone .icon {
-  text-align: center;
-  font-size: 30pt;
-  margin: 0;
-  font-weight: bold;
-  pointer-events: none;
-}
 .profiler .right-panel .drop-zone .label {
   text-align: center;
   pointer-events: none;
@@ -3414,8 +3424,6 @@ div.gradient-box {
 }
 .profiler .right-panel .files-input #process-btn {
   width: 100%;
-  margin-top: 15px;
-  margin-bottom: 15px;
 }
 .hover-parent .hover-reveal {
   visibility: hidden;

+ 72 - 67
bin/style.less

@@ -3785,6 +3785,17 @@ div.gradient-box {
 	height: 100%;
 	display: flex;
 
+	.ico {
+		padding-left: 5px;
+		padding-right: 5px;
+		cursor: pointer;
+		color: lightgray;
+	}
+	.disable {
+		cursor: default;
+		color: gray;
+	}
+
 	.left-panel {
 		height: 100%;
 		width: 80%;
@@ -3792,14 +3803,35 @@ div.gradient-box {
 		background-color: #303030;
 		flex-direction: column;
 
-		.tree-map {
-			background-color: #4c00ff;
+		.hide-tabs {
+			height: 100%;
 			width: 100%;
-			flex-grow: 1;
+			#search-bar {
+				display: inline;
+				float: right;
+			}
+			.tab-content {
+				height: 100%;
+				width: 100%;
+				display: flex;
+				flex-direction: column;
+				padding-bottom: 25px;
+				box-sizing: border-box;
+				margin: 2px;
+				.hide-scroll {
+					overflow-x: hidden;
+				}
+			}
 		}
 
 		table {
 			overflow-y: scroll;
+			margin-top: 10px;
+			margin-bottom: 10px;
+
+			caption {
+				text-align: left;
+			}
 
 			thead {
 				position: sticky;
@@ -3809,9 +3841,14 @@ div.gradient-box {
 				td {
 					font-weight: bold;
 				}
+			}
 
-				.sort-count, .sort-size {
+			#actions {
+				padding-right: 3px;
+				.ico {
 					cursor: pointer;
+					pointer-events: auto;
+					color: lightgray;
 				}
 			}
 
@@ -3829,50 +3866,30 @@ div.gradient-box {
 				}
 			}
 
-			td:last-child {
+			td:nth-last-child(1) {
 				width: 100%;
-			}
-
-			td:nth-child(3) {
-				width: 70%;
 				max-width: 700px;
+				text-wrap: auto;
 			}
 
 			td {
 				padding: 3px 30px 3px 1px;
 				overflow: hidden;
 				white-space: nowrap;
+			}
 
-				.folder {
-					cursor: pointer;
-					margin-right: 10px;
-					margin-left: 5px;
-					font-size: 12pt;
-					text-align: center;
-					vertical-align: text-top;
-				}
-
-				.outer-gauge {
-					background-color: #535353;
-					width: 100%;
-					height: 15px;
-				}
-
-				.inner-gauge {
-					background-color: #d6d6d6;
-					height: 15px;
-				}
+			.arrow {
+				color: #4169E1;
+			}
 
-				.icon {
-					padding-left:10px;
-				}
+			.tid {
+				color: #C71585;
 			}
 
-			.inspect-row {
-				td {
-					width: 100px;
-				}
+			.roots {
+				color: #DAA520;
 			}
+
 		}
 	}
 
@@ -3880,61 +3897,59 @@ div.gradient-box {
 		height: 100%;
 		width: 20%;
 		padding: 10px;
-
-		dl {
-			overflow-x: hidden;
-			margin: 0px;
-		}
+		box-sizing: border-box;
+		overflow-x: hidden;
 
 		dt {
 			width: 80px;
-			text-align: right;
 			font-size: 11px;
 			color: #aaa;
 			user-select: none;
 			text-transform: capitalize;
-			cursor: pointer;
 			font-weight: normal;
-			vertical-align: middle;
+			vertical-align: top;
 			display: inline-block;
-			text-wrap: wrap;
-			word-break: break-word;
 			margin-top: 4px;
+			max-width: 100%;
 		}
 
 		dd {
 			position: relative;
-			width: 200px;
-			margin-left: 10px;
-			vertical-align: middle;
+			vertical-align: top;
 			display: inline-block;
 			text-wrap: wrap;
 			word-break: break-word;
-			margin-top: 4px;
+			margin-left: 0px;
+			padding-left: 10px;
+			padding-top: 4px;
+			width: 200px;
+			max-width: 100%;
+			box-sizing: border-box;
+
+			input {
+				max-width: 100%;
+				box-sizing: border-box;
+			}
 		}
 
 		.outer-gauge {
 			background-color: #535353;
 			width: 100%;
-			height: 20px;
+			height: 15px;
 		}
 
 		.inner-gauge {
 			background-color: #d6d6d6;
-			height: 20px;
+			height: 15px;
 		}
 
 		h4, h5 {
+			margin-top:2px;
 			margin-bottom: 2px;
 		}
 
 		h5 {
-			margin-top:2px;
-		}
-
-		hr {
-			margin-top: 25px;
-			border-top: 1px, solid, #666666;
+			word-wrap: break-word;
 		}
 
 		.title {
@@ -3953,14 +3968,6 @@ div.gradient-box {
 			height: 100px;
 			padding-top: 20px;
 
-			.icon {
-				text-align: center;
-				font-size: 30pt;
-				margin: 0;
-				font-weight: bold;
-				pointer-events: none;
-			}
-
 			.label {
 				text-align: center;
 				pointer-events: none;
@@ -3979,8 +3986,6 @@ div.gradient-box {
 		.files-input {
 			#process-btn {
 				width: 100%;
-				margin-top: 15px;
-				margin-bottom: 15px;
 			}
 		}
 	}

+ 2 - 2
hide/Ide.hx

@@ -1426,9 +1426,9 @@ class Ide extends hide.tools.IdeData {
 		var analysis = menu.find(".analysis");
 		analysis.find(".memprof").click(function(_) {
 			#if (hashlink >= "1.15.0")
-			open("hide.view.Profiler",{});
+			open("hide.view.MemProfiler",{});
 			#else
-			quickMessage("Profiler not available. Please update hashlink to version 1.15.0 or later.");
+			quickMessage("Memory Profiler not available. Please update hashlink to version 1.15.0 or later.");
 			#end
 		});
 		analysis.find(".gpudump").click(function(_) {

+ 635 - 0
hide/view/MemProfiler.hx

@@ -0,0 +1,635 @@
+package hide.view;
+
+#if (hashlink >= "1.15.0")
+class MemProfiler extends hide.ui.View<{}> {
+	public var analyzer : hlmem.Analyzer = null;
+	var hlPath = "";
+	var dumpPaths : Array<String> = [];
+	var currentFilter : hlmem.Memory.FilterMode = None;
+	public var showTid : Bool = false;
+
+	var statsView : Element;
+	var tabsView : Element;
+	var searchBar : MemProfilerSearchBar;
+	var summaryView : MemProfilerSummaryView;
+	var inspectView : MemProfilerInspectView;
+
+	public function new( ?state ) {
+		super(state);
+	}
+
+	override function onDisplay() {
+		new Element('
+		<div class="profiler">
+			<div class="left-panel">
+			</div>
+			<div class="right-panel hide-scroll">
+				<div class="title">Files input</div>
+				<div class="files-input">
+					<div class="drop-zone hidden">
+						<p class="label">Drop .hl and .dump files here</p>
+					</div>
+					<div class="inputs">
+						<dl>
+							<dt>HL file</dt><dd><input class="hl-fileselect" type="fileselect" extensions="hl"/></dd>
+							<dt>Dump files</dt><dd>
+								<input class="dump-fileselect" type="fileselect" extension="dump"/>
+								<input class="dump-fileselect" type="fileselect" extension="dump"/>
+							</dd>
+						</dl>
+						<input type="button" value="Process Files" id="process-btn"/>
+					</div>
+				</div>
+				<div class="info">
+				</div>
+				<div class="options">
+					<dl>
+						<dt><label for="mem-filter">Filter</label></dt><dd>
+							<select id="mem-filter">
+								<option value="0">None</option>
+								<option value="1">Unique</option>
+								<option value="2">Intersected</option>
+							</select>
+						</dd>
+						<dt><label for="mem-table-size">Display size</label></dt><dd>
+							<input type="number" id="mem-table-size" min="0" max="100" value="5"
+								title="Default table display size for inspect. 0 for display all lines."
+							/>
+						</dd>
+						<dt><label for="mem-locate-root">Locate root</label></dt><dd>
+							<input type="checkbox" id="mem-locate-root"
+								title="Auto locate root when inspect, at cost of performance."
+							/>
+						</dd>
+						<dt><label for="mem-show-tid">Show tid</label></dt><dd>
+							<input type="checkbox" id="mem-show-tid"
+								title="Display tid with type when inspect."
+							/>
+						</dd>
+					</dl>
+				</div>
+			</div>
+		</div>'
+		).appendTo(element);
+
+		var hlSelect = new hide.comp.FileSelect(["hl"], null, element.find(".hl-fileselect"));
+		hlSelect.onChange = function() { hlPath = Ide.inst.getPath(hlSelect.path); };
+
+		var fileSelects : Array<hide.comp.FileSelect> = [];
+		for (el in element.find(".dump-fileselect")) {
+			var dumpSelect = new hide.comp.FileSelect(["dump"], null, new Element(el));
+			fileSelects.push(dumpSelect);
+
+			dumpSelect.onChange = function() {
+				dumpPaths = [];
+				for (fs in fileSelects) {
+					if (fs.path != null && fs.path != "")
+						dumpPaths.push(Ide.inst.getPath(fs.path));
+				}
+			};
+		}
+
+		var dropZone = element.find(".drop-zone");
+		dropZone.css({display:'none'});
+
+		var inputs = element.find(".inputs");
+		inputs.css({display:'block'});
+
+		var isDragging = false;
+		var wait = false;
+		var fileInput = element.find(".files-input");
+		fileInput.on('dragenter', function(e) {
+			var dt : js.html.DataTransfer = e.originalEvent.dataTransfer;
+			if (!wait && !isDragging && dt.files != null && dt.files.length > 0) {
+				dropZone.css({display:'block'});
+				inputs.css({display:'none'});
+				dropZone.css({animation:'zoomIn .25s'});
+				isDragging = true;
+				wait = true;
+				haxe.Timer.delay(function() wait = false, 500);
+			}
+		});
+
+		fileInput.on('drop', function(e) {
+			var dt : js.html.DataTransfer = e.originalEvent.dataTransfer;
+			if (dt.files != null && dt.files.length > 0) {
+				dropZone.css({display:'none'});
+				inputs.css({display:'block'});
+				isDragging = false;
+
+				var tmpDumpPaths = [];
+				for (f in dt.files) {
+					var arrSplit = Reflect.getProperty(f, "name").split('.');
+					var ext = arrSplit[arrSplit.length - 1];
+					var p = Reflect.getProperty(f, "path");
+					p = StringTools.replace(p, "\\", "/");
+
+					if (ext == "hl") {
+						hlPath = p;
+						hlSelect.path = p;
+						continue;
+					}
+
+					if (ext == "dump") {
+						tmpDumpPaths.push(p);
+						continue;
+					}
+
+					Ide.inst.error('File ${p} is not supported, please provide .dump file or .hl file');
+				}
+
+				if (tmpDumpPaths.length > 0) dumpPaths = [];
+				for (idx => p in tmpDumpPaths) {
+					dumpPaths.push(p);
+
+					if (idx < fileSelects.length)
+						fileSelects[idx].path = p;
+				}
+			}
+		});
+
+		fileInput.on('dragleave', function(e) {
+			if (!wait && isDragging) {
+				dropZone.css({display:'none'});
+				inputs.css({display:'block'});
+				isDragging = false;
+				wait = true;
+				haxe.Timer.delay(function() wait = false, 500);
+			}
+		});
+
+		var processBtn = element.find("#process-btn");
+		processBtn.on('click', function(e) {
+			if( hlPath == null || hlPath == '' || dumpPaths == null || dumpPaths.length <= 0 ) {
+				Ide.inst.quickMessage('.hl or/and .dump files are missing. Please provide both files before hit the process button');
+				return;
+			}
+
+			clear();
+			load( function(b) {
+				if( b )
+					refresh();
+			});
+		});
+
+		var filterOpt = element.find("#mem-filter");
+		filterOpt.on('change', function(e) {
+			var val : Int = Std.parseInt(filterOpt.val());
+			var enumVal : hlmem.Memory.FilterMode = switch (val) {
+				case 0: None;
+				case 1: Unique;
+				case 2: Intersect;
+				case _:
+					trace("Unknown filter mode " + val);
+					None;
+			}
+			this.onFilterChange(enumVal);
+		});
+
+		var tableSizeOpt = element.find("#mem-table-size");
+		tableSizeOpt.on('change', function(e) {
+			var val : Int = Std.parseInt(tableSizeOpt.val());
+			if( val < 0 ) val = 0;
+			if( val > 100 ) val = 100;
+			tableSizeOpt.val(val);
+			if( inspectView != null )
+				inspectView.defaultTableLines = val;
+		});
+
+		var locateRootOpt = element.find("#mem-locate-root");
+		locateRootOpt.on('change', function(e) {
+			if( inspectView != null )
+				inspectView.defaultLocateRoot = locateRootOpt.is(":checked");
+		});
+
+		var showTidOpt = element.find("#mem-show-tid");
+		showTidOpt.on('change', function(e) {
+			showTid = showTidOpt.is(":checked");
+		});
+
+	}
+
+	override function onBeforeClose():Bool {
+		clear();
+		return super.onBeforeClose();
+	}
+
+	override function getTitle() {
+		return "Memory profiler";
+	}
+
+	function onFilterChange( f : hlmem.Memory.FilterMode ) {
+		if( currentFilter == f )
+			return;
+		currentFilter = f;
+		if( analyzer == null || analyzer.getMainMemory() == null )
+			return;
+		showInfo('Setting filter to ${currentFilter.getName()}...', true);
+		haxe.Timer.delay(() -> {
+			var mainMemory = analyzer.getMainMemory();
+			mainMemory.filterMode = currentFilter;
+			mainMemory.buildFilteredBlocks();
+			refresh();
+			showInfo('Filter set to ${currentFilter.getName()}.');
+		}, 1);
+	}
+
+	function showInfo( msg : String, loading : Bool = false ) {
+		var info = element.find(".info");
+		info.html(loading ? '<p>${msg}<i class="ico ico-spinner fa-spin"></i></p>' : '<p>${msg}</p>');
+	}
+
+	function clear() {
+		analyzer = null;
+		if( statsView != null )
+			statsView.remove();
+		statsView = null;
+		if( tabsView != null )
+			tabsView.remove();
+		tabsView = null;
+		searchBar = null;
+		summaryView = null;
+		inspectView = null;
+	}
+
+	function load( onDone : Bool -> Void ) {
+		try {
+			hlmem.Analyzer.useColor = false;
+			analyzer = new hlmem.Analyzer();
+			showInfo("Loading bytecode...", true);
+			haxe.Timer.delay(() -> {
+				analyzer.loadBytecode(hlPath);
+				showInfo("Bytecode loaded, loading dump...", true);
+				haxe.Timer.delay(() -> {
+					for (i in 0...dumpPaths.length) {
+						analyzer.loadMemoryDump(dumpPaths[i]);
+					}
+					showInfo("Memory dump loaded, building hierarchy...", true);
+					haxe.Timer.delay(() -> {
+						analyzer.build(currentFilter);
+						showInfo("Hierarchy built.");
+						haxe.Timer.delay(() -> {
+							onDone(true);
+						}, 0);
+					}, 0);
+				}, 0);
+			}, 1);
+		} catch(e) {
+			Ide.inst.quickError(e);
+			analyzer = null;
+			onDone(false);
+		}
+	}
+
+	function refresh() {
+		refreshStats();
+		refreshTabsView();
+	}
+
+	function refreshStats() {
+		if( statsView != null )
+			statsView.remove();
+		var statsObj = analyzer != null ? analyzer.getMemStats() : [];
+		statsView = new Element('<div class="stats"><div class="title">Stats</div></div>').appendTo(element.find('.right-panel'));
+		for (idx => s in statsObj) {
+			new Element('
+			<h4>Memory usage</h4>
+			<h5>${s.memFile}</h5>
+			<div class="outer-gauge"><div class="inner-gauge" title="${hlmem.Analyzer.mb(s.used)} used (${ 100 * s.used / s.totalAllocated}% of total)" style="width:${ 100 * s.used / s.totalAllocated}%;"></div></div>
+			<dl>
+				<dt>Allocated</dt><dd>${hlmem.Analyzer.mb(s.totalAllocated)}</dd>
+				<dt>Used</dt><dd>${hlmem.Analyzer.mb(s.used)}</dd>
+				<dt>Free</dt><dd>${hlmem.Analyzer.mb(s.free)}</dd>
+				<dt>GC</dt><dd>${hlmem.Analyzer.mb(s.gc)}</dd>
+				<dt>Pages</dt><dd>${s.pagesCount} (${hlmem.Analyzer.mb(s.pagesSize)})</dd>
+				<dt>Roots</dt><dd>${s.rootsCount}</dd>
+				<dt>Stacks</dt><dd>${s.stackCount}</dd>
+				<dt>Types</dt><dd>${s.typesCount}</dd>
+				<dt>Closures</dt><dd>${s.closuresCount}</dd>
+				<dt>Live blocks</dt><dd>${s.blockCount}</dd>
+				<dt>Filtered blocks</dt><dd>${s.filteredBlockCount}</dd>
+			</dl>
+			').appendTo(statsView);
+		}
+	}
+
+	function refreshTabsView() {
+		if( tabsView != null )
+			tabsView.remove();
+		tabsView = new Element('
+		<div class="hide-tabs">
+			<div id="search-bar"></div>
+			<div class="tabs-header"></div>
+			<div class="tab-content"></div>
+		</div>
+		').appendTo(element.find(".left-panel"));
+		var header = tabsView.find(".tabs-header");
+		var summaryBtn = new Element('<div name="summary" class="active">Summary</button>').appendTo(header);
+		summaryBtn.on('click', (e) -> openSummaryTab());
+		var inspectBtn = new Element('<div name="inspect">Inspect</button>').appendTo(header);
+		inspectBtn.on('click', (e) -> openInspectTab());
+		searchBar = new MemProfilerSearchBar(this);
+		searchBar.element.appendTo(tabsView.find("#search-bar"));
+		var content = tabsView.find(".tab-content");
+		summaryView = new MemProfilerSummaryView(this);
+		summaryView.element.appendTo(content);
+		inspectView = new MemProfilerInspectView(this);
+		inspectView.defaultTableLines = Std.parseInt(element.find("#mem-table-size").val());
+		inspectView.defaultLocateRoot = element.find("#mem-locate-root").is(":checked");
+		inspectView.element.appendTo(content);
+	}
+
+	public function addHistory(str) {
+		searchBar.addHistory(str);
+	}
+
+	public function openSummaryTab() {
+		var header = tabsView.find(".tabs-header");
+		header.find("[name=inspect]").removeClass("active");
+		header.find("[name=summary]").addClass("active");
+		inspectView.element.toggle(false);
+		summaryView.element.toggle(true);
+	}
+
+	public function getTypeStat( ttype : hlmem.TType ) {
+		return summaryView.typeStats.get(ttype);
+	}
+
+	public function openInspectTab( ?tstr : String ) {
+		var header = tabsView.find(".tabs-header");
+		header.find("[name=summary]").removeClass("active");
+		header.find("[name=inspect]").addClass("active");
+		summaryView.element.toggle(false);
+		inspectView.element.toggle(true);
+		inspectView.open(tstr);
+	}
+
+	public function inspectRemoveParent( pstr : String ) {
+		var mainMemory = analyzer.getMainMemory();
+		var ptype = mainMemory.resolveType(pstr);
+		if( inspectView.ttype != null && ptype != null ) {
+			mainMemory.removeParent(inspectView.ttype, ptype);
+			showInfo('Removed parents ${StringTools.htmlEscape(ptype.toString())} for blocks ${inspectView.ttypeName}.');
+		}
+	}
+
+	static var _ = hide.ui.View.register(MemProfiler);
+}
+
+class MemProfilerSearchBar extends hide.comp.Component {
+	var profiler : MemProfiler;
+	public var searchHistory : Array<String> = [];
+	public var searchHistoryIndex : Int = 0;
+	var searchInput : Element;
+	var searchBtn : Element;
+	var searchPrev : Element;
+	var searchNext : Element;
+	public function new( profiler : MemProfiler ) {
+		super(null, null);
+		this.profiler = profiler;
+		searchPrev = new Element('<i class="ico ico-arrow-left disable"></i>').appendTo(element);
+		searchNext = new Element('<i class="ico ico-arrow-right disable"></i>').appendTo(element);
+		searchInput = new Element('<input type="text" placeholder="Search..."
+			title="Enter class full path or #+tid, e.g. sys.FileSystem, #0"
+		/>').appendTo(element);
+		searchBtn = new Element('<i class="ico ico-search"></i>').appendTo(element);
+		searchInput.keydown(function(e) {
+			if (e.key == 'Enter') searchBtn.click();
+			if (e.key == 'ArrowUp') goto(searchHistoryIndex + 1, false);
+			if (e.key == 'ArrowDown') goto(searchHistoryIndex - 1, false);
+		});
+		searchBtn.on('click', function(e) {
+			profiler.openInspectTab(searchInput.val());
+		});
+		searchPrev.on('click', function(e) {
+			goto(searchHistoryIndex + 1, true);
+		});
+		searchNext.on('click', function(e) {
+			goto(searchHistoryIndex - 1, true);
+		});
+		searchPrev.contextmenu(function(e) {
+			e.preventDefault();
+			var menu = buildHistoryMenu(searchHistoryIndex, searchHistory.length);
+			if( menu.length > 0 )
+				hide.comp.ContextMenu.createDropdown(searchPrev.get(0), menu);
+		});
+		searchNext.contextmenu(function(e) {
+			e.preventDefault();
+			var menu = buildHistoryMenu(0, searchHistoryIndex);
+			menu.reverse();
+			if( menu.length > 0 )
+				hide.comp.ContextMenu.createDropdown(searchNext.get(0), menu);
+		});
+		profiler.keys.register("memprof.inspectPrev", () -> searchPrev.click());
+		profiler.keys.register("memprof.inspectNext", () -> searchNext.click());
+	}
+	public function refreshSearchBar() {
+		if( searchHistory[searchHistoryIndex] != null )
+			searchInput.val(searchHistory[searchHistoryIndex]);
+		searchPrev.toggleClass("disable", searchHistoryIndex >= searchHistory.length - 1);
+		searchNext.toggleClass("disable", searchHistoryIndex <= 0);
+	}
+	public function addHistory( str : String ) {
+		if( str != null && str.length > 0 && searchHistory[searchHistoryIndex] != str ) {
+			searchHistory.unshift(str);
+			searchHistoryIndex = 0;
+			refreshSearchBar();
+		}
+	}
+	function goto( index : Int, open : Bool ) {
+		if( index >= searchHistory.length )
+			index = searchHistory.length - 1;
+		if( index < 0 )
+			index = 0;
+		searchHistoryIndex = index;
+		refreshSearchBar();
+		haxe.Timer.delay(() -> searchBtn.click(), 1);
+	}
+	function buildHistoryMenu( begin : Int, end : Int ) : Array<hide.comp.ContextMenu.MenuItem> {
+		var items = [];
+		for( i in begin...end ) {
+			if( searchHistory[i] != null ) {
+				var item = {
+					label : StringTools.htmlEscape(searchHistory[i]),
+					click : () -> goto(i, true),
+				};
+				items.push(item);
+			}
+		}
+		return items;
+	}
+}
+
+class MemProfilerSummaryView extends hide.comp.Component {
+	var profiler : MemProfiler;
+	public var typeStats : hlmem.Result.BlockStats = null;
+	public function new( profiler : MemProfiler ) {
+		super(null, null);
+		this.profiler = profiler;
+		element = new Element('<div class="hide-scroll"></div>');
+		var mainMemory = profiler.analyzer.getMainMemory();
+		typeStats = mainMemory.getBlockStatsByType();
+		typeStats.sort(true, false);
+		var sumTypeStateByCount = typeStats.slice(0, 10);
+		var tabCount = new MemProfilerTable(profiler, "Top 10 type on count", sumTypeStateByCount, 0);
+		tabCount.element.appendTo(element);
+		typeStats.sort(false, false);
+		var sumTypeStateBySize = typeStats.slice(0, 10);
+		var tabSize = new MemProfilerTable(profiler, "Top 10 type on size", sumTypeStateBySize, 0);
+		tabSize.element.appendTo(element);
+		var sumUnknown = mainMemory.getUnknown();
+		var tabUnknown = new MemProfilerTable(profiler, "Unknown blocks", sumUnknown, 0);
+		tabUnknown.element.appendTo(element);
+	}
+}
+
+class MemProfilerInspectView extends hide.comp.Component {
+	var profiler : MemProfiler;
+	public var defaultTableLines : Int = 5;
+	public var defaultLocateRoot : Bool = false;
+	public var ttype : hlmem.TType;
+	public var ttypeName : String;
+	var locateRootBtn : Element;
+	var locateRootTable : MemProfilerTable;
+	public function new( profiler : MemProfiler ) {
+		super(null, null);
+		this.profiler = profiler;
+		element = new Element('<div class="hide-scroll"></div>');
+	}
+	public function open( ?tstr : String ) {
+		if( tstr == null || tstr.length <= 0 ) return;
+		var mainMemory = profiler.analyzer.getMainMemory();
+		ttype = mainMemory.resolveType(tstr);
+		element.empty();
+		if( ttype == null ) {
+			new Element('<div><p>Cannot open type ${tstr}</p></div>').appendTo(element);
+			return;
+		}
+		var tstr = ttype.toString();
+		profiler.addHistory(tstr.length <= 20 ? tstr : "#" + ttype.tid);
+		ttypeName = StringTools.htmlEscape(tstr) + "#" + ttype.tid;
+		var ttypeStat = profiler.getTypeStat(ttype);
+		new Element('
+			<table>
+				<tr><td>Type</td><td>${ttypeName}</td></tr>
+				<tr><td>Blocks count</td><td>${ttypeStat == null ? 0 : ttypeStat.count}</td></tr>
+				<tr><td>Blocks size</td><td>${ttypeStat == null ? "0" : hlmem.Analyzer.mb(ttypeStat.size)}</td></tr>
+			</table>
+		').appendTo(element);
+		var data = mainMemory.locate(ttype);
+		var locateTable = new MemProfilerTable(profiler, "Locate", data, defaultTableLines);
+		locateTable.element.appendTo(element);
+		var data = mainMemory.parents(ttype);
+		var parentsTable = new MemProfilerTable(profiler, "Parents", data, defaultTableLines, true);
+		parentsTable.element.appendTo(element);
+		var data = mainMemory.subs(ttype);
+		var subsTable = new MemProfilerTable(profiler, "Subs", data, defaultTableLines);
+		subsTable.element.appendTo(element);
+		locateRootTable = null;
+		locateRootBtn = new Element('<input type="button" value="Locate Root"/>').appendTo(element);
+		locateRootBtn.on('click', function(e) {
+			if( locateRootTable != null ) return;
+			locateRootBtn.remove();
+			var data = mainMemory.locate(ttype, 10);
+			locateRootTable = new MemProfilerTable(profiler, "Locate 10", data, defaultTableLines);
+			locateRootTable.element.appendTo(element);
+		});
+		if( defaultLocateRoot ) {
+			haxe.Timer.delay(() -> locateRootBtn.click(), 0);
+		}
+	}
+}
+
+class MemProfilerTable extends hide.comp.Component {
+	var profiler : MemProfiler;
+	var title : String;
+	var data : hlmem.Result.BlockStats;
+	var allowRemoveParent : Bool;
+	var currentLine : Int;
+	var expandBtn : Element;
+	public function new( profiler : MemProfiler, title : String, data : hlmem.Result.BlockStats, maxLine : Int, allowRemoveParent : Bool = false ) {
+		super(null, null);
+		this.profiler = profiler;
+		this.title = title;
+		this.data = data;
+		this.currentLine = 0;
+		this.allowRemoveParent = allowRemoveParent;
+		element = new Element('
+		<table rules=none>
+			<caption>${title} (${data.allT.length})</caption>
+			<thead>
+				<td></td>
+				<td>Count</td>
+				<td>Size</td>
+				<td>Name</td>
+			</thead>
+			<tbody>
+			</tbody>
+		</table>'
+		);
+		var size = (maxLine > 0 && maxLine < data.allT.length) ? maxLine : data.allT.length;
+		expand(size);
+	}
+	public function expand( size : Int ) {
+		if( expandBtn != null )
+			expandBtn.remove();
+		var delta = data.allT.length - currentLine;
+		if( delta <= 0 || size <= 0 )
+			return;
+		if( size < delta )
+			delta = size;
+		var maxLine = currentLine + delta;
+		var body = element.find('tbody');
+		for ( i in currentLine...maxLine ) {
+			var l = data.allT[i];
+			var child = new MemProfilerTableLine(profiler, l);
+			child.element.appendTo(body);
+			child.addAction("ico-map-marker", "locate", element -> profiler.openInspectTab("#" + element.tl[0]));
+			if( allowRemoveParent )
+				child.addAction("ico-remove", "remove parent", element -> profiler.inspectRemoveParent("#" + element.tl[0]));
+		}
+		currentLine = maxLine;
+		var delta = data.allT.length - currentLine;
+		if( delta > 0 ) {
+			expandBtn = new Element('<input type="button" value="Expand"/>').appendTo(body);
+			expandBtn.on('click', (e) -> expand(5));
+		}
+	}
+}
+
+class MemProfilerTableLine extends hide.comp.Component {
+	var profiler : MemProfiler;
+	var data : hlmem.Result.BlockStatsElement;
+	var actions : Element;
+	public function new( profiler : MemProfiler, el : hlmem.Result.BlockStatsElement ) {
+		super(null, null);
+		this.profiler = profiler;
+		this.data = el;
+		var names = el.getNames(true, false, false).map(s -> StringTools.htmlEscape(s));
+		var tids = el.getNames(false, true, false).map(s -> profiler.showTid ? '<span class="tid">$s</span>' : "");
+		var fields = el.getNames(false, false, true);
+		if( names[0] == "roots" || names[0] == "stack" )
+			names[0] = '<span class="roots">${names[0]}</span>';
+		for( i in 0...names.length )
+			names[i] = names[i] + tids[i] + fields[i];
+		var name = names.join(' <span class="arrow">&#10148;</span> ');
+		var title = el.getNames(false).join(' > ');
+		element = new Element('
+		<tr tabindex="2">
+			<td id="actions"></td>
+			<td>${el.count}</td>
+			<td>${hlmem.Analyzer.mb(el.size)}</td>
+			<td title="${title}">
+				${name}
+			</td>
+		</tr>'
+		);
+		actions = element.find("#actions");
+	}
+	public function addAction( icon : String, description : String, onClick : hlmem.Result.BlockStatsElement -> Void ) {
+		var btn = new Element('<i class="ico ${icon}" title="${description}"></i>').appendTo(actions);
+		btn.on('click', (e) -> onClick(data));
+	}
+}
+
+#end

+ 0 - 591
hide/view/Profiler.hx

@@ -1,591 +0,0 @@
-package hide.view;
-
-typedef LineData = {
-	count : Int,
-	size : Int,
-	tid : Array<Int>,
-	name : String,
-}
-
-typedef Path = {
-	v: Int,
-	children: Array<Path>,
-	line: LineData,
-	total : {count: Int, mem: Int},
-};
-
-enum SortType {
-	ByMemory;
-	ByCount;
-}
-
-enum Filter {
-	None;
-	Unique;			// Blocks only present in current memory
-	Difference;		// Blocks only present in other memory
-	Intersected; 	// Blocks present in both memories
-}
-
-class Profiler extends hide.ui.View<{}> {
-	#if (hashlink >= "1.15.0")
-	public var mainMemory : hlmem.Memory = null;
-	public var currentMemory : hlmem.Memory = null;
-	public var names(default, null) : Array<String> = [];
-	public var lines(default, null) : Array<LineData> = [];
-	public var locationData(default, null) : Map<String, Array<LineData>> = [];
-
-	var sort : SortType = ByCount;
-	var sortOrderAscending = true;
-	var currentFilter : Filter = None;
-	var hlPath = "";
-	var dumpPaths : Array<String> = [];
-
-	// Cached values
-	var statsObj : Array<Dynamic> = [];
-	var fileSelects : Array<hide.comp.FileSelect> = [];
-
-	public function new( ?state ) {
-		super(state);
-	}
-
-	override function onDisplay() {
-		new Element('
-		<div class="profiler">
-			<div class="left-panel"></div>
-			<div class="right-panel">
-				<div class="title">Files input</div>
-				<div class="files-input">
-					<div class="drop-zone hidden">
-						<p class="icon">+</p>
-						<p class="label">Drop .hl and .dump files here</p>
-					</div>
-					<div class="inputs">
-						<dl>
-							<dt>HL file</dt><dd><input class="hl-fileselect" type="fileselect" extensions="hl"/></dd>
-							<dt>Dump files</dt><dd><input class="dump-fileselect" type="fileselect" extension="dump"/></dd>
-							<dt></dt><dd><input class="dump-fileselect" type="fileselect" extension="dump"/></dd>
-						</dl>
-						<input type="button" value="Process Files" id="process-btn"/>
-					</div>
-				</div>
-				<div class="filters">
-				</div>
-			</div>
-		</div>'
-		).appendTo(element);
-
-		var hlSelect = new hide.comp.FileSelect(["hl"], null, element.find(".hl-fileselect"));
-		hlSelect.onChange = function() { hlPath = Ide.inst.getPath(hlSelect.path); };
-
-		for (el in element.find(".dump-fileselect")) {
-			var dumpSelect = new hide.comp.FileSelect(["dump"], null, new Element(el));
-			fileSelects.push(dumpSelect);
-
-			dumpSelect.onChange = function() {
-				dumpPaths = [];
-				for (fs in fileSelects) {
-					if (fs.path != null && fs.path != "")
-						dumpPaths.push(Ide.inst.getPath(fs.path));
-				}
-			};
-		}
-
-		var dropZone = element.find(".drop-zone");
-		dropZone.css({display:'none'});
-
-		var inputs = element.find(".inputs");
-		inputs.css({display:'block'});
-
-		var isDragging = false;
-		var wait = false;
-		var fileInput = element.find(".files-input");
-		fileInput.on('dragenter', function(e) {
-			var dt : js.html.DataTransfer = e.originalEvent.dataTransfer;
-			if (!wait && !isDragging && dt.files != null && dt.files.length > 0) {
-				dropZone.css({display:'block'});
-				inputs.css({display:'none'});
-				dropZone.css({animation:'zoomIn .25s'});
-				isDragging = true;
-				wait = true;
-				haxe.Timer.delay(function() wait = false, 500);
-			}
-		});
-
-		fileInput.on('drop', function(e) {
-			var dt : js.html.DataTransfer = e.originalEvent.dataTransfer;
-			if (dt.files != null && dt.files.length > 0) {
-				dropZone.css({display:'none'});
-				inputs.css({display:'block'});
-				isDragging = false;
-
-				var tmpDumpPaths = [];
-				for (f in dt.files) {
-					var arrSplit = Reflect.getProperty(f, "name").split('.');
-					var ext = arrSplit[arrSplit.length - 1];
-					var p = Reflect.getProperty(f, "path");
-					p = StringTools.replace(p, "\\", "/");
-
-					if (ext == "hl") {
-						hlPath = p;
-						hlSelect.path = p;
-						continue;
-					}
-
-					if (ext == "dump") {
-						tmpDumpPaths.push(p);
-						continue;
-					}
-
-					Ide.inst.error('File ${p} is not supported, please provide .dump file or .hl file');
-				}
-
-				if (tmpDumpPaths.length > 0) dumpPaths = [];
-				for (idx => p in tmpDumpPaths) {
-					dumpPaths.push(p);
-
-					if (idx < fileSelects.length)
-						fileSelects[idx].path = p;
-				}
-			}
-		});
-
-		fileInput.on('dragleave', function(e) {
-			if (!wait && isDragging) {
-				dropZone.css({display:'none'});
-				inputs.css({display:'block'});
-				isDragging = false;
-				wait = true;
-				haxe.Timer.delay(function() wait = false, 500);
-			}
-		});
-
-		var processBtn = element.find("#process-btn");
-		processBtn.on('click', function() {
-			if (hlPath == null || hlPath == '' || dumpPaths == null || dumpPaths.length <= 0) {
-				Ide.inst.quickMessage('.hl or/and .dump files are missing. Please provide both files before hit the process button');
-				return;
-			}
-
-			clear();
-			load();
-			refresh();
-		});
-
-		refreshFilters();
-	}
-
-	override function getTitle() {
-		return "Memory profiler";
-	}
-
-	function load() {
-		names = dumpPaths;
-
-		var result = loadAll();
-		if ( result != null) {
-			Ide.inst.quickError(result);
-		} else {
-			if (names.length > 0) {
-				this.currentFilter = None;
-				displayTypes(sort, sortOrderAscending);
-				statsObj = mainMemory?.getStats();
-			}
-		}
-	}
-
-	function loadAll() @:privateAccess {
-		if (names.length < 1) return null;
-		for (i in 0...names.length) {
-			var newMem = new hlmem.Memory();
-			try {
-				if (i == 0) { // setup main Memory
-					newMem.loadBytecode(hlPath);
-					currentMemory = mainMemory = newMem;
-				} else {
-					mainMemory.otherMems.push(newMem);
-					newMem.code = mainMemory.code;
-				}
-
-				newMem.otherMems = [];
-				newMem.loadMemory(names[i]);
-				newMem.check();
-			} catch(e) {
-				names.remove(names[i]);
-				if (names.length < 1)
-					mainMemory = currentMemory = null;
-
-				return e.toString();
-			}
-		}
-
-		mainMemory.setFilterMode(None);
-		for (mem in mainMemory.otherMems)
-			mem.setFilterMode(None);
-
-		return null;
-	}
-
-	function clear() {
-		mainMemory = currentMemory = null;
-		lines = [];
-		locationData.clear();
-		statsObj = null;
-	}
-
-	public function displayTypes(sort : SortType = ByCount, asc : Bool = true) @:privateAccess{
-		if (currentMemory == null) throw "memory not loaded";
-
-		lines = [];
-
-		var ctx = new hlmem.Memory.Stats(currentMemory);
-		for ( b in currentMemory.filteredBlocks)
-			ctx.add(b.type, b.size);
-
-		ctx.sort(sort == ByCount, asc);
-
-		for (i in ctx.allT){
-			lines.push({count : i.count, size : i.mem, tid : i.tl, name : getNameString(i.tl)});
-		}
-	}
-
-	public function getNameString(tid : Array<Int>) {
-		var path = hlmem.Memory.Stats.getPathStrings(mainMemory, tid);
-		return path[path.length-1];
-	}
-
-	public function getPathString(tid : Array<Int>) {
-		return hlmem.Memory.Stats.getPathStrings(currentMemory, tid).join(" > ");
-	}
-
-	public function refresh() {
-		refreshStats();
-		refreshFilters();
-		refreshHierarchicalView();
-	}
-
-	public function refreshFilters() {
-		var filters = element.find('.filters');
-		filters.empty();
-
-		var fileNames = [];
-		for (p in dumpPaths) {
-			var arr = p.split('/');
-			fileNames.push(arr[arr.length - 1]);
-		}
-
-		new Element('
-			<div class="title">Filters</div>
-			<dt>Filter</dt><dd>
-				<select class="dd-filters">
-					<option value="0">None</option>
-					<option value="1">Show ${fileNames[0]}</option>
-					<option value="2">Show ${fileNames[1]}</option>
-					<option value="3">Intersected</option>
-				</select>
-			</dd>
-		').appendTo(filters);
-
-		var ddFilters = filters.find('.dd-filters');
-		ddFilters.on('change', function(e) {
-			var enumVal = Filter.None;
-			var val : Int = Std.parseInt(ddFilters.val());
-			switch (val) {
-				case 0: enumVal = Filter.None;
-				case 1: enumVal = Filter.Unique;
-				case 2: enumVal = Filter.Difference;
-				case 3: enumVal = Filter.Intersected;
-			}
-
-			this.filterDatas(enumVal);
-		});
-
-		if (dumpPaths.length >= 2)
-			filters.css({ display:'block' });
-		else
-			filters.css({ display:'none' });
-	}
-
-	public function refreshStats() {
-		element.find('.stats').remove();
-
-		var stats = new Element ('<div class="stats"><div class="title">Stats</div></div>').appendTo(element.find('.right-panel'));
-		for (idx => s in statsObj) {
-			new Element('
-			<h4>Memory usage</h4>
-			<h5>${s.memFile}</h5>
-			<div class="outer-gauge"><div class="inner-gauge" title="${hlmem.Memory.MB(s.used)} used (${ 100 * s.used / s.totalAllocated}% of total)" style="width:${ 100 * s.used / s.totalAllocated}%;"></div></div>
-			<dl>
-				<dt>Allocated</dt><dd>${hlmem.Memory.MB(s.totalAllocated)}</dd>
-				<dt>Used</dt><dd>${hlmem.Memory.MB(s.used)}</dd>
-				<dt>Free</dt><dd>${hlmem.Memory.MB(s.free)}</dd>
-				<dt>GC</dt><dd>${hlmem.Memory.MB(s.gc)}</dd>
-				<dt>&nbsp</dt><dd></dd>
-				<dt>Pages</dt><dd>${s.pagesCount} (${hlmem.Memory.MB(s.pagesSize)})</dd>
-				<dt>Roots</dt><dd>${s.rootsCount}</dd>
-				<dt>Stacks</dt><dd>${s.stackCount}</dd>
-				<dt>Types</dt><dd>${s.typesCount}</dd>
-				<dt>Closures</dt><dd>${s.closuresCount}</dd>
-				<dt>Live blocks</dt><dd>${s.blockCount}</dd>
-			</dl>
-			${idx < statsObj.length - 1 ? '<hr class="solid"></hr>' : ''}
-			').appendTo(stats);
-		}
-	}
-
-	public function refreshHierarchicalView() {
-		element.find('table').parent().remove();
-		var tab = new Element('
-		<div class="hide-scroll">
-			<table rules=none>
-				<thead>
-					<td class="sort-count">Count<div ${sort.match(SortType.ByCount) ? 'class="icon ico ico-caret-${sortOrderAscending ? 'up' : 'down'}"' : ''}></div></td>
-					<td class="sort-size">Size<div ${sort.match(SortType.ByMemory) ? 'class="icon ico ico-caret-${sortOrderAscending ? 'up' : 'down'}"' : ''}></div></td>
-					<td>Name</td>
-					<td class="sort-size">% Impact<div ${sort.match(SortType.ByMemory) ? 'class="icon ico ico-caret-${sortOrderAscending ? 'up' : 'down'}"' : ''}></div></td>
-				</thead>
-				<tbody>
-				</tbody>
-			</table>
-		</div>'
-		).appendTo(element.find(".left-panel"));
-
-		tab.find('.sort-count').on('click', function(e) { sortDatas(SortType.ByCount, sort.match(SortType.ByCount) ? !sortOrderAscending : false); });
-		tab.find('.sort-size').on('click', function(e) { sortDatas(SortType.ByMemory, sort.match(SortType.ByMemory) ? !sortOrderAscending : false); });
-		tab.on('keydown', function(e) e.preventDefault());
-
-		var body = tab.find('tbody');
-		for (idx => l in lines) {
-			var pe = new ProfilerElement(this, l, null, null);
-			pe.element.appendTo(body);
-
-			if (idx == 0)
-				pe.element.focus();
-		}
-	}
-
-	public function locate(str : String) @:privateAccess {
-		var datas = [];
-		if (str == "null" || locationData.exists(str)) return;
-
-		var ctx = @:privateAccess currentMemory.locate(str, 30);
-		ctx.sort();
-		for (i in ctx.allT)
-			datas.push({count : i.count, size : i.mem, tid : i.tl, name : null, state: Unique});
-
-		locationData.set(str, datas);
-	}
-
-	public function getChildren(depth : Int, parent : Int, valid : Array<LineData>) : Array<Path> {
-		var valid = valid.filter(p ->  {
-			var isCurrentPath = depth <= 0 || p.tid[depth-1] == parent;
-			return p.tid.length > depth && isCurrentPath;
-		});
-
-		var children : Array<Dynamic> = [];
-		for (path in valid) {
-			if (parent == -1 || path.tid[depth - 1] == parent) {
-				var copy = children.filter((c) -> c.p == path.tid[depth]);
-				if (copy.length == 0) {
-					children.push({p : path.tid[depth],
-						count : path.count, size : path.size,
-						line : depth == path.tid.length - 1 ? path : null});
-				} else {
-					copy[0].count += path.count;
-					copy[0].size += path.size;
-				}
-			}
-		}
-
-		children.sort((a, b) -> b.count - a.count);
-		return children.map(c -> {v : c.p, children : getChildren(depth+1, c.p, valid), line : c.line, total : {count : c.count, mem : c.size}});
-	}
-
-	public function sortDatas(sort: SortType, isAscending : Bool) {
-		this.sort = sort;
-		this.sortOrderAscending = isAscending;
-
-		if (mainMemory == null) return;
-
-		displayTypes(sort, isAscending);
-		refreshHierarchicalView();
-	}
-
-	public function filterDatas(filter: Filter) @:privateAccess{
-		this.currentFilter = filter;
-
-		switch (currentFilter) {
-			case None :
-				currentMemory = mainMemory;
-				mainMemory.setFilterMode(None);
-			case Unique :
-				currentMemory = mainMemory;
-				mainMemory.setFilterMode(Unique);
-			case Difference :
-				mainMemory.setFilterMode(None);
-				if (mainMemory.otherMems.length > 0){
-					var other = mainMemory.otherMems[0];
-					other.otherMems = [mainMemory];
-					other.setFilterMode(Unique);
-					other.otherMems = [];
-					currentMemory = other;
-				}
-			case Intersected :
-				var other = mainMemory.otherMems[0];
-				other.setFilterMode(None);
-				currentMemory = mainMemory;
-				mainMemory.setFilterMode(Intersect);
-			default:
-				currentMemory = mainMemory;
-				mainMemory.setFilterMode(None);
-		}
-
-		locationData.clear();
-
-		displayTypes(sort, sortOrderAscending);
-		statsObj = mainMemory?.getStats();
-
-		refreshHierarchicalView();
-	}
-
-	#end
-
-	static var _ = hide.ui.View.register(Profiler);
-}
-
-class ProfilerElement extends hide.comp.Component{
-	#if (hashlink >= "1.15.0")
-	public var profiler : Profiler;
-	public var line : LineData;
-	public var path : Path;
-	public var parent : ProfilerElement;
-	public var depth : Int = 0;
-	public var isOpen = false;
-
-	// Cached values
-	var foldBtn : Element;
-	var children : Array<ProfilerElement> = null;
-
-	public function new(profiler : Profiler, line: LineData, path : Path, parent : ProfilerElement = null) @:privateAccess {
-        super(null, null);
-
-		this.profiler = profiler;
-		this.line = line;
-		this.path = path;
-		this.parent = parent;
-		this.depth = parent != null ? parent.depth + 1 : 0;
-
-		var name = path == null ? line.name : hlmem.Memory.Stats.getTypeString(profiler.currentMemory, path.v);
-		var count = path == null ? line.count : path.total.count;
-		var mem = path == null ? line.size : path.total.mem;
-
-		this.element = new Element('<tr tabindex="2"><td><div class="folder icon ico ico-caret-right"></div>${count}</td><td>${hlmem.Memory.MB(mem)}</td><td title="${name}">${name}</td><td><div title="Allocated ${mem} (${100 * mem / Reflect.getProperty(profiler.statsObj[0], "totalAllocated")}% of total)" class="outer-gauge"><div class="inner-gauge" style="width:${100 * mem / Reflect.getProperty(profiler.statsObj[0], "totalAllocated")}%;"></div></div></td></tr>');
-		this.element.find('td').first().css({'padding-left':'${10 * depth}px'});
-
-		this.foldBtn = this.element.find('.folder');
-
-		// Build children profiler
-		if (this.path != null) {
-			for (p in this.path.children??[]) {
-				if (children == null)
-					children = [];
-
-				var pe = new ProfilerElement(this.profiler, null, p, this);
-
-				if (pe.children == null) {
-					pe.foldBtn.css({ opacity : 0 });
-					pe.foldBtn.off();
-				}
-
-				children.push(pe);
-			}
-		}
-
-		// Manage line folding / unfolding to show / unshow details
-		foldBtn.on('click', function(e) {
-			if (!isOpen) {
-				this.open();
-			}
-			else {
-				this.close();
-			}
-		});
-
-		this.element.on('keydown', function(e) {
-
-			function selectUp() {
-				if (this.element.prev('tr').length > 0)
-					this.element.prev('tr').first().focus();
-				else
-					this.element.parent('tr').focus();
-			}
-
-			function selectDown() {
-				if (this.element.children('tr').length > 0)
-					this.element.children('tr').first().focus();
-				else
-					this.element.next('tr').focus();
-			}
-
-			switch( e.keyCode ) {
-				case hxd.Key.LEFT:
-					if (this.isOpen)
-						this.close();
-				case hxd.Key.RIGHT:
-					if (this.isOpen) {
-						selectDown();
-					}
-					else {
-						open();
-					}
-				case hxd.Key.DOWN:
-					selectDown();
-				case hxd.Key.UP:
-					selectUp();
-				default:
-			}
-
-			e.stopPropagation();
-			e.preventDefault();
-		});
-    }
-
-	public function open() {
-		this.isOpen = true;
-		this.foldBtn.removeClass('ico-caret-right').addClass('ico-caret-down');
-
-		// Only root nodes without children will generate their children
-		if (children == null && parent == null) {
-			var d = profiler.locationData.get(line.name);
-			if (d == null) {
-				profiler.locate(line.name);
-				d = profiler.locationData.get(line.name);
-			}
-
-			var pathData = profiler.getChildren(depth, path != null ? path.v : -1, d);
-
-			if (this.children == null)
-				this.children = [];
-
-			for (p in pathData) {
-				var pe = new ProfilerElement(this.profiler, null, p, this);
-				this.children.push(pe);
-				pe.element.insertAfter(this.element);
-			}
-		}
-
-		for (c in children??[]) {
-			c.element.insertAfter(this.element);
-		}
-	}
-
-	public function close() {
-		this.isOpen = false;
-		this.foldBtn.removeClass('ico-caret-down').addClass('ico-caret-right');
-
-		for (c in children??[]) {
-			c.close();
-			c.element.detach();
-		}
-	}
-	#end
-}