Skip to main content

FlowInstance

Struct FlowInstance 

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

A single independent execution context within a story. The default flow runs from the root container; named flows can be spawned at arbitrary entry points via FlowInstance::new_at.

A FlowInstance is opaque from outside the crate: its internal fields (flow, status, stats) are crate-private, but consumers can hold, clone, serialize, and pass &mut FlowInstance to the runtime’s step functions. Use the inherent methods (step_single_line, choose, transcript, status, etc.) for all interaction.

Implementations§

Source§

impl FlowInstance

Source

pub const LINE_LIMIT: usize = 10_000

Maximum lines produced by a single drive_to_terminal call before erroring. Safety net against infinite loops from malformed bytecode.

Source

pub fn new_at_root(program: &Program) -> (Self, World)

Create a new flow instance starting at the program’s root container, along with a fresh World initialized from the program’s global defaults.

Source

pub fn new_at(program: &Program, container_idx: u32) -> (Self, World)

Create a new flow instance starting at an arbitrary container index, along with a fresh World. Use this to spawn a named flow at a specific entry point. The caller is responsible for deciding whether to share the returned World with other flows or discard it and reuse an existing one.

Source

pub fn set_visibility_enforcement(&mut self, enforce: bool)

Enable or disable host visibility enforcement on this flow instance (M-2b, docs/modules-spec.md §4 boundary rule 3). Enforcement is on by default: choose_path_string/ choose_path_string_with_args into a #@private knot/stitch, and begin_function_eval/ begin_function_value_eval of a #@private function, return RuntimeError::PrivateAccess.

This mirrors Story::set_visibility_enforcement for consumers that drive a FlowInstance directly — bevy-brink’s per-entity orchestration and crate::Speculation — rather than through a Story. A Story keeps every FlowInstance it owns synced to its own flag when this is called on the Story, so callers that only ever go through Story never need to call this directly.

Source

pub fn visibility_enforced(&self) -> bool

Whether host visibility enforcement is currently on for this flow instance (default true).

Source

pub fn set_exec_mode(&mut self, mode: ExecMode)

Set the dev/prod execution mode on this flow instance (NS-A4, ExecMode — see its docs for the §4b doctrine). Mirrors Story::set_exec_mode for consumers that drive a FlowInstance directly (bevy-brink, crate::Speculation). Takes effect immediately — the mode is consulted at each ordering-verb execution.

Source

pub fn exec_mode(&self) -> ExecMode

The current dev/prod execution mode (NS-A4, ExecMode).

Source

pub fn step_single_line<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, ) -> Result<Step, RuntimeError>

Execute until one complete line of output is available, or until a yield point (choices/done/ended) if no newline occurs first.

Returns a Step telling the caller what happened (Line/Done/ Choices/End). This is the simple API for consumers whose external handler never defers: if the handler returns ExternalResult::Pending, this errors with UnresolvedExternalCall. For pausable world-access bindings, use advance.

Source

pub fn drive_to_terminal<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, ) -> Result<Vec<Step>, RuntimeError>

Step this flow forward until the next terminal step (Done, Choices, or End), collecting every Step produced along the way.

This is the single Layer-2 “drive to terminal” loop: [Story]’s continue_maximally* family is a thin wrapper over it, and any other holder of a FlowInstance (e.g. an engine integration like bevy-brink) should reach for this instead of hand-rolling the same loop. Semantics:

§Errors

Any error step_single_line itself can produce, plus RuntimeError::LineLimitExceeded if the drive produces Self::LINE_LIMIT lines without reaching a terminal one.

Source

pub fn drive<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, budget: &mut usize, ) -> Result<DriveOutcome, RuntimeError>

The pausable Layer-2 “drive to terminal or external pause” op: drive_to_terminal’s sibling for callers (e.g. bevy-brink) whose external bindings need to pause mid-drive for out-of-band (world-access) resolution rather than erroring.

Steps via advance instead of step_single_line: a deferred external yields DriveOutcome::AwaitingExternal (carrying every line produced so far this call) instead of RuntimeError::UnresolvedExternalCall. Resolve it and call drive again to continue — the drive is logically one operation spanning however many pauses it takes.

