Wire the sink and the three devices to libakgl

src/sink_akgl.c, src/graphics_akgl.c, src/audio_akgl.c and src/input_akgl.c, in
the akbasic_akgl target, which is the only thing here that links SDL.
-DAKBASIC_WITH_AKGL=ON had never been configured in this repository before, and
it now builds and passes.

The sink is what section 3 has been waiting on. Its character grid comes from
akgl_text_measure(font, "A", &w, &h), the direct equivalent of the reference's
font.SizeUTF8("A") and the call that did not exist until 42b60f7. Wrapping is
done on the character grid rather than by handing SDL_ttf a wraplength, because
the cursor has to land somewhere definite: a program that PRINTs a long string
and then PRINTs again expects the second to start on the row after the first
ended, and only the code that placed the characters knows which row that is.

tests/akgl_backends.c draws into a 128x128 software renderer under the dummy
video driver and reads the pixels back -- the pattern deps/libakgl/tests/draw.c
established, which needs no display and no offscreen harness. It asserts the
seam rather than libakgl's own behaviour: a BASIC line in, a lit pixel of the
right colour out.

Four things in libakgl had to be worked around to get here. All four are
commented at their site with "filed upstream" and recorded in TODO.md section 3:

- An embedded libakgl requires SDL, SDL_image, SDL_mixer, SDL_ttf and jansson to
  be *installed*. It builds its own vendored copies only when it is top-level,
  and they are sitting right there in deps/libakgl/deps. Every lookup is guarded
  with if(NOT TARGET ...), so this adds those five subdirectories before
  add_subdirectory(deps/libakgl) -- the same trick and the same ordering
  requirement akerror::akerror and akstdlib::akstdlib already need.
- akgl/controller.h does not compile on its own: it declares handlers taking an
  akgl_Actor * and includes nothing that declares the type.
- There is no way to attach a 2D backend to a renderer you already have.
  akgl_render_init2d() installs the vtable but also creates its own window and
  writes the camera global, so it belongs to the akgl_game_init() path -- which
  is exactly the path an embedding host is not on. The test assigns the six
  pointers by hand.
- akgl_text_rendertextat() segfaults on a backend whose vtable is empty; it
  reaches through renderer->draw_texture without checking it. Same class of
  defect 42b60f7's own commit added a draw test for.

The sink's readline reports end of input rather than reading: a drawn text layer
is not a source of lines, and INPUT through one wants a line editor built on the
keystroke ring. EOF rather than an error is the contract sink.h states, so INPUT
already handles it. That editor is the next piece of work there.

70/70 core ctest with no SDL on the include path, 71/71 with the akgl suite,
clean under -Wall -Wextra, doxygen clean.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 08:46:53 -04:00
parent 83185bafa8
commit 4fc763efb3
9 changed files with 1402 additions and 43 deletions

69
src/input_akgl.c Normal file
View File

@@ -0,0 +1,69 @@
/**
* @file input_akgl.c
* @brief Wires the input backend record to libakgl's keystroke ring.
*
* The thinnest of the four adaptors, and the one whose *shape* carries the
* design: akgl_controller_poll_key() reads a ring that
* akgl_controller_handle_event() fills as the **host** pumps SDL events. The
* interpreter therefore answers "is there a key waiting" without owning an event
* loop, which is the whole of goal 3 for input.
*
* The caveat a host needs, repeated here because this is the file that causes
* it: 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. Withhold the backend, or supply a filtered one.
*/
#include <akerror.h>
/*
* akgl/actor.h before akgl/controller.h, and it is not optional: controller.h
* declares two handler function pointers taking an akgl_Actor * and includes
* nothing that declares the type, so on its own it does not compile. Not a
* workaround anybody should copy -- filed upstream against libakgl, whose own
* style rules call for self-contained headers. Delete this note and the include
* when controller.h includes what it uses.
*/
#include <akgl/actor.h>
#include <akgl/controller.h>
#include <akgl/error.h>
#include <akbasic/akgl.h>
#include <akbasic/error.h>
static akerr_ErrorContext *in_poll_key(akbasic_InputBackend *self, int *keycode, bool *available)
{
PREPARE_ERROR(errctx);
(void)self;
/*
* An empty ring comes back as success with available false, both here and
* upstream. Passed through unchanged: GET reports it as the empty string,
* which is ordinary BASIC rather than a failure.
*/
PASS(errctx, akgl_controller_poll_key(keycode, available));
SUCCEED_RETURN(errctx);
}
static akerr_ErrorContext *in_flush_keys(akbasic_InputBackend *self)
{
PREPARE_ERROR(errctx);
(void)self;
PASS(errctx, akgl_controller_flush_keys());
SUCCEED_RETURN(errctx);
}
akerr_ErrorContext *akbasic_input_init_akgl(akbasic_InputBackend *obj)
{
PREPARE_ERROR(errctx);
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
"NULL backend in input_init_akgl");
PASS(errctx, akgl_error_init());
obj->self = NULL; /* the ring is libakgl's, not ours */
obj->poll_key = in_poll_key;
obj->flush_keys = in_flush_keys;
SUCCEED_RETURN(errctx);
}