Skip to main content

lattice_runtime/
lib.rs

1//! Async runtime for lattice (DESIGN.md §5.2.1, §5.6.8, §5.7).
2//!
3//! Sits between [`lattice_core`] (the synchronous data layer -- `Buffer`,
4//! `Document`, `Edit`) and `lattice_ui_tui` (rendering + input). Owns
5//! three load-bearing async primitives:
6//!
7//! 1. **Document actor** ([`actor`]): one tokio task per open document.
8//!    The task owns the writable [`lattice_core::Document`]; mutations
9//!    arrive via an unbounded mpsc mailbox. No callsite holds a lock on
10//!    document state -- exclusive access is statically guaranteed by
11//!    the actor pattern. Audit slice 6 / H3 dropped the previous
12//!    bounded-mailbox + `RuntimeError::Busy` design after observing
13//!    that App-side callers silently discarded the Busy variant
14//!    under bursts; the unbounded channel makes "edit lands or
15//!    actor is gone" the only outcome a caller can observe.
16//!
17//! 2. **Document snapshots** ([`snapshot`]): after every committed
18//!    mutation the actor builds an immutable [`DocumentSnapshot`] and
19//!    publishes it to a single `arc_swap::ArcSwap` cell. Renderers
20//!    read with one wait-free atomic load per visible document per
21//!    frame and use that snapshot for the entire frame. There are no
22//!    actor round-trips on the render hot path. (DESIGN.md §5.6.8.)
23//!
24//! 3. **`Pending<T>`** ([`pending`]): every mutating call returns a
25//!    `Pending` -- a typed handle wrapping a oneshot receiver. Callers
26//!    that want the result `await` (or `.blocking_recv()`); callers
27//!    that don't (input loop, macro replay) drop it. This is the
28//!    seam DESIGN.md §5.2.1 specifies.
29//!
30//! ## Why `lattice-grammar` stays sync
31//!
32//! `lattice_grammar::execute` is a pure function from
33//! `(registry, document, cursor, invocation)` to `Effect`. Async
34//! coordination is a runtime concern, not a grammar concern -- the
35//! actor calls `execute` *inside* its own task, then publishes the
36//! resulting snapshot. Grammar gets no tokio dependency, no async
37//! signature, and no per-evaluator scheduling complexity.
38//!
39//! ## Tokio runtime
40//!
41//! A single multi-threaded runtime is created lazily on first use
42//! ([`shared_runtime`]) and shared across the process. Tests share
43//! it but isolate at the actor-task level: each [`spawn_document`]
44//! call creates a fresh task with its own mailbox + snapshot.
45//!
46//! ## Cancellation
47//!
48//! [`RopeDocumentHandle::dispatch_with_cancel`] threads a
49//! [`lattice_grammar::CancellationToken`] into the grammar
50//! `execute` call. The caller (App) holds a clone and flips it
51//! (e.g. on user Esc) to short-circuit a long-running motion or
52//! operator. The plain [`RopeDocumentHandle::dispatch`] form uses a
53//! no-op token; use it when no cancellation seam is required.
54//!
55//! ## What's NOT here in v1
56//!
57//! - **`LatencyClass` deadline timers** (DESIGN.md §5.2.5) --
58//!   arrive when `CommandSpec` grows the field. v1 supports
59//!   user-Esc cancellation only.
60//! - **Veto / observation hook split** (DESIGN.md §5.2.1, §5.10) --
61//!   needs the event-bus primitive that doesn't exist yet.
62//! - **Multi-document / cross-document atoms** -- one actor per
63//!   document, but the App still tracks a single document.
64
65pub mod actor;
66pub mod document;
67pub mod events;
68pub mod glob;
69pub mod handle;
70pub mod messages;
71pub mod messages_subscriber;
72pub mod pending;
73pub mod runtime;
74pub mod snapshot;
75
76pub use actor::DocumentActor;
77pub use document::{
78    ActiveDocument, DispatchEnv, DisplayResolverHandle, Document, FoldResolverHandle,
79    IndentResolverHandle, MarkResolverHandle, ScopeResolverHandle, ViewportResolverHandle,
80};
81pub use events::{
82    EventAck, EventBus, EventFilter, EventPredicate, PluginEventSink, SubscriptionId,
83    SubscriptionTarget,
84};
85pub use glob::compile_glob_set;
86pub use handle::{RopeDocumentHandle, spawn_document};
87// M.2.b.1 (2026-05-31): multibuffer types moved out to the
88// dedicated `lattice-multibuffer` crate. The Document trait
89// they impl + the PublishedSnapshot they publish through stay
90// here.
91pub use lattice_grammar::CancellationToken;
92pub use messages::{MessagePushed, MessageRecord, MessagesRing};
93pub use messages_subscriber::{
94    MessagesFilterReloadError, MessagesLayer, boot_log_level, boot_stderr_enabled,
95    install_messages_subscriber, reload_messages_filter, set_boot_log_level,
96    set_boot_stderr_enabled,
97};
98pub use pending::{InvocationId, Pending, RuntimeError};
99pub use runtime::{block_on, shared_runtime, spawn_task};
100pub use snapshot::{DocumentSnapshot, PublishedSnapshot, SnapshotCache};