6. 组合与 HMR(热模块替换)

到目前为止构建的每项能力都是插件;组合文档则选择应用的插件树。本章把组合移到 YAML、 热重载一个插件,并诊断始终无法加载的插件。

组合项不只有名称

Cordis.Loader 的组合文档(cordis.yml 的对应物)接受 id / name / config / disabled:

plugins:
  - id: greeter             # 稳定标识:patch 按 id 区分"修改现有行"与"先删除再添加"
    name: greet
    config:
      targets: [alpha, beta]
  - id: consumer
    name: consumer
    disabled: true          # 保留行但不挂载;改回后插件连同依赖方一起重新加载

加载并挂载:

var catalog = new PluginCatalog();
catalog.Register<GreetConfig>("greet", config => Plugin.From(c =>
{
    foreach (var target in config.Targets)
        Console.WriteLine($"{config.Greeting}, {target}!");
}));
// ... 其余插件注册

var loader = new CompositionLoader(catalog);
await using var mounted = await loader.LoadFileAsync(root, "cordis.yml");

LoadFileAsync 先 RenderFileAsync 渲染出不可变的 RenderedComposition(include 深度优先展开、 环境变量替换、patch 层按序应用),再由 MountedComposition.CreateAsync 事务式挂载: 任一行失败,整份候选回滚。返回的 MountedComposition 可以用 ReplaceAsync(candidate) 原子地 换成新候选,也可以整体 DisposeAsync()。

cordis.yml plugins/patches RenderFileAsync include 深度优先展开 环境变量替换 patch 层按序应用 RenderedComposition 不可变候选 Mounted Handles 事务式 失败回滚 ReplaceAsync 换新候选 · DisposeAsync 整体卸载
渲染与挂载分离:可以先 `Render` 出候选检查(dry-run),再决定挂载;挂载以 stable id 对齐差异,未变的行不动。

热模块替换

卸载释放 effect(第 2 章),加载遵循依赖关系 (第 3 章)。Cordis.Hmr 的 HotReloadWatcher (src/Cordis.Hmr/HotReloadWatcher.cs)把两者组织成 last-good swap:

  1. 旧插件仍运行时先编译 candidate;编译失败就保留旧版本。
  2. 编译成功后 dispose 旧 PluginHandle,但暂留旧 CompiledScript 作为 rollback material。
  3. apply candidate;成功才请求卸载旧 ALC。
  4. candidate apply 失败时清理 candidate,并从仍加载的旧 assembly 重建 last-good 插件。

因此它能回滚失败版本,但不承诺零空窗的原子替换。HotReloadWatcher 暴露 ReloadAsync(file) / UnmountAsync(file) / Error 事件,宿主(见 Hot Reload Watcher)负责接线。

第 2 章的纪律是它能工作的唯一保障:任何活引用(事件处理器、静态缓存、timer 回调)都会阻止 ALC 卸载。

诊断始终无法加载的插件

依赖驱动加载的另一面:inject 指定了无人提供的服务时,插件一直等待,不输出任何内容。 这不是错误——PENDING 是合法状态,提供方可能稍后才挂载。

代码内可以直接查看这些状态:

// mounted.Handles 是 MountedComposition 的全部 PluginHandle
foreach (var handle in mounted.Handles)
{
    if (!handle.IsActive && !handle.Completion.IsCompleted)
        Console.WriteLine($"{handle.Plugin.GetType().Name} is PENDING — a required service is missing");
}

向 catalog 注册对应的提供方(或取消该行的 disabled)后,插件就会加载。插件既不执行任何操作 也不报告任何内容时,先检查它的 handle 状态。

下一章:进入 harness:把相同模式用于真实的 harness 服务。

在 GitHub 上编辑此页