81 lines
2.5 KiB
TypeScript
81 lines
2.5 KiB
TypeScript
|
|
import type { Task } from './taskbuffer.classes.task.js';
|
||
|
|
import type { ITaskConstraintGroupOptions } from './taskbuffer.interfaces.js';
|
||
|
|
|
||
|
|
export class TaskConstraintGroup<TData extends Record<string, unknown> = Record<string, unknown>> {
|
||
|
|
public name: string;
|
||
|
|
public maxConcurrent: number;
|
||
|
|
public cooldownMs: number;
|
||
|
|
private constraintKeyForTask: (task: Task<any, any, TData>) => string | null | undefined;
|
||
|
|
|
||
|
|
private runningCounts = new Map<string, number>();
|
||
|
|
private lastCompletionTimes = new Map<string, number>();
|
||
|
|
|
||
|
|
constructor(options: ITaskConstraintGroupOptions<TData>) {
|
||
|
|
this.name = options.name;
|
||
|
|
this.constraintKeyForTask = options.constraintKeyForTask;
|
||
|
|
this.maxConcurrent = options.maxConcurrent ?? Infinity;
|
||
|
|
this.cooldownMs = options.cooldownMs ?? 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public getConstraintKey(task: Task<any, any, TData>): string | null {
|
||
|
|
const key = this.constraintKeyForTask(task);
|
||
|
|
return key ?? null;
|
||
|
|
}
|
||
|
|
|
||
|
|
public canRun(subGroupKey: string): boolean {
|
||
|
|
const running = this.runningCounts.get(subGroupKey) ?? 0;
|
||
|
|
if (running >= this.maxConcurrent) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (this.cooldownMs > 0) {
|
||
|
|
const lastCompletion = this.lastCompletionTimes.get(subGroupKey);
|
||
|
|
if (lastCompletion !== undefined) {
|
||
|
|
const elapsed = Date.now() - lastCompletion;
|
||
|
|
if (elapsed < this.cooldownMs) {
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
public acquireSlot(subGroupKey: string): void {
|
||
|
|
const current = this.runningCounts.get(subGroupKey) ?? 0;
|
||
|
|
this.runningCounts.set(subGroupKey, current + 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
public releaseSlot(subGroupKey: string): void {
|
||
|
|
const current = this.runningCounts.get(subGroupKey) ?? 0;
|
||
|
|
const next = Math.max(0, current - 1);
|
||
|
|
if (next === 0) {
|
||
|
|
this.runningCounts.delete(subGroupKey);
|
||
|
|
} else {
|
||
|
|
this.runningCounts.set(subGroupKey, next);
|
||
|
|
}
|
||
|
|
this.lastCompletionTimes.set(subGroupKey, Date.now());
|
||
|
|
}
|
||
|
|
|
||
|
|
public getCooldownRemaining(subGroupKey: string): number {
|
||
|
|
if (this.cooldownMs <= 0) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
const lastCompletion = this.lastCompletionTimes.get(subGroupKey);
|
||
|
|
if (lastCompletion === undefined) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
const elapsed = Date.now() - lastCompletion;
|
||
|
|
return Math.max(0, this.cooldownMs - elapsed);
|
||
|
|
}
|
||
|
|
|
||
|
|
public getRunningCount(subGroupKey: string): number {
|
||
|
|
return this.runningCounts.get(subGroupKey) ?? 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
public reset(): void {
|
||
|
|
this.runningCounts.clear();
|
||
|
|
this.lastCompletionTimes.clear();
|
||
|
|
}
|
||
|
|
}
|