Merge pull request #13943 from huffman/perf/create-profile-no-listjs

Improvements to entity list loading times
This commit is contained in:
Ryan Huffman 2018-09-11 09:33:28 -07:00 committed by GitHub
commit c7f6bf181b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 326 additions and 178 deletions

View file

@ -9,9 +9,10 @@
--> -->
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<link rel="stylesheet" type="text/css" href="css/edit-style.css"> <link rel="stylesheet" type="text/css" href="css/edit-style.css">
<script src="js/list.min.js"></script>
<script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script> <script type="text/javascript" src="qrc:///qtwebchannel/qwebchannel.js"></script>
<script type="text/javascript" src="js/jquery-2.1.4.min.js"></script>
<script type="text/javascript" src="js/eventBridgeLoader.js"></script> <script type="text/javascript" src="js/eventBridgeLoader.js"></script>
<script type="text/javascript" src="js/spinButtons.js"></script> <script type="text/javascript" src="js/spinButtons.js"></script>
<script type="text/javascript" src="js/entityList.js"></script> <script type="text/javascript" src="js/entityList.js"></script>

View file

@ -6,12 +6,8 @@
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var entities = {}; const ASCENDING_SORT = 1;
var selectedEntities = []; const DESCENDING_SORT = -1;
var currentSortColumn = 'type';
var currentSortOrder = 'des';
var entityList = null;
var refreshEntityListTimer = null;
const ASCENDING_STRING = '&#x25B4;'; const ASCENDING_STRING = '&#x25B4;';
const DESCENDING_STRING = '&#x25BE;'; const DESCENDING_STRING = '&#x25BE;';
const LOCKED_GLYPH = "&#xe006;"; const LOCKED_GLYPH = "&#xe006;";
@ -23,14 +19,51 @@ const DELETE = 46; // Key code for the delete key.
const KEY_P = 80; // Key code for letter p used for Parenting hotkey. const KEY_P = 80; // Key code for letter p used for Parenting hotkey.
const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities. const MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
debugPrint = function (message) { const COMPARE_ASCENDING = function(a, b) {
console.log(message); let va = a[currentSortColumn];
let vb = b[currentSortColumn];
if (va < vb) {
return -1;
} else if (va > vb) {
return 1;
}
return 0;
}
const COMPARE_DESCENDING = function(a, b) {
return COMPARE_ASCENDING(b, a);
}
// List of all entities
let entities = []
// List of all entities, indexed by Entity ID
var entitiesByID = {};
// The filtered and sorted list of entities
var visibleEntities = [];
var selectedEntities = [];
var currentSortColumn = 'type';
var currentSortOrder = ASCENDING_SORT;
const ENABLE_PROFILING = false;
var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args);
} ;
const PROFILE = !ENABLE_PROFILING ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Web " + profileIndent + "(" + name + ") Begin");
var previousIndent = profileIndent;
profileIndent += ' ';
var before = Date.now();
fn.apply(this, args);
var delta = Date.now() - before;
profileIndent = previousIndent;
console.log("PROFILE-Web " + profileIndent + "(" + name + ") End " + delta + "ms");
}; };
function loaded() { function loaded() {
openEventBridge(function() { openEventBridge(function() {
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS});
entityList.clear();
elEntityTable = document.getElementById("entity-table"); elEntityTable = document.getElementById("entity-table");
elEntityTableBody = document.getElementById("entity-table-body"); elEntityTableBody = document.getElementById("entity-table-body");
elRefresh = document.getElementById("refresh"); elRefresh = document.getElementById("refresh");
@ -89,10 +122,10 @@ function loaded() {
}; };
function onRowClicked(clickEvent) { function onRowClicked(clickEvent) {
var id = this.dataset.entityId; let entityID = this.dataset.entityID;
var selection = [this.dataset.entityId]; let selection = [entityID];
if (clickEvent.ctrlKey) { if (clickEvent.ctrlKey) {
var selectedIndex = selectedEntities.indexOf(id); let selectedIndex = selectedEntities.indexOf(entityID);
if (selectedIndex >= 0) { if (selectedIndex >= 0) {
selection = selectedEntities; selection = selectedEntities;
selection.splice(selectedIndex, 1) selection.splice(selectedIndex, 1)
@ -100,22 +133,23 @@ function loaded() {
selection = selection.concat(selectedEntities); selection = selection.concat(selectedEntities);
} }
} else if (clickEvent.shiftKey && selectedEntities.length > 0) { } else if (clickEvent.shiftKey && selectedEntities.length > 0) {
var previousItemFound = -1; let previousItemFound = -1;
var clickedItemFound = -1; let clickedItemFound = -1;
for (var entity in entityList.visibleItems) { for (let i = 0, len = visibleEntities.length; i < len; ++i) {
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) { let entity = visibleEntities[i];
clickedItemFound = entity; if (clickedItemFound === -1 && entityID === entity.id) {
} else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) { clickedItemFound = i;
previousItemFound = entity; } else if (previousItemFound === -1 && selectedEntities[0] === entity.id) {
} previousItemFound = i;
} }
};
if (previousItemFound !== -1 && clickedItemFound !== -1) { if (previousItemFound !== -1 && clickedItemFound !== -1) {
var betweenItems = []; let betweenItems = [];
var toItem = Math.max(previousItemFound, clickedItemFound); let toItem = Math.max(previousItemFound, clickedItemFound);
// skip first and last item in this loop, we add them to selection after the loop // skip first and last item in this loop, we add them to selection after the loop
for (var i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) { for (let i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) {
entityList.visibleItems[i].elm.className = 'selected'; visibleEntities[i].el.className = 'selected';
betweenItems.push(entityList.visibleItems[i].values().id); betweenItems.push(visibleEntities[i].id);
} }
if (previousItemFound > clickedItemFound) { if (previousItemFound > clickedItemFound) {
// always make sure that we add the items in the right order // always make sure that we add the items in the right order
@ -125,6 +159,12 @@ function loaded() {
} }
} }
selectedEntities.forEach(function(entityID) {
if (selection.indexOf(entityID) === -1) {
entitiesByID[entityID].el.className = '';
}
});
selectedEntities = selection; selectedEntities = selection;
this.className = 'selected'; this.className = 'selected';
@ -142,7 +182,7 @@ function loaded() {
EventBridge.emitWebEvent(JSON.stringify({ EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate", type: "selectionUpdate",
focus: true, focus: true,
entityIds: [this.dataset.entityId], entityIds: [this.dataset.entityID],
})); }));
} }
@ -156,64 +196,140 @@ function loaded() {
return number ? number : ""; return number ? number : "";
} }
function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent, function getFilename(url) {
isBaked, drawCalls, hasScript) { let urlParts = url.split('/');
return urlParts[urlParts.length - 1];
}
var urlParts = url.split('/'); // Update the entity list with the new set of data sent from edit.js
var filename = urlParts[urlParts.length - 1]; function updateEntityList(entityData) {
const IMAGE_MODEL_NAME = 'default-image-model.fbx';
var IMAGE_MODEL_NAME = 'default-image-model.fbx'; entities = []
entitiesByID = {};
visibleEntities = [];
PROFILE("map-data", function() {
entityData.forEach(function(entity) {
let type = entity.type;
let filename = getFilename(entity.url);
if (filename === IMAGE_MODEL_NAME) { if (filename === IMAGE_MODEL_NAME) {
type = "Image"; type = "Image";
} }
if (entities[id] === undefined) { let entityData = {
entityList.add([{ id: entity.id,
id: id, name: name, type: type, url: filename, locked: locked, visible: visible, name: entity.name,
verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount), type: type,
texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent, url: filename,
isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript fullUrl: entity.url,
}], locked: entity.locked ? LOCKED_GLYPH : null,
function (items) { visible: entity.visible ? VISIBLE_GLYPH : null,
var currentElement = items[0].elm; verticesCount: displayIfNonZero(entity.verticesCount),
var id = items[0]._values.id; texturesCount: displayIfNonZero(entity.texturesCount),
entities[id] = { texturesSize: decimalMegabytes(entity.texturesSize),
id: id, hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null,
name: name, isBaked: entity.isBaked ? BAKED_GLYPH : null,
el: currentElement, drawCalls: displayIfNonZero(entity.drawCalls),
item: items[0] hasScript: entity.hasScript ? SCRIPT_GLYPH : null,
};
currentElement.setAttribute('id', 'entity_' + id);
currentElement.setAttribute('title', url);
currentElement.dataset.entityId = id;
currentElement.onclick = onRowClicked;
currentElement.ondblclick = onRowDoubleClicked;
});
} else {
var item = entities[id].item;
item.values({ name: name, url: filename, locked: locked, visible: visible });
} }
entities.push(entityData);
entitiesByID[entityData.id] = entityData;
});
});
PROFILE("create-rows", function() {
entities.forEach(function(entity) {
let row = document.createElement('tr');
row.dataset.entityID = entity.id;
row.attributes.title = entity.fullUrl;
function addColumn(cls, text) {
let col = document.createElement('td');
col.className = cls;
col.innerText = text;
row.append(col);
}
function addColumnHTML(cls, text) {
let col = document.createElement('td');
col.className = cls;
col.innerHTML = text;
row.append(col);
}
addColumn('type', entity.type);
addColumn('name', entity.name);
addColumn('url', entity.url);
addColumnHTML('locked glyph', entity.locked);
addColumnHTML('visible glyph', entity.visible);
addColumn('verticesCount', entity.verticesCount);
addColumn('texturesCount', entity.texturesCount);
addColumn('texturesSize', entity.texturesSize);
addColumnHTML('hasTransparent glyph', entity.hasTransparent);
addColumnHTML('isBaked glyph', entity.isBaked);
addColumn('drawCalls', entity.drawCalls);
addColumn('hasScript glyph', entity.hasScript);
row.addEventListener('click', onRowClicked);
row.addEventListener('dblclick', onRowDoubleClicked);
entity.el = row;
});
});
refreshEntityList();
updateSelectedEntities(selectedEntities);
}
function refreshEntityList() {
PROFILE("refresh-entity-list", function() {
PROFILE("filter", function() {
let searchTerm = elFilter.value;
if (searchTerm === '') {
visibleEntities = entities.slice(0);
} else {
visibleEntities = entities.filter(function(e) {
return e.name.indexOf(searchTerm) > -1
|| e.type.indexOf(searchTerm) > -1
|| e.fullUrl.indexOf(searchTerm) > -1;
});
}
});
PROFILE("sort", function() {
let cmp = currentSortOrder === ASCENDING_SORT ? COMPARE_ASCENDING : COMPARE_DESCENDING;
visibleEntities.sort(cmp);
});
PROFILE("update-dom", function() {
elEntityTableBody.innerHTML = '';
for (let i = 0, len = visibleEntities.length; i < len; ++i) {
elEntityTableBody.append(visibleEntities[i].el);
}
});
});
} }
function removeEntities(deletedIDs) { function removeEntities(deletedIDs) {
for (i = 0, length = deletedIDs.length; i < length; i++) { // Loop from the back so we can pop items off while iterating
delete entities[deletedIDs[i]]; for (let j = entities.length - 1; j >= 0; --j) {
entityList.remove("id", deletedIDs[i]); let id = entities[j];
for (let i = 0, length = deletedIDs.length; i < length; ++i) {
if (id === deletedIDs[i]) {
entities.splice(j, 1);
entitiesByID[id].el.remove();
delete entitiesByID[id];
break;
} }
} }
function scheduleRefreshEntityList() {
var REFRESH_DELAY = 50;
if (refreshEntityListTimer) {
clearTimeout(refreshEntityListTimer);
} }
refreshEntityListTimer = setTimeout(refreshEntityListObject, REFRESH_DELAY); refreshEntities();
} }
function clearEntities() { function clearEntities() {
entities = {}; entities = []
entityList.clear(); entitiesByID = {};
visibleEntities = [];
elEntityTableBody.innerHTML = '';
refreshFooter(); refreshFooter();
} }
@ -232,15 +348,17 @@ function loaded() {
hasScript: document.querySelector('#entity-hasScript .sort-order'), hasScript: document.querySelector('#entity-hasScript .sort-order'),
} }
function setSortColumn(column) { function setSortColumn(column) {
if (currentSortColumn == column) { PROFILE("set-sort-column", function() {
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc"; if (currentSortColumn === column) {
currentSortOrder *= -1;
} else { } else {
elSortOrder[currentSortColumn].innerHTML = ""; elSortOrder[currentSortColumn].innerHTML = "";
currentSortColumn = column; currentSortColumn = column;
currentSortOrder = "asc"; currentSortOrder = ASCENDING_SORT;
} }
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING; elSortOrder[column].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
entityList.sort(currentSortColumn, { order: currentSortOrder }); refreshEntityList();
});
} }
setSortColumn('type'); setSortColumn('type');
@ -254,32 +372,27 @@ function loaded() {
elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected"; elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected";
} else if (selectedEntities.length === 1) { } else if (selectedEntities.length === 1) {
elFooter.firstChild.nodeValue = "1 entity selected"; elFooter.firstChild.nodeValue = "1 entity selected";
} else if (entityList.visibleItems.length === 1) { } else if (visibleEntities.length === 1) {
elFooter.firstChild.nodeValue = "1 entity found"; elFooter.firstChild.nodeValue = "1 entity found";
} else { } else {
elFooter.firstChild.nodeValue = entityList.visibleItems.length + " entities found"; elFooter.firstChild.nodeValue = visibleEntities.length + " entities found";
} }
} }
function refreshEntityListObject() {
refreshEntityListTimer = null;
entityList.sort(currentSortColumn, { order: currentSortOrder });
entityList.search(elFilter.value);
refreshFooter();
}
function updateSelectedEntities(selectedIDs) { function updateSelectedEntities(selectedIDs) {
var notFound = false; let notFound = false;
for (var id in entities) {
entities[id].el.className = ''; selectedEntities.forEach(function(id) {
} entitiesByID[id].el.className = '';
});
selectedEntities = []; selectedEntities = [];
for (var i = 0; i < selectedIDs.length; i++) { for (let i = 0; i < selectedIDs.length; i++) {
var id = selectedIDs[i]; let id = selectedIDs[i];
selectedEntities.push(id); selectedEntities.push(id);
if (id in entities) { if (id in entitiesByID) {
var entity = entities[id]; let entity = entitiesByID[id];
entity.el.className = 'selected'; entity.el.className = 'selected';
} else { } else {
notFound = true; notFound = true;
@ -363,31 +476,20 @@ function loaded() {
refreshEntities(); refreshEntities();
} }
} else if (data.type === "update" && data.selectedIDs !== undefined) { } else if (data.type === "update" && data.selectedIDs !== undefined) {
PROFILE("update", function() {
var newEntities = data.entities; var newEntities = data.entities;
if (newEntities && newEntities.length == 0) { if (newEntities && newEntities.length === 0) {
elNoEntitiesMessage.style.display = "block"; elNoEntitiesMessage.style.display = "block";
elFooter.firstChild.nodeValue = "0 entities found"; elFooter.firstChild.nodeValue = "0 entities found";
} else if (newEntities) { } else if (newEntities) {
elNoEntitiesMessage.style.display = "none"; elNoEntitiesMessage.style.display = "none";
for (var i = 0; i < newEntities.length; i++) { updateEntityList(newEntities);
var id = newEntities[i].id;
addEntity(id, newEntities[i].name, newEntities[i].type, newEntities[i].url,
newEntities[i].locked ? LOCKED_GLYPH : null,
newEntities[i].visible ? VISIBLE_GLYPH : null,
newEntities[i].verticesCount, newEntities[i].texturesCount, newEntities[i].texturesSize,
newEntities[i].hasTransparent ? TRANSPARENCY_GLYPH : null,
newEntities[i].isBaked ? BAKED_GLYPH : null,
newEntities[i].drawCalls,
newEntities[i].hasScript ? SCRIPT_GLYPH : null);
}
updateSelectedEntities(data.selectedIDs); updateSelectedEntities(data.selectedIDs);
scheduleRefreshEntityList();
resize();
} }
});
} else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) { } else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) {
removeEntities(data.deletedIDs); removeEntities(data.deletedIDs);
updateSelectedEntities(data.selectedIDs); updateSelectedEntities(data.selectedIDs);
scheduleRefreshEntityList();
} else if (data.type === "deleted" && data.ids) { } else if (data.type === "deleted" && data.ids) {
removeEntities(data.ids); removeEntities(data.ids);
refreshFooter(); refreshFooter();
@ -434,7 +536,13 @@ function loaded() {
}; };
window.onresize = resize; window.onresize = resize;
elFilter.onchange = resize;
elFilter.onkeyup = refreshEntityList;
elFilter.onpaste = refreshEntityList;
elFilter.onchange = function() {
refreshEntityList();
resize();
};
elFilter.onblur = refreshFooter; elFilter.onblur = refreshFooter;

View file

@ -0,0 +1,11 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtWebChannel 1.0
import QtGraphicalEffects 1.0
import "qrc:///qml/controls" as HifiControls
HifiControls.WebView {
id: entityListToolWebView
url: Qt.resolvedUrl("../html/entityList.html")
enabled: true
}

View file

@ -11,6 +11,22 @@
/* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages,
cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */ cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible */
var PROFILING_ENABLED = false;
var profileIndent = '';
const PROFILE_NOOP = function(_name, fn, args) {
fn.apply(this, args);
} ;
PROFILE = !PROFILING_ENABLED ? PROFILE_NOOP : function(name, fn, args) {
console.log("PROFILE-Script " + profileIndent + "(" + name + ") Begin");
var previousIndent = profileIndent;
profileIndent += ' ';
var before = Date.now();
fn.apply(this, args);
var delta = Date.now() - before;
profileIndent = previousIndent;
console.log("PROFILE-Script " + profileIndent + "(" + name + ") End " + delta + "ms");
};
EntityListTool = function(shouldUseEditTabletApp) { EntityListTool = function(shouldUseEditTabletApp) {
var that = {}; var that = {};
@ -20,7 +36,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
var ENTITY_LIST_WIDTH = 495; var ENTITY_LIST_WIDTH = 495;
var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778; var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778;
var entityListWindow = new CreateWindow( var entityListWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml", Script.resolvePath("EditEntityList.qml"),
'Entity List', 'Entity List',
'com.highfidelity.create.entityListWindow', 'com.highfidelity.create.entityListWindow',
function () { function () {
@ -66,11 +82,16 @@ EntityListTool = function(shouldUseEditTabletApp) {
that.setVisible(false); that.setVisible(false);
function emitJSONScriptEvent(data) { function emitJSONScriptEvent(data) {
var dataString = JSON.stringify(data); var dataString;
PROFILE("Script-JSON.stringify", function() {
dataString = JSON.stringify(data);
});
PROFILE("Script-emitScriptEvent", function() {
webView.emitScriptEvent(dataString); webView.emitScriptEvent(dataString);
if (entityListWindow.window) { if (entityListWindow.window) {
entityListWindow.window.emitScriptEvent(dataString); entityListWindow.window.emitScriptEvent(dataString);
} }
});
} }
that.toggleVisible = function() { that.toggleVisible = function() {
@ -116,19 +137,24 @@ EntityListTool = function(shouldUseEditTabletApp) {
} }
that.sendUpdate = function() { that.sendUpdate = function() {
PROFILE('Script-sendUpdate', function() {
var entities = []; var entities = [];
var ids; var ids;
PROFILE("findEntities", function() {
if (filterInView) { if (filterInView) {
ids = Entities.findEntitiesInFrustum(Camera.frustum); ids = Entities.findEntitiesInFrustum(Camera.frustum);
} else { } else {
ids = Entities.findEntities(MyAvatar.position, searchRadius); ids = Entities.findEntities(MyAvatar.position, searchRadius);
} }
});
var cameraPosition = Camera.position; var cameraPosition = Camera.position;
PROFILE("getProperties", function() {
for (var i = 0; i < ids.length; i++) { for (var i = 0; i < ids.length; i++) {
var id = ids[i]; var id = ids[i];
var properties = Entities.getEntityProperties(id); var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked',
'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']);
if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) { if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) {
var url = ""; var url = "";
@ -159,6 +185,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
}); });
} }
} }
});
var selectedIDs = []; var selectedIDs = [];
for (var j = 0; j < selectionManager.selections.length; j++) { for (var j = 0; j < selectionManager.selections.length; j++) {
@ -170,6 +197,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
entities: entities, entities: entities,
selectedIDs: selectedIDs, selectedIDs: selectedIDs,
}); });
});
}; };
function onFileSaveChanged(filename) { function onFileSaveChanged(filename) {