# 10. Embedding The whole design of this interpreter is shaped by one requirement: a game must be able to embed it as a scripting engine without giving up control. That produces four rules, and they explain most of what looks unusual elsewhere in this guide. 1. **Nothing in the library terminates the process.** Errors come back as `akerr_ErrorContext *` for you to handle. 2. **The interpreter owns no window, no renderer and no event loop.** It draws through whatever you already created. 3. **It never blocks.** `SLEEP`, `GETKEY`, `PLAY` and `WAIT` hold the program without holding your frame rate. 4. **You can bound it.** A script with `10 GOTO 10` cannot take your game with it. ## Linking ```cmake add_subdirectory(deps/akbasic EXCLUDE_FROM_ALL) target_link_libraries(YOUR_GAME PRIVATE akbasic::akbasic) ``` | Target | What it is | Link it? | |---|---|---| | `akbasic` | The interpreter. No SDL, nothing that exits. | Always | | `akbasic_akgl` | The graphics, sound, input and sprite backends, drawing through *your* renderer | If you want them | | `akbasic_frontend` | The standalone program's host: creates the window, owns the loop | **No.** You are the host | ## The shortest useful host ```c wrap=hostloop #include #include static akbasic_Runtime RUNTIME; /* too big for a stack */ static akbasic_TextSink SINK; static akbasic_StdioSink SINKSTATE; akerr_ErrorContext AKERR_NOIGNORE *run_script(const char *source) { PREPARE_ERROR(e); PASS(e, akbasic_sink_init_stdio(&SINK, &SINKSTATE, stdout, NULL)); PASS(e, akbasic_runtime_init(&RUNTIME, &SINK)); PASS(e, akbasic_runtime_load(&RUNTIME, source)); PASS(e, akbasic_runtime_start(&RUNTIME, AKBASIC_MODE_RUN)); while ( RUNTIME.mode != AKBASIC_MODE_QUIT ) { PASS(e, akbasic_runtime_settime(&RUNTIME, your_clock_ms())); PASS(e, akbasic_runtime_run(&RUNTIME, 256)); /* 256 steps, then return */ your_draw_a_frame(); } SUCCEED_RETURN(e); } ``` `akbasic_runtime_run(rt, n)` runs at most `n` steps and returns. That bound is what keeps a runaway script from owning your process. `akbasic_runtime_settime()` is how the interpreter knows what time it is. It reads no clock of its own, because it owns no loop. If you never call it, every duration expires immediately — audible, but never a hang. ## Exchanging variables with a script Use `akbasic_runtime_global()`. It finds or creates the variable in the script's outermost scope, which is the only place both of you can reliably see: ```c wrap=hostbody akbasic_Variable *health = NULL; int64_t subscript[1] = { 0 }; PASS(e, akbasic_runtime_global(&RUNTIME, "HEALTH#", &health)); PASS(e, akbasic_variable_set_integer(health, 100, subscript, 1)); ``` Do not reach for `akbasic_environment_get()`. A script suspended part-way through a bounded run is usually inside a `FOR` or `GOSUB` body, and a variable created there dies when the body pops — silently, with the script reading it correctly right up until it stops. ## Where the output goes `PRINT` writes through an `akbasic_TextSink`, which is a record of function pointers plus whatever state you hang off `self`: ```c excerpt=include/akbasic/sink.h typedef struct akbasic_TextSink { void *self; akerr_ErrorContext AKERR_NOIGNORE *(*write)(struct akbasic_TextSink *self, const char *text); akerr_ErrorContext AKERR_NOIGNORE *(*writeln)(struct akbasic_TextSink *self, const char *text); akerr_ErrorContext AKERR_NOIGNORE *(*readline)(struct akbasic_TextSink *self, char *dest, size_t len, bool *eof); akerr_ErrorContext AKERR_NOIGNORE *(*clear)(struct akbasic_TextSink *self); akerr_ErrorContext AKERR_NOIGNORE *(*moveto)(struct akbasic_TextSink *self, int col, int row); akerr_ErrorContext AKERR_NOIGNORE *(*window)(struct akbasic_TextSink *self, int left, int top, int right, int bottom); } akbasic_TextSink; ``` `akbasic_sink_init_stdio()` ships with the library and is what the driver uses. A game supplies its own and draws into a text layer. `readline` is expected to set `*eof` rather than block — that is how `INPUT` behaves sanely inside a frame. `akbasic_sink_init_tee()` also ships, and composes two sinks into one: writes go to both, and `readline` comes from whichever of the two you name as the reader. That is how the SDL build puts `PRINT` in a window *and* on stdout. It needs no SDL, so you can use it to log a script's output to a file while you draw. ## Lending devices ```c wrap=hostbody PASS(e, akbasic_runtime_set_devices(&RUNTIME, &graphics, &audio, &input, &sprites)); ``` Any of them may be `NULL`, and that is how you withhold a capability: a script given no audio backend gets an error from `SOUND` rather than silence. Each is a record of function pointers, so you can supply your own and never link the graphics library at all. `akbasic_akgl` provides implementations that draw through a renderer *you* created: ```c wrap=akglbody requires=akgl PASS(e, akbasic_graphics_init_akgl(&graphics, &gstate, my_renderer)); PASS(e, akbasic_sprite_init_akgl(&sprites, &sstate, my_renderer, &gstate)); ``` Sprites become real actors in your registry, so your game can see them. ## Where a script's errors go A BASIC-level error is reported through the sink and stops the script; it does not come back to you as a failure. What comes back to you is an error in the *interpreter* — pool exhaustion, a NULL argument — which is yours to handle. That is the split to hold on to: a script's mistakes are the script's problem, and your program keeps running. ## Reading it all Two complete hosts are checked in and built by every build, so neither can rot: `examples/embed.c` runs a script a bounded number of steps at a time, and `examples/hostvars.c` passes integers, floats and strings in both directions. The full API surface is the headers under `include/akbasic/`, which `doxygen Doxyfile` renders; the pool limits are the table at the end of [Chapter 13](13-differences.md).