|
1 | | -# f4 Plugin Architecture |
| 1 | +# f4 Plugin Architecture (F4-RPC) |
2 | 2 |
|
3 | | -f4 uses a hybrid, in-process plugin architecture designed to be secure, fast, and cross-platform. It avoids the latency of JSON-RPC while maintaining isolation. |
| 3 | +`f4` uses an **Out-of-Process RPC** architecture based on `stdin`/`stdout` and the **MessagePack** binary protocol. |
4 | 4 |
|
5 | | -## Core Concepts |
| 5 | +## Why Out-of-Process? |
6 | 6 |
|
7 | | -The architecture relies on the "Adapter" (Wrapper) pattern. The application (`f4`) exposes a single, modern Go interface called `HostAPI`. |
| 7 | +In earlier versions, `f4` experimented with embedded WASM (`wazero`) and Lua (`gopher-lua`) interpreters to keep plugins strictly in-process while maintaining safety. This approach was abandoned due to several critical flaws: |
| 8 | +1. **Binary Bloat**: Embedding multiple interpreters bloated the `f4` core binary. |
| 9 | +2. **Platform Access Limitations**: WASM (WASI) severely restricts access to native OS capabilities (raw network sockets, complex file system operations), making it nearly impossible to write powerful plugins like process manager or Widows registry editor without building massive, fragile bridging layers. |
| 10 | +3. **Language Lock-in**: Forcing developers to write in a specific dialect of Lua constrained ecosystem growth. |
8 | 11 |
|
9 | | -Plugins are not executed via `os/exec` or RPC. They run in the same memory space but within sandboxed runtimes. |
| 12 | +## The F4-RPC Solution |
10 | 13 |
|
11 | | -We support four types of plugins: |
12 | | -1. **Internal Plugins**: Compiled directly into the `f4` binary. Used for critical, performance-sensitive features (e.g., SFTP, VFS). |
13 | | -2. **Modern WASM Plugins**: Written in Go (or Rust/Zig) and compiled to `wasm32-wasi`. They use the modern `f4` API. |
14 | | -3. **far2l Compat WASM Plugins**: Written in C/C++ using the legacy Far Manager / far2l headers. |
15 | | -4. **Lua Scripts**: Loaded via `gopher-lua`. Provides far2m/far3 compatible API (`far.Message`, `far.AdvControl`). |
| 14 | +By moving to a separate-process model communicating over standard pipes: |
| 15 | +1. **Zero Core Bloat**: The `f4` binary remains extremely lean. |
| 16 | +2. **Language Agnostic**: You can write a plugin in Go, Python, Rust, C++, Node.js, or LuaJIT. As long as the process can read/write MessagePack to stdin/stdout, it works. |
| 17 | +3. **Unrestricted Power**: Plugins run as native OS processes and can access the network, native libraries, and the full filesystem directly. |
| 18 | +4. **Binary Efficiency**: We chose `MessagePack` over `JSON-RPC` to minimize serialization overhead and reduce data transfer latency for large `ReadDir` chunks. |
16 | 19 |
|
17 | | -## Why WebAssembly (WASM)? |
| 20 | +## How it works |
18 | 21 |
|
19 | | -Traditional Far plugins use dynamic libraries (`.dll`, `.so`). This causes several issues: |
20 | | -- **Dependency Hell**: Requires separate builds for Windows, Linux (amd64, arm64), macOS, etc. |
21 | | -- **CGO Requirement**: Forces the Go host to use CGO, complicating cross-compilation. |
22 | | -- **Stability**: A segfault in a C++ plugin crashes the entire file manager. |
| 22 | +1. `f4` scans the `plugins/` directory for executable files. |
| 23 | +2. It launches each executable via `os/exec`, hooking into its `stdin`, `stdout`, and `stderr`. |
| 24 | +3. `stderr` is immediately piped to `f4`'s internal `debug.log` for easy debugging. |
| 25 | +4. `f4` sends a `Plugin.Init` request over `stdin`. |
| 26 | +5. The plugin replies with its capabilities (e.g., registered Virtual File Systems). |
| 27 | +6. When a user interacts with the plugin's VFS, `f4` transparently routes `ReadDir`, `Stat`, `Open`, etc., as RPC calls. |
23 | 28 |
|
24 | | -WASM solves this: |
25 | | -- **100% Portability**: One `.wasm` file runs everywhere. |
26 | | -- **Zero CGO**: The `wazero` engine executes WASM natively in Go. |
27 | | -- **Sandboxing**: Memory panics in the plugin are trapped as normal Go errors. The host survives. |
| 29 | +## Building a Plugin (Go SDK) |
28 | 30 |
|
29 | | -## Far2l C-API Compatibility Layer |
30 | | - |
31 | | -To support legacy C/C++ plugins without source modification, we implement a memory thunking bridge in `far2l_compat.go`. |
32 | | - |
33 | | -**How it works:** |
34 | | -1. The C plugin is compiled to WASM using `clang --target=wasm32-wasi`. |
35 | | -2. The Go host allocates memory inside the WASM guest's linear memory. |
36 | | -3. The Go host constructs the `PluginStartupInfo` struct inside this memory. |
37 | | -4. The function pointers inside `PluginStartupInfo` point to WebAssembly import indices. |
38 | | -5. These imports are intercepted by `wazero` and routed to Go methods (e.g., `far2lMessage` -> `HostAPI.Message`). |
39 | | -6. Finally, Go calls the WASM exported function `SetStartupInfoW` with the pointer to the struct. |
40 | | - |
41 | | -## Future Roadmap |
42 | | - |
43 | | -To develop this foundation into a fully-fledged system, the following steps are required: |
44 | | - |
45 | | -1. **Guest Memory Management**: Expose `malloc` and `free` from the WASM guest so the Go host can dynamically allocate structs like `PluginPanelItem` and `PluginStartupInfo` safely. |
46 | | -2. **Complete HostAPI**: Extend the `HostAPI` interface with methods for interacting with `vtui` (Dialogs, Menus, InputBoxes). |
47 | | -3. **Virtual File System (VFS)**: Add `GetFindData` and `GetOpenPluginInfo` wrappers to allow plugins to act as virtual panels (e.g., Archives, FTP). |
48 | | -4. **Lua Expansion**: Map the rest of the Lua far3 API to `gopher-lua` tables. |
| 31 | +For Go developers, an SDK is provided in `sdk/f4plugin`. It handles all the multiplexing and binary protocol details, exposing a clean, synchronous interface. Check `plugins/dummy/main.go` for a reference implementation. |
0 commit comments