import { RETRY_DELAYS } from './nameResolution' /** 排一遍视图数据用的:同步地问(交出已经查到的),和这一遍问了哪些问题。 */ export interface AnswerSession { ask: (question: Q) => A | undefined questions: () => Q[] } /** * 查一个问题:查到了是答案,查了没有是 null;语言扩展没答(还没起来)是 undefined,已知的留着不动,隔一会儿再查。 */ export type Answer = (question: Q) => Promise /** * 一份打开着的文档里,排视图数据时要问、却要异步才查得到的问题(库规则问的名字的定义、调用的名字定义成哪种块)的答案, * 按问题的 id 记,id 一样的问题按一个查。这里不碰 VS Code。 * 排视图数据是同步的:先交出已经查到的(文档改了也照旧用上一版查到的),记下这一遍问了什么;发出预览后再查, * 每个版本同一个问题查一次(语言扩展没答的照 RETRY_DELAYS 隔一会儿再查),查到的和已知的不一样就 changed,宿主按新结果重排、重发。 */ export class DeferredAnswers { /** 查到的:查了没有是 null */ private readonly known = new Map() private version: number | undefined /** 眼下这个版本查过(或正在查)的问题 */ private readonly checked = new Set() private timer: ReturnType | undefined /** 查到的答案变过几次:按眼下的答案排出来的东西(对比用的旧版本)比这个数就知道还能不能接着用 */ private changes = 0 constructor( private readonly idOf: (question: Q) => string, /** 两个答案对问的人来说一样不一样 */ private readonly same: (a: A | null | undefined, b: A | null | undefined) => boolean, private readonly changed: () => void, private readonly delays: readonly number[] = RETRY_DELAYS, private readonly schedule: (run: () => void, ms: number) => ReturnType = setTimeout, ) {} get revision(): number { return this.changes } session(): AnswerSession { const asked = new Map() return { ask: (question) => { const id = this.idOf(question) if (!asked.has(id)) asked.set(id, question) return this.known.get(id) ?? undefined }, questions: () => [...asked.values()], } } /** 查 version 这一版问的问题:这个版本查过的不再查。 */ resolve(version: number, questions: readonly Q[], answer: Answer): void { if (this.version !== version) { clearTimeout(this.timer) this.version = version this.checked.clear() } const fresh = questions.filter(question => !this.checked.has(this.idOf(question))) for (const question of fresh) this.checked.add(this.idOf(question)) if (fresh.length === 0) return const attempt = (pending: readonly Q[], tries: number): void => { void this.check(pending, answer).then(({ unanswered, changed }) => { if (this.version !== version) return if (changed) this.changed() if (unanswered.length > 0 && tries < this.delays.length) this.timer = this.schedule(() => attempt(unanswered, tries + 1), this.delays[tries]) }) } attempt(fresh, 0) } dispose(): void { clearTimeout(this.timer) this.version = undefined } /** 查一遍:没答的问题留着已知的不动,交回去等下一次再查。 */ private async check(questions: readonly Q[], answer: Answer): Promise<{ unanswered: Q[], changed: boolean }> { const unanswered: Q[] = [] let changed = false await Promise.all(questions.map(async (question) => { const found = await answer(question) if (found === undefined) { unanswered.push(question) return } const id = this.idOf(question) if (!this.same(this.known.get(id), found)) { changed = true this.changes++ } this.known.set(id, found) })) return { unanswered, changed } } }