From bd51c161c292def0f6bd785a96f0f0b16974da05 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Tue, 22 Nov 2022 13:28:28 -0800 Subject: [PATCH 1/6] Adds a hacky AJAX concurrency limiter when initially loading tile sources. Based on this gist: https://gist.github.com/Terrance/158a4c436baa64c4324803467844b00f --- src/openseadragon.js | 98 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 79 insertions(+), 19 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 1a9c79de..d3ea55e0 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -788,6 +788,12 @@ function OpenSeadragon( options ){ (function( $ ){ + $.ajaxQueue = { + requestFuncs: [], + numRequests: 0, + numActiveRequests: 0, + maxConcurrency: 50, + }; /** * The OpenSeadragon version. @@ -2332,6 +2338,70 @@ function OpenSeadragon( options ){ return $.createAjaxRequest( local ); }, + queueAjaxRequest: function ( + request, + method, + url, + headers, + withCredentials, + responseType, + postData + ) { + var oldOnStateChange = request.onreadystatechange; + + var sendRequest = function () { + request.open( method, url, true ); + + if (responseType) { + request.responseType = responseType; + } + + if (headers) { + for (var headerName in headers) { + if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) { + request.setRequestHeader(headerName, headers[headerName]); + } + } + } + + if (withCredentials) { + request.withCredentials = true; + } + + request.send(postData); + }; + + var callback = function() { + + $.ajaxQueue.numRequests--; + + if ( + $.ajaxQueue.numActiveRequests === $.ajaxQueue.maxConcurrency && + $.ajaxQueue.requestFuncs.length > 0 + ) { + $.ajaxQueue.requestFuncs.shift()(); + } else { + $.ajaxQueue.numActiveRequests--; + } + }; + + request.onreadystatechange = function() { + if ( request.readyState === 4 ) { + callback(); + oldOnStateChange(); + } + }; + + $.ajaxQueue.numRequests++; + + if ($.ajaxQueue.numActiveRequests === $.ajaxQueue.maxConcurrency) { + $.ajaxQueue.requestFuncs.push(sendRequest); + } else { + $.ajaxQueue.numActiveRequests++; + sendRequest(); + } + }, + /** * Makes an AJAX request. * @param {Object} options @@ -2395,25 +2465,15 @@ function OpenSeadragon( options ){ var method = postData ? "POST" : "GET"; try { - request.open( method, url, true ); - - if (responseType) { - request.responseType = responseType; - } - - if (headers) { - for (var headerName in headers) { - if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) { - request.setRequestHeader(headerName, headers[headerName]); - } - } - } - - if (withCredentials) { - request.withCredentials = true; - } - - request.send(postData); + $.queueAjaxRequest( + request, + method, + url, + headers, + withCredentials, + responseType, + postData + ); } catch (e) { $.console.error( "%s while making AJAX request: %s", e.name, e.message ); From 4227e4fd9b280d73546bcc86db6814e36e0fb0a9 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Mon, 28 Nov 2022 12:27:39 -0800 Subject: [PATCH 2/6] Refactors the tile source AJAX queue function with a nicer signature. --- src/openseadragon.js | 74 +++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 45 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index d3ea55e0..be45ac07 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -2338,41 +2338,10 @@ function OpenSeadragon( options ){ return $.createAjaxRequest( local ); }, - queueAjaxRequest: function ( - request, - method, - url, - headers, - withCredentials, - responseType, - postData - ) { + queueAjaxRequest: function (request, sendRequestFunc) { var oldOnStateChange = request.onreadystatechange; - var sendRequest = function () { - request.open( method, url, true ); - - if (responseType) { - request.responseType = responseType; - } - - if (headers) { - for (var headerName in headers) { - if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) { - request.setRequestHeader(headerName, headers[headerName]); - } - } - } - - if (withCredentials) { - request.withCredentials = true; - } - - request.send(postData); - }; - - var callback = function() { - + var onCompleteRequest = function() { $.ajaxQueue.numRequests--; if ( @@ -2387,7 +2356,7 @@ function OpenSeadragon( options ){ request.onreadystatechange = function() { if ( request.readyState === 4 ) { - callback(); + onCompleteRequest(); oldOnStateChange(); } }; @@ -2395,10 +2364,10 @@ function OpenSeadragon( options ){ $.ajaxQueue.numRequests++; if ($.ajaxQueue.numActiveRequests === $.ajaxQueue.maxConcurrency) { - $.ajaxQueue.requestFuncs.push(sendRequest); + $.ajaxQueue.requestFuncs.push(sendRequestFunc); } else { $.ajaxQueue.numActiveRequests++; - sendRequest(); + sendRequestFunc(); } }, @@ -2464,16 +2433,31 @@ function OpenSeadragon( options ){ }; var method = postData ? "POST" : "GET"; + + var sendRequest = function () { + request.open( method, url, true ); + + if (responseType) { + request.responseType = responseType; + } + + if (headers) { + for (var headerName in headers) { + if (Object.prototype.hasOwnProperty.call(headers, headerName) && headers[headerName]) { + request.setRequestHeader(headerName, headers[headerName]); + } + } + } + + if (withCredentials) { + request.withCredentials = true; + } + + request.send(postData); + }; + try { - $.queueAjaxRequest( - request, - method, - url, - headers, - withCredentials, - responseType, - postData - ); + $.queueAjaxRequest(request, sendRequest); } catch (e) { $.console.error( "%s while making AJAX request: %s", e.name, e.message ); From 55bb9cd3d1d1f84b31ed6a5e00936d8068074028 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Mon, 28 Nov 2022 12:53:00 -0800 Subject: [PATCH 3/6] Passes the tileSourceLoaderLimit option through the viewer constructor. --- src/openseadragon.js | 11 ++++++++++- src/viewer.js | 4 ++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index be45ac07..cb9a534c 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -312,6 +312,10 @@ * it is set to 0 allowing the browser to make the maximum number of * image requests in parallel as allowed by the browsers policy. * + * @property {Number} [tileSourceLoaderLimit=0] + * The maximum number of tile source requests to make concurrently. By default + * it is set to 0 allowing an unlimited number of concurrent requests. + * * @property {Number} [clickTimeThreshold=300] * The number of milliseconds within which a pointer down-up event combination * will be treated as a click gesture. @@ -792,7 +796,7 @@ function OpenSeadragon( options ){ requestFuncs: [], numRequests: 0, numActiveRequests: 0, - maxConcurrency: 50, + maxConcurrency: 0, }; /** @@ -1339,6 +1343,7 @@ function OpenSeadragon( options ){ //PERFORMANCE SETTINGS imageLoaderLimit: 0, + tileSourceLoaderLimit: 0, maxImageCacheCount: 200, timeout: 30000, useCanvas: true, // Use canvas element for drawing if available @@ -2339,6 +2344,10 @@ function OpenSeadragon( options ){ }, queueAjaxRequest: function (request, sendRequestFunc) { + if(!$.ajaxQueue.maxConcurrency) { + sendRequestFunc(); + } + var oldOnStateChange = request.onreadystatechange; var onCompleteRequest = function() { diff --git a/src/viewer.js b/src/viewer.js index 3928ee02..48a77bcc 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -394,6 +394,10 @@ $.Viewer = function( options ) { timeout: options.timeout }); + // TODO: Instantiating a viewer shouldn't have + // a side effect on the global queue + $.ajaxQueue.maxConcurrency = options.tileSourceLoaderLimit; + // Create the tile cache this.tileCache = new $.TileCache({ maxImageCacheCount: this.maxImageCacheCount From 537c2b21adf32e6a42dd305cf38c6d8daca848c2 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Thu, 30 Mar 2023 10:49:22 -0700 Subject: [PATCH 4/6] Renames tileSourceLoaderLimit to ajaxLoaderLimit. --- src/openseadragon.js | 4 ++-- src/viewer.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index cb9a534c..d5cd12d1 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -312,7 +312,7 @@ * it is set to 0 allowing the browser to make the maximum number of * image requests in parallel as allowed by the browsers policy. * - * @property {Number} [tileSourceLoaderLimit=0] + * @property {Number} [ajaxLoaderLimit=0] * The maximum number of tile source requests to make concurrently. By default * it is set to 0 allowing an unlimited number of concurrent requests. * @@ -1343,7 +1343,7 @@ function OpenSeadragon( options ){ //PERFORMANCE SETTINGS imageLoaderLimit: 0, - tileSourceLoaderLimit: 0, + ajaxLoaderLimit: 0, maxImageCacheCount: 200, timeout: 30000, useCanvas: true, // Use canvas element for drawing if available diff --git a/src/viewer.js b/src/viewer.js index 48a77bcc..d9a2dfc7 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -396,7 +396,7 @@ $.Viewer = function( options ) { // TODO: Instantiating a viewer shouldn't have // a side effect on the global queue - $.ajaxQueue.maxConcurrency = options.tileSourceLoaderLimit; + $.ajaxQueue.ajaxLoaderLimit = options.ajaxLoaderLimit; // Create the tile cache this.tileCache = new $.TileCache({ From 3b0857b54508e2e7bfb0b8d5e1a1065eb9faf316 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Thu, 30 Mar 2023 11:36:04 -0700 Subject: [PATCH 5/6] Updates the ajax concurrency limit so that it's defined globally using a new `OpenSeadragon.settings` object. --- src/openseadragon.js | 69 ++++++++++++++++++++++++++++++-------------- src/viewer.js | 4 --- 2 files changed, 47 insertions(+), 26 deletions(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index d5cd12d1..806291f5 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -312,10 +312,6 @@ * it is set to 0 allowing the browser to make the maximum number of * image requests in parallel as allowed by the browsers policy. * - * @property {Number} [ajaxLoaderLimit=0] - * The maximum number of tile source requests to make concurrently. By default - * it is set to 0 allowing an unlimited number of concurrent requests. - * * @property {Number} [clickTimeThreshold=300] * The number of milliseconds within which a pointer down-up event combination * will be treated as a click gesture. @@ -792,13 +788,6 @@ function OpenSeadragon( options ){ (function( $ ){ - $.ajaxQueue = { - requestFuncs: [], - numRequests: 0, - numActiveRequests: 0, - maxConcurrency: 0, - }; - /** * The OpenSeadragon version. * @@ -1343,7 +1332,6 @@ function OpenSeadragon( options ){ //PERFORMANCE SETTINGS imageLoaderLimit: 0, - ajaxLoaderLimit: 0, maxImageCacheCount: 200, timeout: 30000, useCanvas: true, // Use canvas element for drawing if available @@ -1480,6 +1468,24 @@ function OpenSeadragon( options ){ ALWAYS: 2 }, + /** + * Global settings, shared across all viewers. + * @static + * @type {Object} + * @property {Number} ajaxLoaderLimit The maximum number of ajax requests to make concurrently. By default it is set to 0 allowing an unlimited number of concurrent requests. + */ + settings: { + ajaxLoaderLimit: 0 + }, + + setAjaxLoaderLimit: function ( maxNumAjaxRequests ) { + if( maxNumAjaxRequests < 0) { + throw new Error("The ajax loader limit must be >= 0"); + } + + $.settings.ajaxLoaderLimit = maxNumAjaxRequests; + }, + /** * Keep track of which {@link Viewer}s have been created. * - Key: {@link Element} to which a Viewer is attached. @@ -2343,23 +2349,42 @@ function OpenSeadragon( options ){ return $.createAjaxRequest( local ); }, + /** + * An object holding the AJAX queue state. + * @static + * @type {Object} + * @property {Array} requestFuncs Array of functions representing queued AJAX requests + * @property {Array} numIncompleteRequests The number of incomplete AJAX requests, including queued and active (but not completed) requests + * @property {Array} numActiveRequests The number of AJAX requests currently in-flight + */ + AjaxQueue: { + requestFuncs: [], + numIncompleteRequests: 0, + numActiveRequests: 0, + }, + + /** + * Queues an AJAX request. + * @param {XMLHttpRequest} request The request to be queued + * @param {Function} sendRequestFunc A function which, when called, will send the request + */ queueAjaxRequest: function (request, sendRequestFunc) { - if(!$.ajaxQueue.maxConcurrency) { + if(!$.settings.ajaxLoaderLimit) { sendRequestFunc(); } var oldOnStateChange = request.onreadystatechange; var onCompleteRequest = function() { - $.ajaxQueue.numRequests--; + $.AjaxQueue.numIncompleteRequests--; if ( - $.ajaxQueue.numActiveRequests === $.ajaxQueue.maxConcurrency && - $.ajaxQueue.requestFuncs.length > 0 + $.AjaxQueue.numActiveRequests === $.settings.ajaxLoaderLimit && + $.AjaxQueue.requestFuncs.length > 0 ) { - $.ajaxQueue.requestFuncs.shift()(); + $.AjaxQueue.requestFuncs.shift()(); } else { - $.ajaxQueue.numActiveRequests--; + $.AjaxQueue.numActiveRequests--; } }; @@ -2370,12 +2395,12 @@ function OpenSeadragon( options ){ } }; - $.ajaxQueue.numRequests++; + $.AjaxQueue.numIncompleteRequests++; - if ($.ajaxQueue.numActiveRequests === $.ajaxQueue.maxConcurrency) { - $.ajaxQueue.requestFuncs.push(sendRequestFunc); + if ($.AjaxQueue.numActiveRequests === $.settings.ajaxLoaderLimit) { + $.AjaxQueue.requestFuncs.push(sendRequestFunc); } else { - $.ajaxQueue.numActiveRequests++; + $.AjaxQueue.numActiveRequests++; sendRequestFunc(); } }, diff --git a/src/viewer.js b/src/viewer.js index d9a2dfc7..3928ee02 100644 --- a/src/viewer.js +++ b/src/viewer.js @@ -394,10 +394,6 @@ $.Viewer = function( options ) { timeout: options.timeout }); - // TODO: Instantiating a viewer shouldn't have - // a side effect on the global queue - $.ajaxQueue.ajaxLoaderLimit = options.ajaxLoaderLimit; - // Create the tile cache this.tileCache = new $.TileCache({ maxImageCacheCount: this.maxImageCacheCount From 3020243d04331c72f4333963983239e350e9b501 Mon Sep 17 00:00:00 2001 From: Chili Johnson Date: Thu, 30 Mar 2023 11:40:57 -0700 Subject: [PATCH 6/6] Documents the new `OpenSeadragon.setAjaxLoaderLimit` method. --- src/openseadragon.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/openseadragon.js b/src/openseadragon.js index 806291f5..899cdfe8 100644 --- a/src/openseadragon.js +++ b/src/openseadragon.js @@ -1472,12 +1472,17 @@ function OpenSeadragon( options ){ * Global settings, shared across all viewers. * @static * @type {Object} - * @property {Number} ajaxLoaderLimit The maximum number of ajax requests to make concurrently. By default it is set to 0 allowing an unlimited number of concurrent requests. + * @property {Number} ajaxLoaderLimit The maximum number of AJAX requests to make concurrently. By default it is set to 0 allowing an unlimited number of concurrent requests. */ settings: { ajaxLoaderLimit: 0 }, + /** + * Sets the global concurrency limit for AJAX requests + * @function + * @param {Number} maxNumAjaxRequests The maximum number of AJAX requests to make concurrently. Setting this to 0 will allow an unlimited number of requests. + */ setAjaxLoaderLimit: function ( maxNumAjaxRequests ) { if( maxNumAjaxRequests < 0) { throw new Error("The ajax loader limit must be >= 0");