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>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<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="js/jquery-2.1.4.min.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/entityList.js"></script>

View file

@ -6,12 +6,8 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
var entities = {};
var selectedEntities = [];
var currentSortColumn = 'type';
var currentSortOrder = 'des';
var entityList = null;
var refreshEntityListTimer = null;
const ASCENDING_SORT = 1;
const DESCENDING_SORT = -1;
const ASCENDING_STRING = '&#x25B4;';
const DESCENDING_STRING = '&#x25BE;';
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 MAX_ITEMS = Number.MAX_VALUE; // Used to set the max length of the list of discovered entities.
debugPrint = function (message) {
console.log(message);
const COMPARE_ASCENDING = function(a, b) {
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() {
openEventBridge(function() {
entityList = new List('entity-list', { valueNames: ['name', 'type', 'url', 'locked', 'visible'], page: MAX_ITEMS});
entityList.clear();
elEntityTable = document.getElementById("entity-table");
elEntityTableBody = document.getElementById("entity-table-body");
elRefresh = document.getElementById("refresh");
@ -89,10 +122,10 @@ function loaded() {
};
function onRowClicked(clickEvent) {
var id = this.dataset.entityId;
var selection = [this.dataset.entityId];
let entityID = this.dataset.entityID;
let selection = [entityID];
if (clickEvent.ctrlKey) {
var selectedIndex = selectedEntities.indexOf(id);
let selectedIndex = selectedEntities.indexOf(entityID);
if (selectedIndex >= 0) {
selection = selectedEntities;
selection.splice(selectedIndex, 1)
@ -100,22 +133,23 @@ function loaded() {
selection = selection.concat(selectedEntities);
}
} else if (clickEvent.shiftKey && selectedEntities.length > 0) {
var previousItemFound = -1;
var clickedItemFound = -1;
for (var entity in entityList.visibleItems) {
if (clickedItemFound === -1 && this.dataset.entityId == entityList.visibleItems[entity].values().id) {
clickedItemFound = entity;
} else if(previousItemFound === -1 && selectedEntities[0] == entityList.visibleItems[entity].values().id) {
previousItemFound = entity;
let previousItemFound = -1;
let clickedItemFound = -1;
for (let i = 0, len = visibleEntities.length; i < len; ++i) {
let entity = visibleEntities[i];
if (clickedItemFound === -1 && entityID === entity.id) {
clickedItemFound = i;
} else if (previousItemFound === -1 && selectedEntities[0] === entity.id) {
previousItemFound = i;
}
}
};
if (previousItemFound !== -1 && clickedItemFound !== -1) {
var betweenItems = [];
var toItem = Math.max(previousItemFound, clickedItemFound);
let betweenItems = [];
let toItem = Math.max(previousItemFound, clickedItemFound);
// 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++) {
entityList.visibleItems[i].elm.className = 'selected';
betweenItems.push(entityList.visibleItems[i].values().id);
for (let i = (Math.min(previousItemFound, clickedItemFound) + 1); i < toItem; i++) {
visibleEntities[i].el.className = 'selected';
betweenItems.push(visibleEntities[i].id);
}
if (previousItemFound > clickedItemFound) {
// 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;
this.className = 'selected';
@ -142,7 +182,7 @@ function loaded() {
EventBridge.emitWebEvent(JSON.stringify({
type: "selectionUpdate",
focus: true,
entityIds: [this.dataset.entityId],
entityIds: [this.dataset.entityID],
}));
}
@ -156,64 +196,140 @@ function loaded() {
return number ? number : "";
}
function addEntity(id, name, type, url, locked, visible, verticesCount, texturesCount, texturesSize, hasTransparent,
isBaked, drawCalls, hasScript) {
function getFilename(url) {
let urlParts = url.split('/');
return urlParts[urlParts.length - 1];
}
var urlParts = url.split('/');
var filename = urlParts[urlParts.length - 1];
// Update the entity list with the new set of data sent from edit.js
function updateEntityList(entityData) {
const IMAGE_MODEL_NAME = 'default-image-model.fbx';
var IMAGE_MODEL_NAME = 'default-image-model.fbx';
entities = []
entitiesByID = {};
visibleEntities = [];
if (filename === IMAGE_MODEL_NAME) {
type = "Image";
}
PROFILE("map-data", function() {
entityData.forEach(function(entity) {
let type = entity.type;
let filename = getFilename(entity.url);
if (filename === IMAGE_MODEL_NAME) {
type = "Image";
}
if (entities[id] === undefined) {
entityList.add([{
id: id, name: name, type: type, url: filename, locked: locked, visible: visible,
verticesCount: displayIfNonZero(verticesCount), texturesCount: displayIfNonZero(texturesCount),
texturesSize: decimalMegabytes(texturesSize), hasTransparent: hasTransparent,
isBaked: isBaked, drawCalls: displayIfNonZero(drawCalls), hasScript: hasScript
}],
function (items) {
var currentElement = items[0].elm;
var id = items[0]._values.id;
entities[id] = {
id: id,
name: name,
el: currentElement,
item: items[0]
};
currentElement.setAttribute('id', 'entity_' + id);
currentElement.setAttribute('title', url);
currentElement.dataset.entityId = id;
currentElement.onclick = onRowClicked;
currentElement.ondblclick = onRowDoubleClicked;
let entityData = {
id: entity.id,
name: entity.name,
type: type,
url: filename,
fullUrl: entity.url,
locked: entity.locked ? LOCKED_GLYPH : null,
visible: entity.visible ? VISIBLE_GLYPH : null,
verticesCount: displayIfNonZero(entity.verticesCount),
texturesCount: displayIfNonZero(entity.texturesCount),
texturesSize: decimalMegabytes(entity.texturesSize),
hasTransparent: entity.hasTransparent ? TRANSPARENCY_GLYPH : null,
isBaked: entity.isBaked ? BAKED_GLYPH : null,
drawCalls: displayIfNonZero(entity.drawCalls),
hasScript: entity.hasScript ? SCRIPT_GLYPH : null,
}
entities.push(entityData);
entitiesByID[entityData.id] = entityData;
});
} else {
var item = entities[id].item;
item.values({ name: name, url: filename, locked: locked, visible: visible });
}
});
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) {
for (i = 0, length = deletedIDs.length; i < length; i++) {
delete entities[deletedIDs[i]];
entityList.remove("id", deletedIDs[i]);
// Loop from the back so we can pop items off while iterating
for (let j = entities.length - 1; j >= 0; --j) {
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() {
entities = {};
entityList.clear();
entities = []
entitiesByID = {};
visibleEntities = [];
elEntityTableBody.innerHTML = '';
refreshFooter();
}
@ -232,15 +348,17 @@ function loaded() {
hasScript: document.querySelector('#entity-hasScript .sort-order'),
}
function setSortColumn(column) {
if (currentSortColumn == column) {
currentSortOrder = currentSortOrder == "asc" ? "desc" : "asc";
} else {
elSortOrder[currentSortColumn].innerHTML = "";
currentSortColumn = column;
currentSortOrder = "asc";
}
elSortOrder[column].innerHTML = currentSortOrder == "asc" ? ASCENDING_STRING : DESCENDING_STRING;
entityList.sort(currentSortColumn, { order: currentSortOrder });
PROFILE("set-sort-column", function() {
if (currentSortColumn === column) {
currentSortOrder *= -1;
} else {
elSortOrder[currentSortColumn].innerHTML = "";
currentSortColumn = column;
currentSortOrder = ASCENDING_SORT;
}
elSortOrder[column].innerHTML = currentSortOrder === ASCENDING_SORT ? ASCENDING_STRING : DESCENDING_STRING;
refreshEntityList();
});
}
setSortColumn('type');
@ -254,32 +372,27 @@ function loaded() {
elFooter.firstChild.nodeValue = selectedEntities.length + " entities selected";
} else if (selectedEntities.length === 1) {
elFooter.firstChild.nodeValue = "1 entity selected";
} else if (entityList.visibleItems.length === 1) {
} else if (visibleEntities.length === 1) {
elFooter.firstChild.nodeValue = "1 entity found";
} 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) {
var notFound = false;
for (var id in entities) {
entities[id].el.className = '';
}
let notFound = false;
selectedEntities.forEach(function(id) {
entitiesByID[id].el.className = '';
});
selectedEntities = [];
for (var i = 0; i < selectedIDs.length; i++) {
var id = selectedIDs[i];
for (let i = 0; i < selectedIDs.length; i++) {
let id = selectedIDs[i];
selectedEntities.push(id);
if (id in entities) {
var entity = entities[id];
if (id in entitiesByID) {
let entity = entitiesByID[id];
entity.el.className = 'selected';
} else {
notFound = true;
@ -363,31 +476,20 @@ function loaded() {
refreshEntities();
}
} else if (data.type === "update" && data.selectedIDs !== undefined) {
var newEntities = data.entities;
if (newEntities && newEntities.length == 0) {
elNoEntitiesMessage.style.display = "block";
elFooter.firstChild.nodeValue = "0 entities found";
} else if (newEntities) {
elNoEntitiesMessage.style.display = "none";
for (var i = 0; i < newEntities.length; i++) {
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);
PROFILE("update", function() {
var newEntities = data.entities;
if (newEntities && newEntities.length === 0) {
elNoEntitiesMessage.style.display = "block";
elFooter.firstChild.nodeValue = "0 entities found";
} else if (newEntities) {
elNoEntitiesMessage.style.display = "none";
updateEntityList(newEntities);
updateSelectedEntities(data.selectedIDs);
}
updateSelectedEntities(data.selectedIDs);
scheduleRefreshEntityList();
resize();
}
});
} else if (data.type === "removeEntities" && data.deletedIDs !== undefined && data.selectedIDs !== undefined) {
removeEntities(data.deletedIDs);
updateSelectedEntities(data.selectedIDs);
scheduleRefreshEntityList();
} else if (data.type === "deleted" && data.ids) {
removeEntities(data.ids);
refreshFooter();
@ -434,7 +536,13 @@ function loaded() {
};
window.onresize = resize;
elFilter.onchange = resize;
elFilter.onkeyup = refreshEntityList;
elFilter.onpaste = refreshEntityList;
elFilter.onchange = function() {
refreshEntityList();
resize();
};
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,
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) {
var that = {};
@ -20,7 +36,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
var ENTITY_LIST_WIDTH = 495;
var MAX_DEFAULT_CREATE_TOOLS_HEIGHT = 778;
var entityListWindow = new CreateWindow(
Script.resourcesPath() + "qml/hifi/tablet/EditEntityList.qml",
Script.resolvePath("EditEntityList.qml"),
'Entity List',
'com.highfidelity.create.entityListWindow',
function () {
@ -66,13 +82,18 @@ EntityListTool = function(shouldUseEditTabletApp) {
that.setVisible(false);
function emitJSONScriptEvent(data) {
var dataString = JSON.stringify(data);
webView.emitScriptEvent(dataString);
if (entityListWindow.window) {
entityListWindow.window.emitScriptEvent(dataString);
}
var dataString;
PROFILE("Script-JSON.stringify", function() {
dataString = JSON.stringify(data);
});
PROFILE("Script-emitScriptEvent", function() {
webView.emitScriptEvent(dataString);
if (entityListWindow.window) {
entityListWindow.window.emitScriptEvent(dataString);
}
});
}
that.toggleVisible = function() {
that.setVisible(!visible);
};
@ -103,7 +124,7 @@ EntityListTool = function(shouldUseEditTabletApp) {
selectedIDs: selectedIDs
});
};
that.deleteEntities = function (deletedIDs) {
emitJSONScriptEvent({
type: "deleted",
@ -116,59 +137,66 @@ EntityListTool = function(shouldUseEditTabletApp) {
}
that.sendUpdate = function() {
var entities = [];
PROFILE('Script-sendUpdate', function() {
var entities = [];
var ids;
if (filterInView) {
ids = Entities.findEntitiesInFrustum(Camera.frustum);
} else {
ids = Entities.findEntities(MyAvatar.position, searchRadius);
}
var cameraPosition = Camera.position;
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var properties = Entities.getEntityProperties(id);
if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) {
var url = "";
if (properties.type === "Model") {
url = properties.modelURL;
} else if (properties.type === "Material") {
url = properties.materialURL;
var ids;
PROFILE("findEntities", function() {
if (filterInView) {
ids = Entities.findEntitiesInFrustum(Camera.frustum);
} else {
ids = Entities.findEntities(MyAvatar.position, searchRadius);
}
entities.push({
id: id,
name: properties.name,
type: properties.type,
url: url,
locked: properties.locked,
visible: properties.visible,
verticesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.verticesCount) : ""),
texturesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesCount) : ""),
texturesSize: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesSize) : ""),
hasTransparent: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.hasTransparent) : ""),
isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false,
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
});
});
var cameraPosition = Camera.position;
PROFILE("getProperties", function() {
for (var i = 0; i < ids.length; i++) {
var id = ids[i];
var properties = Entities.getEntityProperties(id, ['name', 'type', 'locked',
'visible', 'renderInfo', 'type', 'modelURL', 'materialURL', 'script']);
if (!filterInView || Vec3.distance(properties.position, cameraPosition) <= searchRadius) {
var url = "";
if (properties.type === "Model") {
url = properties.modelURL;
} else if (properties.type === "Material") {
url = properties.materialURL;
}
entities.push({
id: id,
name: properties.name,
type: properties.type,
url: url,
locked: properties.locked,
visible: properties.visible,
verticesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.verticesCount) : ""),
texturesCount: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesCount) : ""),
texturesSize: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.texturesSize) : ""),
hasTransparent: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.hasTransparent) : ""),
isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false,
drawCalls: (properties.renderInfo !== undefined ?
valueIfDefined(properties.renderInfo.drawCalls) : ""),
hasScript: properties.script !== ""
});
}
}
});
var selectedIDs = [];
for (var j = 0; j < selectionManager.selections.length; j++) {
selectedIDs.push(selectionManager.selections[j]);
}
}
var selectedIDs = [];
for (var j = 0; j < selectionManager.selections.length; j++) {
selectedIDs.push(selectionManager.selections[j]);
}
emitJSONScriptEvent({
type: "update",
entities: entities,
selectedIDs: selectedIDs,
emitJSONScriptEvent({
type: "update",
entities: entities,
selectedIDs: selectedIDs,
});
});
};