Maybe Runtime subsystem done
This commit is contained in:
218
doc/Architecture-Runtime-and-Plugin.md
Normal file
218
doc/Architecture-Runtime-and-Plugin.md
Normal file
@@ -0,0 +1,218 @@
|
||||
# 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 |
|
||||
Reference in New Issue
Block a user