Files
Everjoy/doc/Architecture-Runtime-and-Plugin.md

219 lines
8.0 KiB
Markdown

# Everjoy Architecture: Runtime Subsystem + Plugin Subsystem
## Design Principle
**Separate concerns:**
- **Runtime Subsystem** = the engine. Defines interfaces, manages lifecycle, routes events, schedules rendering. Everything (built-in + external) depends on it.
- **Plugin Subsystem** = the adapter. Loads external code, translates C-ABI ↔ Rust trait. Only external modules go through it.
```
┌─────────────────────────────────────────┐
│ Runtime Subsystem │
│ (supporter for everything) │
├─────────────────────────────────────────┤
│ Module Registry │
│ - PanelProvider registry │
│ - MenuItem registry │
│ - StatusWidget registry │
│ - Tool registry │
├─────────────────────────────────────────┤
│ Lifecycle Manager │
│ - init → render → event → hide → │
│ show → shutdown │
├─────────────────────────────────────────┤
│ Event Router │
│ - Layer 1: System shortcuts │
│ - Layer 2: Focused PanelProvider │
│ - Layer 3: Global shortcuts / Menubar │
├─────────────────────────────────────────┤
│ Render Scheduler │
│ - Calls Provider::render() each frame │
│ - Blits cell grid → Buffer for │
│ external providers │
└─────────────────────────────────────────┘
↑ ↓ traits
┌─────────────────────────────────────────┐
│ Plugin Subsystem │
│ (loader + translator) │
├─────────────────────────────────────────┤
│ Extension Loader │
│ - Scan plugin directory │
│ - Load .wasm / .so / static │
│ - Symbol resolution │
├─────────────────────────────────────────┤
│ Translation Layer │
│ - C-ABI ↔ Rust trait │
│ - EjSurface memory management │
│ - Cell grid → Buffer blitting │
│ - Style resolution │
│ - Panic isolation │
└─────────────────────────────────────────┘
```
---
## Runtime Subsystem
Defines the **trait interfaces** that all modules must satisfy. Built-in modules implement these directly. External modules satisfy them via the Plugin Subsystem.
### Core Traits
```rust
/// Anything that can render into a panel
pub trait PanelProvider: Send {
fn init(&mut self, ctx: &mut dyn ProviderCtx);
fn render(&mut self, area: Rect, buf: &mut Buffer);
fn handle_event(&mut self, event: Event) -> EventResult;
fn focus_changed(&mut self, focused: bool);
fn hide(&mut self);
fn show(&mut self);
fn shutdown(&mut self);
fn can_open(&self, context: &OpenContext) -> bool;
fn keep_alive_on_hide(&self) -> bool { false }
}
/// Callable by Agents
pub trait Tool: Send {
fn name(&self) -> &str;
fn description(&self) -> &str;
fn schema(&self) -> ToolSchema; // JSON Schema for LLM function calling
fn execute(&self, input: ToolInput) -> ToolOutput;
}
/// Menubar entry
pub trait MenuItem: Send {
fn id(&self) -> &str;
fn label(&self) -> &str;
fn parent(&self) -> Option<&str>;
fn shortcut(&self) -> Option<&str>;
fn invoke(&self);
}
/// Status bar widget
pub trait StatusWidget: Send {
fn id(&self) -> &str;
fn position(&self) -> StatusPosition;
fn render(&self, area: Rect, buf: &mut Buffer);
}
```
### Module Registry
```rust
pub struct ModuleRegistry {
panel_providers: HashMap<String, Box<dyn PanelProvider>>,
menu_items: Vec<Box<dyn MenuItem>>,
status_widgets: Vec<Box<dyn StatusWidget>>,
tools: HashMap<String, Box<dyn Tool>>,
}
```
Built-in modules register directly:
```rust
// In everjoy/src/main.rs or a built-in module initializer
runtime.register_panel_provider("editor", Box::new(EditorProvider::new()));
runtime.register_panel_provider("terminal", Box::new(TerminalProvider::new()));
runtime.register_panel_provider("tasktree", Box::new(TaskTreeProvider::new()));
runtime.register_tool("read_file", Box::new(ReadFileTool));
runtime.register_menu_item(Box::new(QuitMenuItem));
```
### Lifecycle (Runtime-managed)
```
Load (built-in = static init, external = via Plugin Subsystem)
bootstrap() — register all contributions
Running — render / event / command loop
Unload — shutdown() all providers, unregister all contributions
```
---
## Plugin Subsystem
Only responsible for **external modules**. Built-in modules never touch it.
### Extension Loader
```rust
pub struct ExtensionLoader {
plugin_dir: PathBuf,
}
impl ExtensionLoader {
pub fn scan_and_load(&self, runtime: &mut Runtime) -> Result<()> {
for entry in fs::read_dir(&self.plugin_dir)? {
let path = entry?.path();
let plugin = self.load(&path)?;
plugin.bootstrap(runtime.registrar())?;
}
Ok(())
}
fn load(&self, path: &Path) -> Result<Box<dyn Plugin>> {
match path.extension() {
Some("wasm") => self.load_wasm(path),
Some("so") | Some("dll") => self.load_dylib(path),
_ => Err("Unknown plugin format"),
}
}
}
```
### Translation Layer
For external plugins (especially WASM), the Plugin Subsystem provides a translation layer:
```rust
/// Wraps a C-ABI / WASM plugin into Runtime-compatible traits
pub struct TranslatedProvider {
wasm_instance: wasmtime::Instance,
// ... cached function handles
}
impl PanelProvider for TranslatedProvider {
fn render(&mut self, area: Rect, buf: &mut Buffer) {
// 1. Allocate EjSurface
let mut surface = EjSurface::new(area.width, area.height);
// 2. Call plugin's C render function
self.call_render(&mut surface);
// 3. Blit cell grid into ratatui Buffer
self.blit(&surface, area, buf);
}
// ... other methods translated similarly
}
```
### Key Rule
> **Built-in modules implement Runtime traits directly.**
> **External modules are wrapped by Plugin Subsystem to satisfy Runtime traits.**
```rust
// Built-in: direct trait implementation
runtime.register_panel_provider("editor", Box::new(EditorProvider::new()));
// External: loaded via Plugin Subsystem, wrapped to satisfy trait
let plugin = plugin_subsystem.load("image-viewer.wasm")?;
let provider = plugin.create_provider("image-viewer.main")?;
runtime.register_panel_provider("image-viewer", Box::new(provider));
// ^^^^ provider here is a TranslatedProvider
// that implements PanelProvider
```
---
## Summary
| Concern | Runtime Subsystem | Plugin Subsystem |
|---------|-------------------|------------------|
| **Defines** | Trait interfaces | Loading + translation mechanism |
| **Used by** | Everyone (built-in + external) | Only external modules |
| **Manages** | Registry, lifecycle, events, render | File I/O, WASM runtime, FFI |
| **Depends on** | ratatui, crossterm, tokio | wasmtime / libloading, Runtime traits |
| **Can be omitted?** | No — without it, no app | Yes — without it, only built-in modules work |