Files
akbasic/include/akbasic/akgl.h
Tachikoma a9059423e9 Say what was hit, which way is out, and how far
The narrowphase has been producing a contact since it went in, and the interpreter
was throwing it away. `RCOLLISION(n, f)` reports it: what was hit (a sprite or a
`SOLID` rectangle), which one, the contact normal, the penetration depth, the
contact point, and which axis to reverse.

**The normal points out of the other thing and toward this one**, so a program
moves along it by the depth and is exactly clear. That sign is the one assertion
in the new test that could not be caught any other way -- both parties of a
sprite-against-sprite hit get their own record, each pointing the way *that*
sprite has to move, and sharing one would tell both to go the same direction,
which is how two things end up stuck inside each other.

**Field 7 is the one that deletes the most BASIC.** It is the minimum translation
axis, computed from the normal in C, and it is there because doing it in BASIC
means comparing two floats -- which is exactly where this dialect's left-operand
rule catches people. `BALLBRICKS`/`TESTCELL` in the artwork breakout spend six
lines computing an overlap rectangle and comparing its width to its height to get
this number.

The record is **sticky and deepest-wins**: replaced whenever that sprite is in a
contact and otherwise left alone, so `BUMP` stays the event and this stays the
detail of it. Making it clear itself when nothing touches would break the pairing,
because `BUMP` accumulates across steps and a once-a-frame poll would find the
detail already gone. Reading `BUMP` clears both, so they cannot disagree.

Deliberately narrower than `akgl_Contact`: no actor pointers, because BASIC has no
actor; no tile fields, because there is no tilemap; no z, because every test is
planar; and **no `dt` and no `sensor`**, which libakgl documents as filled in by
the resolver. This interpreter never resolves anything, so those two come back
zero and mean nothing, and an always-zero field in a reference table is a lie.

Documented with the two caveats that matter: fields 2, 3 and 4 are floats and want
a `%` variable, and the contact *point* is exact only for two boxes -- libakgl's
solver returns a point on the portal it converged to, while the normal and depth
are exact for every pair.

Chapter 8's collision section stops claiming only type 1 exists, which has been
false since the previous commit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
2026-08-02 10:53:26 -04:00

590 lines
27 KiB
C

