[Tables] limit digests to increase performance

This commit is contained in:
Henry 2017-01-15 10:59:28 -08:00
parent 2a4944d6ee
commit 50f303bbdc
4 changed files with 74 additions and 61 deletions

View File

@ -5,7 +5,7 @@
Export Export
</a> </a>
</div> </div>
<div class="l-view-section scrolling" style="overflow: auto;"> <div class="l-view-section scrolling" style="overflow: auto;" mct-resize="resize()">
<table class="sizing-table"> <table class="sizing-table">
<tbody> <tbody>
<tr> <tr>
@ -32,7 +32,8 @@
enableSort ? 'sortable' : '', enableSort ? 'sortable' : '',
sortColumn === header ? 'sort' : '', sortColumn === header ? 'sort' : '',
sortDirection || '' sortDirection || ''
].join(' ')"> ].join(' ')"
ng-click="toggleSort(header)">
{{ header }} {{ header }}
</th> </th>
</tr> </tr>
@ -58,7 +59,8 @@
</td> </td>
</tr> </tr>
<tr ng-repeat-end <tr ng-repeat-end
ng-style="{ top: visibleRow.offsetY + 'px' }"> ng-style="{ top: visibleRow.offsetY + 'px' }"
ng-click="table.onRowClick($event, visibleRow.rowIndex) ">
<td ng-repeat="header in displayHeaders" <td ng-repeat="header in displayHeaders"
ng-style=" { ng-style=" {
width: columnWidths[$index] + 'px', width: columnWidths[$index] + 'px',

View File

@ -1,11 +1,14 @@
<div ng-controller="TelemetryTableController as tableController" <div ng-controller="TelemetryTableController as tableController"
ng-class="{'loading': loading}"> ng-class="{'loading': loading}">
Request time: {{tableController.lastRequestTime | date : 'HH:mm:ss:sss'}}
<mct-table <mct-table
headers="headers" headers="headers"
time-columns="tableController.timeColumns"
rows="rows" rows="rows"
time-columns="tableController.timeColumns"
on-show-cell=""
enableFilter="true" enableFilter="true"
enableSort="true" enableSort="true"
auto-scroll="autoScroll"
default-sort="defaultSort" default-sort="defaultSort"
class="tabular-holder has-control-bar"> class="tabular-holder has-control-bar">
</mct-table> </mct-table>

View File

@ -163,8 +163,6 @@ define(
} }
}.bind(this)); }.bind(this));
console.log('constructed');
$scope.$on('$destroy', function() { $scope.$on('$destroy', function() {
this.scrollable.off('scroll', this.onScroll); this.scrollable.off('scroll', this.onScroll);
this.destroyConductorListeners(); this.destroyConductorListeners();
@ -193,15 +191,7 @@ define(
* @private * @private
*/ */
MCTTableController.prototype.scrollToBottom = function () { MCTTableController.prototype.scrollToBottom = function () {
var self = this; this.scrollable[0].scrollTop = this.scrollable[0].scrollHeight;
//Use timeout to defer execution until next digest when any
// pending UI changes have completed, eg. a new row in the table.
if (this.$scope.autoScroll) {
this.digest(function () {
self.scrollable[0].scrollTop = self.scrollable[0].scrollHeight;
});
}
}; };
/** /**
@ -219,8 +209,12 @@ define(
//Resize the columns , then update the rows visible in the table //Resize the columns , then update the rows visible in the table
this.resize([this.$scope.sizingRow, row]) this.resize([this.$scope.sizingRow, row])
.then(this.setVisibleRows.bind(this)) .then(this.setVisibleRows)
.then(this.scrollToBottom.bind(this)); .then(function () {
if (this.$scope.autoScroll) {
this.scrollToBottom();
}
}.bind(this));
var toi = this.conductor.timeOfInterest(); var toi = this.conductor.timeOfInterest();
if (toi !== -1) { if (toi !== -1) {
@ -250,16 +244,24 @@ define(
* @private * @private
*/ */
MCTTableController.prototype.onScroll = function (event) { MCTTableController.prototype.onScroll = function (event) {
if (!this.scrolling) {
this.scrolling = true;
requestAnimationFrame(function () {
this.setVisibleRows();
this.digest();
// If user scrolls away from bottom, disable auto-scroll. // If user scrolls away from bottom, disable auto-scroll.
// Auto-scroll will be re-enabled if user scrolls to bottom again. // Auto-scroll will be re-enabled if user scrolls to bottom again.
if (this.scrollable[0].scrollTop < if (this.scrollable[0].scrollTop <
(this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight)) { (this.scrollable[0].scrollHeight - this.scrollable[0].offsetHeight) - 20) {
this.$scope.autoScroll = false; this.$scope.autoScroll = false;
} else { } else {
this.$scope.autoScroll = true; this.$scope.autoScroll = true;
} }
this.setVisibleRows(); this.scrolling = false;
this.$scope.$digest(); }.bind(this));
}
}; };
/** /**
@ -338,7 +340,7 @@ define(
this.$scope.visibleRows[this.$scope.visibleRows.length - 1] this.$scope.visibleRows[this.$scope.visibleRows.length - 1]
.rowIndex === end) { .rowIndex === end) {
return; // don't update if no changes are required. return Promise.resolve(); // don't update if no changes are required.
} }
} }
//Set visible rows from display rows, based on calculated offset. //Set visible rows from display rows, based on calculated offset.
@ -351,6 +353,7 @@ define(
contents: row contents: row
}; };
}); });
return this.digest();
}; };
/** /**
@ -566,25 +569,25 @@ define(
return largestRow; return largestRow;
}; };
MCTTableController.prototype.digest = function (callback) { // Will effectively cap digests at 60Hz
// Also turns digest into a promise allowing code to force digest, then
// schedule something to happen afterwards
MCTTableController.prototype.digest = function () {
var scope = this.$scope; var scope = this.$scope;
var callbacks = this.callbacks; var self = this;
var requestAnimationFrame = this.$window.requestAnimationFrame; var requestAnimationFrame = this.$window.requestAnimationFrame;
var promise = callbacks[callback]; if (!this.digestPromise) {
this.digestPromise = new Promise(function (resolve) {
if (!promise){
promise = new Promise(function (resolve) {
requestAnimationFrame(function() { requestAnimationFrame(function() {
scope.$digest(); scope.$digest();
delete callbacks[callback]; delete self.digestPromise;
resolve(callback && callback()); resolve();
}); });
}); });
callbacks[callback] = promise;
} }
return promise; return this.digestPromise;
}; };
/** /**
@ -598,7 +601,7 @@ define(
*/ */
MCTTableController.prototype.resize = function (rows) { MCTTableController.prototype.resize = function (rows) {
this.$scope.sizingRow = this.buildLargestRow(rows); this.$scope.sizingRow = this.buildLargestRow(rows);
return this.digest(this.setElementSizes); return this.digest().then(this.setElementSizes);
}; };
/** /**
@ -631,7 +634,6 @@ define(
.then(this.setVisibleRows) .then(this.setVisibleRows)
//Timeout following setVisibleRows to allow digest to //Timeout following setVisibleRows to allow digest to
// perform DOM changes, otherwise scrollTo won't work. // perform DOM changes, otherwise scrollTo won't work.
.then(this.digest)
.then(function () { .then(function () {
//If TOI specified, scroll to it //If TOI specified, scroll to it
var timeOfInterest = this.conductor.timeOfInterest(); var timeOfInterest = this.conductor.timeOfInterest();
@ -732,7 +734,9 @@ define(
*/ */
MCTTableController.prototype.changeBounds = function (bounds) { MCTTableController.prototype.changeBounds = function (bounds) {
this.setTimeOfInterestRow(this.conductor.timeOfInterest()); this.setTimeOfInterestRow(this.conductor.timeOfInterest());
if (this.$scope.toiRowIndex !== -1) {
this.scrollToRow(this.$scope.toiRowIndex); this.scrollToRow(this.$scope.toiRowIndex);
}
}; };
/** /**

View File

@ -76,15 +76,23 @@ define(
'loadColumns', 'loadColumns',
'getHistoricalData', 'getHistoricalData',
'subscribeToNewData', 'subscribeToNewData',
'changeBounds' 'changeBounds',
'setScroll'
]); ]);
this.getData(); this.getData();
this.registerChangeListeners(); this.registerChangeListeners();
this.openmct.conductor.on('follow', this.setScroll);
this.setScroll(this.openmct.conductor.follow());
this.$scope.$on("$destroy", this.destroy); this.$scope.$on("$destroy", this.destroy);
} }
TelemetryTableController.prototype.setScroll = function (scroll){
this.$scope.autoScroll = scroll;
};
/** /**
* Based on the selected time system, find a matching domain column * Based on the selected time system, find a matching domain column
* to sort by. By default will just match on key. * to sort by. By default will just match on key.
@ -171,6 +179,7 @@ define(
this.openmct.conductor.off('timeSystem', this.sortByTimeSystem); this.openmct.conductor.off('timeSystem', this.sortByTimeSystem);
this.openmct.conductor.off('bounds', this.changeBounds); this.openmct.conductor.off('bounds', this.changeBounds);
this.openmct.conductor.off('follow', this.setScroll);
this.subscriptions.forEach(function (subscription) { this.subscriptions.forEach(function (subscription) {
subscription(); subscription();
@ -229,20 +238,18 @@ define(
var openmct = this.openmct; var openmct = this.openmct;
var bounds = openmct.conductor.bounds(); var bounds = openmct.conductor.bounds();
var scope = this.$scope; var scope = this.$scope;
var rowData = [];
var processedObjects = 0; var processedObjects = 0;
var requestTime = this.lastRequestTime = Date.now(); var requestTime = this.lastRequestTime = Date.now();
return new Promise(function (resolve, reject){ return new Promise(function (resolve, reject){
console.log('Created promise');
function finishProcessing(tableRows){ function finishProcessing(tableRows){
scope.rows = tableRows; scope.rows = tableRows.concat(scope.rows);
scope.loading = false; scope.loading = false;
console.log('Resolved promise');
resolve(tableRows); resolve(tableRows);
} }
function processData(historicalData, index, rowData, limitEvaluator){ function processData(historicalData, index, limitEvaluator){
console.log("Processing batch");
if (index >= historicalData.length) { if (index >= historicalData.length) {
processedObjects++; processedObjects++;
@ -250,13 +257,14 @@ define(
finishProcessing(rowData); finishProcessing(rowData);
} }
} else { } else {
rowData = rowData.concat(historicalData.slice(index, index + this.batchSize) rowData = rowData.concat(
.map(this.table.getRowValues.bind(this.table, limitEvaluator))); historicalData.slice(index, index + this.batchSize).map(
this.table.getRowValues.bind(this.table, limitEvaluator))
);
this.timeoutHandle = this.$timeout(processData.bind( this.timeoutHandle = this.$timeout(processData.bind(
this, this,
historicalData, historicalData,
index + this.batchSize, index + this.batchSize,
rowData,
limitEvaluator limitEvaluator
)); ));
} }
@ -265,12 +273,10 @@ define(
function makeTableRows(object, historicalData) { function makeTableRows(object, historicalData) {
// Only process one request at a time // Only process one request at a time
if (requestTime === this.lastRequestTime) { if (requestTime === this.lastRequestTime) {
console.log('Processing request');
var limitEvaluator = openmct.telemetry.limitEvaluator(object); var limitEvaluator = openmct.telemetry.limitEvaluator(object);
processData.call(this, historicalData, 0, [], limitEvaluator); processData.call(this, historicalData, 0, limitEvaluator);
} else { } else {
console.log('Ignoring returned data because of staleness'); resolve(rowData);
resolve([]);
} }
} }
@ -287,7 +293,6 @@ define(
objects.forEach(requestData.bind(this)); objects.forEach(requestData.bind(this));
} else { } else {
scope.loading = false; scope.loading = false;
console.log('Resolved promise');
resolve([]); resolve([]);
} }
}.bind(this)); }.bind(this));
@ -317,16 +322,15 @@ define(
this.$scope.$broadcast('remove:row', 0); this.$scope.$broadcast('remove:row', 0);
this.$scope.rows.shift(); this.$scope.rows.shift();
} }
if (!this.$scope.loading) {
this.$scope.$broadcast('add:row', this.$scope.$broadcast('add:row',
this.$scope.rows.length - 1); this.$scope.rows.length - 1);
}
} }
objects.forEach(function (object){ objects.forEach(function (object){
this.subscriptions.push( this.subscriptions.push(
telemetryApi.subscribe(object, newData.bind(this, object), {})); telemetryApi.subscribe(object, newData.bind(this, object), {}));
console.log('subscribed');
}.bind(this)); }.bind(this));
return objects; return objects;
@ -374,7 +378,7 @@ define(
getDomainObjects() getDomainObjects()
.then(filterForTelemetry) .then(filterForTelemetry)
.then(this.loadColumns) .then(this.loadColumns)
//.then(this.subscribeToNewData) .then(this.subscribeToNewData)
.then(this.getHistoricalData) .then(this.getHistoricalData)
.catch(error) .catch(error)
}; };