Merge pull request #5 from nasa/open1202
[Plot] Improve plotting performance
This commit is contained in:
@@ -184,6 +184,11 @@
|
||||
{
|
||||
"key": "now",
|
||||
"implementation": "services/Now.js"
|
||||
},
|
||||
{
|
||||
"key": "throttle",
|
||||
"implementation": "services/Throttle.js",
|
||||
"depends": [ "$timeout" ]
|
||||
}
|
||||
],
|
||||
"roots": [
|
||||
|
||||
63
platform/core/src/services/Throttle.js
Normal file
63
platform/core/src/services/Throttle.js
Normal file
@@ -0,0 +1,63 @@
|
||||
/*global define*/
|
||||
|
||||
define(
|
||||
[],
|
||||
function () {
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Throttler for function executions, registered as the `throttle`
|
||||
* service.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* throttle(fn, delay, [apply])
|
||||
*
|
||||
* Returns a function that, when invoked, will invoke `fn` after
|
||||
* `delay` milliseconds, only if no other invocations are pending.
|
||||
* The optional argument `apply` determines whether.
|
||||
*
|
||||
* The returned function will itself return a `Promise` which will
|
||||
* resolve to the returned value of `fn` whenever that is invoked.
|
||||
*
|
||||
* @returns {Function}
|
||||
*/
|
||||
function Throttle($timeout) {
|
||||
/**
|
||||
* Throttle this function.
|
||||
* @param {Function} fn the function to throttle
|
||||
* @param {number} [delay] the delay, in milliseconds, before
|
||||
* executing this function; defaults to 0.
|
||||
* @param {boolean} apply true if a `$apply` call should be
|
||||
* invoked after this function executes; defaults to
|
||||
* `false`.
|
||||
*/
|
||||
return function (fn, delay, apply) {
|
||||
var activeTimeout;
|
||||
|
||||
// Clear active timeout, so that next invocation starts
|
||||
// a new one.
|
||||
function clearActiveTimeout() {
|
||||
activeTimeout = undefined;
|
||||
}
|
||||
|
||||
// Defaults
|
||||
delay = delay || 0;
|
||||
apply = apply || false;
|
||||
|
||||
return function () {
|
||||
// Start a timeout if needed
|
||||
if (!activeTimeout) {
|
||||
activeTimeout = $timeout(fn, delay, apply);
|
||||
activeTimeout.then(clearActiveTimeout);
|
||||
}
|
||||
// Return whichever timeout is active (to get
|
||||
// a promise for the results of fn)
|
||||
return activeTimeout;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
return Throttle;
|
||||
}
|
||||
);
|
||||
49
platform/core/test/services/ThrottleSpec.js
Normal file
49
platform/core/test/services/ThrottleSpec.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/*global define,Promise,describe,it,expect,beforeEach,waitsFor,jasmine*/
|
||||
|
||||
define(
|
||||
["../../src/services/Throttle"],
|
||||
function (Throttle) {
|
||||
"use strict";
|
||||
|
||||
describe("The 'throttle' service", function () {
|
||||
var throttle,
|
||||
mockTimeout,
|
||||
mockFn,
|
||||
mockPromise;
|
||||
|
||||
beforeEach(function () {
|
||||
mockTimeout = jasmine.createSpy("$timeout");
|
||||
mockPromise = jasmine.createSpyObj("promise", ["then"]);
|
||||
mockFn = jasmine.createSpy("fn");
|
||||
mockTimeout.andReturn(mockPromise);
|
||||
throttle = new Throttle(mockTimeout);
|
||||
});
|
||||
|
||||
it("provides functions which run on a timeout", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
// Verify precondition: Not called at throttle-time
|
||||
expect(mockTimeout).not.toHaveBeenCalled();
|
||||
expect(throttled()).toEqual(mockPromise);
|
||||
expect(mockTimeout).toHaveBeenCalledWith(mockFn, 0, false);
|
||||
});
|
||||
|
||||
it("schedules only one timeout at a time", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
throttled();
|
||||
throttled();
|
||||
throttled();
|
||||
expect(mockTimeout.calls.length).toEqual(1);
|
||||
});
|
||||
|
||||
it("schedules additional invocations after resolution", function () {
|
||||
var throttled = throttle(mockFn);
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0](); // Resolve timeout
|
||||
throttled();
|
||||
mockPromise.then.mostRecentCall.args[0]();
|
||||
throttled();
|
||||
expect(mockTimeout.calls.length).toEqual(3);
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
@@ -24,6 +24,7 @@
|
||||
"objects/DomainObjectProvider",
|
||||
|
||||
"services/Now",
|
||||
"services/Throttle",
|
||||
|
||||
"types/MergeModels",
|
||||
"types/TypeCapability",
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
{
|
||||
"key": "PlotController",
|
||||
"implementation": "PlotController.js",
|
||||
"depends": [ "$scope", "telemetryFormatter", "telemetryHandler" ]
|
||||
"depends": [ "$scope", "telemetryFormatter", "telemetryHandler", "throttle" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -51,13 +51,14 @@ define(
|
||||
*
|
||||
* @constructor
|
||||
*/
|
||||
function PlotController($scope, telemetryFormatter, telemetryHandler) {
|
||||
function PlotController($scope, telemetryFormatter, telemetryHandler, throttle) {
|
||||
var subPlotFactory = new SubPlotFactory(telemetryFormatter),
|
||||
modeOptions = new PlotModeOptions([], subPlotFactory),
|
||||
subplots = [],
|
||||
cachedObjects = [],
|
||||
updater,
|
||||
handle,
|
||||
scheduleUpdate,
|
||||
domainOffset;
|
||||
|
||||
// Populate the scope with axis information (specifically, options
|
||||
@@ -89,9 +90,7 @@ define(
|
||||
|
||||
// Update all sub-plots
|
||||
function update() {
|
||||
modeOptions.getModeHandler()
|
||||
.getSubPlots()
|
||||
.forEach(updateSubplot);
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
// Reinstantiate the plot updater (e.g. because we have a
|
||||
@@ -162,6 +161,12 @@ define(
|
||||
|
||||
// Unsubscribe when the plot is destroyed
|
||||
$scope.$on("$destroy", releaseSubscription);
|
||||
|
||||
// Create a throttled update function
|
||||
scheduleUpdate = throttle(function () {
|
||||
modeOptions.getModeHandler().getSubPlots()
|
||||
.forEach(updateSubplot);
|
||||
});
|
||||
|
||||
return {
|
||||
/**
|
||||
|
||||
@@ -62,8 +62,6 @@ define(
|
||||
points: buf.getLength()
|
||||
};
|
||||
});
|
||||
|
||||
subplot.update();
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -58,8 +58,6 @@ define(
|
||||
color: PlotPalette.getFloatColor(0),
|
||||
points: buffer.getLength()
|
||||
}];
|
||||
|
||||
subplot.update();
|
||||
}
|
||||
|
||||
function plotTelemetry(prepared) {
|
||||
|
||||
@@ -33,6 +33,7 @@ define(
|
||||
var mockScope,
|
||||
mockFormatter,
|
||||
mockHandler,
|
||||
mockThrottle,
|
||||
mockHandle,
|
||||
mockDomainObject,
|
||||
mockSeries,
|
||||
@@ -56,6 +57,7 @@ define(
|
||||
"telemetrySubscriber",
|
||||
["handle"]
|
||||
);
|
||||
mockThrottle = jasmine.createSpy("throttle");
|
||||
mockHandle = jasmine.createSpyObj(
|
||||
"subscription",
|
||||
[
|
||||
@@ -73,12 +75,18 @@ define(
|
||||
);
|
||||
|
||||
mockHandler.handle.andReturn(mockHandle);
|
||||
mockThrottle.andCallFake(function (fn) { return fn; });
|
||||
mockHandle.getTelemetryObjects.andReturn([mockDomainObject]);
|
||||
mockHandle.getMetadata.andReturn([{}]);
|
||||
mockHandle.getDomainValue.andReturn(123);
|
||||
mockHandle.getRangeValue.andReturn(42);
|
||||
|
||||
controller = new PlotController(mockScope, mockFormatter, mockHandler);
|
||||
controller = new PlotController(
|
||||
mockScope,
|
||||
mockFormatter,
|
||||
mockHandler,
|
||||
mockThrottle
|
||||
);
|
||||
});
|
||||
|
||||
it("provides plot colors", function () {
|
||||
@@ -224,4 +232,4 @@ define(
|
||||
});
|
||||
});
|
||||
}
|
||||
);
|
||||
);
|
||||
|
||||
@@ -35,28 +35,68 @@ define(
|
||||
* @constructor
|
||||
*/
|
||||
function TelemetryQueue() {
|
||||
var queue = [];
|
||||
// General approach here:
|
||||
// * Maintain a queue as an array of objects containing key-value
|
||||
// pairs. Putting values into the queue will assign to the
|
||||
// earliest-available queue position for the associated key
|
||||
// (appending to the array if necessary.)
|
||||
// * Maintain a set of counts for each key, such that determining
|
||||
// the next available queue position is easy; O(1) insertion.
|
||||
// * When retrieving objects, pop off the queue and decrement
|
||||
// counts. This provides O(n+k) or O(k) retrieval for a queue
|
||||
// of length n with k unique keys; this depends on whether
|
||||
// the browser's implementation of Array.prototype.shift is
|
||||
// O(n) or O(1).
|
||||
|
||||
// Graphically (indexes at top, keys along side, values as *'s),
|
||||
// if we have a queue that looks like:
|
||||
// 0 1 2 3 4
|
||||
// a * * * * *
|
||||
// b * *
|
||||
// c * * *
|
||||
//
|
||||
// And we put a new value for b, we expect:
|
||||
// 0 1 2 3 4
|
||||
// a * * * * *
|
||||
// b * * *
|
||||
// c * * *
|
||||
var queue = [],
|
||||
counts = {};
|
||||
|
||||
// Look up an object in the queue that does not have a value
|
||||
// assigned to this key (or, add a new one)
|
||||
function getFreeObject(key) {
|
||||
var index = 0, object;
|
||||
var index = counts[key] || 0, object;
|
||||
|
||||
// Look for an existing queue position where we can store
|
||||
// a value to this key without overwriting an existing value.
|
||||
for (index = 0; index < queue.length; index += 1) {
|
||||
if (queue[index][key] === undefined) {
|
||||
return queue[index];
|
||||
}
|
||||
// Track the largest free position for this key
|
||||
counts[key] = index + 1;
|
||||
|
||||
// If it's before the end of the queue, add it there
|
||||
if (index < queue.length) {
|
||||
return queue[index];
|
||||
}
|
||||
|
||||
// If we made it through the loop, values have been assigned
|
||||
// Otherwise, values have been assigned
|
||||
// to that key in all queued containers, so we need to queue
|
||||
// up a new container for key-value pairs.
|
||||
object = {};
|
||||
queue.push(object);
|
||||
return object;
|
||||
}
|
||||
|
||||
// Decrement counts for a specific key
|
||||
function decrementCount(key) {
|
||||
if (counts[key] < 2) {
|
||||
delete counts[key];
|
||||
} else {
|
||||
counts[key] -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Decrement all counts
|
||||
function decrementCounts() {
|
||||
Object.keys(counts).forEach(decrementCount);
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
@@ -74,6 +114,8 @@ define(
|
||||
* @return {object} key-value pairs
|
||||
*/
|
||||
poll: function () {
|
||||
// Decrement counts for the object that will be popped
|
||||
decrementCounts();
|
||||
return queue.shift();
|
||||
},
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user