Skip to content

Commit

Permalink
diferred laoding of plugin while not rendering text
Browse files Browse the repository at this point in the history
  • Loading branch information
Arindam Bose committed Oct 16, 2019
1 parent 5bb6ff6 commit 51158d9
Show file tree
Hide file tree
Showing 7 changed files with 111 additions and 32 deletions.
4 changes: 2 additions & 2 deletions debug/rtl-plugin-autoload.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@
<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>
mapboxgl.setRTLTextPlugin('https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js', null, true);

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 12.5,
center: [-77.01866, 38.888],
style: 'mapbox://styles/mapbox/streets-v10',
hash: true,
rtlTextPluginURL: 'https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-rtl-text/v0.2.3/mapbox-gl-rtl-text.js'
hash: true
});

</script>
Expand Down
6 changes: 5 additions & 1 deletion src/data/bucket/symbol_bucket.js
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,11 @@ class SymbolBucket implements Bucket {
this.hasRTLText = true;
}

if (this.hasRTLText && globalRTLTextPlugin.isLoaded() || !this.hasRTLText) {
if (
this.hasRTLText && globalRTLTextPlugin.isLoaded() || // Use the rtlText plugin shape text
!this.hasRTLText || // non-rtl terxt so can proceed safely
!globalRTLTextPlugin.isAvailableInWorker() // We-doent intend to async-load the rtl text plugin, so proceed with incorrect shaping
) {
text = transformText(formattedText, layer, feature);
}
}
Expand Down
99 changes: 84 additions & 15 deletions src/source/rtl_text_plugin.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,73 @@
// @flow

import {Event, Evented} from '../util/evented';
import {getArrayBuffer} from '../util/ajax';
import browser from '../util/browser';
import window from '../util/window';

const status = {
unavailable: 'unavailable',
loading: 'loading',
unavailable: 'unavailable', // Not loaded
available: 'available', // Host url specified, but we havent started loading yet
loading: 'loading', // request in-flight
downloaded: 'downloaded', //plugin loaded and cached on main-thread and pluginBlobURL for worker is generated

This comment has been minimized.

Copy link
@asheemmamoowala

asheemmamoowala Oct 17, 2019

Contributor

What is the difference between downloaded and loaded.

loaded: 'loaded',
error: 'error'
};
let pluginStatus = status.unavailable;
let pluginURL = null;
let pluginBlobURL = null;
let lazy = null;

// store `pluginAvailable` that have occurred before the `registerPluginAvailability` bind
// so we can flush all the state updates to the workers
let eventQueue = [];
let _pluginAvailableCb = null;

let _workerAvailable = false;

export const evented = new Evented();
evented.on('pluginAvailable', (args) => {
if (!_pluginAvailableCb) {
eventQueue.push(args);
} else {
_pluginAvailableCb(args);
}
});

type CompletionCallback = (error?: Error) => void;
type ErrorCallback = (error: Error) => void;
type PluginAvailableCallback = (args: {pluginURL: ?string, lazy: ?boolean, completionCallback: CompletionCallback}) => void;

let _completionCallback;

export const getRTLTextPluginStatus = function () {
return pluginStatus;
};

