-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: use worker to prevent timer throttling
- Loading branch information
1 parent
4ec9488
commit 1daaa7b
Showing
8 changed files
with
208 additions
and
10 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
#!/usr/bin/env bash | ||
|
||
npx tsc src/timers/worker.ts \ | ||
--skipLibCheck \ | ||
--removeComments \ | ||
--module preserve \ | ||
--lib ES2020,WebWorker \ | ||
--outDir worker-dist | ||
|
||
cat <<EOF >src/timers/worker.build.ts | ||
export const timerWorker = { | ||
src: \`$(<worker-dist/worker.js)\`, | ||
}; | ||
EOF | ||
|
||
rm -r worker-dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import { lazy } from '../helpers/lazy'; | ||
import { getLogger } from '../logger'; | ||
import { TimerWorkerEvent, TimerWorkerRequest } from './types'; | ||
import { timerWorker } from './worker.build'; | ||
|
||
class TimerWorker { | ||
private currentTimerId = 1; | ||
private callbacks = new Map<number, () => void>(); | ||
private worker: Worker | undefined; | ||
private fallback = false; | ||
|
||
setup(): void { | ||
try { | ||
const source = timerWorker.src; | ||
const blob = new Blob([source], { | ||
type: 'application/javascript; charset=utf-8', | ||
}); | ||
const script = URL.createObjectURL(blob); | ||
this.worker = new Worker(script, { name: 'str-timer-worker' }); | ||
this.worker.addEventListener('message', (event) => { | ||
const { type, id } = event.data as TimerWorkerEvent; | ||
if (type === 'tick') { | ||
this.callbacks.get(id)?.(); | ||
} | ||
}); | ||
} catch (err: any) { | ||
getLogger(['timer-worker'])('error', err); | ||
this.fallback = true; | ||
} | ||
} | ||
|
||
destroy(): void { | ||
this.callbacks.clear(); | ||
this.worker?.terminate(); | ||
this.worker = undefined; | ||
this.fallback = false; | ||
} | ||
|
||
get ready() { | ||
return this.fallback || Boolean(this.worker); | ||
} | ||
|
||
setInterval(callback: () => void, timeout: number): number { | ||
return this.setTimer('setInterval', callback, timeout); | ||
} | ||
|
||
clearInterval(id?: number): void { | ||
this.clearTimer('clearInterval', id); | ||
} | ||
|
||
setTimeout(callback: () => void, timeout: number): number { | ||
return this.setTimer('setTimeout', callback, timeout); | ||
} | ||
|
||
clearTimeout(id?: number): void { | ||
this.clearTimer('clearTimeout', id); | ||
} | ||
|
||
private setTimer( | ||
type: 'setTimeout' | 'setInterval', | ||
callback: () => void, | ||
timeout: number, | ||
) { | ||
if (!this.ready) { | ||
this.setup(); | ||
} | ||
|
||
if (this.fallback) { | ||
return (type === 'setTimeout' ? setTimeout : setInterval)( | ||
callback, | ||
timeout, | ||
) as unknown as number; | ||
} | ||
|
||
const id = this.getTimerId(); | ||
this.callbacks.set(id, callback); | ||
this.sendMessage({ type, id, timeout }); | ||
return id; | ||
} | ||
|
||
private clearTimer(type: 'clearTimeout' | 'clearInterval', id?: number) { | ||
if (!id) { | ||
return; | ||
} | ||
|
||
if (!this.ready) { | ||
this.setup(); | ||
} | ||
|
||
if (this.fallback) { | ||
this.clearInterval(id); | ||
return; | ||
} | ||
|
||
this.callbacks.delete(id); | ||
this.sendMessage({ type, id }); | ||
} | ||
|
||
private getTimerId() { | ||
return this.currentTimerId++; | ||
} | ||
|
||
private sendMessage(message: TimerWorkerRequest) { | ||
if (!this.worker) { | ||
throw new Error("Cannot use timer worker before it's set up"); | ||
} | ||
|
||
this.worker.postMessage(message); | ||
} | ||
} | ||
|
||
export const getTimers = lazy(() => { | ||
const instance = new TimerWorker(); | ||
instance.setup(); | ||
return instance; | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
export type TimerWorkerRequest = | ||
| { | ||
type: 'setInterval' | 'setTimeout'; | ||
id: number; | ||
timeout: number; | ||
} | ||
| { | ||
type: 'clearInterval' | 'clearTimeout'; | ||
id: number; | ||
}; | ||
|
||
export type TimerWorkerEvent = { | ||
type: 'tick'; | ||
id: number; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
// Do not modify this file manually. You can edit worker.ts if necessary | ||
// and the run ./generate-timer-worker.sh | ||
export const timerWorker = { | ||
get src(): string { | ||
throw new Error( | ||
'Timer worker source missing. Did you forget to run generate-timer-worker.sh?', | ||
); | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* eslint-disable */ | ||
|
||
import type { TimerWorkerEvent, TimerWorkerRequest } from './types'; | ||
|
||
const timerIdMapping = new Map<number, NodeJS.Timeout>(); | ||
|
||
self.addEventListener('message', (event: MessageEvent) => { | ||
const request = event.data as TimerWorkerRequest; | ||
|
||
switch (request.type) { | ||
case 'setTimeout': | ||
case 'setInterval': | ||
timerIdMapping.set( | ||
request.id, | ||
(request.type === 'setTimeout' ? setTimeout : setInterval)( | ||
() => tick(request.id), | ||
request.timeout, | ||
), | ||
); | ||
break; | ||
|
||
case 'clearTimeout': | ||
case 'clearInterval': | ||
(request.type === 'clearTimeout' ? clearTimeout : clearInterval)( | ||
timerIdMapping.get(request.id), | ||
); | ||
timerIdMapping.delete(request.id); | ||
break; | ||
} | ||
}); | ||
|
||
function tick(id: number) { | ||
const message: TimerWorkerEvent = { type: 'tick', id }; | ||
self.postMessage(message); | ||
} | ||
|
||
/* eslint-enable */ |