Skip to content

Commit b4e6935

Browse files
committed
refactor: fix slow rendering bug and create context class
Previously, the UI would wait until an SSE had been sent from the server before considering the SSE connection "open". This commit resolves that issue by immediately sending a keep-alive message as soon as the request is received. --- type: pre_commit_static_analysis_report description: Results of running static analysis checks when committing changes. report: - task: lint_filenames status: passed - task: lint_editorconfig status: passed - task: lint_markdown status: na - task: lint_package_json status: na - task: lint_repl_help status: na - task: lint_javascript_src status: passed - task: lint_javascript_cli status: na - task: lint_javascript_examples status: na - task: lint_javascript_tests status: na - task: lint_javascript_benchmarks status: na - task: lint_python status: na - task: lint_r status: na - task: lint_c_src status: na - task: lint_c_examples status: na - task: lint_c_benchmarks status: na - task: lint_c_tests_fixtures status: na - task: lint_shell status: na - task: lint_typescript_declarations status: na - task: lint_typescript_tests status: na - task: lint_license_headers status: passed ---
1 parent d9baf7d commit b4e6935

File tree

4 files changed

+110
-36
lines changed

4 files changed

+110
-36
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
/**
2+
* @license Apache-2.0
3+
*
4+
* Copyright (c) 2025 The Stdlib Authors.
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
/* eslint-disable no-restricted-syntax, no-invalid-this */
20+
21+
'use strict';
22+
23+
// MODULES //
24+
25+
var setNonEnumerableReadOnlyAccessor = require( '@stdlib/utils/define-nonenumerable-read-only-accessor' ); // eslint-disable-line id-length
26+
var setNonEnumerableReadOnly = require( '@stdlib/utils/define-nonenumerable-read-only-property' );
27+
var setNonEnumerable = require( '@stdlib/utils/define-nonenumerable-property' );
28+
var string2buffer = require( '@stdlib/buffer/from-string' );
29+
30+
31+
// MAIN //
32+
33+
/**
34+
* Context constructor.
35+
*
36+
* @private
37+
* @constructor
38+
* @param {Object} plot - plot instance
39+
* @returns {Context} context instance
40+
*/
41+
function Context( plot ) {
42+
var self;
43+
if ( !( this instanceof Context ) ) {
44+
return new Context( plot );
45+
}
46+
self = this;
47+
48+
setNonEnumerableReadOnly( this, '_plot', plot );
49+
setNonEnumerable( this, '_spec', string2buffer( JSON.stringify( plot ) ) );
50+
setNonEnumerableReadOnly( this, '_events', [] );
51+
52+
plot.on( 'change', onChange );
53+
return this;
54+
55+
/**
56+
* Callback invoked upon a plot "change" event.
57+
*
58+
* @private
59+
* @param {Object} event - event object
60+
*/
61+
function onChange() {
62+
self._spec = string2buffer( JSON.stringify( plot ) ); // eslint-disable-line no-underscore-dangle
63+
}
64+
}
65+
66+
/**
67+
* Events database.
68+
*
69+
* @name events
70+
* @memberof Context.prototype
71+
* @type {Array}
72+
*/
73+
setNonEnumerableReadOnlyAccessor( Context.prototype, 'events', function getEvents() {
74+
return this._events;
75+
});
76+
77+
/**
78+
* Plot instance.
79+
*
80+
* @name plot
81+
* @memberof Context.prototype
82+
* @type {Object}
83+
*/
84+
setNonEnumerableReadOnlyAccessor( Context.prototype, 'plot', function getPlot() {
85+
return this._plot;
86+
});
87+
88+
/**
89+
* Serialized plot specification.
90+
*
91+
* @name spec
92+
* @memberof Context.prototype
93+
* @type {Buffer}
94+
*/
95+
setNonEnumerableReadOnlyAccessor( Context.prototype, 'spec', function getSpec() {
96+
return this._spec;
97+
});
98+
99+
100+
// EXPORTS //
101+
102+
module.exports = Context;

lib/node_modules/@stdlib/plot/base/view/lib/browser/index.js

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,11 @@
2121
// MODULES //
2222

2323
var logger = require( 'debug' );
24-
var string2buffer = require( '@stdlib/buffer/from-string' );
2524
var openURL = require( '@stdlib/utils/open-url' );
2625
var createServer = require( '@stdlib/plot/base/server' );
2726
var routes = require( './routes' );
2827
var Router = require( './router.js' );
28+
var Context = require( './context.js' );
2929

3030

3131
// VARIABLES //
@@ -58,24 +58,17 @@ function displayPlot( plot ) {
5858
function view( plot ) {
5959
var server;
6060
var router;
61-
var events;
62-
var spec;
61+
var ctx;
6362

64-
// Register a listener for tracking plot changes:
65-
plot.on( 'change', onChange );
66-
67-
// Cache the current plot state by serializing to JSON:
68-
spec = string2buffer( JSON.stringify( plot ) );
63+
// Create a new request context:
64+
ctx = new Context( plot );
6965

7066
// Initialize a new request router:
7167
router = new Router();
7268

7369
// Register routes:
7470
routes( router );
7571

76-
// Initialize an events database:
77-
events = [];
78-
7972
server = plot.server;
8073
if ( !server || server.address() === null ) { // note: server.address() is `null` if a server has already been closed
8174
debug( 'Creating a server...' );
@@ -112,39 +105,16 @@ function view( plot ) {
112105
*/
113106
function onRequest( request, response ) {
114107
var handler;
115-
var ctx;
116108

117109
// Resolve a route handler:
118110
handler = router.resolve( request );
119111

120-
// Decorate route handler `this` contexts with additional functionality:
121-
ctx = {
122-
// Plot instance:
123-
'plot': plot,
124-
125-
// Cached plot schema:
126-
'spec': spec,
127-
128-
// Events database:
129-
'events': events
130-
};
131-
132112
// Decorate each `request` object with a `locals` object for passing along intermediate results within middleware handlers:
133113
request.locals = {};
134114

135115
// Invoke the route handler:
136116
handler.call( ctx, request, response );
137117
}
138-
139-
/**
140-
* Callback invoked upon a plot 'change' event.
141-
*
142-
* @private
143-
* @param {Event} event - event object
144-
*/
145-
function onChange() {
146-
spec = string2buffer( JSON.stringify( plot ) );
147-
}
148118
}
149119

150120

lib/node_modules/@stdlib/plot/base/view/lib/browser/routes/app/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,8 @@
350350
*/
351351
function onOpen() {
352352
log( 'Established server connection.' );
353-
setInterval( sendAcknowledgment, ACK_INTERVAL );
354353
clbk();
354+
setInterval( sendAcknowledgment, ACK_INTERVAL );
355355
}
356356

357357
/**

lib/node_modules/@stdlib/plot/base/view/lib/browser/routes/events/main.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,9 @@ function handler( request, response ) {
6161
debug( 'Last event id: %s', id );
6262
replay( id );
6363
}
64+
// Send an immediate ping in order to send some data over the wire:
6465
debug( 'Starting connection heartbeat...' );
66+
keepAlive();
6567
timer = setInterval( keepAlive, KEEP_ALIVE_INTERVAL );
6668

6769
request.on( 'close', closeConnection );
@@ -85,7 +87,7 @@ function handler( request, response ) {
8587
counter += 1;
8688

8789
// FIXME: only send the JSON patch
88-
data = JSON.stringify( self.plot );
90+
data = self.spec.toString();
8991

9092
self.events.push({
9193
'id': counter.toString(),

0 commit comments

Comments
 (0)