Skip to content

Commit 19e44b9

Browse files
authored
add requestIdleCallback support for performance (#2181)
* add requestIdleCallback support for performance * updates * fix lint * fix typo * GeoJSON add toGeometryAsync * updates * update doc * fix typo * updates * fix doc * add globalConfig * update globalconfig * updates
1 parent 2462b72 commit 19e44b9

File tree

8 files changed

+267
-13
lines changed

8 files changed

+267
-13
lines changed

src/core/Browser.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ if (!IS_NODE) {
3535
imageBitMap = typeof window !== 'undefined' && isFunction(window.createImageBitmap),
3636
resizeObserver = typeof window !== 'undefined' && isFunction(window.ResizeObserver),
3737
btoa = typeof window !== 'undefined' && isFunction(window.btoa),
38-
proxy = typeof window !== 'undefined' && isFunction(window.Proxy);
38+
proxy = typeof window !== 'undefined' && isFunction(window.Proxy),
39+
requestIdleCallback = typeof window !== 'undefined' && isFunction(window.requestIdleCallback);
3940

4041

4142
let chromeVersion = 0;
@@ -127,6 +128,7 @@ if (!IS_NODE) {
127128
monitorDPRChange: true,
128129
supportsPassive,
129130
proxy,
131+
requestIdleCallback,
130132
// removeDPRListening: (map) => {
131133
// // if (map) {
132134
// // delete maps[map.id];

src/core/MicroTask.js

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
import PromisePolyfill from './Promise';
2+
import { requestAnimFrame } from './util';
3+
import { isFunction, isNil, isNumber } from './util/common';
4+
import { getGlobalWorkerPool } from './worker/WorkerPool';
5+
import Browser from './Browser';
6+
import globalConfig from '../globalConfig';
7+
8+
let tasks = [];
9+
10+
/**
11+
*
12+
* @param {Object|Function} task - a micro task(Promise)
13+
* @param {Number} task.count - task run count
14+
* @param {Function} task.run - task run function
15+
* @return {Promise}
16+
* @example
17+
* const run =()=>{
18+
* //do some things
19+
* };
20+
* runTaskAsync({count:4,run}).then(result=>{})
21+
* runTaskAsync(run).then(result=>{})
22+
*/
23+
export function runTaskAsync(task) {
24+
startTasks();
25+
const promise = new PromisePolyfill((resolve, reject) => {
26+
if (!task) {
27+
reject(new Error('task is null'));
28+
return;
29+
}
30+
if (isFunction(task)) {
31+
task = { count: 1, run: task };
32+
}
33+
if (!task.run) {
34+
reject(new Error('task.run is null'));
35+
return;
36+
}
37+
if (isNil(task.count)) {
38+
task.count = 1;
39+
}
40+
task.count = Math.ceil(task.count);
41+
if (!isNumber(task.count)) {
42+
reject(new Error('task.count is not number'));
43+
return;
44+
}
45+
task.results = [];
46+
tasks.push(task);
47+
task.resolve = resolve;
48+
});
49+
return promise;
50+
}
51+
52+
function executeMicroTasks() {
53+
if (tasks.length === 0) {
54+
return;
55+
}
56+
const runingTasks = [], endTasks = [];
57+
let len = tasks.length;
58+
for (let i = 0; i < len; i++) {
59+
const task = tasks[i];
60+
task.count--;
61+
if (task.count === -1) {
62+
endTasks.push(task);
63+
} else {
64+
runingTasks.push(task);
65+
const result = task.run();
66+
task.results.push(result);
67+
}
68+
}
69+
tasks = runingTasks;
70+
len = endTasks.length;
71+
for (let i = 0; i < len; i++) {
72+
const task = endTasks[i];
73+
if (task.resolve) {
74+
task.resolve(task.results);
75+
}
76+
}
77+
}
78+
79+
let broadcastIdleMessage = true;
80+
function loop() {
81+
if (broadcastIdleMessage) {
82+
getGlobalWorkerPool().broadcastIdleMessage();
83+
} else {
84+
getGlobalWorkerPool().commit();
85+
}
86+
executeMicroTasks();
87+
broadcastIdleMessage = !broadcastIdleMessage;
88+
}
89+
90+
function frameLoop(deadline) {
91+
const { idleTimeRemaining, idleLog, idleTimeout } = globalConfig;
92+
if (Browser.requestIdleCallback) {
93+
if (deadline && deadline.timeRemaining) {
94+
const t = deadline.timeRemaining();
95+
if (t > idleTimeRemaining || deadline.didTimeout) {
96+
if (deadline.didTimeout && idleLog) {
97+
console.error('idle timeout in', idleTimeout);
98+
}
99+
loop();
100+
} else if (t <= idleTimeRemaining && idleLog) {
101+
console.warn('currrent page is busy,the timeRemaining is', t);
102+
}
103+
}
104+
requestIdleCallback(frameLoop, { timeout: idleTimeout });
105+
} else {
106+
loop();
107+
// Fallback to requestAnimFrame
108+
requestAnimFrame(frameLoop);
109+
if (idleLog) {
110+
console.warn('current env not support requestIdleCallback. Fallback to requestAnimFrame');
111+
}
112+
}
113+
}
114+
115+
let started = false;
116+
export function startTasks() {
117+
if (started) {
118+
return;
119+
}
120+
started = true;
121+
const { idleTimeout } = globalConfig;
122+
if (Browser.requestIdleCallback) {
123+
requestIdleCallback(frameLoop, { timeout: idleTimeout });
124+
} else {
125+
requestAnimFrame(frameLoop);
126+
}
127+
}

src/core/worker/Actor.js

+2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { getGlobalWorkerPool } from './WorkerPool';
22
import { UID } from '../util';
33
import { createAdapter } from './Worker';
44
import { adapterHasCreated, pushAdapterCreated, workersHasCreated } from './CoreWorkers';
5+
import { startTasks } from '../MicroTask';
56

67
let dedicatedWorker = 0;
78

@@ -47,6 +48,7 @@ const EMPTY_BUFFERS = [];
4748
class Actor {
4849

4950
constructor(workerKey) {
51+
startTasks();
5052
this._delayMessages = [];
5153
this.initializing = false;
5254
const hasCreated = adapterHasCreated(workerKey);

src/core/worker/Worker.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,12 @@ const header = `
5353
postMessage({adapterName:key});
5454
return;
5555
}
56+
// postMessage when main thread idle
57+
if(msg.messageType==='idle'){
58+
var messageCount = msg.messageCount||5;
59+
handleMessageQueue(messageCount);
60+
return;
61+
}
5662
if (msg.messageType === 'batch') {
5763
const messages = msg.messages;
5864
if (messages) {
@@ -81,6 +87,19 @@ const header = `
8187
}
8288
}
8389
90+
var messageResultQueue = [];
91+
92+
function handleMessageQueue(messageCount){
93+
if(messageResultQueue.length===0){
94+
return;
95+
}
96+
var queues = messageResultQueue.slice(0,messageCount);
97+
queues.forEach(function(queue){
98+
post(queue.callback,queue.err,queue.data,queue.buffers);
99+
});
100+
messageResultQueue=messageResultQueue.slice(messageCount,Infinity);
101+
}
102+
84103
function post(callback, err, data, buffers) {
85104
var msg = {
86105
callback : callback
@@ -96,9 +115,19 @@ const header = `
96115
postMessage(msg);
97116
}
98117
}
118+
119+
function joinQueue(callback,err,data,buffers){
120+
messageResultQueue.push({
121+
callback:callback,
122+
err:err,
123+
data:data,
124+
buffers:buffers
125+
});
126+
}
127+
99128
function wrap(callback) {
100129
return function (err, data, buffers) {
101-
post(callback, err, data, buffers);
130+
joinQueue(callback, err, data, buffers);
102131
};
103132
}
104133
var workerExports;

src/core/worker/WorkerPool.js

+28-9
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { requestAnimFrame } from '../util';
1+
// import { requestAnimFrame } from '../util';
22
import { setWorkerPool, setWorkersCreated } from './CoreWorkers';
33
import { getWorkerSourcePath } from './Worker';
4+
import globalConfig from '../../globalConfig';
45

56
const hardwareConcurrency = typeof window !== 'undefined' ? (window.navigator.hardwareConcurrency || 4) : 0;
67
const workerCount = Math.max(Math.floor(hardwareConcurrency / 2), 1);
@@ -42,7 +43,7 @@ class MessageBatch {
4243
export default class WorkerPool {
4344
constructor() {
4445
this.active = {};
45-
this.workerCount = typeof window !== 'undefined' ? (window.MAPTALKS_WORKER_COUNT || workerCount) : 0;
46+
this.workerCount = typeof window !== 'undefined' ? (globalConfig.workerCount || window.MAPTALKS_WORKER_COUNT || workerCount) : 0;
4647
this._messages = [];
4748
this._messageBuffers = [];
4849
}
@@ -101,6 +102,24 @@ export default class WorkerPool {
101102
}
102103
}
103104
}
105+
106+
getWorkers() {
107+
return this.workers || [];
108+
}
109+
110+
broadcastMessage(message) {
111+
const workers = this.getWorkers();
112+
workers.forEach(worker => {
113+
worker.postMessage({ messageType: message, messageCount: globalConfig.workerConcurrencyCount });
114+
});
115+
return this;
116+
}
117+
118+
broadcastIdleMessage() {
119+
this.broadcastMessage('idle');
120+
return this;
121+
}
122+
104123
}
105124

106125
let globalWorkerPool;
@@ -112,10 +131,10 @@ export function getGlobalWorkerPool() {
112131
return globalWorkerPool;
113132
}
114133

115-
function frameLoop() {
116-
getGlobalWorkerPool().commit();
117-
requestAnimFrame(frameLoop);
118-
}
119-
if (requestAnimFrame) {
120-
requestAnimFrame(frameLoop);
121-
}
134+
// function frameLoop() {
135+
// getGlobalWorkerPool().commit();
136+
// requestAnimFrame(frameLoop);
137+
// }
138+
// if (requestAnimFrame) {
139+
// requestAnimFrame(frameLoop);
140+
// }

src/geometry/GeoJSON.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import {
33
isString,
44
parseJSON,
55
isArrayHasData,
6-
pushIn
6+
pushIn,
7+
isNumber
78
} from '../core/util';
89
import Marker from './Marker';
910
import LineString from './LineString';
@@ -14,6 +15,8 @@ import MultiPolygon from './MultiPolygon';
1415
import GeometryCollection from './GeometryCollection';
1516
import Geometry from './Geometry';
1617
import { GEOJSON_TYPES } from '../core/Constants';
18+
import PromisePolyfill from './../core/Promise';
19+
import { runTaskAsync } from '../core/MicroTask';
1720

1821
const types = {
1922
'Marker': Marker,
@@ -96,6 +99,55 @@ const GeoJSON = {
9699
}
97100

98101
},
102+
/**
103+
* async Convert one or more GeoJSON objects to geometry
104+
* @param {String|Object|Object[]} geoJSON - GeoJSON objects or GeoJSON string
105+
* @param {Function} [foreachFn=undefined] - callback function for each geometry
106+
* @param {Number} [countPerTime=2000] - Number of graphics converted per time
107+
* @return {Promise}
108+
* @example
109+
* GeoJSON.toGeometryAsync(geoJSON).then(geos=>{
110+
* console.log(geos);
111+
* })
112+
* */
113+
toGeometryAsync(geoJSON, foreachFn, countPerTime = 2000) {
114+
if (isString(geoJSON)) {
115+
geoJSON = parseJSON(geoJSON);
116+
}
117+
return new PromisePolyfill((resolve) => {
118+
const resultGeos = [];
119+
if (geoJSON && (Array.isArray(geoJSON) || Array.isArray(geoJSON.features))) {
120+
const pageSize = isNumber(countPerTime) ? Math.round(countPerTime) : 2000;
121+
const features = geoJSON.features || geoJSON;
122+
const count = Math.ceil(features.length / pageSize);
123+
let page = 1;
124+
const run = () => {
125+
const startIndex = (page - 1) * pageSize, endIndex = (page) * pageSize;
126+
const fs = features.slice(startIndex, endIndex);
127+
const geos = GeoJSON.toGeometry(fs, foreachFn);
128+
page++;
129+
return geos;
130+
};
131+
runTaskAsync({ count, run }).then((geoList) => {
132+
for (let i = 0, len = geoList.length; i < len; i++) {
133+
const geo = geoList[i];
134+
if (!geo) {
135+
continue;
136+
}
137+
if (Array.isArray(geo)) {
138+
pushIn(resultGeos, geo);
139+
} else {
140+
resultGeos.push(geo);
141+
}
142+
}
143+
resolve(resultGeos);
144+
});
145+
} else {
146+
const geo = GeoJSON.toGeometry(geoJSON, foreachFn);
147+
resolve(geo);
148+
}
149+
});
150+
},
99151

100152
/**
101153
* Convert single GeoJSON object

src/globalConfig.js

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* global config
3+
* idle/worker etc
4+
*/
5+
const globalConfig = {
6+
//dev env
7+
dev: false,
8+
//test env
9+
test: false,
10+
//idle logging
11+
idleLog: false,
12+
//idle 时间阈值
13+
idleTimeRemaining: 8,
14+
//idle 超时阈值
15+
idleTimeout: 1000,
16+
//worker 数量
17+
workerCount: 0,
18+
//worker 通信并发数量
19+
workerConcurrencyCount: 5
20+
};
21+
export default globalConfig;

src/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import { version } from '../package.json';
2+
export { default as globalconfig } from './globalConfig';
23
export * from './core/Constants';
34
export { default as Browser } from './core/Browser';
45
import * as Util from './core/util';
56
import * as DomUtil from './core/util/dom';
67
import * as StringUtil from './core/util/strings';
78
import * as MapboxUtil from './core/mapbox';
8-
export { Util, DomUtil, StringUtil, MapboxUtil };
9+
import * as MicroTask from './core/MicroTask';
10+
export { Util, DomUtil, StringUtil, MapboxUtil, MicroTask };
911
export { default as LRUCache } from './core/util/LRUCache';
1012
export { default as Ajax } from './core/Ajax';
1113
export { default as Canvas } from './core/Canvas';

0 commit comments

Comments
 (0)