You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
269 lines
7.8 KiB
HTML
269 lines
7.8 KiB
HTML
<html>
|
|
<head>
|
|
<title>Lumberyard Asset Memory Viewer</title>
|
|
|
|
<link href="../../External/tabulator-master/dist/css/tabulator.css" rel="stylesheet">
|
|
<script src="../../External/tabulator-master/dist/js/tabulator.js"></script>
|
|
|
|
<style>
|
|
#dropzone {
|
|
position: relative;
|
|
border: 2px dashed #0087F7;
|
|
border-radius: 5px;
|
|
width: 50%;
|
|
height: 240px;
|
|
margin: 0 auto;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.dz-active {
|
|
background: #CCCCCC;
|
|
}
|
|
|
|
#dz-message {
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 50%;
|
|
transform: translate(-50%, -50%);
|
|
color: red;
|
|
pointer-events: none;
|
|
}
|
|
|
|
html, body{
|
|
font-family: Helvetica, sans-serif;
|
|
}
|
|
|
|
.clear-button {
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 4px;
|
|
background-color: rgba(8, 8, 8, 0.25);
|
|
margin: 4px;
|
|
text-align: center;
|
|
}
|
|
|
|
</style>
|
|
|
|
</head>
|
|
<body>
|
|
|
|
<div id="dropzone" ondrop="dropHandler(event);" ondragenter="dragEnterHandler(event);" ondragleave="dragLeaveHandler(event);" ondragover="event.preventDefault();" onclick="loadFileClicked(event);">
|
|
<div id="dz-message">
|
|
Drop your assetmem JSON file here, or click to browse to it.
|
|
</div>
|
|
<input id="file-input" type="file" name="file-input" style="display: none;" accept=".json,.txt" onchange="loadFileSelected(event);" />
|
|
|
|
<template id="filter-template">
|
|
<div style="width: 100%; display: flex;" onclick="event.stopPropagation();">
|
|
<select id="sel-not" onchange="UpdateFilter();">
|
|
<option></option>
|
|
<option>NOT</option>
|
|
</select>
|
|
<select id="sel-function" onchange="UpdateFilter();">
|
|
</select>
|
|
<input id="txt-filter" type="text" style="flex-grow: 100" onchange="UpdateFilter();">
|
|
</input>
|
|
<div onclick="ResetFilter();" class="clear-button">
|
|
×
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
</div>
|
|
|
|
<div id="main-table">
|
|
<div>
|
|
|
|
<script>
|
|
|
|
let mainTable = null;
|
|
let mainTableFilter = null;
|
|
|
|
function OverrideTableSort(table) {
|
|
// tabulator.js doesn't innately support recursive sorting on hierarchical tables.
|
|
// This function replaces the table's sort function with one that does recursive sorting.
|
|
table.modules.sort._sortItem = function (column, dir, sortList, i) {
|
|
let self = table.modules.sort;
|
|
|
|
let recursiveSort = function(rows) {
|
|
rows.sort(function (a, b) {
|
|
let result = self._sortRow(a, b, column, dir, params);
|
|
|
|
//if results match recurse through previous searchs to be sure
|
|
if (result === 0 && i) {
|
|
for (let j = i - 1; j >= 0; j--) {
|
|
result = self._sortRow(a, b, sortList[j].column, sortList[j].dir, params);
|
|
|
|
if (result !== 0) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result;
|
|
});
|
|
|
|
rows.forEach(function(row) {
|
|
let sub = row.modules.dataTree;
|
|
|
|
if (sub !== undefined && sub.children instanceof Array) {
|
|
recursiveSort(sub.children);
|
|
}
|
|
});
|
|
};
|
|
|
|
let activeRows = self.table.rowManager.activeRows;
|
|
|
|
let params = typeof column.modules.sort.params === "function" ? column.modules.sort.params(column.getComponent(), dir) : column.modules.sort.params;
|
|
|
|
recursiveSort(activeRows);
|
|
};
|
|
};
|
|
|
|
function stringToNumber(s) {
|
|
return parseFloat(s.replace(/,/g, ""));
|
|
}
|
|
|
|
function stringColumnSum(values, data, calcParams) {
|
|
return values.reduce(function(total, val) { return total + stringToNumber(val); }, 0).toLocaleString();
|
|
}
|
|
|
|
function stringColumnSort(a, b) {
|
|
return stringToNumber(a) - stringToNumber(b);
|
|
}
|
|
|
|
function UpdateFilter() {
|
|
let shouldInvert = mainTableFilter.selNot.selectedIndex > 0;
|
|
let filterOption = mainTableFilter.selFunction.options[mainTableFilter.selFunction.selectedIndex];
|
|
let filterContent = mainTableFilter.txtFilter.value;
|
|
let shouldFilter = filterContent.length > 0;
|
|
|
|
if (filterOption.text == "regex") {
|
|
try {
|
|
filterContent = new RegExp(filterContent);
|
|
}
|
|
catch (error) {
|
|
shouldFilter = false;
|
|
}
|
|
}
|
|
|
|
let filterFunction = function(data, filterParams) {
|
|
if (!shouldFilter) {
|
|
return true;
|
|
}
|
|
|
|
return filterOption.fn(data.label, filterContent) ^ shouldInvert;
|
|
};
|
|
|
|
mainTable.setFilter(filterFunction);
|
|
}
|
|
|
|
function ResetFilter() {
|
|
mainTableFilter.txtFilter.value = "";
|
|
mainTableFilter.selNot.selectedIndex = 0;
|
|
mainTableFilter.selFunction.selectedIndex = 0;
|
|
UpdateFilter();
|
|
}
|
|
|
|
function AddLabelFilter(table) {
|
|
let filterFunctions = [
|
|
['contains', function(x, y) { return x.indexOf(y) != -1; }],
|
|
['equals', function(x, y) { return x == y; }],
|
|
['startswith', function(x, y) { return x.startsWith(y); }],
|
|
['endswith', function(x, y) { return x.endsWith(y); }],
|
|
['regex', function(x, y) { return x.search(y) != -1; }]
|
|
];
|
|
|
|
let contentDiv = table.columnManager.columns[0].contentElement;
|
|
let filterTemplate = document.querySelector("#filter-template");
|
|
let importFragment = document.importNode(filterTemplate.content, true);
|
|
mainTableFilter = importFragment.querySelector("div");
|
|
|
|
// Cache the fields we will be querying for filter updates
|
|
mainTableFilter.selNot = mainTableFilter.querySelector("#sel-not");
|
|
mainTableFilter.selFunction = mainTableFilter.querySelector("#sel-function");
|
|
mainTableFilter.txtFilter = mainTableFilter.querySelector("#txt-filter");
|
|
|
|
// Add the filter functions to the dropdown
|
|
filterFunctions.forEach(function(info) {
|
|
let option = new Option(info[0]);
|
|
option.fn = info[1];
|
|
mainTableFilter.selFunction.add(option);
|
|
});
|
|
|
|
contentDiv.appendChild(mainTableFilter);
|
|
}
|
|
|
|
function loadTable(data) {
|
|
let width = 120;
|
|
let root = data[0];
|
|
let displayItems = root['_children'];
|
|
|
|
mainTable = new Tabulator("#main-table", {
|
|
height: "100%",
|
|
data: displayItems,
|
|
layout: "fitColumns",
|
|
dataTree: "true",
|
|
columns: [
|
|
{ title: "Label", field: "label" },
|
|
{ title: "Heap Allocations", columns: [
|
|
{ title: "#", field: "heap.count", sorter: "number", width: width, bottomCalc: "sum" },
|
|
{ title: "kb", field: "heap.kb", sorter: stringColumnSort, width: width, bottomCalc: stringColumnSum }
|
|
]},
|
|
{ title: "VRAM Allocations", columns: [
|
|
{ title: "#", field: "vram.count", sorter: "number", width: width, bottomCalc: "sum" },
|
|
{ title: "kb", field: "vram.kb", sorter: stringColumnSort, width: width, bottomCalc: stringColumnSum }
|
|
]}
|
|
]
|
|
});
|
|
|
|
OverrideTableSort(mainTable);
|
|
AddLabelFilter(mainTable);
|
|
|
|
// Hide the dropzone
|
|
document.querySelector("#dropzone").style.display = "none";
|
|
}
|
|
|
|
function loadFile(file) {
|
|
let reader = new FileReader();
|
|
reader.onload = function(event) {
|
|
let data = JSON.parse(event.target.result);
|
|
loadTable(data);
|
|
};
|
|
reader.readAsText(file);
|
|
}
|
|
|
|
function dragEnterHandler(event) {
|
|
event.dropEffect = "copy";
|
|
event.target.classList.add("dz-active");
|
|
event.preventDefault();
|
|
}
|
|
|
|
function dragLeaveHandler(event) {
|
|
event.target.classList.remove("dz-active");
|
|
event.preventDefault();
|
|
}
|
|
|
|
function dropHandler(event) {
|
|
if (event.dataTransfer.files.length) {
|
|
loadFile(event.dataTransfer.files[0]);
|
|
}
|
|
|
|
event.target.classList.remove("dz-active");
|
|
event.preventDefault();
|
|
}
|
|
|
|
function loadFileClicked(event) {
|
|
document.querySelector("#file-input").click();
|
|
}
|
|
|
|
function loadFileSelected(event) {
|
|
if (event.target.files.length > 0) {
|
|
loadFile(event.target.files[0]);
|
|
}
|
|
}
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|