Skip to main content

Program

Struct Program 

Source
pub struct Program { /* private fields */ }
Expand description

The runtime types that appear in bevy-brink’s own public signatures, re-exported so consumers can name them without depending on brink-runtime: FlowInstance, Program, Choice, Step’s return, RuntimeError’s error, FallbackHandler for the “no bindings” advance path, the scoped story-state types a host needs to build a policy and a per-step routing view (see docs/scoped-flow-state-spec.md): WorldPolicy, Scope, PolicyError, and ContextView (usually built via flow_context_view instead of by hand) — and the per-entity durability types produced/consumed by BrinkGlobals::save_state/load_state and save_flow_state/load_flow_state (F6.3, see the globals module’s “Save/load” docs): SaveState and LoadReport.

World is deliberately absent here — it collides with bevy::prelude::World under a glob import, so it is re-exported under the alias BrinkWorld. A linked, ready-to-execute program.

Created from StoryData via link(). Immutable after creation — mutable per-instance state lives in Story.

Implementations§

Source§

impl Program

Source

pub fn container_path(&self, idx: u32) -> Option<&str>

The knot or knot.stitch path a container names, if it names one. Exact: an anonymous container (a choice body, gather, sequence branch) is None — the save format relies on that to write such a container’s visit entry without a path. For “where is this container” see Program::scope_path.

Source

pub fn scope_path(&self, idx: u32) -> Option<&str>

The knot or knot.stitch a container sits in: its own path when it names one, otherwise its lexical scope’s — a choice body, gather, or sequence branch reports the knot/stitch that holds it (after choose, the frame holds only the chosen branch, and the story is still in that knot). None for the root scope and anything directly under it. The runtime’s own vocabulary for “where am I” — see Story::current_path.

Source

pub fn resolve_address(&self, id: DefinitionId) -> Option<(u32, usize)>

Resolve a definition ID to (container_idx, byte_offset).

Promoted from #[cfg(feature = "testing")] to real public API by W2 (#3295): with Self::definition_id_for_path it is the name-based half of source→program addressing (“break on tavern.order”), which the wasm bridge composes as resolve_path_address. A pure lookup over the container table — nothing here touches the VM hot path, so the step_once promotion warning (docs/debugger-spec.md §1.4) does not apply.

Source

pub fn resolve_debug_position( &self, position: DebugPosition, ) -> Option<DebugSourceLocation>

