brink_runtime/story/external.rs
1//! External-function-call protocol: [`ExternalResult`], [`ExternalFnHandler`],
2//! [`FallbackHandler`], and engine→ink function-evaluation outcomes
3//! ([`FunctionEval`]).
4
5use brink_format::Value;
6
7/// Result of an external function handler call.
8#[derive(Debug, Clone)]
9pub enum ExternalResult {
10 /// The handler resolved the call and returned a value.
11 /// `Value::Null` is valid for fire-and-forget calls.
12 Resolved(Value),
13 /// The handler declined — use the ink fallback body if available.
14 Fallback,
15 /// The handler cannot resolve the call yet (async resolution).
16 /// The VM freezes with the `External` frame intact. The caller must
17 /// resolve via `story.resolve_external(value)` before continuing.
18 Pending,
19}
20
21/// Trait for handling external function calls from ink.
22///
23/// Implement this to provide runtime-injected external function behavior.
24/// The orchestration layer calls [`call`](ExternalFnHandler::call) when the
25/// VM encounters a `CallExternal` opcode. The handler can resolve the call
26/// immediately, decline to handle it (triggering fallback), or in the future,
27/// indicate that resolution is pending (async/WASM).
28pub trait ExternalFnHandler {
29 /// Handle an external function call.
30 ///
31 /// `name` is the ink-declared function name. `args` are the values
32 /// popped from the value stack, in declaration order.
33 fn call(&self, name: &str, args: &[Value]) -> ExternalResult;
34}
35
36/// Default handler that always falls back to the ink function body.
37///
38/// Use this as the `handler` argument to [`FlowInstance::step_single_line`]
39/// or [`FlowInstance::choose`] when you don't want to provide a custom
40/// external-function binding registry. Every external call returns
41/// [`ExternalResult::Fallback`], delegating to the in-story fallback
42/// container declared on the `EXTERNAL` declaration.
43pub struct FallbackHandler;
44
45impl ExternalFnHandler for FallbackHandler {
46 fn call(&self, _name: &str, _args: &[Value]) -> ExternalResult {
47 ExternalResult::Fallback
48 }
49}
50
51/// Outcome of an engine→ink function evaluation
52/// ([`FlowInstance::begin_function_eval`] / [`resume_function_eval`](FlowInstance::resume_function_eval)).
53///
54/// Evaluating an ink function from engine code does not advance the
55/// player-visible story: its output is isolated and discarded, and the
56/// transcript is untouched. The only result is the function's return
57/// value — unless the function calls an external that can't be resolved
58/// synchronously.
59#[derive(Debug, Clone)]
60pub enum FunctionEval {
61 /// The function returned this value and evaluation is complete.
62 /// (Functions with no explicit `~ return` yield [`Value::Null`].)
63 Returned(Value),
64 /// The function called an external whose handler returned
65 /// [`ExternalResult::Pending`] — typically a binding that needs
66 /// engine/World access resolved out-of-band. Evaluation is paused
67 /// with its full state intact. Inspect the pending call via
68 /// [`pending_external_name`](FlowInstance::pending_external_name) /
69 /// [`pending_external_args`](FlowInstance::pending_external_args),
70 /// supply the result with
71 /// [`resolve_external`](FlowInstance::resolve_external), then call
72 /// [`resume_function_eval`](FlowInstance::resume_function_eval).
73 AwaitingExternal,
74}