budget is the caller-owned line budget for that whole logical operation: each line drive produces (whether the call ends in Terminal or AwaitingExternal) decrements it by one, and it is not reset between calls — the caller passes the same &mut usize back in on resume, so a drive spanning many external pauses still has exactly one bound on total output, not a fresh Self::LINE_LIMIT per resume (see the “guard against unbounded growth” rule). Start a fresh logical drive with a fresh budget = FlowInstance::LINE_LIMIT (or any caller-chosen cap).

Like drive_to_terminal, the terminal step is always the last element of the returned Vec and every step before it is Step::Line.

§Errors

Any error advance itself can produce, plus RuntimeError::LineLimitExceeded if budget reaches zero before a terminal step is produced.

Source

pub fn advance<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, ) -> Result<StepOutcome, RuntimeError>

Like step_single_line, but surfaces a deferred external (ExternalResult::Pending) as StepOutcome::AwaitingExternal instead of an error — so a world-access binding hit during normal playback can pause cleanly. Resolve the pending external and call advance again to continue.

Source

pub fn choose( &mut self, context: &mut (impl ContextAccess + ?Sized), index: usize, ) -> Result<(), RuntimeError>

Select a choice by index. Call step_single_line afterward to continue execution from the chosen branch.

Source

pub fn choose_path_string( &mut self, program: &Program, context: &mut (impl ContextAccess + ?Sized), path: &str, ) -> Result<(), RuntimeError>

Move the play head to a named knot/stitch path — the equivalent of ink’s Story.ChoosePathString(path) (with its default resetCallstack: true). Call step_single_line (or any continue method) afterward to run from there.

