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>
590 lines
27 KiB
C
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_
|