The program→source resolver (D9, issue #3187; wire encoding: D6, docs/debugger-spec.md §2.2). Resolves a runtime execution position — crate::DebugPosition, as reported by crate::DebugSnapshot::position/crate::DebugFrame::position (D4, #3182) — to the source range it was compiled from, via this program’s DebugInfo section.

None when:

  • no DebugInfo section is present (a release-exported or --debug-info-less compile — §1.2 ship policy: this is the expected, non-error case for most builds, not a fault);
  • container_idx is out of range for the section’s container table (defensive — should not happen for a position this same Program produced);
  • offset is before the container’s first recorded entry (the section’s coverage guarantee, §2.2, means this should not happen for a real instruction boundary either, but a reader must not panic on an adversarial/malformed position).

The returned range’s file is None for the reserved synthetic sentinel file (index 0, §2.5) — a compiler-synthesized construct with no author source to point at — and Some(path) (project-root- relative) otherwise. This is exactly the path/span pair the studio’s source Location space needs (docs/studio-shell-spec.md §6.1) — a caller’s program resolver wraps this method and returns { kind: "source", file, span: { start: range_start, end: range_start + range_len } }.

Entries within a container are sorted ascending by bytecode_offset and cover the container’s full address range with no gaps (§2.2), so a floor lookup — the last entry whose bytecode_offset is <= offset — always names the instruction’s own statement, matching how a running VM’s offset (the next instruction to execute, always itself a decoded instruction boundary) lines up against entries recorded at instruction boundaries during codegen’s own walk.

Source§

impl Program

Source

pub fn resolve_debug_line( &self, position: DebugPosition, ) -> Option<ResolvedDebugLine<'_>>

The file:line — plus the covering entry’s exact byte range — of a bytecode position (W6/#3299). The line places the execution highlight’s band and the paused chip; the RANGE rides along so finer-than-line consumers need no new seam: expression-level entries (D1’s unflagged rows, once #3183’s Expr provenance lands), instruction stepping in the editor, and the one mid-line case that exists TODAY — a step-out lands at the call site, not a line start (docs/debugger-spec.md §4’s finish semantics).

0-based line (UIs showing 1-based convert at their edge). None when the position doesn’t resolve, resolves to the synthetic sentinel, or that file carries no line index.

Source

pub fn line_at(&self, file: &str, byte: u32) -> Option<u32>

0-based line containing byte in file (#3264) — the public form of the lookup [Self::debug_line_key] uses internally. None when the file is unknown or carries no line index.

Source

pub fn resolve_source_range( &self, file: &str, start: u32, end: u32, ) -> Option<DebugPosition>

The inverse of Self::resolve_debug_position (D9/#3187): the program address to break on for a span of source text — issue #3246, the half a breakpoint gutter needs. BreakpointSet is keyed by (container_idx, offset); an editor speaks in source. This maps the latter to the former.

§Why a byte range and not a line number

The DebugInfo section records byte ranges, and a Program holds neither source text nor a line table — so it physically cannot turn “line 7” into bytes. That conversion belongs where the source already lives (the editor, the CLI’s own file read), which also keeps the UTF-8/UTF-16 question out of the runtime entirely. The caller passes the half-open byte range [start, end) it considers “the line” (or a selection, or any span), and this answers where to break within it.

§Which candidate wins

Every entry in every container whose file is file and whose range_start lies in [start, end) is a candidate. The winner is the minimum by (range_start, container_idx, bytecode_offset):

  • range_start first — the textually earliest construct in the span, which is what “break on this line” means to a person. Note this is deliberately not “lowest container_idx”: containers are independent bytecode streams with no execution order between them, so ordering by container index would be arbitrary dressed up as a rule.
  • then container_idx, then bytecode_offset — pure tie-breaking, so a given span always yields the same address rather than whichever entry iteration happened to reach first (CLAUDE.md: determinism matters).
§None is a real answer, not a failure

Returns None when the span contains no executable code at all — a comment, a blank line, a line whose code folded away — and when the artifact carries no DebugInfo or names no such file. Callers must surface that: a gutter has to refuse to arm visibly, because a breakpoint that silently never hits is worse than no breakpoint.

Entries whose file is the reserved synthetic sentinel (§2.5) never match, since no author-facing path names it.

Source

pub fn has_debug_info(&self) -> bool

Whether this program carries a DebugInfo section at all (#3248).

Every other debug accessor returns None for two very different reasons — “this artifact was compiled without --debug-info” and “that particular position/line has nothing on it” — and a debugger front-end must tell a user which. Reporting “that line has no executable code” for a story compiled without the flag sends the author hunting a bug in their source when the fix is a compiler flag. This is the cheap discriminator that keeps that message honest; it says nothing about whether any particular lookup will succeed.

Source

pub fn resolve_source_line( &self, file: &str, line: u32, ) -> Option<DebugPosition>

The program address to break on for a line of source, with no source text required (#3261) — the DebugInfo file table carries a per-file line index, so the engine can answer file:line directly.

line is 0-based. Every UI that shows 1-based line numbers converts at its own edge; keeping the engine 0-based means the fencepost lives in exactly one place per consumer instead of being re-decided here.

This is the shape a remote debugger frontend needs — DAP’s setBreakpoints is file + line, and an adapter may hold no source at all. It is a thin wrapper over Self::resolve_source_range: the line index turns the line into its half-open byte span, and the same “textually earliest construct wins” rule picks the address.

None when the file is unknown, carries no line index (compiled before this data existed, or with no source text supplied), the line is past the end of the file, or the line holds no executable code — a comment, a blank, a line whose code folded away. Callers must surface that: a gutter has to refuse to arm visibly, because a breakpoint that silently never hits is worse than no breakpoint.

Source

pub fn line_span(&self, file: &str, line: u32) -> Option<(u32, u32)>

The half-open byte span [start, end) of a 0-based line in file, from the DebugInfo file table’s line index (#3261). None when the file is unknown, has no line index, or the line is past its end.

The last line runs to the end of the file, which the index does not record — so it is represented as u32::MAX, an end bound no real range_start can reach. That is deliberate rather than clamping to a length the section does not carry.

Source

pub fn source_matches(&self, file: &str, text: &str) -> Option<bool>

Whether text is byte-identical to the source file was compiled from, by the DebugInfo file table’s source_hash (#3261).

The problem this exists for: both debug resolvers happily answer questions about source they were never built from. Author types, the recompile is still debounced, the gutter asks about the current buffer against the previous program — and gets a confidently wrong address rather than an error. That applies to byte ranges every bit as much as to line numbers; offsets shift on every inserted character.

Per-file on purpose: one dirty file degrades debugging in that file alone, where a whole-program checksum degrades everything.

None — “cannot tell” — when the artifact carries no DebugInfo, names no such file, or recorded no hash (compiled without source text). Deliberately tri-state rather than defaulting to false: “unknown” and “stale” call for different handling, and collapsing them would make every hash-less artifact look permanently stale.

A change detector, not a proof — see brink_format::content_hash.

Source

pub fn container_count(&self) -> u32

Number of containers. Promoted from #[cfg(feature = "testing")] to real public API for the structural-transcript re-render road (RULED 2026-08-30): a transcript saved against an older compile can carry container indices this program no longer has, and the caller must be able to bounds-filter them before scope_table_idx would panic.

Source

pub fn source_checksum(&self) -> u32

CRC-32 checksum from the source .inkb, used for transcript validation.

Source

pub fn name_checked(&self, id: NameId) -> Option<&str>

Look up a name by id, returning None if the id is out of range. Used by function-value rehydration (T1c, #700): a closure loaded from a save produced against a different compile can carry a NameId that no longer indexes this program’s table — treated as a mismatch (fault), never a panic.

Public (T1d, docs/t1d-spec.md §6): the same “index by id, None if out of range” contract a host needs to resolve a brink_format::Value::Handle’s kind to its manifest-declared name — e.g. for dev-tooling display or a host-side capability check. bevy-brink re-exports Program (decision 2026-07-10), so this is reachable from engine code without a direct brink-runtime dependency.

Source

pub fn name_id(&self, name: &str) -> Option<NameId>

Reverse of name_checked: look up the NameId a string interns to in this program’s name table, if any.

Public (T1d-3, docs/t1d-spec.md §4): a host minting a brink_format::Value::Handle from a binding (e.g. spawn_timer() returning a fresh Handle<Timer>) needs the compiled program’s NameId for the manifest-declared kind name ("Timer") to build the token — the wire form carries only the interned id, never the string. None means this compile never interned that name (e.g. no Handle<Timer>-typed signature or annotation anywhere in the source graph), so no token of that kind can be minted against this program. Linear scan, same cost class as global_index.

Source

pub fn find_address(&self, path: &str) -> Option<(u32, usize)>

Resolve a qualified ink path to its (container_idx, byte_offset).

Supports knot names (intro), qualified stitches (knot.stitch), and, for programs compiled by brink-compiler, author labels (knot.label, knot.stitch.label). Programs without the compiler’s address_paths table (legacy .inkb or converter output) resolve knot/stitch scope paths only. Use this to spawn flows at named entry points:

use brink_runtime::FlowInstance;

if let Some((idx, _)) = program.find_address("intro_scene") {
    let (flow, ctx) = FlowInstance::new_at(program, idx);
}
Source

pub fn definition_id_for_path(&self, path: &str) -> Option<DefinitionId>

Public wrapper on find_path_target: resolve a qualified ink path (same grammar as find_address) to the DefinitionId of its target. Used by hosts that need the id itself — e.g. bevy-brink’s wake-condition purity check (issue #995), which looks the id up in the story’s EffectRows table to inspect a FlowSleep condition’s effect row before admitting it into the wake contract.

Source

pub fn global_defaults(&self) -> Vec<Value>

Build the initial globals vector from slot defaults.

Source

pub fn global_index(&self, name: &str) -> Option<u32>

Find the global variable slot index for a variable name, if declared. Used by host-facing variable get/set (Story::variable/set_variable).

Source

pub fn global_slot(&self, id: DefinitionId) -> Option<u32>

Resolve a global cell’s DefinitionId to its slot index — the numbering ContextAccess::set_global and Self::global_index use.

Public because effect rows (brink_format::DirectEffects::reads / writes) name global cells by DefinitionId while the runtime’s world writes are keyed by slot: a host consuming rows for scheduling (bevy-brink’s row-directed wake dirtying, issue #1146) needs exactly this bridge. None for an id this program declares no global for (a stale row, a VAR removed by a story patch).

Source

pub fn global_name(&self, idx: u32) -> Option<&str>

Resolve a global slot index to its variable name.

Source

pub fn has_local_defaults(&self) -> bool

Whether the compiler marked anything flow-private. When false (all existing unannotated ink), policy resolution keeps its all-World fast path.

Public so the bevy host (bevy-brink’s batch driver) can guard against batching a #@local-annotated story: batch mode routes only the shared World, never a flow’s private FlowLocal, so a story carrying compiled flow-private defaults must stay on the serial API (docs/effects-spec.md §12; bevy-brink #925).

Source

pub fn global_count(&self) -> u32

Number of global variable slots.

Source

pub fn global_var_name(&self, id: DefinitionId) -> Option<&str>

Variable name for a global’s defining DefinitionId (e.g. a VariablePointer target, or a T1e projection’s root cell). pub (not pub(crate)) since brink-web’s program-model/speculation disassembly needs it to render a projection’s root name at the wasm boundary, the same way divert_target_path already resolves a divert’s DefinitionId for that consumer.

Source

pub fn list_members(&self, list: &ListValue) -> Vec<ListMember>

Resolve the active members of a list value for host-facing display: each member’s origin list name, unqualified item name, and ordinal. Sorted the same way in-story list stringification orders them (ordinal, then origin name) so the two presentations agree.

Source

pub fn divert_target_path(&self, id: DefinitionId) -> Option<String>

The qualified knot/stitch path a DefinitionId names, if it resolves to a named scope entry (offset-0 in address_by_path) — the destination of a Value::DivertTarget for host-facing display. Deterministic on collision: shortest path, then lexicographically smallest, independent of the map’s iteration order (mirrors debug::NameResolver’s reverse lookup).

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> ConditionalSend for T
where T: Send,

Source§

impl<T> Downcast for T
where T: Any,

Source§

fn into_any(self: Box<T>) -> Box<dyn Any>

Converts Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>, which can then be downcast into Box<dyn ConcreteType> where ConcreteType implements Trait.
Source§

fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>

Converts Rc<Trait> (where Trait: Downcast) to Rc<Any>, which can then be further downcast into Rc<ConcreteType> where ConcreteType implements Trait.
Source§

fn as_any(&self) -> &(dyn Any + 'static)

Converts &Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &Any’s vtable from &Trait’s.
Source§

fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)

Converts &mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot generate &mut Any’s vtable from &mut Trait’s.
Source§

impl<T> DowncastSend for T
where T: Any + Send,

Source§

fn into_any_send(self: Box<T>) -> Box<dyn Any + Send>

Converts Box<Trait> (where Trait: DowncastSend) to Box<dyn Any + Send>, which can then be downcast into Box<ConcreteType> where ConcreteType implements Trait.
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, W> HasTypeWitness<W> for T
where W: MakeTypeWitness<Arg = T>, T: ?Sized,

Source§

const WITNESS: W = W::MAKE

A constant of the type witness
Source§

impl<T> Identity for T
where T: ?Sized,

Source§

const TYPE_EQ: TypeEq<T, <T as Identity>::Type> = TypeEq::NEW

Proof that Self is the same type as Self::Type, provides methods for casting between Self and Self::Type.
Source§

type Type = T

The same type as Self, used to emulate type equality bounds (T == U) with associated type equality constraints (T: Identity<Type = U>).
Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoResult<T> for T

Source§

fn into_result(self) -> Result<T, RunSystemError>

Converts this type into the system output type.
Source§

impl<A> Is for A
where A: Any,

Source§

fn is<T>() -> bool
where T: Any,

Checks if the current type “is” another type, using a TypeId equality comparison. This is most useful in the context of generic logic. Read more
Source§

impl<T> Lookup<T> for T

Source§

fn into_owned(self) -> T

Source§

impl<T> Settings for T
where T: 'static + Send + Sync,

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more