Skip to main content

brink_runtime/
lib.rs

1//! Runtime/VM for executing compiled ink stories.
2//!
3//! The runtime takes a [`StoryData`](brink_format::StoryData) from the compiler,
4//! links it into an immutable [`Program`], and executes it via [`Story`].
5//!
6//! ```no_run
7//! # fn example(story_data: &brink_format::StoryData) -> Result<(), brink_runtime::RuntimeError> {
8//! use std::sync::Arc;
9//! use brink_runtime::Step;
10//!
11//! let (program, line_tables) = brink_runtime::link(story_data)?;
12//! let mut story: brink_runtime::Story = brink_runtime::Story::new(Arc::new(program), line_tables);
13//! loop {
14//!     match story.continue_single()? {
15//!         Step::Line(line) => print!("{}", line.text),
16//!         Step::Done => {}
17//!         Step::Choices(choices) => {
18//!             let _ = choices;
19//!             // pick a choice...
20//!             story.choose(0)?;
21//!         }
22//!         Step::End => break,
23//!         Step::Suspended => break,
24//!     }
25//! }
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! `no_std` + `alloc`: this crate builds without the standard library when
31//! the default `std` feature is disabled (see `docs/no-std-portability.md`).
32#![cfg_attr(not(feature = "std"), no_std)]
33// `Option::ok_or(RuntimeError::…)` builds the error eagerly and, on the
34// `Some` path, drops it — and `RuntimeError`'s drop glue is an out-of-line
35// call (the enum carries `String` payloads), paid even for a unit variant.
36// The VM's per-step path does this two to three times per step: measured
37// at 6.5% of all instructions on `crucible-8` (callgrind,
38// `drop_glue::<RuntimeError>` called 2.3M times over 933K steps). Every
39// such site uses `ok_or_else` so that no error value exists unless the
40// `None` arm is taken. Clippy's heuristic that an enum constructor is free
41// to build is wrong for this type, hence the crate-wide expectation; it
42// goes stale (and CI says so) if the last `ok_or_else(|| RuntimeError::…)`
43// disappears.
44#![expect(
45    clippy::unnecessary_lazy_evaluations,
46    reason = "RuntimeError's drop glue is a real out-of-line cost on the Some path; see the note above"
47)]
48
49extern crate alloc;
50
51#[cfg(feature = "bench-counters")]
52pub mod bench_counters;
53mod collection_ops;
54mod collections;
55mod conversion_ops;
56mod debug;
57#[cfg(feature = "debug-hooks")]
58pub mod debug_control;
59#[cfg(feature = "effect-trace")]
60pub mod effect_trace;
61mod error;
62mod external_policy;
63mod iter;
64mod linker;
65mod list_ops;
66mod locale;
67mod output;
68mod program;
69mod proj_ops;
70mod rand_ops;
71mod range_ops;
72mod record_ops;
73mod replay;
74pub mod rng;
75mod save;
76mod session;
77mod speculation;
78mod state;
79mod story;
80mod string_ops;
81mod tower_ops;
82pub mod transcript;
83mod value_ops;
84mod vm;
85mod world;
86
87pub use brink_format::{LoadReport, SAVE_FORMAT_VERSION, SaveState, VisitEntry};
88pub use debug::{
89    DebugChoice, DebugFrame, DebugGlobal, DebugLocal, DebugPosition, DebugRng, DebugSnapshot,
90    DebugSourceLocation, DebugValue, DebugVisit,
91};
92/// Scripted debug sessions (#3247/#3248): the shared verb set the test
93/// harness, the CLI debugger and the studio all drive, so there is one
94/// definition of "step over" rather than three.
95#[cfg(feature = "debug-hooks")]
96pub mod debug_session;
97
98#[cfg(feature = "debug-hooks")]
99pub use debug_control::{
100    Breakpoint, BreakpointId, BreakpointSet, DEFAULT_DEBUG_BUDGET, DebugRunOutcome,
101    DebugStopReason, StepMode, WatchHit, WatchpointObserver,
102};
103pub use error::{RUNTIME_WARNING_CAP, RanOutOfContentCause, RuntimeError, RuntimeWarning};
104pub use external_policy::{EvalContext, ExternalsReport, KindTieredHandler, PolicyKind};
105pub use iter::ValueIter;
106pub use linker::link;
107pub use locale::{LocaleMode, apply_locale};
108pub use output::{Fragment, FragmentRef, Fragments, OutputPart};
109pub use program::{ListMember, Program};
110pub use replay::{
111    RECORDING_CAP, RecordedExternal, RecordingHandler, ReplayHandler, ReplayMode, ReplayRecorder,
112};
113pub use rng::{DotNetRng, FastRng, StoryRng};
114pub use save::{load_state, save_state};
115pub use session::{
116    DivergenceFound, EventKind, ExternalReplayMode, FailReason, JournalEvent, ListDelta,
117    ReplayOutcome, ReplayWarning, SESSION_JOURNAL_CAP, SESSION_JOURNAL_VERSION, SessionError,
118    SessionJournal, SnapshotFrame, SnapshotList, SnapshotStatus, StateDiff, StateSnapshot,
119    StorySession, diff,
120};
121pub use speculation::{Budget, Speculation, SpeculationStep};
122pub use state::{ContextAccess, ObservedContext, WriteObserver};
123#[cfg(feature = "debug-hooks")]
124pub use story::DrainedLine;
125pub use story::{
126    BlockId, Choice, DriveOutcome, Element, ExecMode, ExternalFnHandler, ExternalResult,
127    FallbackHandler, FlowInstance, FunctionEval, OutputLine, Stats, Step, StepOutcome, Story,
128    StorySnapshot, StoryStatus,
129};
130pub use world::{
131    CommitError, ContextView, FlowLocal, FrameStartView, Mode, PolicyError, ResolvedPolicy, Scope,
132    World, WorldPolicy, commit,
133};