path is a dot-separated runtime path: a knot (intro), a qualified stitch (intro.dock), or — for programs compiled by brink-compiler — an author label (knot.label, knot.stitch.label; an extension over C#, which cannot address labels).

Mirroring the C# reference (Story.ChoosePathStringResetCallstack/ForceEndChoosePathstate.SetChosenPath + VisitChangedContainersDueToDivert):

  • The current flow is force-completed first: the call stack collapses to a single fresh root frame (abandoning any tunnels, threads, or in-progress weave), pending choices are cleared, and the jump counts as a safe exit (as if the story had hit -> DONE).
  • The jump counts as a visit to the target, with exactly the semantics of an in-story -> path divert (it goes through the same goto machinery, so counting flags are honored identically).
  • Output already produced but not yet consumed is kept (C# leaves the output stream untouched); it is delivered before content from the new location. The value stack is likewise left as-is.
  • A permanently ended story (-> END) may be re-entered by jumping, matching C# where ChoosePathString + Continue works after the story has ended.
§Errors
  • UnknownPath if path resolves to no target (the message names the path).
  • JumpWhileAwaitingExternal if the flow is parked on an unresolved external call — a pending host call must be resolved, not silently abandoned.
  • AlreadyEvaluatingFunction if an engine→ink function evaluation is in progress (C# likewise refuses to redirect mid-function).
Source

pub fn choose_path_string_with_args( &mut self, program: &Program, context: &mut (impl ContextAccess + ?Sized), path: &str, args: &[Value], ) -> Result<(), RuntimeError>

Like choose_path_string but binds the target knot’s declared parameters from args — host-directed entry into a parameterized knot/stitch (=== call(action, present) ===), which a plain path jump can’t reach with its params bound.

Semantics are otherwise identical to choose_path_string (force-ends the current flow, counts as a visit, etc.). The args are pushed onto the value stack in declaration order and bound by the target’s prologue — exactly as an in-story -> call(a, b) divert binds them, so this enters at the container start (where the prologue runs).

§Errors

In addition to choose_path_string’s errors: ArgCountMismatch if args.len() differs from the target container’s declared parameter count. (Programs built by the converter record no param counts, so they report 0 — pass no args.)

Source

pub fn status(&self) -> StoryStatus

The current execution status of this flow.

Source

pub fn did_safe_exit(&self) -> bool

Whether the most recent execution cycle ended with a safe exit — an explicit -> DONE opcode — as opposed to falling off the end of its content with nothing left to run.

Both cases deliver a terminal Step::Done; this is the only way to tell them apart without issuing an extra advance/ step_single_line call and observing whether it returns RuntimeError::RanOutOfContent. Read it right after receiving a Step::Done — it is cleared at the start of the next execution cycle, so a value read before a terminal step is not meaningful.

true: the story chose to stop (a knot/stitch reached -> DONE); resuming later is well-formed. false: the flow ran out of content; the trailing text was still delivered, but resuming will fault.

Source

pub fn current_path(&self, program: &Program) -> Option<String>

The knot or knot.stitch this flow is executing in — see Story::current_path. Hosts that drive instances directly (bevy-brink) pass the program they run.

Source

pub fn stats(&self) -> &Stats

Runtime statistics (instructions, materialization counts, etc.) accumulated over this flow’s execution.

Source

pub fn take_runtime_warnings(&mut self) -> Vec<RuntimeWarning>

Take every non-fatal crate::RuntimeWarning this flow has raised since the last drain, leaving the list empty (issue #3354).

Draining rather than borrowing is deliberate: a host that prints warnings as it plays wants each one once, and a host that ignores them wants the list not to grow. Accumulation between drains is capped at crate::RUNTIME_WARNING_CAP.

Source

pub fn transcript(&self) -> &[OutputPart]

The full append-only transcript of all output parts produced so far.

The transcript stores structural references (e.g. LineRef) rather than resolved strings, so it can be re-rendered in any locale by passing a different set of line tables to transcript::render_transcript.

Source

pub fn transcript_len(&self) -> usize

Number of parts in the transcript.

Source

pub fn reset_cursor(&mut self)

Reset the transcript read cursor to the beginning (for re-rendering, e.g. after a locale swap).

Source

pub fn fragments(&self) -> &Fragments

The fragments captured during execution (for re-rendering choice display text and computed substrings in a different locale).

Source

pub fn has_pending_external(&self) -> bool

Returns true if this flow is frozen on an unresolved external call — i.e. the VM hit a CallExternal opcode and the handler returned ExternalResult::Pending, leaving the External frame on top of the call stack.

The orchestration layer (e.g. a Bevy resolver system) polls this to decide whether the flow needs an external resolved before it can be driven further. Resolve via resolve_external.

Source

pub fn pending_external_fn_id(&self) -> Option<DefinitionId>

The DefinitionId of the pending external function, if this flow is frozen on one. Returns None otherwise.

Source

pub fn pending_external_args(&self) -> &[Value]

The arguments to the pending external call, in declaration order. Empty if no external call is pending.

Source

pub fn pending_external_name<'p>(&self, program: &'p Program) -> Option<&'p str>

The ink-declared name of the pending external function, resolved against program’s name table. Returns None if no external is pending (or the entry is missing, which would indicate a malformed program).

The orchestration layer uses this to look up the binding registered for this name.

Source

pub fn resolve_external(&mut self, value: Value)

Resolve a pending external call by supplying its return value. Pops the External frame and pushes value onto the value stack so the VM can resume. For fire-and-forget externals, pass Value::Null.

No-op if no external call is pending. After resolving, drive the flow forward with step_single_line.

Source

pub fn begin_function_eval<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, container_idx: u32, args: &[Value], resolver: Option<&dyn PluralResolver>, ) -> Result<FunctionEval, RuntimeError>

Evaluate an ink function from engine code, returning its value.

This does not advance the player-visible story: a FunctionEvalFromGame boundary frame is pushed, args are passed in declaration order (exactly as a normal call site would), output is captured and discarded, and the function runs until it returns.

If the function calls an external whose handler returns ExternalResult::Pending (e.g. a binding that needs Bevy World access), evaluation pauses and returns FunctionEval::AwaitingExternal; the caller resolves the external (see resolve_external) and calls resume_function_eval.

container_idx is the function’s container, typically obtained from Program::find_address on the function name. Unlike a normal Call, this does not increment the function’s visit count — an engine query is out-of-band, matching C#’s EvaluateFunction.

§Errors
Source

pub fn begin_function_eval_with_limit<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, container_idx: u32, args: &[Value], resolver: Option<&dyn PluralResolver>, step_limit: u64, ) -> Result<FunctionEval, RuntimeError>

Like begin_function_eval, but the VM step budget for the whole evaluation is step_limit rather than the hardcoded Self::STEP_LIMIT (#1868).

This is what lets a caller give an engine→ink evaluation its own, appropriately scoped budget — e.g. a compile-time registry walk, which wants a small ceiling of its own rather than the 1,000,000-step production default — mirroring how advance_with_limit already lets crate::Speculation cap the line-stepping path. begin_function_eval itself is a thin wrapper over this with step_limit: Self::STEP_LIMIT — every existing call site keeps its exact prior behavior.

§Errors

Same as begin_function_eval, plus StepLimitExceeded is now bounded by the caller-supplied step_limit rather than the fixed default.

Source

pub fn begin_function_value_eval<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, callee: &Value, args: &[Value], resolver: Option<&dyn PluralResolver>, ) -> Result<FunctionEval, RuntimeError>

Evaluate an ink function value (FnRef/Closure) from engine code — the host callback-invocation surface (T1c-3, docs/t1c-spec.md §6). A function value crosses to the host as an opaque token {DefinitionId, env}; the host never dereferences the env — invocation always re-enters the VM here and is journaled exactly like begin_function_eval.

callee must be a Value::FnRef / Value::Closure; args supply the remaining (val-only) params after the value’s bound prefix. The same fault set as in-story dispatch applies — non-function callee, wrong arity, rehydration mismatch, cross-flow ref-#@local (docs/t1c-spec.md §3/§6) — surfaced as the Err here rather than as a turn-terminating story fault, since this is out-of-band evaluation.

Like begin_function_eval this does not advance the player-visible story (output isolated, transcript untouched, no visit-count increment) and pauses on world-access externals — resume with resume_function_eval.

§Errors
  • AlreadyEvaluatingFunction if an evaluation is already in progress on this flow.
  • The function-value dispatch faults above (via vm::prepare_fn_value_call), before any frame is pushed.
  • The same evaluation errors as begin_function_eval.
Source

pub fn begin_function_value_eval_with_limit<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, callee: &Value, args: &[Value], resolver: Option<&dyn PluralResolver>, step_limit: u64, ) -> Result<FunctionEval, RuntimeError>