export const registerForPluginAvailability = function(
callback: (args: {pluginURL: string, completionCallback: CompletionCallback}) => void
) {
if (pluginURL) {
callback({pluginURL, completionCallback: _completionCallback});
} else {
evented.once('pluginAvailable', callback);
export const registerForPluginAvailability = function(callback: PluginAvailableCallback) {
for (const event of eventQueue) {
callback(event);
}
eventQueue = [];

_pluginAvailableCb = callback;
return callback;
};

export const clearRTLTextPlugin = function() {
pluginStatus = status.unavailable;
pluginURL = null;
pluginBlobURL = null;
lazy = null;
};

export const setRTLTextPlugin = function(url: string, callback: ?ErrorCallback, lazy: ?boolean) {
if (pluginStatus === status.loading || pluginStatus === status.loaded) {
export const setRTLTextPlugin = function(url: string, callback: ?ErrorCallback, lazyLoad: ?boolean) {
if (pluginStatus === status.available || pluginStatus === status.loading || pluginStatus === status.loaded) {
throw new Error('setRTLTextPlugin cannot be called multiple times.');
}
pluginStatus = status.loading;
pluginURL = browser.resolveURL(url);
lazy = !!lazyLoad;
pluginStatus = status.available;
_completionCallback = (error?: Error) => {
if (error) {
// Clear loaded state to allow retries
Expand All @@ -55,18 +78,58 @@ export const setRTLTextPlugin = function(url: string, callback: ?ErrorCallback,
}
} else {
// Called once for each worker
pluginStatus = status.loaded;
if (!lazy) {
pluginStatus = status.loaded;
}
}
};
evented.fire(new Event('pluginAvailable', {pluginURL, completionCallback: _completionCallback}));

if (lazy) {
// Inform the worker-threads that we intend to load the plugin lazily later,
// This is so the workers can skip RTL text parsing.
evented.fire(new Event('pluginAvailable', {
pluginURL: null,
lazy,
completionCallback: _completionCallback
}));
} else {
downloadRTLTextPlugin();
}
};

export const downloadRTLTextPlugin = function() {
if (pluginStatus !== status.available) {
throw new Error('rtl-text-plugin cannot be downloaded unless a pluginURL is specified');
}
pluginStatus = status.loading;

if (pluginURL) {
getArrayBuffer({url: pluginURL}, (error, data) => {
if (error || !data) {
throw error;
} else {
const rtlBlob = new window.Blob([data], {type: 'application/javascript'});
const URL = window.URL || window.webkitURL;
pluginBlobURL = URL.createObjectURL(rtlBlob);
pluginStatus = status.downloaded;
evented.fire(new Event('pluginAvailable', {
pluginURL: pluginBlobURL,
lazy,
completionCallback: _completionCallback
}));
}
});
}
};

export const plugin: {
applyArabicShaping: ?Function,
processBidirectionalText: ?(string, Array<number>) => Array<string>,
processStyledBidirectionalText: ?(string, Array<number>, Array<number>) => Array<[string, Array<number>]>,
isLoaded: () => boolean,
isLoading: () => boolean
isLoading: () => boolean,
markWorkerAvailable: () => void,
isAvailableInWorker: () => boolean
} = {
applyArabicShaping: null,
processBidirectionalText: null,
Expand All @@ -77,5 +140,11 @@ export const plugin: {
},
isLoading() { // Main Thread Only: query the loading status, this function does not return the correct value in the worker context.
return pluginStatus === status.loading;
},
markWorkerAvailable() { // Worker thread only: this tells the worker threads that the plugin is available on the Main thread
_workerAvailable = true;
},
isAvailableInWorker() {
return !!_workerAvailable;
}
};
7 changes: 3 additions & 4 deletions src/source/vector_tile_source.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import TileBounds from './tile_bounds';
import {ResourceType} from '../util/ajax';
import browser from '../util/browser';
import {cacheEntryPossiblyAdded} from '../util/tile_request_cache';
import {plugin as rtlTextPlugin, setRTLTextPlugin} from './rtl_text_plugin';
import {plugin as rtlTextPlugin, getRTLTextPluginStatus, downloadRTLTextPlugin} from './rtl_text_plugin';

import type {Source} from './source';
import type {OverscaledTileID} from './tile_id';
Expand Down Expand Up @@ -158,10 +158,9 @@ class VectorTileSource extends Evented implements Source {
const plugin = rtlTextPlugin;
if (!plugin.isLoading() &&
!plugin.isLoaded() &&
this.map != null &&
this.map._rtlTextPluginURL
getRTLTextPluginStatus() === 'available'
) {
setRTLTextPlugin(this.map._rtlTextPluginURL);
downloadRTLTextPlugin();
}
}

Expand Down
18 changes: 12 additions & 6 deletions src/source/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,19 @@ export default class Worker {
}
}

loadRTLTextPlugin(map: string, pluginURL: string, callback: Callback<void>) {
loadRTLTextPlugin(map: string, pluginData: Object, callback: Callback<void>) {
try {
if (!globalRTLTextPlugin.isLoaded()) {
this.self.importScripts(pluginURL);
callback(globalRTLTextPlugin.isLoaded() ?
null :
new Error(`RTL Text Plugin failed to import scripts from ${pluginURL}`));
const {pluginURL, lazy} = pluginData;
if (pluginURL) {
if (!globalRTLTextPlugin.isLoaded()) {
this.self.importScripts(pluginURL);
callback(globalRTLTextPlugin.isLoaded() ?
null :
new Error(`RTL Text Plugin failed to import scripts from ${pluginURL}`));
}
} else if (lazy) {
// Set the state of the rtl text plugin in worker scope, to load the plugin if necessary.
globalRTLTextPlugin.markWorkerAvailable();
}
} catch (e) {
callback(e.toString());
Expand Down
8 changes: 5 additions & 3 deletions src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,9 +154,11 @@ class Style extends Evented {

const self = this;
this._rtlTextPluginCallback = Style.registerForPluginAvailability((args) => {
self.dispatcher.broadcast('loadRTLTextPlugin', args.pluginURL, args.completionCallback);
for (const id in self.sourceCaches) {
self.sourceCaches[id].reload(); // Should be a no-op if the plugin loads before any tiles load
self.dispatcher.broadcast('loadRTLTextPlugin', {pluginURL: args.pluginURL, lazy: args.lazy}, args.completionCallback);
if (args.pluginURL) {
for (const id in self.sourceCaches) {
self.sourceCaches[id].reload(); // Should be a no-op if the plugin loads before any tiles load
}
}
});

Expand Down
1 change: 0 additions & 1 deletion src/ui/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@ const defaultOptions = {
* @param {number} [options.fadeDuration=300] Controls the duration of the fade-in/fade-out animation for label collisions, in milliseconds. This setting affects all symbol layers. This setting does not affect the duration of runtime styling transitions or raster tile cross-fading.
* @param {boolean} [options.crossSourceCollisions=true] If `true`, symbols from multiple sources can collide with each other during collision detection. If `false`, collision detection is run separately for the symbols in each source.
* @param {string} [options.accessToken=null] If specified, map will use this token instead of the one defined in mapboxgl.accessToken.
* @param {string} [options.rtlTextPluginURL=null] If specified, map will use this url to load the RTL text plugin, the first time RTL text is encountered.
* @example
* var map = new mapboxgl.Map({
Expand Down

0 comments on commit 51158d9

Please sign in to comment.