/**
* @file akgl.h
* @brief The libakgl-backed implementations of the sink and the three devices.
*
* Everything declared here lives in the separate `akbasic_akgl` target, which is
* the only part of this project that links SDL. The core library builds and its
* whole test suite runs on a machine with no SDL on it; that is why the records
* these initializers populate are plain function-pointer structs and why this
* header is the only one that includes a libakgl header.
*
* **The interpreter owns no window, no renderer and no event loop.** Every one
* of these takes something the host already created and draws or plays through
* it. None of them creates a device, and none of them pumps events.
*
* Each initializer calls akgl_error_init() first. It reserves libakgl's 256-261
* status band and names every AKGL_ERR_* code; akgl_game_init() calls it as its
* first statement, but a program driving subsystems directly -- which is exactly
* what an embedded interpreter does -- never goes through akgl_game_init() and
* has to call it itself. Skip it and every AKGL_ERR_* that reaches a stack trace
* prints "Unknown Error". It is idempotent, so a host that already called it
* loses nothing.
*/
#ifndef _AKBASIC_AKGL_H_
#define _AKBASIC_AKGL_H_
#include <SDL3/SDL.h>
#include <SDL3_ttf/SDL_ttf.h>
#include <akerror.h>
#include <akgl/actor.h>
#include <akgl/character.h>
#include <akgl/collision.h>
#include <akgl/renderer.h>
#include <akgl/sprite.h>
#include <akgl/version.h>
/*
* **0.5.0 was a hard floor, API as well as ABI**, and it is worth keeping the
* reason: it namespaced every exported symbol, so the spellings this target
* compiles against did not exist before it. `akgl_render_bind2d` is
* `akgl_render_2d_bind`, `akgl_sprite_sheet_coords_for_frame` is
* `akgl_spritesheet_coords_for_frame`, and the `renderer`, `camera` and `window`
* globals carry the prefix -- with `_akgl_renderer` and `_akgl_camera` now
* `akgl_default_renderer` and `akgl_default_camera`.
*
* That rename was not cosmetic and libakgl's TODO.md says why at length: an
* exported global called `renderer` collided with a test's own variable of the
* same name, the executable's definition preempted the library's, and every
* texture load in that suite failed while the suite reported success.
*
* **0.6.0 and 0.7.0 broke nothing here**, and the floor moves anyway. 0.6.0 is
* three arcade-physics fixes and a `physics.max_timestep` property this target
* does not use; 0.7.0 reports failures libakstdlib's wrappers were already
* catching, and takes libakerror 2.0.1 so it can drop the exit-status trap its
* own suites needed -- the same defect `include/akbasic/error.h` guards for this
* band, fixed at the source rather than worked around twice.
*
* **0.8.0 broke nothing here either**, and is the largest of the three: a whole
* collision subsystem -- shapes, pooled proxies, a pluggable broad phase, a
* narrowphase answering with a contact -- none of which this target called on the
* day the floor moved. It is compatible by luck rather than by design, which is
* the argument for moving the floor rather than deciding for ourselves that a
* minor release was really compatible.
*
* Two things about it a reader here has to know. `akgl_Actor` grew fields, so
* its `sizeof` changed and a translation unit compiled against a 0.7 `actor.h`
* writes `renderfunc` and `actorData` at the wrong offsets -- and
* `src/sprite_akgl.c` writes exactly those two. The symptom is a jump through a
* garbage pointer on the first frame, which is precisely the case this guard
* exists to turn into a compile error. And libakgl's reserved status band grew
* from five codes to six with `AKGL_ERR_COLLISION`, so it now owns 256 to 261;
* `MAINTENANCE.md`'s coordinated range map and `docs/15-error-codes.md` both say
* so.
*
* The soname carries MAJOR.MINOR while the major is 0, so 0.5, 0.6 and 0.7 are
* different ABIs *by declaration* -- libakgl's versioning policy says a 0.x minor
* bump may break, and the soname is built to match. The floor moves with every
* one of them, because the alternative is deciding for ourselves which of
* libakgl's minor releases were really compatible, and that is exactly the
* judgement the soname exists to take away from us.
*
* What it catches is the case the soname cannot: a build against 0.6 headers
* that happens to find a 0.7 library, or the reverse. Refused here rather than
* at link time, because a missing symbol names a function and this names the
* release.
*/
#if !AKGL_VERSION_AT_LEAST(0, 8, 0)
#error "akbasic's libakgl adaptors require libakgl 0.8.0 or later"
#endif
#include <akbasic/audio.h>
#include <akbasic/graphics.h>
#include <akbasic/input.h>
#include <akbasic/sink.h>
#include <akbasic/sprite.h>
/**
* @brief One full cursor blink in milliseconds, half on and half off.
*
* Half a second, which is about what a Commodore does and is slow enough to read
* under.
*/
#define AKBASIC_SINK_CURSOR_BLINK_MS 500
/** @brief How many saved SSHAPE regions the graphics backend will hold at once. */
#define AKBASIC_AKGL_MAX_SHAPES 16
/**
* @brief One frame of the host's own loop, borrowed by the sink's line editor.
*
* The sink has to be able to wait for a typed line without owning an event loop,
* and those two requirements only meet in one place: the host hands over a
* callback that does exactly one frame -- pump events, redraw, present -- and
* the editor calls it between keystrokes. The loop is still the host's; the
* editor just borrows it a frame at a time.
*
* Setting @p running false is how the host says the window closed. The editor
* reports end of input rather than raising, because that is what running out of
* input means everywhere else in sink.h.
*
* @param self Whatever the host passed to akbasic_sink_akgl_set_pump().
* @param running Set false to stop waiting; the editor then reports EOF.
* @return `NULL` on success, otherwise an error context owned by the caller.
*/
typedef akerr_ErrorContext AKERR_NOIGNORE *(*akbasic_AkglPump)(void *self, bool *running);
/**
* @brief State for the libakgl-backed text sink.
*
* The cursor, the wrap and the scroll live here rather than in the interpreter:
* everything in the reference's basicruntime_graphics.go except Write and
* Println, which are the sink interface itself.
*/
typedef struct
{
akgl_RenderBackend *renderer;
TTF_Font *font;
SDL_Color color;
/**
* What a text cell is erased to before its glyphs are drawn. Opaque black
* by default, which is a Commodore console and is what makes the text layer
* *authoritative* over the rows it occupies rather than transparent over
* them -- see akbasic_sink_akgl_render().
*/
SDL_Color background;
int x; /* pixel origin of the text area */
int y;
int width; /* pixel size of the text area */
int height;
/*
* The whole drawable area, kept so WINDOW can grow back out to it. Without
* it a window could only ever shrink, since `x`/`width` have by then been
* overwritten with the current window's.
*/
int fullx;
int fully;
int fullwidth;
int fullheight;
int cellw; /* one character cell, measured from the font */
int cellh;
int columns; /* the character grid the cell size works out to */
int rows;
int cursorcol;
int cursorrow;
/*
* The scrollback the sink redraws every frame. A fixed grid rather than a
* list of lines: the interpreter allocates nothing, and neither does this.
*/
char text[64][256];
/**
* Milliseconds for one full cursor blink, half on and half off. Zero holds
* the cursor solid, which is what a test wanting a deterministic frame sets.
*/
uint64_t cursorperiodms;
/*
* The line editor. Nothing here is live except while readline is running,
* and `editing` is what says so -- render() draws a cursor only then, and
* the scroll adjusts `editrow` only then.
*/
akbasic_AkglPump pump;
void *pumpself;
bool editing;
int editrow; /* where the line being typed starts */
int editcol;
int editlen; /* characters accepted so far */
int echolen; /* characters currently drawn, so a backspace */
/* knows how much to erase */
char editline[256];
} akbasic_AkglSink;
/**
* @brief State for the libakgl-backed graphics backend.
*
* The shape pool is why this exists at all. SSHAPE hands the BASIC program a
* handle rather than the pixels -- see TODO.md section 5 -- and these are the
* surfaces those handles refer to.
*/
typedef struct
{
akgl_RenderBackend *renderer;
SDL_Surface *shapes[AKBASIC_AKGL_MAX_SHAPES];
int shapecount;
/**
* The drawing layer: a texture the drawing verbs render into and the frame
* composites, so a drawing stays on screen without the program redrawing it.
*
* **Without this a drawing lasts exactly one frame.** The verbs are
* immediate and go to the renderer's back buffer, SDL double-buffers, and
* the frontend never clears -- so what a program drew is gone the moment the
* frame it was issued in is presented. The only way to keep a picture was to
* capture it with `SSHAPE` and install it as a sprite, which is what
* `examples/breakout/sprites/breakout.bas` spends two of its eight sprites
* doing and what TODO.md section 9 item 9 was filed about.
*
* NULL until the first drawing verb runs. A program that never draws pays
* for no texture, which matters: this is the size of the window.
*/
SDL_Texture *layer;
/**
* What the target was before the layer was made current, so it can be put
* back. The frontend brackets the whole step phase rather than each verb --
* one pair of SDL_SetRenderTarget calls a frame instead of one per DRAW.
*/
SDL_Texture *savedtarget;
bool layeractive;
} akbasic_AkglGraphics;
/**
* @brief State for the libakgl-backed sprite backend.
*
* One libakgl actor per BASIC sprite, plus the sheet, sprite and character each
* actor needs -- built by hand rather than loaded from a sprite document, since
* a Commodore sprite has no animation, no frame list and no state map to
* describe. Everything is pooled by libakgl; what is held here are the pointers
* and the one texture per slot that this backend owns and must destroy.
*
* `graphics` is the graphics backend's state, borrowed so `SPRSAV A$, n` can
* resolve a handle SSHAPE minted. The two devices are separate records on
* purpose -- a host may supply one and withhold the other -- so this is a
* pointer that may be NULL rather than an assumption that both exist.
*/
typedef struct
{
akgl_RenderBackend *renderer;
akbasic_AkglGraphics *graphics;
akgl_SpriteSheet *sheets[AKBASIC_MAX_SPRITES];
akgl_Sprite *sprites[AKBASIC_MAX_SPRITES];
akgl_Character *characters[AKBASIC_MAX_SPRITES];
akgl_Actor *actors[AKBASIC_MAX_SPRITES];
/** Per-axis expansion, which an actor's single `scale` cannot carry. */
bool xexpand[AKBASIC_MAX_SPRITES];
bool yexpand[AKBASIC_MAX_SPRITES];
/**
* One collision proxy per slot, claimed once at init and held for the life
* of the backend.
*
* Claimed up front rather than per scan for two reasons. The pool is shared
* with whatever host this interpreter is embedded in -- a game with its own
* shaped actors draws from the same #AKGL_MAX_HEAP_COLLISION_PROXY -- so
* running out is a real possibility, and it should be an init-time failure
* naming the pool rather than a collision scan that starts refusing halfway
* through a game. And a proxy carries a *copy* of its shape, so there is
* nothing to rebuild per scan beyond the position.
*
* These are not registered with any partitioner. akgl_collision_test()
* takes two positioned proxies and needs no world, which is what lets eight
* sprites be tested pairwise without paying for a broad phase that eight
* objects would not repay.
*/
akgl_CollisionProxy *proxies[AKBASIC_MAX_SPRITES];
/**
* The shape each proxy is synced from.
*
* Kept here rather than read back off the proxy because
* akgl_collision_proxy_sync() wants a live shape to copy from, and because
* the shape is derived from the sprite's frame and expansion bits every scan
* -- a sprite whose picture or expansion changed has a different box, and
* the cheapest correct thing is to rebuild it rather than track what
* invalidates it.
*/
akgl_CollisionShape shapes[AKBASIC_MAX_SPRITES];
/**
* The rectangle each proxy was last synced from, so a scan can tell whether
* there is anything to sync. See sync_proxy() in src/sprite_akgl.c for why
* this is compared rather than flagged.
*/
SDL_FRect syncedbox[AKBASIC_MAX_SPRITES];
/**
* SPRHIT's shape per slot, as the device was told it.
*
* A second copy of what akbasic_Sprite already carries, and deliberately: a
* device is told about changes and never asked what they were
* (include/akbasic/sprite.h), so the backend has to be able to answer from
* its own state rather than reaching back into the runtime it does not have
* a pointer to.
*/
int shapekind[AKBASIC_MAX_SPRITES];
float32_t shapex1[AKBASIC_MAX_SPRITES];
float32_t shapey1[AKBASIC_MAX_SPRITES];
float32_t shapex2[AKBASIC_MAX_SPRITES];
float32_t shapey2[AKBASIC_MAX_SPRITES];
bool shapeexplicit[AKBASIC_MAX_SPRITES];
/**
* SOLID's static geometry: a proxy and a shape per registered rectangle.
*
* **Not registered with any partitioner, and there is no
* `akgl_CollisionWorld` here at all.** libakgl's uniform grid keeps its cell
* heads, its cell size and its origin in file-scope statics
* (`deps/libakgl/src/collision_grid.c`), so it is one index per process --
* and `akgl_collision_world_init()` ends in a `reset()` that memsets those
* heads *and* calls `akgl_heap_init_collision_cells()`. An interpreter
* embedded in a game that has its own collision world would destroy every
* registration that game had made, on the first `SOLID` a script ran.
*
* So the geometry is indexed here, by an ordinary array, and pairs go
* straight to `akgl_collision_test()` -- which takes two positioned proxies
* and needs no world. At sixty-four rectangles that is the right answer
* anyway: libakgl's own measurements put a naive all-pairs sweep at 0.7% of
* a frame at sixty-four objects, and a broad phase is bookkeeping that only
* repays itself well above that.
*/
akgl_CollisionProxy *solidproxies[AKBASIC_MAX_SOLIDS];
akgl_CollisionShape solidshapes[AKBASIC_MAX_SOLIDS];
SDL_FRect solidbox[AKBASIC_MAX_SOLIDS];
bool solidactive[AKBASIC_MAX_SOLIDS];
/**
* What the last scan saw, so an unchanged scan can answer without redoing
* the work.
*
* The scan runs at the top of every interpreter step -- up to 256 times a
* rendered frame -- and its inputs are the sprites' boxes, which sprite
* slots are collidable, and the static geometry. A sprite moves at most once
* in that time. If none of those changed, the answer cannot have changed
* either, and eight rectangle comparisons say so far more cheaply than
* recomputing it.
*
* This is what makes static geometry affordable at all: eight sprites
* against sixty-four rectangles is five hundred and twelve tests, which is
* fine once a frame and is not fine two hundred and fifty-six times.
*/
SDL_FRect lastbox[AKBASIC_MAX_SPRITES];
bool lastlive[AKBASIC_MAX_SPRITES];
uint16_t lastmask;
uint16_t lastsolidmask;
bool lastvalid;
/**
* The deepest contact each sprite was in when the scan last ran.
*
* Kept as a side effect of the pass that computes the masks, because that is
* the only place the information exists -- `akgl_collision_test()` produces
* it and a caller that discards it cannot get it back. Deepest wins, because
* the deepest overlap is the one a program has to undo.
*/
akbasic_Contact contacts[AKBASIC_MAX_SPRITES];
bool hascontact[AKBASIC_MAX_SPRITES];
} akbasic_AkglSprites;
/**
* @brief Point a text sink at a renderer and a font the host already has.
*
* The character grid is derived by measuring one glyph with akgl_text_measure(),
* which is the direct equivalent of the reference's font.SizeUTF8("A") -- and
* which did not exist in libakgl until 42b60f7. A font that is not monospaced
* still works; the grid is then sized by whatever "A" happens to measure, and
* proportional glyphs simply do not line up in columns.
*
* @param obj Object to initialize, inspect, or modify.
* @param state Storage for the sink's own state; must outlive the sink.
* @param renderer The renderer the host already initialized; not created here.
* @param font An open font; not opened or closed here.
* @param w Width in pixels of the text area.
* @param h Height in pixels of the text area.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When any pointer argument is NULL.
* @throws AKBASIC_ERR_VALUE When the font measures a zero-width cell, which would divide by zero.
* @throws AKBASIC_ERR_BOUNDS When the area is too small for even one character.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_init_akgl(akbasic_TextSink *obj, akbasic_AkglSink *state, akgl_RenderBackend *renderer, TTF_Font *font, int w, int h);
/**
* @brief Draw whatever the sink currently holds.
*
* Separate from the sink's write path because the interpreter does not own the
* frame: a host calls this when it is drawing, not when the script happens to
* PRINT. A driver that only wants stdout never calls it at all.
*
* @param obj The sink to draw.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
* @throws AKGL_ERR_SDL When the renderer refuses the text.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_render(akbasic_TextSink *obj);
/**
* @brief Give the sink a line editor by lending it one frame of the host's loop.
*
* Without this, readline reports end of input immediately: a drawn text layer is
* not a source of lines, and a sink that blocked on the keyboard with no way to
* pump events would deadlock the process on its first INPUT. With it, readline
* collects keystrokes from libakgl's ring -- the one the host's own event pump
* fills -- echoes them into the grid, and returns the line on Return.
*
* **It is a real keyboard as of libakgl 0.3.0.** The ring carries the composed
* UTF-8 text SDL worked out from the keystroke, so shifted characters, a
* keyboard layout, a compose key and a dead key all do what the person typing
* expects -- including the double quote, without which a BASIC string literal
* cannot be typed at all. Until 0.3.0 the ring reported a keycode and nothing
* else, so none of that was reachable and letters were folded to upper case;
* that was libakgl API-gap item 10, filed from here.
*
* Composed text is taken over the keycode wherever there is any, which is what
* makes the layout work: on an AZERTY keyboard the key SDL calls `SDLK_Q`
* composes to "a", and "a" is what the program should see.
*
* Editing is Backspace to erase one character and Escape to abandon the line.
* There is no cursor movement within the line: the ring reports the arrow keys,
* but a script's own GET loop wants them.
*
* @param obj The sink to give an editor to.
* @param pump One frame of the host's loop, or NULL to take the editor away.
* @param self Passed back to @p pump untouched.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL or carries no sink state.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sink_akgl_set_pump(akbasic_TextSink *obj, akbasic_AkglPump pump, void *self);
/**
* @brief Point a graphics backend at a renderer the host already has.
* @param obj Object to initialize, inspect, or modify.
* @param state Storage for the shape pool; must outlive the backend.
* @param renderer The renderer the host already initialized; not created here.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When any argument is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_init_akgl(akbasic_GraphicsBackend *obj, akbasic_AkglGraphics *state, akgl_RenderBackend *renderer);
/**
* @brief Make the drawing layer current, so what the program draws is kept.
*
* Called by a host **before** running the interpreter's steps, and paired with
* akbasic_graphics_akgl_end(). Between the two, every drawing verb renders into
* a texture that survives the frame instead of into a back buffer that does not.
*
* Bracketing the whole step phase rather than each verb is deliberate: it is one
* pair of SDL_SetRenderTarget calls a frame instead of one per `DRAW`, and it is
* also what makes `SSHAPE` read back what the program has just drawn rather than
* whatever the previous frame left.
*
* A host that never calls these gets the old behaviour -- drawings live one
* frame -- which is what `tools/screenshot.c` wants, since it presents nothing.
*
* @param obj The graphics backend, or NULL to do nothing.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKGL_ERR_SDL When the layer texture cannot be created or made current.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_begin(akbasic_GraphicsBackend *obj);
/**
* @brief Put the previous render target back. Pairs with akbasic_graphics_akgl_begin().
*
* @param obj The graphics backend, or NULL to do nothing.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKGL_ERR_SDL When the previous target cannot be restored.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_end(akbasic_GraphicsBackend *obj);
/**
* @brief Draw the layer onto the frame, under the text and the sprites.
*
* Called once a frame by a host, before the sink renders. Does nothing when no
* program has drawn anything, so a text-only program composites nothing.
*
* @param obj The graphics backend, or NULL to do nothing.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKGL_ERR_SDL When the layer cannot be drawn.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_graphics_akgl_render(akbasic_GraphicsBackend *obj);
/**
* @brief Is the drawing layer currently the render target?
*
* A host needs this because SDL refuses to present while a target is current,
* and the sink's line editor calls a host's pump from *inside* a step -- when
* the layer is on -- while the frame loop calls it between steps, when it is
* not. Answering the question is what lets one pump serve both.
*
* @param obj The graphics backend, or NULL for false.
*/
bool akbasic_graphics_akgl_layer_active(akbasic_GraphicsBackend *obj);
/** @brief Destroy the layer texture. Safe on a backend that never made one. */
void akbasic_graphics_akgl_shutdown(akbasic_GraphicsBackend *obj);
/**
* @brief Point a sprite backend at a renderer the host already has.
*
* Requires akgl_heap_init() and akgl_registry_init() to have run -- every
* akgl_*_initialize ends in a registry write -- and requires the global `camera`
* to point somewhere, because akgl_actor_render() dereferences it without
* checking. akgl_game_init() does all three; a host that skips it, which is
* every host embedding this interpreter, does them itself.
* src/frontend_akgl.c is the worked example.
*
* @param obj Object to initialize, inspect, or modify.
* @param state Storage for the sprite pool; must outlive the backend.
* @param renderer The renderer the host already initialized; not created here.
* @param graphics The graphics backend's state, so SPRSAV can resolve an SSHAPE handle; may be NULL.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj`, `state` or `renderer` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sprite_init_akgl(akbasic_SpriteBackend *obj, akbasic_AkglSprites *state, akgl_RenderBackend *renderer, akbasic_AkglGraphics *graphics);
/**
* @brief Draw every defined, enabled sprite.
*
* Separate from the verbs for the same reason akbasic_sink_akgl_render() is: the
* interpreter does not own the frame. A host calls this when it is drawing.
*
* The actors are swept directly rather than through akgl_registry_iterate_actor(),
* which ends in FINISH_NORETURN -- an unhandled error inside it terminates the
* process, and goal 3 forbids anything in this library doing that. Sweeping our
* own eight also leaves a host's own actors alone.
*
* @param obj The backend to draw.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL or carries no state.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_sprite_akgl_render(akbasic_SpriteBackend *obj);
/**
* @brief Release every texture the sprite backend created.
*
* The actors, sprites, sheets and characters are libakgl's pooled objects and go
* back with akgl_heap_init(); the SDL textures behind the sheets are this
* backend's own and are not anybody else's to free.
*
* @param obj The backend to tear down. NULL is not an error.
*/
void akbasic_sprite_akgl_shutdown(akbasic_SpriteBackend *obj);
/**
* @brief Wire an audio backend to libakgl's tone generator.
*
* Calls akgl_audio_init(), which opens an SDL audio device. A host that owns its
* own audio pipeline can call akgl_audio_init() itself beforehand -- it is
* idempotent in the sense that mattered upstream: the voice table works whether
* or not a device is open.
*
* @param obj Object to initialize, inspect, or modify.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
* @throws AKGL_ERR_SDL When no audio device can be opened.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_audio_init_akgl(akbasic_AudioBackend *obj);
/**
* @brief Wire an input backend to libakgl's keystroke ring.
*
* Reads only. The ring is filled by akgl_controller_handle_event(), which the
* *host* calls as it pumps SDL events -- so a script gets keystrokes without the
* interpreter owning the event loop.
*
* Worth knowing before lending this to a script: that ring is process-global and
* the host's own control maps read the same events. A script sitting in a GET
* loop drains keystrokes the game will then never see. A host that cares either
* withholds the input backend or supplies a filtered one of its own.
*
* @param obj Object to initialize, inspect, or modify.
* @return `NULL` on success, otherwise an error context owned by the caller.
* @throws AKERR_NULLPOINTER When `obj` is NULL.
*/
akerr_ErrorContext AKERR_NOIGNORE *akbasic_input_init_akgl(akbasic_InputBackend *obj);
#endif // _AKBASIC_AKGL_H_