docs(canvas): explain compute workers and display resource lifetimes
This commit is contained in:
@@ -1,17 +1,31 @@
|
||||
/**
|
||||
* 可恢复解码状态合同与只读图片计算池。Blob 属于文件仓库,解码仅产生尺寸,不重编码/替换原字节。
|
||||
* 等待/执行都在内存调度,结果由 engine 校验生命周期后写入资源事实;不能直接在回调中改元素。
|
||||
*/
|
||||
import type {Json} from '../canvas-core/types.ts';
|
||||
import {fault,freeze} from '../canvas-core/values.ts';
|
||||
/** 解码器返回的正整数宽高,不包含临时 bitmap 或浏览器对象。 */
|
||||
export type ImageDimensions={readonly width:number;readonly height:number};
|
||||
/** 当前 v1 的 pending/ready/failed;ready 记录尺寸来源为 decode 或 server。 */
|
||||
type KnownImageDecodeState={readonly version:1;readonly status:'pending'}|{readonly version:1;readonly status:'ready';readonly width:number;readonly height:number;readonly source:'decode'|'server'}|{readonly version:1;readonly status:'failed';readonly code:string};
|
||||
/** 保留未来版本记录,恢复时不能因不识别就过滤;实际解释/回收要检查版本。 */
|
||||
export type ImageDecodeState=KnownImageDecodeState|Readonly<{version:number;[key:string]:Json|undefined}>;
|
||||
/** 宿主可注入 Worker 或其它解码执行器,close 属于计算池生命周期,不属于 view。 */
|
||||
export type ImageDecoderPort={decode(bytes:Blob,signal?:AbortSignal):Promise<ImageDimensions>;close():void};
|
||||
/** 这里只验证正安全整数和乘积可安全表示,不等同于已应用产品的像素预算。 */
|
||||
export function validImageDimensions(value:unknown):value is ImageDimensions{const size=value as ImageDimensions;return !!size&&Number.isSafeInteger(size.width)&&size.width>0&&Number.isSafeInteger(size.height)&&size.height>0&&Number.isSafeInteger(size.width*size.height);}
|
||||
/** Bounded read-only compute. Cancellation rejects the caller; a slot remains occupied until execution settles. */
|
||||
/** 默认2并发/64等待,最多8并发;取消结果与执行容量分开,防止无视 abort 的任务挤爆池。 */
|
||||
export function createImageDecodePool(options:{execute:(bytes:Blob,signal:AbortSignal)=>Promise<ImageDimensions>;concurrency?:number;maxQueued?:number}):ImageDecoderPort{
|
||||
const concurrency=options.concurrency??2,maxQueued=options.maxQueued??64;
|
||||
if(!Number.isSafeInteger(concurrency)||concurrency<1||concurrency>8||!Number.isSafeInteger(maxQueued)||maxQueued<1)throw fault('invalid_image_decode_budget');
|
||||
/** 每个解码请求独立控制器/完成标记;cleanup 解除外部取消监听,迟到执行结果不再采纳。 */
|
||||
type Task={id:number;bytes:Blob;controller:AbortController;resolve:(size:ImageDimensions)=>void;reject:(error:unknown)=>void;done:boolean;cleanup():void};
|
||||
const waiting=new Map<number,Task>(),active=new Map<number,Task>();let next=0,closed=false;
|
||||
/** 幂等 settle 调用者,成功捕获冻结尺寸;不提前归还仍在执行的 active 槽。 */
|
||||
function finish(task:Task,error?:unknown,size?:ImageDimensions){if(task.done)return;task.done=true;task.cleanup();waiting.delete(task.id);if(error)task.reject(error);else task.resolve(freeze({...size!}));}
|
||||
/** FIFO 取任务并在微任务执行,校验返回尺寸,最后 finally 删除 active 再继续泵;异常不毒化后续队列。 */
|
||||
function pump(){while(!closed&&active.size<concurrency&&waiting.size){const task=waiting.values().next().value!;waiting.delete(task.id);active.set(task.id,task);void Promise.resolve().then(()=>{if(task.controller.signal.aborted)throw fault('image_decode_aborted');return options.execute(task.bytes,task.controller.signal);}).then(size=>{if(!validImageDimensions(size))throw fault('invalid_image_dimensions');finish(task,undefined,size);}).catch(error=>finish(task,error)).finally(()=>{active.delete(task.id);pump();});}}
|
||||
/** decode 校验图片 MIME/容量并建立任务;close 取消所有调用结果,active 实际结束仍由 finally 收尾。 */
|
||||
return Object.freeze({decode(bytes:Blob,signal?:AbortSignal){if(closed)return Promise.reject(fault('image_decoder_closed'));if(signal?.aborted)return Promise.reject(fault('image_decode_aborted'));if(!(bytes instanceof Blob)||!bytes.type.startsWith('image/'))return Promise.reject(fault('invalid_image_input'));if(active.size>=concurrency&&waiting.size>=maxQueued)return Promise.reject(fault('image_decode_capacity'));return new Promise<ImageDimensions>((resolve,reject)=>{const abort=()=>{task.controller.abort();finish(task,fault('image_decode_aborted'));pump();},task:Task={id:++next,bytes,controller:new AbortController(),resolve,reject,done:false,cleanup:()=>signal?.removeEventListener('abort',abort)};signal?.addEventListener('abort',abort,{once:true});waiting.set(task.id,task);pump();});},close(){if(closed)return;closed=true;for(const task of [...waiting.values(),...active.values()]){task.controller.abort();finish(task,fault('image_decode_aborted'));}}});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user