From b811e66fa8416bcc2875f67ad7960374e0f23e68 Mon Sep 17 00:00:00 2001 From: Shigma Date: Tue, 26 Mar 2024 02:37:58 +0800 Subject: [PATCH] fix(loader): fix group plugin partial reload --- packages/loader/src/entry.ts | 96 ++++++++++++++++++++++++++++++++ packages/loader/src/shared.ts | 100 ++-------------------------------- 2 files changed, 100 insertions(+), 96 deletions(-) create mode 100644 packages/loader/src/entry.ts diff --git a/packages/loader/src/entry.ts b/packages/loader/src/entry.ts new file mode 100644 index 0000000..cb90b72 --- /dev/null +++ b/packages/loader/src/entry.ts @@ -0,0 +1,96 @@ +import { Context, ForkScope } from '@cordisjs/core' +import { Dict } from 'cosmokit' +import Loader from './shared.ts' + +export namespace Entry { + export interface Options { + id: string + name: string + config?: any + disabled?: boolean + intercept?: Dict + isolate?: Dict + when?: any + } +} + +function swapAssign(target: T, source?: T): T { + const result = { ...target } + for (const key in result) { + delete target[key] + } + Object.assign(target, source) + return result +} + +export class Entry { + public fork: ForkScope | null = null + public isUpdate = false + + constructor(public loader: Loader, public parent: Context, public options: Entry.Options) {} + + amend(ctx: Context) { + swapAssign(ctx[Context.intercept], this.options.intercept) + const neoMap: Dict = Object.create(Object.getPrototypeOf(ctx[Context.isolate])) + for (const [key, label] of Object.entries(this.options.isolate ?? {})) { + if (typeof label === 'string') { + neoMap[key] = (this.loader.realms[label] ??= Object.create(null))[key] ??= Symbol(key) + } else if (label) { + neoMap[key] = Symbol(key) + } + } + for (const key in { ...ctx[Context.isolate], ...neoMap }) { + if (neoMap[key] === ctx[Context.isolate][key]) continue + const self = Object.create(null) + self[Context.filter] = (ctx2: Context) => { + return ctx[Context.isolate][key] === ctx2[Context.isolate][key] + } + ctx.emit(self, 'internal/before-service', key) + } + const oldMap = swapAssign(ctx[Context.isolate], neoMap) + for (const key in { ...oldMap, ...ctx[Context.isolate] }) { + if (oldMap[key] === ctx[Context.isolate][key]) continue + const self = Object.create(null) + self[Context.filter] = (ctx2: Context) => { + return ctx[Context.isolate][key] === ctx2[Context.isolate][key] + } + ctx.emit(self, 'internal/service', key) + } + } + + // TODO: handle parent change + update(parent: Context, options: Entry.Options) { + this.options = options + if (!this.loader.isTruthyLike(options.when) || options.disabled) { + this.stop() + } else { + this.start() + } + } + + async start() { + if (this.fork) { + this.isUpdate = true + this.amend(this.fork.parent) + this.fork.update(this.options.config) + } else { + this.parent.emit('loader/entry', 'apply', this) + const plugin = await this.loader.resolve(this.options.name) + if (!plugin) return + const ctx = this.parent.extend({ + [Context.intercept]: Object.create(this.parent[Context.intercept]), + [Context.isolate]: Object.create(this.parent[Context.isolate]), + }) + this.amend(ctx) + this.fork = ctx.plugin(plugin, this.loader.interpolate(this.options.config)) + this.fork.entry = this + } + } + + stop() { + if (!this.fork) return + this.parent.emit('loader/entry', 'unload', this) + this.fork.dispose() + this.fork = null + } +} diff --git a/packages/loader/src/shared.ts b/packages/loader/src/shared.ts index 6c9f1a3..2a7dba9 100644 --- a/packages/loader/src/shared.ts +++ b/packages/loader/src/shared.ts @@ -2,6 +2,7 @@ import { Context, EffectScope, ForkScope, Service } from '@cordisjs/core' import { defineProperty, Dict, isNullable, valueMap } from 'cosmokit' import { constants, promises as fs } from 'fs' import { interpolate } from './utils.ts' +import { Entry } from './entry.ts' import * as yaml from 'js-yaml' import * as path from 'path' @@ -42,99 +43,6 @@ if (typeof require !== 'undefined') { } } -export namespace Entry { - export interface Options { - id: string - name: string - config?: any - disabled?: boolean - intercept?: Dict - isolate?: Dict - when?: any - } -} - -function swapAssign(target: T, source?: T): T { - const result = { ...target } - for (const key in result) { - delete target[key] - } - Object.assign(target, source) - return result -} - -export class Entry { - public fork: ForkScope | null = null - public isUpdate = false - - constructor(public loader: Loader, public parent: Context, public options: Entry.Options) {} - - amend(ctx: Context) { - swapAssign(ctx[Context.intercept], this.options.intercept) - const neoMap: Dict = Object.create(Object.getPrototypeOf(ctx[Context.isolate])) - for (const [key, label] of Object.entries(this.options.isolate ?? {})) { - if (typeof label === 'string') { - neoMap[key] = (this.loader.realms[label] ??= Object.create(null))[key] ??= Symbol(key) - } else if (label) { - neoMap[key] = Symbol(key) - } - } - for (const key in { ...ctx[Context.isolate], ...neoMap }) { - if (neoMap[key] === ctx[Context.isolate][key]) continue - const self = Object.create(null) - self[Context.filter] = (ctx2: Context) => { - return ctx[Context.isolate][key] === ctx2[Context.isolate][key] - } - ctx.emit(self, 'internal/before-service', key) - } - const oldMap = swapAssign(ctx[Context.isolate], neoMap) - for (const key in { ...oldMap, ...ctx[Context.isolate] }) { - if (oldMap[key] === ctx[Context.isolate][key]) continue - const self = Object.create(null) - self[Context.filter] = (ctx2: Context) => { - return ctx[Context.isolate][key] === ctx2[Context.isolate][key] - } - ctx.emit(self, 'internal/service', key) - } - } - - // TODO: handle parent change - update(parent: Context, options: Entry.Options) { - this.options = options - if (!this.loader.isTruthyLike(options.when) || options.disabled) { - this.stop() - } else { - this.start() - } - } - - async start() { - if (this.fork) { - this.isUpdate = true - this.amend(this.fork.parent) - this.fork.update(this.options.config) - } else { - this.parent.emit('loader/entry', 'apply', this) - const plugin = await this.loader.resolve(this.options.name) - if (!plugin) return - const ctx = this.parent.extend({ - [Context.intercept]: Object.create(this.parent[Context.intercept]), - [Context.isolate]: Object.create(this.parent[Context.isolate]), - }) - this.amend(ctx) - this.fork = ctx.plugin(plugin, this.loader.interpolate(this.options.config)) - this.fork.entry = this - } - } - - stop() { - if (!this.fork) return - this.parent.emit('loader/entry', 'unload', this) - this.fork.dispose() - this.fork = null - } -} - export namespace Loader { export interface Options { name: string @@ -362,11 +270,11 @@ export function group(ctx: Context, config: Entry.Options[]) { ctx.accept((neo: Entry.Options[]) => { // update config reference const old = ctx.scope.config as Entry.Options[] - const oldMap = Object.fromEntries(old.map(entry => [entry.id, entry])) - const neoMap = Object.fromEntries(neo.map(entry => [entry.id, entry])) + const oldMap: any = Object.fromEntries(old.map(entry => [entry.id || Symbol('anonymous'), entry])) + const neoMap: any = Object.fromEntries(neo.map(entry => [entry.id || Symbol('anonymous'), entry])) // update inner plugins - for (const id in { ...oldMap, ...neoMap }) { + for (const id of Reflect.ownKeys({ ...oldMap, ...neoMap })) { if (!neoMap[id]) { ctx.loader.remove(ctx, oldMap[id]) } else {