Like begin_function_value_eval, but the VM step budget for the whole evaluation is step_limit rather than the hardcoded Self::STEP_LIMIT (#1868) — the function-value sibling of begin_function_eval_with_limit.

§Errors

Same as begin_function_value_eval, plus StepLimitExceeded is now bounded by the caller-supplied step_limit rather than the fixed default.

Source

pub fn resume_function_eval<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, ) -> Result<FunctionEval, RuntimeError>

Resume a function evaluation that paused on FunctionEval::AwaitingExternal, after the pending external has been resolved via resolve_external.

§Errors
Source

pub fn resume_function_eval_with_limit<R: StoryRng>( &mut self, program: &Program, line_tables: &[Vec<LineEntry>], context: &mut (impl ContextAccess + ?Sized), handler: &dyn ExternalFnHandler, resolver: Option<&dyn PluralResolver>, step_limit: u64, ) -> Result<FunctionEval, RuntimeError>

Like resume_function_eval, but the VM step budget for the remainder of the evaluation is step_limit rather than the hardcoded Self::STEP_LIMIT (#1868). A caller that began the evaluation with begin_function_eval_with_limit/ begin_function_value_eval_with_limit should resume with the same step_limit to keep one consistent budget across pauses — this call’s step count starts fresh (mirrors advance_with_limit: each call gets its own step_limit-sized allowance, not a running total).

§Errors

Same as resume_function_eval, plus StepLimitExceeded is now bounded by the caller-supplied step_limit rather than the fixed default.

Source

pub fn is_evaluating_function(&self) -> bool

Returns true if a function evaluation is in progress (possibly paused awaiting an external).

Trait Implementations§

Source§

impl Clone for FlowInstance

Source§

fn clone(&self) -> FlowInstance

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for FlowInstance

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

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> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

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> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
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.