Expand description
PH7.4c.1b — the per-plugin actor task + call protocol (the bridge).
Design fragment: docs/dev/architecture/plugin-host.md §3 (the
Store-per-plugin, task-per-Store model) and §4.3 (host owns the async
plumbing). Slice plan: slice-plans/plugin-host.md PH7.4c.1b.
§The problem this solves
A picker source’s exports (spec/init/accept, picker_host.rs) are
async, single-threaded, and fuel-bounded: they run against a
wasmtime::Store<PluginState>, which is !Sync and must not be touched
concurrently. But the host wraps a source as Arc<dyn PickerSourceGenerator>
(PH7.4c.2) — a Send + Sync trait object the picker registry calls from
anywhere. Something has to bridge a Send + Sync caller to a !Sync,
single-threaded, async guest.
§The shape (locked with Dhruva, design option A)
Each plugin runs as one dedicated async task (PickerActor) that owns
its Store for its whole life. The task loops over an mpsc channel; each
[PickerCall] carries the call’s inputs plus a oneshot reply sender. The
Send + Sync PickerClient holds the mpsc Sender: init/accept/spec
send a request and await the oneshot reply. The Store is never locked and
never leaves the task, so it stays single-threaded by construction; per-call
fuel/epoch is armed inside the loop, before each guest call.
This is chosen over Arc<async_mutex<Store>> (a lock held across .await,
and a runtime dependency baked into the lib) because it keeps the Store
genuinely single-threaded and reuses cleanly for every future guest-backed
Arc<dyn> adapter (completion, grammar, modes). See the fragment’s
heuristic-#1 note.
§Runtime ownership
The lib owns no async runtime. PluginHost::spawn_picker_source returns
the (PickerClient, PickerActor) pair; the caller drives the actor by
spawning PickerActor::run on its own multi-thread runtime (never the
current_thread editor actor — paramount #4 + the no-UI-thread-work rule).
The channels are futures::channel, runtime-agnostic on purpose.
Re-exports§
pub use crate::lattice::plugin_host::types::ActiveBufferSnapshot;pub use crate::lattice::plugin_host::types::PickerAcceptOutcome;pub use crate::lattice::plugin_host::types::PickerContext;pub use crate::lattice::plugin_host::types::PickerSourceSpec;pub use crate::lattice::plugin_host::types::Position;pub use crate::lattice::plugin_host::types::RoutingPayload;
Structs§
- Candidate
Pair - One
(candidate, routing)pair — the WIT form of the nativeCandidateBatchelement (Vec<(RawCandidate, RoutingPayload)>). Theroutingtoken is opaque to the picker; the source emits it here and consumes it inaccept. - Picker
Actor - The per-plugin actor: owns the
Store+ picker bindings for the plugin’s whole life and serves calls off the channel until everyPickerClientis dropped. Construct viaPluginHost::spawn_picker_source; drive by spawningrunon a multi-thread runtime. - Picker
Client - The
Send + Synchandle a host adapter (PH7.4c.2) holds. Cloning it is cheap (an mpscSenderclone); every clone talks to the same actor /Store, so calls are serialized by the single-consumer loop — the guarantee the!SyncStoreneeds. Dropping the last clone ends the actor loop (teardown).