Skip to main content

Crate cordis_loader

Crate cordis_loader 

Source
Expand description

Config-file driven plugin loader for cordis-rs.

This crate is the assembly half of porting upstream Cordis’ loader: it connects cordis-include’s entry trees to cordis fibers. Everything a config-driven cordis application needs is re-exported here — depend on cordis-loader alone.

§How it works

  • Static registry (PluginRegistry) replaces upstream’s dynamic import(name): register plugins at startup, entries resolve by name. The group builtin is pre-registered. With the dynamic feature, names can also resolve to plugins compiled as dynamic libraries (see the [dynamic] module).
  • Startup: Loader::open reads the entry file (writing initial when missing), builds the EntryTree, and starts every enabled entry — group entries as cordis_group::Group fibers, children beneath their parent group’s context, so disposing a group cascades.
  • Config: entries carry a cordis_include::Node config (never ()); loader plugins read it via config.downcast::<Node>(). ${{ env.X }} templates expand at hand-off time; the file keeps the raw text.
  • Reload (Loader::reload, wired to the watch feature): re-read the file, diff the tree, and reconcile fibers — created entries start, removed subtrees stop, moved entries restart under their new parent, entries whose plugin name / inject declaration / enabled flag changed stop and restart with their new options, and config-only changes patch in place via Fiber::update_value. A corrupt or unreadable main file fails the operation instead of silently booting an empty tree (import files keep a tolerant record-and-skip path).
  • Document sources (LoaderConfig::with_document, Loader::update): compose from an in-memory document instead of the entry file. Boot then never reads or writes the file — concurrent boots on one shared draft cannot race, and the directory may stay read-only — while reloads recompose from the stored document (import files are still read). update reconciles a fresh composition with the same diff → stop → patch → start machinery but no write-back, making it the HMR primitive for layer-based composition: a watcher recomposes and hands the result over.
  • Inject: an entry’s inject list is merged into the plugin’s own declaration, so the core fiber machinery reconciles entries when services come and go — “hot-swapped service restarts its dependents” for free.
  • Self-kill vs. removal: a fiber that reaches Disposed outside loader operation was killed by its own plugin; the loader persists disabled: true for that entry shortly after, deferred off the dying fiber’s transition lock. Removing an entry from the file just stops it.
  • Write-back: Loader::update_config is the runtime entry point — it updates the fiber and persists the config. Reloads apply their patches without writing them back; only newly generated ids are persisted. For a document-backed loader the entry file is a pure write-back draft: rows materialized into it never re-enter the composition. reload, update, update_config, and dispose serialize through one operation lock (reentrant from event listeners), so a watch-thread reload cannot interleave with a plugin-thread update_config.

§Example

use cordis_loader::{Loader, LoaderConfig, PluginRegistry};

let root = cordis::Context::new();
let mut registry = PluginRegistry::new();
// registry.register_plugin(my_plugin);  // your plugins, by name
let config = LoaderConfig::new("cordis.yml").with_registry(registry);
let loader = Loader::open(&root, config)?;

Register the plugins first (the group builtin is pre-registered), then open; plugins registered later via Loader::register_plugin are picked up by the next Loader::reload.

§Imports

An entry with name: import and config: { url: "…" } mounts another config file as its subtree. Reloads compose every involved file into one tree (so diffs and id reuse work across files), while write-back decomposes: mounted children are persisted to the file they came from, never to the importing file. Import cycles are reported through Loader::last_error instead of recursing. With the watch feature, import files are watched like the main file.

§Events and write coalescing

Lifecycle transitions are observable through the events module’s event names on the root context’s bus; listener failures are recorded, never propagated. Write-backs can be debounced via LoaderConfig::with_write_debounce or Loader::set_write_debounce: rapid successive writes coalesce into one physical write after the quiet window.

§Dynamic library plugins

With the dynamic feature, plugins can be compiled as cdylib libraries and resolved from a directory instead of being registered statically:

let registry = PluginRegistry::new().with_dynamic_dirs(["./plugins"]);

A plugin library exports its implementation through [dynamic::export_plugin!] and must be built by the exact same toolchain, target, panic strategy, and cordis-rs version as the loading process — the loader verifies a build fingerprint before accepting the library. Libraries are never unloaded within a process; reloading a changed library is the worker-restart HMR flow implemented by cordis-cli.

§Not in scope yet

Isolate/service migration is future work.

Re-exports§

pub use error::LoaderError;
pub use error::Result;
pub use loader::Loader;
pub use loader::LoaderConfig;
pub use loader::LoaderHandle;
pub use registry::PluginRegistry;

Modules§

error
Error type combining core and include failures.
events
Names of the events the loader emits on the root context’s event bus.
loader
The loader: entry tree ⇄ fiber lifecycle, file reloads, write-back.
registry
Static plugin registry — the Rust replacement for upstream’s dynamic import(name).

Structs§

Document
The parsed content of one config file: the entry list plus any unknown top-level keys, which are preserved on write-back.
Entry
A live entry in an crate::EntryTree.
EntryOptions
One entry in a config file: a plugin instance plus its group position.
EntryTree
An in-memory tree of Entrys mirroring one config file.
Group
Nesting marker plugin for group entries.
LoaderFile
A handle to one config file on disk.

Enums§

Node
A dynamically typed value that round-trips through YAML and JSON while preserving object key order.