103 lines
3.8 KiB
TypeScript
103 lines
3.8 KiB
TypeScript
export interface PiManagedInputRevision {
|
|
provider: number;
|
|
resources: number;
|
|
}
|
|
|
|
export type PiManagedInputAction =
|
|
| { action: 'reuse'; revision: PiManagedInputRevision }
|
|
| { action: 'rebuild-before-prompt'; revision: PiManagedInputRevision }
|
|
| { action: 'rebuild-after-settled'; revision: PiManagedInputRevision }
|
|
| { action: 'defer-until-settled'; revision: PiManagedInputRevision };
|
|
|
|
interface WorkerRevisionState {
|
|
applied: PiManagedInputRevision;
|
|
activeRun: PiManagedInputRevision | null;
|
|
}
|
|
|
|
function copyRevision(revision: PiManagedInputRevision): PiManagedInputRevision {
|
|
return { provider: revision.provider, resources: revision.resources };
|
|
}
|
|
|
|
function revisionsEqual(left: PiManagedInputRevision, right: PiManagedInputRevision): boolean {
|
|
return left.provider === right.provider && left.resources === right.resources;
|
|
}
|
|
|
|
export class PiManagedInputRevisionCoordinator {
|
|
private currentRevision: PiManagedInputRevision = { provider: 1, resources: 1 };
|
|
private readonly workers = new Map<string, WorkerRevisionState>();
|
|
|
|
get current(): PiManagedInputRevision {
|
|
return copyRevision(this.currentRevision);
|
|
}
|
|
|
|
markProviderStale(): PiManagedInputRevision {
|
|
this.currentRevision = {
|
|
provider: this.currentRevision.provider + 1,
|
|
resources: this.currentRevision.resources,
|
|
};
|
|
return this.current;
|
|
}
|
|
|
|
markResourcesStale(): PiManagedInputRevision {
|
|
this.currentRevision = {
|
|
provider: this.currentRevision.provider,
|
|
resources: this.currentRevision.resources + 1,
|
|
};
|
|
return this.current;
|
|
}
|
|
|
|
registerWorker(workerId: string, applied: PiManagedInputRevision = this.currentRevision): void {
|
|
if (!workerId.trim()) throw new Error('Worker id is required');
|
|
if (this.workers.has(workerId)) throw new Error(`Worker is already registered: ${workerId}`);
|
|
this.workers.set(workerId, { applied: copyRevision(applied), activeRun: null });
|
|
}
|
|
|
|
removeWorker(workerId: string): void {
|
|
this.workers.delete(workerId);
|
|
}
|
|
|
|
beforePrompt(workerId: string): PiManagedInputAction {
|
|
const worker = this.requireWorker(workerId);
|
|
if (worker.activeRun) {
|
|
return revisionsEqual(worker.applied, this.currentRevision)
|
|
? { action: 'reuse', revision: copyRevision(worker.activeRun) }
|
|
: { action: 'defer-until-settled', revision: copyRevision(worker.activeRun) };
|
|
}
|
|
return revisionsEqual(worker.applied, this.currentRevision)
|
|
? { action: 'reuse', revision: copyRevision(worker.applied) }
|
|
: { action: 'rebuild-before-prompt', revision: this.current };
|
|
}
|
|
|
|
applyCurrentRevision(workerId: string): PiManagedInputRevision {
|
|
const worker = this.requireWorker(workerId);
|
|
if (worker.activeRun) throw new Error('Cannot rebuild managed input while a run is active');
|
|
worker.applied = this.current;
|
|
return copyRevision(worker.applied);
|
|
}
|
|
|
|
beginRun(workerId: string): PiManagedInputRevision {
|
|
const worker = this.requireWorker(workerId);
|
|
if (worker.activeRun) throw new Error('Worker already has an active run');
|
|
if (!revisionsEqual(worker.applied, this.currentRevision)) {
|
|
throw new Error('Worker managed input is stale');
|
|
}
|
|
worker.activeRun = copyRevision(worker.applied);
|
|
return copyRevision(worker.activeRun);
|
|
}
|
|
|
|
settleRun(workerId: string): PiManagedInputAction {
|
|
const worker = this.requireWorker(workerId);
|
|
if (!worker.activeRun) throw new Error('Worker has no active run');
|
|
worker.activeRun = null;
|
|
return revisionsEqual(worker.applied, this.currentRevision)
|
|
? { action: 'reuse', revision: copyRevision(worker.applied) }
|
|
: { action: 'rebuild-after-settled', revision: this.current };
|
|
}
|
|
|
|
private requireWorker(workerId: string): WorkerRevisionState {
|
|
const worker = this.workers.get(workerId);
|
|
if (!worker) throw new Error(`Worker is not registered: ${workerId}`);
|
|
return worker;
|
|
}
|
|
}
|