Skip to main content

Module picker_task

Module picker_task 

Source
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§

CandidatePair
One (candidate, routing) pair — the WIT form of the native CandidateBatch element (Vec<(RawCandidate, RoutingPayload)>). The routing token is opaque to the picker; the source emits it here and consumes it in accept.
PickerActor
The per-plugin actor: owns the Store + picker bindings for the plugin’s whole life and serves calls off the channel until every PickerClient is dropped. Construct via PluginHost::spawn_picker_source; drive by spawning run on a multi-thread runtime.
PickerClient
The Send + Sync handle a host adapter (PH7.4c.2) holds. Cloning it is cheap (an mpsc Sender clone); every clone talks to the same actor / Store, so calls are serialized by the single-consumer loop — the guarantee the !Sync Store needs. Dropping the last clone ends the actor loop (teardown).