diff --git a/packages-user/client-base/src/load/loader.ts b/packages-user/client-base/src/load/loader.ts index 28d95f2..f3599e5 100644 --- a/packages-user/client-base/src/load/loader.ts +++ b/packages-user/client-base/src/load/loader.ts @@ -1,5 +1,5 @@ import { - ILoadProgressTotal, + ILoadManager, LoadDataType, ILoadTask, LoadTask, @@ -51,7 +51,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { private readonly rowSplitter: ITextureSplitter; constructor( - readonly progress: ILoadProgressTotal, + readonly progress: ILoadManager, private readonly dataLoader: IMotaDataLoader, private readonly ac: IMotaAudioContext, private readonly sounds: ISoundPlayer, @@ -207,9 +207,11 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-font/${font}`, dataType: LoadDataType.ArrayBuffer, + // @ts-expect-error 需要重构 processor: this.fontProcessor, progress: this.progress }); + task.setProcessor(this.fontProcessor); this.addCustomLoadTask(task, data => this.fontLoaded(font, data)); }); @@ -220,6 +222,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-image/${image}`, dataType: LoadDataType.Blob, + // @ts-expect-error 需要重构 processor: this.imageProcessor, progress: this.progress }); @@ -238,6 +241,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-sound/${sound}`, dataType: LoadDataType.Uint8Array, + // @ts-expect-error 需要重构 processor: this.audioProcessor, progress: this.progress }); @@ -251,6 +255,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-tileset/${tileset}`, dataType: LoadDataType.Blob, + // @ts-expect-error 需要重构 processor: this.imageProcessor, progress: this.progress }); @@ -268,6 +273,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-autotile/${key}`, dataType: LoadDataType.Blob, + // @ts-expect-error 需要重构 processor: this.imageProcessor, progress: this.progress }); @@ -294,6 +300,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url, identifier: `@system-material/${materialName}`, dataType: LoadDataType.Blob, + // @ts-expect-error 需要重构 processor: this.imageProcessor, progress: this.progress }); @@ -308,6 +315,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url: animatesUrl, identifier: '@system-animates', dataType: LoadDataType.Text, + // @ts-expect-error 需要重构 processor: this.textProcessor, progress: this.progress }); @@ -454,6 +462,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url: `loadList.json`, dataType: LoadDataType.JSON, identifier: '@system-loadList', + // @ts-expect-error 需要重构 processor: this.dataLoader.jsonProcessor, progress: { onProgress() {} } }); @@ -465,6 +474,7 @@ export class MotaAssetsLoader implements IMotaAssetsLoader { url: loadList.file, identifier: `@system-zip/${loadList.file}`, dataType: LoadDataType.ArrayBuffer, + // @ts-expect-error 需要重构 processor: this.zipProcessor, progress: this.progress }); diff --git a/packages-user/client-base/src/load/types.ts b/packages-user/client-base/src/load/types.ts index 516c8e1..e1c707d 100644 --- a/packages-user/client-base/src/load/types.ts +++ b/packages-user/client-base/src/load/types.ts @@ -1,5 +1,5 @@ import { - ILoadProgressTotal, + ILoadManager, ILoadTask, ILoadTaskProcessor, LoadDataType @@ -37,7 +37,7 @@ export interface ICompressedMotaAssetsLoadList { export interface IMotaAssetsLoader { /** 加载进度对象 */ - readonly progress: ILoadProgressTotal; + readonly progress: ILoadManager; /** 当前是否正在加载 */ readonly loading: boolean; /** 当前是否已经加载完毕 */ diff --git a/packages-user/client-modules/src/client.ts b/packages-user/client-modules/src/client.ts index 1a579aa..e5388b5 100644 --- a/packages-user/client-modules/src/client.ts +++ b/packages-user/client-modules/src/client.ts @@ -63,7 +63,7 @@ export class ClientCore extends CoreState implements IClientCore { readonly bgmPlayer: IBGMPlayer; constructor() { - super(); + super({}); //#region Layer 4 diff --git a/packages-user/client-modules/src/render/ui/load.tsx b/packages-user/client-modules/src/render/ui/load.tsx index c8e43b6..1b20bb7 100644 --- a/packages-user/client-modules/src/render/ui/load.tsx +++ b/packages-user/client-modules/src/render/ui/load.tsx @@ -74,6 +74,7 @@ export const LoadScene = defineComponent(props => { loader.load().then(() => { loadEnd(); }); + // @ts-expect-error 需要重构 for await (const _ of loader.progress) { taskProgress.set(loader.progress.getLoadedTasks()); byteProgress.set(loader.progress.getLoadedByte()); diff --git a/packages-user/data-base/src/load/loader.ts b/packages-user/data-base/src/load/loader.ts index 25b300f..263825a 100644 --- a/packages-user/data-base/src/load/loader.ts +++ b/packages-user/data-base/src/load/loader.ts @@ -1,5 +1,5 @@ import { - ILoadProgressTotal, + ILoadManager, LoadDataType, ILoadTask, ILoadTaskProcessor @@ -24,7 +24,7 @@ export class MotaDataLoader implements IMotaDataLoader { readonly jsonProcessor: ILoadTaskProcessor; - constructor(readonly progress: ILoadProgressTotal) { + constructor(readonly progress: ILoadManager) { this.jsonProcessor = new LoadJSONProcessor(); } diff --git a/packages-user/data-base/src/load/types.ts b/packages-user/data-base/src/load/types.ts index b56c3b7..4e11cb7 100644 --- a/packages-user/data-base/src/load/types.ts +++ b/packages-user/data-base/src/load/types.ts @@ -1,5 +1,5 @@ import { - ILoadProgressTotal, + ILoadManager, ILoadTask, ILoadTaskProcessor, LoadDataType @@ -9,7 +9,7 @@ export type CustomLoadFunc = (data: R) => Promise; export interface IMotaDataLoader { /** 加载进度对象 */ - readonly progress: ILoadProgressTotal; + readonly progress: ILoadManager; /** json 处理器 */ readonly jsonProcessor: ILoadTaskProcessor; diff --git a/packages-user/data-state/src/core.ts b/packages-user/data-state/src/core.ts index 168007f..62f31f9 100644 --- a/packages-user/data-state/src/core.ts +++ b/packages-user/data-state/src/core.ts @@ -1,5 +1,5 @@ import { logger } from '@motajs/common'; -import { ILoadProgressTotal, LoadProgressTotal } from '@motajs/loader'; +import { ILoadManager, LoadManager } from '@motajs/loader'; import { IRoleFaceBinder, IFaceManager, @@ -48,7 +48,7 @@ import { IPathfindingSystem, PathfindingSystem } from '@user/data-system'; -import { ICoreState, ISaveableExecutor } from './types'; +import { ICoreState, ICoreStateConfig, ISaveableExecutor } from './types'; import { CommonAuraConverter, EnemyLegacyBridge, @@ -93,7 +93,7 @@ export class CoreState implements ICoreState { readonly pathfinding: IPathfindingSystem; // Layer 3 用户层,也就是最顶层的内容,一般仅用于初始化以及仅供渲染端调用的顶层模块 - readonly loadProgress: ILoadProgressTotal; + readonly loadProgress: ILoadManager; readonly dataLoader: IMotaDataLoader; /** 可存档对象映射 */ @@ -106,7 +106,7 @@ export class CoreState implements ICoreState { ISaveableExecutor > = new Map(); - constructor() { + constructor(config: Readonly) { //#region L0 初始化 // 朝向 @@ -143,7 +143,7 @@ export class CoreState implements ICoreState { const heroState = new HeroState(this, dir8, heroAttribute); this.hero = heroState; - this.loadProgress = new LoadProgressTotal(); + this.loadProgress = new LoadManager(); this.dataLoader = new MotaDataLoader(this.loadProgress); // 怪物管理器 @@ -323,7 +323,3 @@ export class CoreState implements ICoreState { //#endregion } - -export function createCoreState(): CoreState { - return new CoreState(); -} diff --git a/packages-user/data-state/src/ins.ts b/packages-user/data-state/src/ins.ts index ae88703..46053b5 100644 --- a/packages-user/data-state/src/ins.ts +++ b/packages-user/data-state/src/ins.ts @@ -7,4 +7,4 @@ import { CoreState } from './core'; * 此对象是数据端状态,本身不负责任何渲染操作,仅会向渲染端发送数据让渲染端渲染,不要把渲染操作直接放到此对象上, * 否则可能导致录像验证失败。 */ -export const state = new CoreState(); +export const state = new CoreState({}); diff --git a/packages-user/data-state/src/types.ts b/packages-user/data-state/src/types.ts index 84329a1..37a4010 100644 --- a/packages-user/data-state/src/types.ts +++ b/packages-user/data-state/src/types.ts @@ -1,8 +1,10 @@ import { IMotaDataLoader, IStateBase } from '@user/data-base'; -import { ILoadProgressTotal } from '@motajs/loader'; +import { ILoadManager } from '@motajs/loader'; import { IStateSystem } from '@user/data-system'; import { ISaveableContent } from '@user/data-common'; +export interface ICoreStateConfig {} + export interface ISaveableExecutor { /** * 当数据读取后执行的函数,允许对其他存档对象进行读取 @@ -15,7 +17,7 @@ export interface ISaveableExecutor { export interface ICoreState extends IStateSystem, ISaveableContent> { /** 加载进度对象 */ - readonly loadProgress: ILoadProgressTotal; + readonly loadProgress: ILoadManager; /** 数据端加载对象 */ readonly dataLoader: IMotaDataLoader; diff --git a/packages-user/data-state/test/tileLegacy.test.ts b/packages-user/data-state/test/tileLegacy.test.ts new file mode 100644 index 0000000..0d301bb --- /dev/null +++ b/packages-user/data-state/test/tileLegacy.test.ts @@ -0,0 +1,44 @@ +import { describe, expect, it } from 'vitest'; +import { TileStore } from '@user/data-common'; +import { LegacyTileData, TileLegacyBridge } from '../src/legacy/tile'; + +describe('TileLegacyBridge events-map contract', () => { + // 验证旧样板事件对象会转换为 TileStore 可消费的默认事件映射 + it('converts legacy events into the TileStore event map', () => { + const bridge = new TileLegacyBridge(); + const store = new TileStore(); + store.attachLegacyConverter(bridge); + const legacy: LegacyTileData = { + id: 'yellowWall', + cls: 'terrains', + events: { 10: 'onEnter', 20: 'onTouch' }, + noPass: true + }; + + const converted = store.fromLegacy(1, legacy); + + expect(converted.events).toEqual({ 10: 'onEnter', 20: 'onTouch' }); + expect(store.getEvent(1)).toEqual( + new Map([ + [10, 'onEnter'], + [20, 'onTouch'] + ]) + ); + expect(converted.eventPass).toBe(false); + expect(converted).not.toHaveProperty('trigger'); + }); + + // 验证旧样板缺少事件输入时仍生成合法的空事件映射 + it('uses an empty events map when legacy input has no events', () => { + const bridge = new TileLegacyBridge(); + const legacy: LegacyTileData = { + id: 'whiteWall', + cls: 'terrains' + }; + + const converted = bridge.fromLegacy(2, legacy); + + expect(converted.events).toEqual({}); + expect(converted).not.toHaveProperty('trigger'); + }); +}); diff --git a/packages/common/src/logger.json b/packages/common/src/logger.json index 41f2c94..a3ef83f 100644 --- a/packages/common/src/logger.json +++ b/packages/common/src/logger.json @@ -29,7 +29,7 @@ "27": "No autotile connection data, please ensure you have created autotile connection map.", "28": "Cannot compile map render shader.", "29": "Cannot get uniform location of map render shader.", - "30": "", + "30": "Load task needs a load starter binding before loading.", "31": "No asset data is specified when rending map.", "32": "Every layer added to map renderer must share the same size. Different layer: $1.", "33": "Map layer transfered to vertex generator must belong to the renderer that the generator use.", diff --git a/packages/loader/src/index.ts b/packages/loader/src/index.ts index 2030c0c..fd7d204 100644 --- a/packages/loader/src/index.ts +++ b/packages/loader/src/index.ts @@ -1,4 +1,5 @@ export * from './progress'; +export * from './starter'; export * from './stream'; export * from './task'; export * from './types'; diff --git a/packages/loader/src/progress.ts b/packages/loader/src/progress.ts index 0b6ec80..0141a3b 100644 --- a/packages/loader/src/progress.ts +++ b/packages/loader/src/progress.ts @@ -1,17 +1,22 @@ import { clamp } from 'lodash-es'; -import { ILoadProgressTotal, ILoadTask, LoadDataType } from './types'; -import { logger } from '@motajs/common'; +import { ILoadManager, ILoadManagerHooks, ILoadTask } from './types'; +import { + Hookable, + HookController, + IHookController, + logger +} from '@motajs/common'; -export class LoadProgressTotal< - T extends LoadDataType = LoadDataType, - R = any -> implements ILoadProgressTotal { +export class LoadManager + extends Hookable + implements ILoadManager +{ /** 当前已经附着的加载任务 */ - private readonly attached: Map, number> = new Map(); + private readonly attached: Map = new Map(); /** 当前已经加载完毕的任务 */ - readonly loadedTasks: Set> = new Set(); + readonly loadedTasks: Set = new Set(); /** 当前已经添加的任务 */ - readonly addedTasks: Set> = new Set(); + readonly addedTasks: Set = new Set(); /** 总加载量 */ private total: number = 0; @@ -23,26 +28,37 @@ export class LoadProgressTotal< /** 兑现当前的 `nextPromise` */ private nextResolve: () => void; - async *[Symbol.asyncIterator]() { - while (true) { - if (this.loadedTasks.size === this.addedTasks.size) { - return; - } - yield this.nextPromise; - } - } - constructor() { + super(); const { promise, resolve } = Promise.withResolvers(); this.nextPromise = promise; this.nextResolve = resolve; } - addTask(task: ILoadTask) { - this.addedTasks.add(task); + protected createController( + hook: Partial + ): IHookController { + return new HookController(this, hook); } - onProgress(task: ILoadTask, loaded: number, total: number): void { + addTask(task: ILoadTask) { + this.addedTasks.add(task); + task.addHook({ + onProgress: (loaded, total) => this.onProgress(task, loaded, total) + }); + } + + async *load(): AsyncIterable { + while (true) { + if (this.loadedTasks.size === this.addedTasks.size) { + return; + } + await this.nextPromise; + yield this.loaded; + } + } + + onProgress(task: ILoadTask, loaded: number, total: number): void { if (!this.addedTasks.has(task)) { logger.warn(95); return; diff --git a/packages/loader/src/starter.ts b/packages/loader/src/starter.ts new file mode 100644 index 0000000..96154c2 --- /dev/null +++ b/packages/loader/src/starter.ts @@ -0,0 +1,11 @@ +import { ILoadTaskRequest, ILoadTaskStarter } from './types'; + +export class WebLoadStarter implements ILoadTaskStarter { + start(request: ILoadTaskRequest): Promise { + return fetch(request.url, { + method: request.method, + body: request.body, + headers: request.headers + }); + } +} diff --git a/packages/loader/src/task.ts b/packages/loader/src/task.ts index 4e1eb3c..4954a8e 100644 --- a/packages/loader/src/task.ts +++ b/packages/loader/src/task.ts @@ -2,26 +2,39 @@ import { sumBy } from 'lodash-es'; import { ILoadDataTypeMap, ILoadTask, + ILoadTaskHooks, ILoadTaskInit, ILoadTaskProcessor, - ILoadTaskProgress, + ILoadTaskStarter, + IResponseLike, + IResponseReaderLike, LoadDataType, RequestMethod } from './types'; +import { + Hookable, + HookController, + IHookController, + logger +} from '@motajs/common'; /** 文字解码 */ const loadTextDecoder = new TextDecoder(); -export class LoadTask implements ILoadTask { +export class LoadTask + extends Hookable> + implements ILoadTask +{ readonly dataType: T; readonly identifier: string; - readonly url: string | URL; - readonly processor: ILoadTaskProcessor; - readonly progress: ILoadTaskProgress; + readonly url: string; readonly method?: RequestMethod; readonly body?: BodyInit; readonly headers?: HeadersInit; + processor: ILoadTaskProcessor | null = null; + starter: ILoadTaskStarter | null = null; + contentLoaded: boolean = false; loadedByte: number = 0; totalByte: number = 0; @@ -34,12 +47,12 @@ export class LoadTask implements ILoadTask { /** 加载结果 */ private loadedData: R | null = null; - constructor(init: ILoadTaskInit) { + constructor(init: ILoadTaskInit) { + super(); + this.dataType = init.dataType; this.identifier = init.identifier; - this.url = this.resolveURL(init.url); - this.processor = init.processor; - this.progress = init.progress; + this.url = init.url; this.method = init.method; this.body = init.body; this.headers = init.headers; @@ -49,16 +62,26 @@ export class LoadTask implements ILoadTask { this.loadResolve = resolve; } - private resolveURL(url: string | URL) { - if (typeof url === 'string') { - return `${import.meta.env.BASE_URL}${url}`; - } else { - return url; - } + protected createController( + hook: Partial> + ): IHookController> { + return new HookController(this, hook); } + setProcessor(processor: ILoadTaskProcessor | null): void { + this.processor = processor; + } + + setStarter(starter: ILoadTaskStarter): void { + this.starter = starter; + } + + /** + * 当不使用流加载时,使用此方法直接处理响应对象 + * @param response 响应体对象 + */ private processUnstreamableResponse( - response: Response + response: IResponseLike ): Promise { switch (this.dataType) { case LoadDataType.ArrayBuffer: @@ -74,6 +97,10 @@ export class LoadTask implements ILoadTask { } } + /** + * 当使用流加载时,处理每次流加载的分块 + * @param chunks 流式加载的二进制分块 + */ private processStreamChunkResponse( chunks: Uint8Array[] ): ILoadDataTypeMap[T] { @@ -102,23 +129,44 @@ export class LoadTask implements ILoadTask { } } - private async processResponse(response: Response) { - const reader = response.body?.getReader(); - const contentLength = response.headers.get('Content-Length') ?? '0'; - const total = parseInt(contentLength, 10); - this.loadedByte = 0; - this.totalByte = total; - this.progress.onProgress(this, 0, total); - if (!reader) { - const data = await this.processUnstreamableResponse(response); - this.loadedByte = this.totalByte; - this.contentLoaded = true; - this.progress.onProgress(this, this.loadedByte, this.totalByte); - const processed = await this.processor.process(data, this); - this.loadedData = processed; - this.loadResolve(processed); - return; + /** + * 处理加载数据 + * @param data 加载获取的原始数据 + */ + private processData(data: ILoadDataTypeMap[T]): Promise { + if (this.processor) { + return this.processor.process(data, this); + } else { + return Promise.resolve(data as R); } + } + + /** + * 使用非流式加载方式进行加载 + * @param response 响应体 + * @param total 加载总字节数 + */ + private async *loadUnstream( + response: IResponseLike, + total: number + ): AsyncGenerator { + const data = await this.processUnstreamableResponse(response); + this.loadedByte = this.totalByte; + this.contentLoaded = true; + this.forEachHook(hook => hook.onProgress?.(total, total)); + yield total; + return data; + } + + /** + * 使用流式加载方式进行加载 + * @param reader 流式读取器 + * @param total 加载总字节数 + */ + private async *loadStream( + reader: IResponseReaderLike, + total: number + ): AsyncGenerator { let received = 0; const chunks: Uint8Array[] = []; while (true) { @@ -129,23 +177,70 @@ export class LoadTask implements ILoadTask { } if (done) this.contentLoaded = true; this.loadedByte = received; - this.progress.onProgress(this, received, total); - if (done) break; + this.forEachHook(hook => hook.onProgress?.(received, total)); + yield received; + if (done) { + this.loadedByte = total; + yield total; + break; + } } const data = this.processStreamChunkResponse(chunks); - const processed = await this.processor.process(data, this); - this.loadedData = processed; - this.loadResolve(processed); + return data; } - async start(): Promise { - const response = await fetch(this.url, { - method: this.method, - body: this.body, - headers: this.headers - }); - this.processResponse(response); - return; + /** + * 执行加载任务,自动决定使用流式加载或非流式加载 + * @param response 响应体 + * @param total 加载总字节数 + */ + private loadTask( + response: IResponseLike, + total: number + ): AsyncGenerator { + const reader = response.body?.getReader(); + if (reader) { + return this.loadStream(reader, total); + } else { + return this.loadUnstream(response, total); + } + } + + /** + * 处理响应体,并进行加载工作 + * @param response 响应体 + */ + private async *processResponse( + response: IResponseLike + ): AsyncIterable { + const contentLength = response.headers.get('Content-Length') ?? '0'; + const total = parseInt(contentLength, 10); + this.loadedByte = 0; + this.totalByte = total; + this.forEachHook(hook => hook.onLoadStart?.(total)); + this.forEachHook(hook => hook.onProgress?.(0, total)); + + const iter = this.loadTask(response, total); + const data = yield* iter; + + const processed = await this.processData(data); + this.loadedData = processed; + this.loadResolve(processed); + this.forEachHook(hook => hook.onLoadEnd?.(processed, data, total)); + } + + async *start(): AsyncIterable { + if (this.loadedData) return; + + if (!this.starter) { + logger.error(30); + return; + } + const response = await this.starter.start(this); + yield* this.processResponse(response); + + // 加载完毕后就可以清空钩子了 + this.forEachHook(hook => hook).forEach(v => this.removeHook(v)); } loaded(): Promise { diff --git a/packages/loader/src/types.ts b/packages/loader/src/types.ts index f477db5..aec1986 100644 --- a/packages/loader/src/types.ts +++ b/packages/loader/src/types.ts @@ -1,5 +1,7 @@ //#region 流传输 +import { IHookable, IHookBase } from '@motajs/common'; + export interface IStreamController { /** 当前是否正在加载 */ readonly loading: boolean; @@ -85,33 +87,6 @@ export const enum LoadDataType { JSON } -export interface ILoadDataTypeMap { - [LoadDataType.ArrayBuffer]: ArrayBuffer; - [LoadDataType.Uint8Array]: Uint8Array; - [LoadDataType.Blob]: Blob; - [LoadDataType.Text]: string; - [LoadDataType.JSON]: any; -} - -export interface ILoadTaskProcessor { - /** - * 处理加载内容 - * @param response 处理前加载结果 - * @param task 加载任务对象 - */ - process(response: ILoadDataTypeMap[T], task: ILoadTask): Promise; -} - -export interface ILoadTaskProgress { - /** - * 更新加载进度 - * @param task 加载任务对象 - * @param loaded 已加载的字节数 - * @param total 文件总计字节数,如果此值为零说明无法读取到 `Content-Length` - */ - onProgress(task: ILoadTask, loaded: number, total: number): void; -} - export const enum RequestMethod { GET = 'GET', POST = 'POST', @@ -124,17 +99,17 @@ export const enum RequestMethod { PATCH = 'PATCH' } -export interface ILoadTaskInit { - /** 请求响应格式 */ - readonly dataType: T; - /** 加载任务标识符 */ - readonly identifier: string; +export interface ILoadDataTypeMap { + [LoadDataType.ArrayBuffer]: ArrayBuffer; + [LoadDataType.Uint8Array]: Uint8Array; + [LoadDataType.Blob]: Blob; + [LoadDataType.Text]: string; + [LoadDataType.JSON]: any; +} + +export interface ILoadTaskRequest { /** 加载目标 URL */ - readonly url: string | URL; - /** 加载的处理对象,用于处理加载结果等 */ - readonly processor: ILoadTaskProcessor; - /** 加载进度对象,用于监控加载进度 */ - readonly progress: ILoadTaskProgress; + readonly url: string; /** 请求模式 */ readonly method?: RequestMethod; /** 请求体 */ @@ -143,21 +118,141 @@ export interface ILoadTaskInit { readonly headers?: HeadersInit; } -export interface ILoadTask extends ILoadTaskInit< - T, - R -> { +export interface ILoadTaskProcessor { + /** + * 处理加载内容 + * @param response 处理前加载结果 + * @param task 加载任务对象 + */ + process(response: ILoadDataTypeMap[T], task: ILoadTask): Promise; +} + +export interface IResponseReaderLike { + /** + * 进行下一个分块的读取 + */ + read(): Promise>>; +} + +export interface IResponseBodyLike { + /** + * 获取流式读取器 + */ + getReader(): ReadableStreamDefaultReader; +} + +export interface IResponseHeadersLike { + /** + * 获取指定响应头的内容 + * @param name 响应头字段名称 + */ + get(name: string): string | null; +} + +export interface IResponseLike { + /** 请求地址 */ + readonly url: string; + /** 响应体 */ + readonly body: IResponseBodyLike | null; + /** 响应头 */ + readonly headers: IResponseHeadersLike; + + /** + * 输出为 `ArrayBuffer` + */ + arrayBuffer(): Promise; + + /** + * 输出为 `Blob` + */ + blob(): Promise; + + /** + * 输出为 `Uint8Array` + */ + bytes(): Promise>; + + /** + * 输出为 `JSON` + */ + json(): Promise; + + /** + * 输出为纯文本 + */ + text(): Promise; +} + +export interface ILoadTaskStarter { + /** + * 进行加载请求,获取 `Response` 对象 + * @param request 请求配置 + */ + start(request: ILoadTaskRequest): Promise; +} + +export interface ILoadTaskInit< + T extends LoadDataType +> extends ILoadTaskRequest { + /** 请求响应格式 */ + readonly dataType: T; + /** 加载任务标识符 */ + readonly identifier: string; +} + +export interface ILoadTaskHooks extends IHookBase { + /** + * 当加载开始时触发 + * @param total 加载总字节数 + */ + onLoadStart?(total: number): void; + + /** + * 当加载进度变动时触发 + * @param loaded 已加载的字节数 + * @param total 加载总字节数 + */ + onProgress?(loaded: number, total: number): void; + + /** + * 当加载完成时触发 + * @param data 经过处理后的加载内容 + * @param raw 加载完毕时获取的原始数据 + * @param total 加载总字节数 + */ + onLoadEnd?(data: R, raw: ILoadDataTypeMap[T], total: number): void; +} + +export interface ILoadTask + extends ILoadTaskInit, IHookable> { /** 当前是否加载完毕 */ readonly contentLoaded: boolean; /** 已经加载的字节数 */ readonly loadedByte: number; /** 该加载任务的总体字节数 */ readonly totalByte: number; + /** 当前任务使用的数据处理器 */ + readonly processor: ILoadTaskProcessor | null; + /** 当前任务使用的加载启动器 */ + readonly starter: ILoadTaskStarter | null; + + /** + * 设置当前任务使用的数据处理器 + * @param processor 数据处理器 + */ + setProcessor(processor: ILoadTaskProcessor | null): void; + + /** + * 设置当前任务使用的加载启动器 + * @param starter 加载启动器 + */ + setStarter(starter: ILoadTaskStarter): void; /** * 开始此加载计划,返回一个 `Promise`,当得到服务器的响应后兑现 + * @returns 异步迭代器,当加载进度变动时进行一次迭代,迭代值为当前加载的字节数 */ - start(): Promise; + start(): AsyncIterable; /** * 返回一个 `Promise`,当本计划加载完毕后兑现,兑现结果是加载结果 @@ -170,29 +265,43 @@ export interface ILoadTask extends ILoadTaskInit< getLoadedData(): R | null; } -//#endregion - -//#region 内置组件 - -export interface ILoadProgressTotal< - T extends LoadDataType = LoadDataType, - R = any -> extends ILoadTaskProgress { - /** 已经添加的加载任务对象 */ - readonly addedTasks: Set>; - /** 当前已经加载完毕的任务对象 */ - readonly loadedTasks: Set>; +export interface ILoadManagerHooks extends IHookBase { + /** + * 当开始加载时触发 + */ + onStartLoad?(): void; /** - * 迭代加载进度,当 `yield` 的值被兑现时,说明加载进度更新 + * 当加载进度发生变化时触发 + * @param loaded 已加载的字节数 + * @param total 加载总字节数,此数值可能不准确,因为必须等所有的加载全部连接完成时才能够得知总字节数 */ - [Symbol.asyncIterator](): AsyncGenerator; + onProgress?(loaded: number, total: number): void; + + /** + * 当所有加载任务完成时触发 + * @param total 加载的总字节数 + */ + onLoadEnd?(total: number): void; +} + +export interface ILoadManager extends IHookable { + /** 已经添加的加载任务对象 */ + readonly addedTasks: Set; + /** 当前已经加载完毕的任务对象 */ + readonly loadedTasks: Set; /** * 向该进度监听器添加加载任务对象 * @param task 加载任务对象 */ - addTask(task: ILoadTask): void; + addTask(task: ILoadTask): void; + + /** + * 开始执行加载 + * @returns 加载的异步迭代器,当加载进度更新时迭代,迭代值为当前已加载的总字节数 + */ + load(): AsyncIterable; /** * 获取总体已加载的字节数