-
Notifications
You must be signed in to change notification settings - Fork 100
Common shared WorkerPool for both Potree1 and Potree2 pointclouds #182
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,74 +1,54 @@ | ||
import { AsyncBlockingQueue } from './async-blocking-queue'; | ||
import { AutoTerminatingWorker, WorkerQueue } from './worker-queue'; | ||
|
||
export class AutoTerminatingWorker { | ||
private timeoutId: number | undefined = undefined; | ||
private terminated: boolean = false; | ||
export enum WorkerType { | ||
// Potree 1 workers | ||
BINARY_DECODER_WORKER = 'BINARY_DECODER_WORKER', | ||
|
||
constructor(private wrappedWorker: Worker, private maxIdle: number) {} | ||
// Potree 2 workers | ||
DECODER_WORKER = 'DECODER_WORKER', | ||
DECODER_WORKER_GLTF = 'DECODER_WORKER_GLTF', | ||
} | ||
|
||
public get worker(): Worker { | ||
return this.wrappedWorker; | ||
} | ||
export const DEFAULT_MAX_WORKERS_PER_POOL = 32; | ||
|
||
get isTerminated(): boolean { | ||
return this.terminated; | ||
} | ||
export class WorkerPool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The new WorkerPool is a Singleton and has a pool that consists of multiple WorkerQueues for each worker type. We set a maximum number of workers per queue, and this allows us to manage all the different Queues in a single place |
||
public _maxWorkersPerPool = DEFAULT_MAX_WORKERS_PER_POOL; | ||
|
||
private static instance: WorkerPool | undefined; | ||
private constructor() {} | ||
|
||
private pool: { [key in WorkerType]: WorkerQueue } = { | ||
BINARY_DECODER_WORKER: new WorkerQueue( | ||
this._maxWorkersPerPool, | ||
require('../workers/binary-decoder.worker.js').default, | ||
), | ||
DECODER_WORKER: new WorkerQueue( | ||
this._maxWorkersPerPool, | ||
require('../loading2/decoder.worker.js').default, | ||
), | ||
DECODER_WORKER_GLTF: new WorkerQueue( | ||
this._maxWorkersPerPool, | ||
require('../loading2/gltf-decoder.worker.js').default, | ||
), | ||
}; | ||
|
||
static getInstance(): WorkerPool { | ||
if (!this.instance) { | ||
this.instance = new WorkerPool(); | ||
} | ||
|
||
markIdle(): void { | ||
this.timeoutId = window.setTimeout(() => { | ||
this.terminated = true; | ||
this.wrappedWorker.terminate(); | ||
}, this.maxIdle); | ||
return this.instance; | ||
} | ||
|
||
markInUse(): void { | ||
if (this.timeoutId) { | ||
window.clearTimeout(this.timeoutId); | ||
} | ||
set maxWorkersPerPool(count: number) { | ||
Object.entries(this.pool).forEach(([_, pool]) => (pool.maxWorkers = count)); | ||
} | ||
} | ||
|
||
export class WorkerPool { | ||
/** | ||
* The maximum amount of idle time that can elapse before a worker from this pool is automatically terminated | ||
*/ | ||
private static readonly POOL_MAX_IDLE = 7000; | ||
|
||
private pool = new AsyncBlockingQueue<AutoTerminatingWorker>(); | ||
private poolSize = 0; | ||
|
||
constructor(public maxWorkers: number, private workerType: any) {} | ||
|
||
/** | ||
* Returns a worker promise which is resolved when one is available. | ||
*/ | ||
public getWorker(): Promise<AutoTerminatingWorker> { | ||
// If the number of active workers is smaller than the maximum, return a new one. | ||
// Otherwise, return a promise for worker from the pool. | ||
if (this.poolSize < this.maxWorkers) { | ||
this.poolSize++; | ||
return Promise.resolve( | ||
new AutoTerminatingWorker(new this.workerType(), WorkerPool.POOL_MAX_IDLE), | ||
); | ||
} else { | ||
return this.pool.dequeue().then(worker => { | ||
worker.markInUse(); | ||
// If the dequeued worker has been terminated, decrease the pool size and make a recursive call to get a new worker | ||
if (worker.isTerminated) { | ||
this.poolSize--; | ||
return this.getWorker(); | ||
} | ||
return worker; | ||
}); | ||
} | ||
public getWorker(workerType: WorkerType): Promise<AutoTerminatingWorker> { | ||
return this.pool[workerType].getWorker(); | ||
} | ||
|
||
/** | ||
* Releases a Worker back into the pool | ||
* @param worker | ||
*/ | ||
public releaseWorker(worker: AutoTerminatingWorker): void { | ||
worker.markIdle(); | ||
this.pool.enqueue(worker); | ||
public releaseWorker(workerType: WorkerType, worker: AutoTerminatingWorker): void { | ||
return this.pool[workerType].releaseWorker(worker); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import { AsyncBlockingQueue } from './async-blocking-queue'; | ||
|
||
export class AutoTerminatingWorker { | ||
private timeoutId: number | undefined = undefined; | ||
private terminated: boolean = false; | ||
|
||
constructor(private wrappedWorker: Worker, private maxIdle: number) {} | ||
|
||
public get worker(): Worker { | ||
return this.wrappedWorker; | ||
} | ||
|
||
get isTerminated(): boolean { | ||
return this.terminated; | ||
} | ||
|
||
markIdle(): void { | ||
this.timeoutId = window.setTimeout(() => { | ||
this.terminated = true; | ||
this.wrappedWorker.terminate(); | ||
}, this.maxIdle); | ||
} | ||
|
||
markInUse(): void { | ||
if (this.timeoutId) { | ||
window.clearTimeout(this.timeoutId); | ||
} | ||
} | ||
} | ||
|
||
export class WorkerQueue { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Renamed and moved the previous Potree1 WorkerPool -> WorkerQueue. The WorkerQueue is responsible for returning a single worker type The AsyncBlockingQueue with AutoTerminatingWorkers are awesome. By reusing this functionality as a Queue, we know that workers will be cleaned up when they are not being used |
||
/** | ||
* The maximum amount of idle time that can elapse before a worker from this pool is automatically terminated | ||
*/ | ||
private static readonly QUEUE_MAX_IDLE = 7000; | ||
|
||
private queue = new AsyncBlockingQueue<AutoTerminatingWorker>(); | ||
private queueSize = 0; | ||
|
||
constructor(public maxWorkers: number, private workerType: any) {} | ||
|
||
/** | ||
* Returns a worker promise which is resolved when one is available. | ||
*/ | ||
public getWorker(): Promise<AutoTerminatingWorker> { | ||
// If the number of active workers is smaller than the maximum, return a new one. | ||
// Otherwise, return a promise for worker from the pool. | ||
if (this.queueSize < this.maxWorkers) { | ||
this.queueSize++; | ||
return Promise.resolve( | ||
new AutoTerminatingWorker(new this.workerType(), WorkerQueue.QUEUE_MAX_IDLE), | ||
); | ||
} else { | ||
return this.queue.dequeue().then(worker => { | ||
worker.markInUse(); | ||
// If the dequeued worker has been terminated, decrease the pool size and make a recursive call to get a new worker | ||
if (worker.isTerminated) { | ||
this.queueSize--; | ||
return this.getWorker(); | ||
} | ||
return worker; | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Releases a Worker back into the pool | ||
* @param worker | ||
*/ | ||
public releaseWorker(worker: AutoTerminatingWorker): void { | ||
worker.markIdle(); | ||
this.queue.enqueue(worker); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previously setting
maxLoaderWorkers
had no impact on Potree2 pointclouds, which could spawn an unlimited number of workers. Now, we can set the maximum number of workers that each pool is allowed to spawn