/** Small independent edit jobs cannot queue behind whole-view lighting. */ export class EditMeshController { constructor({capture,onError=()=>{},workerFactory}={}) { this.capture=capture;this.onError=onError;this.epoch=0;this.job=0; this.pending=new Map();this.tickets=new Map();this.ready=new Map();this.busy=null; try { this.worker=workerFactory ? workerFactory() : new Worker(new URL('./edit-mesh-worker.js',import.meta.url),{type:'module'}); this.worker.onmessage=({data})=>this.receive(data); this.worker.onerror=event=>{event.preventDefault();this.fail(event.message);}; } catch(error) {this.fail(error.message);} } fail(error) {this.error=error;this.dispose();this.onError(error);} request(ids) { if(this.error||this.disposed)return; for(const id of ids) { const job=++this.job; this.pending.set(id,job);this.tickets.set(id,job);this.ready.delete(id); } } cancel(id) {this.pending.delete(id);this.tickets.delete(id);this.ready.delete(id);} reset() {this.epoch++;this.pending.clear();this.tickets.clear();this.ready.clear();this.busy=null;} isCurrent(result) {return result.epoch===this.epoch&&this.tickets.get(result.id)===result.editJob;} pump() { if(this.error||this.disposed||this.busy||this.ready.size>=2||!this.pending.size)return; const [id,editJob]=this.pending.entries().next().value; this.pending.delete(id); try { const start=performance.now(),packet={...this.capture(id),epoch:this.epoch,editJob}; const transfers=packet.sections.map(s=>s.cells.buffer); if(packet.light)transfers.push(packet.light.data.buffer,packet.light.blockLevels.buffer,packet.light.skyLevels.buffer); this.busy={id,editJob,epoch:this.epoch};this.worker.postMessage(packet,transfers); this.prepareMilliseconds=performance.now()-start; } catch(error) {this.fail(error.message);} } receive(result) { if(this.disposed||result.epoch!==this.epoch||result.editJob!==this.busy?.editJob)return; this.busy=null; if(!this.isCurrent(result))return; if(result.error){this.fail(result.error);return;} this.meshMilliseconds=result.milliseconds;this.ready.set(result.id,result); } dispose() {this.disposed=true;this.reset();this.worker?.terminate();} }