Files
makelore/electron/coding-runtime/pi/managed-input-revision.ts

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;
}
}