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:
115
TODO.md
115
TODO.md
@@ -369,44 +369,72 @@ such ceiling because it called `make()`.
|
||||
---
|
||||
|
||||
|
||||
## 3. Remaining work: the akgl text sink
|
||||
## 3. The akgl-backed sink and devices — **done**
|
||||
|
||||
**Unblocked as of `libakgl` 42b60f7** — the text-measurement call this waited on now exists.
|
||||
`-DAKBASIC_WITH_AKGL=ON` builds and its suite passes. Four adaptors in the `akbasic_akgl`
|
||||
target, which is the only thing here that links SDL:
|
||||
|
||||
**`src/sink_akgl.c`.** Implement the §1.5 vtable against `akgl_text_loadfont`,
|
||||
`akgl_text_rendertextat` and `akgl_text_measure`. Owns the cursor, the wrap, and the scroll —
|
||||
everything in `basicruntime_graphics.go` except `Write`/`Println`, which are now the sink
|
||||
interface itself.
|
||||
| File | Backs |
|
||||
|---|---|
|
||||
| `src/sink_akgl.c` | the §1.5 text sink, over `akgl_text_measure` and `akgl_text_rendertextat` |
|
||||
| `src/graphics_akgl.c` | `akbasic_GraphicsBackend`, over `akgl_draw_*`, and the SSHAPE surface pool |
|
||||
| `src/audio_akgl.c` | `akbasic_AudioBackend`, over `akgl_audio_*` |
|
||||
| `src/input_akgl.c` | `akbasic_InputBackend`, over `akgl_controller_poll_key` |
|
||||
|
||||
The character grid comes from `akgl_text_measure(font, "A", &w, &h)`, which is the direct
|
||||
equivalent of the `font.SizeUTF8("A")` at `basicruntime.go:96` that the reference derives
|
||||
`maxCharsW`/`maxCharsH` from. For wrapping, `akgl_text_measure_wrapped` takes the same
|
||||
`wraplength` argument `akgl_text_rendertextat` already does, so the measurement and the draw
|
||||
cannot disagree about where a line breaks.
|
||||
The character grid comes from `akgl_text_measure(font, "A", &w, &h)` — the direct equivalent of
|
||||
the `font.SizeUTF8("A")` at `basicruntime.go:96`, and the call this was blocked on 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 `PRINT`s a long string and
|
||||
then `PRINT`s 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.
|
||||
|
||||
**The interpreter does not own the window, the renderer, or the game loop.** The sink draws
|
||||
through whatever renderer the host already initialized. `akbasic_sink_init_akgl()` takes the
|
||||
renderer; it does not create one.
|
||||
**The interpreter owns no window, renderer or event loop.** Every initializer takes something
|
||||
the host already made. Each calls `akgl_error_init()` first: `akgl_game_init()` would have,
|
||||
but we drive subsystems directly and never call it, and a code raised before that registration
|
||||
carries no name into its stack trace. It is idempotent.
|
||||
|
||||
**Call `akgl_error_init()` before anything else in `libakgl`.** New in `libakgl` 0.1.0
|
||||
(`deps/libakgl/src/error.c`): it reserves the 256–260 status band and registers a name for
|
||||
each `AKGL_ERR_*` code. `akgl_game_init()` calls it first, but we drive subsystems directly and
|
||||
never call `akgl_game_init()`, so it is ours to call. Skip it and every `AKGL_ERR_*` that
|
||||
reaches a stack trace prints "Unknown Error" — which is exactly the defect the upstream commit
|
||||
found in `game.c`, where an SDL failure five lines ahead of the old registration site was
|
||||
guaranteed to be unnamed. It is idempotent, so calling it from both `akbasic_sink_init_akgl()`
|
||||
and a host that already did is harmless.
|
||||
`PASS` its result; a range collision is an initialization failure, not a warning.
|
||||
*Acceptance:* `tests/akgl_backends.c`, a 128x128 software renderer under the dummy video
|
||||
driver read back with `SDL_RenderReadPixels` — the pattern `deps/libakgl/tests/draw.c`
|
||||
established, which needs no display and no offscreen harness. Registered as the `akgl_backends`
|
||||
CTest case, and only when `AKBASIC_WITH_AKGL` is on.
|
||||
|
||||
**Still to check before starting:** `-DAKBASIC_WITH_AKGL=ON` has never been configured in this
|
||||
repository, because until now there was nothing to build against. It needs `libakgl`'s own
|
||||
submodules present (`git submodule update --init --recursive` pulls SDL and friends), and the
|
||||
`akbasic_akgl` target in `CMakeLists.txt` has therefore never been compiled. Expect to fix
|
||||
something there on the first attempt.
|
||||
### Four things that had to be worked around to get there
|
||||
|
||||
*Acceptance:* `tests/sink_akgl.c` against the offscreen renderer harness described in
|
||||
`deps/libakgl/TODO.md` under "Remaining work". If that harness still does not exist, a
|
||||
known-failing test plus a `libakgl` TODO entry saying so.
|
||||
All four are `libakgl`'s and all four are filed in `deps/libakgl/TODO.md`. Each workaround is
|
||||
commented at its site with the words "filed upstream" so it can be found and deleted later.
|
||||
|
||||
1. **An embedded `libakgl` demands its dependencies be *installed*.** It builds its vendored
|
||||
SDL, SDL_image, SDL_mixer, SDL_ttf and jansson only when it is top-level; embedded, it goes
|
||||
down a `find_package` path and fails on a machine that has none of them — while the
|
||||
submodules sit right there in `deps/libakgl/deps`. Every lookup is guarded with
|
||||
`if(NOT TARGET ...)`, so our `CMakeLists.txt` adds those five subdirectories *before*
|
||||
`add_subdirectory(deps/libakgl)` and the vendored copies get used after all. Same trick and
|
||||
same ordering requirement `akerror::akerror` and `akstdlib::akstdlib` already need.
|
||||
|
||||
2. **`akgl/controller.h` does not compile on its own.** It declares two handler function
|
||||
pointers taking an `akgl_Actor *` and includes nothing that declares the type.
|
||||
`src/input_akgl.c` includes `akgl/actor.h` ahead of it.
|
||||
|
||||
3. **There is no way to attach a 2D backend to a renderer you already have.**
|
||||
`akgl_render_init2d()` installs the six vtable pointers, but it also creates its own window
|
||||
from the game properties and writes to the `camera` global, so it belongs to the
|
||||
`akgl_game_init()` path — which is exactly the path an embedding host is not on.
|
||||
`tests/akgl_backends.c` assigns the six pointers by hand. What is wanted upstream is the
|
||||
vtable half of `init2d` on its own.
|
||||
|
||||
4. **`akgl_text_rendertextat()` segfaults on a backend whose vtable is empty.** It reaches
|
||||
through `renderer->draw_texture` without checking it, so the first `PRINT` through the sink
|
||||
dereferences NULL. That is the same class of defect 42b60f7's own commit added a draw test
|
||||
for — "a backend that exists but was never given an `SDL_Renderer`" — and the text path
|
||||
still has it.
|
||||
|
||||
### Still missing from the sink
|
||||
|
||||
`readline` reports end of input rather than reading anything: a drawn text layer is not a
|
||||
source of lines, and `INPUT` through a graphics sink wants a line editor built on the keystroke
|
||||
ring. EOF rather than an error is the contract `sink.h` states, so `INPUT` handles it already.
|
||||
That editor is the next piece of work here, and it is what `WINDOW` and `KEY` in group E want
|
||||
as well.
|
||||
|
||||
---
|
||||
|
||||
@@ -781,7 +809,7 @@ Dependency baseline:
|
||||
|---|---|---|
|
||||
| `deps/libakerror` | 1.0.0 | Private ownership-enforced status registry. akbasic reserves 512–767 in `akbasic_error_register()`. Does not namespace its `coverage` target when embedded — worked around in our `CMakeLists.txt`. |
|
||||
| `deps/libakstdlib` | 0.1.0 | soname `libakstdlib.so.0.1`. `AKSL_VERSION_CHECK()` asserted in `tests/version_check.c`. Its `aksl_ato*` family is banned here — see §1.9. |
|
||||
| `deps/libakgl` | 0.1.0, **pinned at commit 42b60f7** | Migrated to the 1.0.0 registry and owns 256–260. Not yet linked: `AKBASIC_WITH_AKGL` defaults OFF and `src/sink_akgl.c` does not exist. The version number cannot carry this requirement — see below. |
|
||||
| `deps/libakgl` | 0.1.0, **pinned at commit 42b60f7** | Migrated to the 1.0.0 registry and owns 256–260. Linked and tested under `-DAKBASIC_WITH_AKGL=ON`, which still defaults OFF so the core library and its whole suite build on a machine with no SDL. The version number cannot carry this requirement — see below. |
|
||||
|
||||
**The `libakgl` requirement is pinned by commit, not by version, and that is a defect
|
||||
upstream.** 42b60f7 added 22 public symbols across four headers — `akgl_draw_*` (8),
|
||||
@@ -823,16 +851,21 @@ keeping:
|
||||
|
||||
What remains, in priority order:
|
||||
|
||||
1. **§3 — the akgl text sink.** No longer blocked: `libakgl` 42b60f7 landed
|
||||
`akgl_text_measure`, so this is now ordinary work. Note that `-DAKBASIC_WITH_AKGL=ON` has
|
||||
never been configured here, so the `akbasic_akgl` target is unproven.
|
||||
2. **§4 — the language completion work queue.** Nothing in it is blocked on `libakgl` any more:
|
||||
groups G, I and part of E were, and `libakgl` 42b60f7 closed all three. Multiple statements
|
||||
per line (the `COLON` token exists and nothing consumes it) should still come first, because
|
||||
`DO`/`LOOP` reads badly without it.
|
||||
3. **§6 items 12–17** — the defects the port uncovered. Item 12 is the one that produces a
|
||||
1. **§4 — the language completion work queue.** Groups G and I and the `GET`/`GETKEY`/`SCNCLR`
|
||||
part of E are done. Of what is left, **multiple statements per line** should come first:
|
||||
the `COLON` token exists and nothing consumes it, and `DO`/`LOOP` in group A reads badly
|
||||
without it. Then groups A, B, D, F and J, none of which need anything from `libakgl`.
|
||||
2. **A line editor for the akgl sink.** `readline` reports EOF, so `INPUT` does not work
|
||||
through a drawn text layer. It wants the keystroke ring plus a cursor, and it is also what
|
||||
`WINDOW` and `KEY` in group E need. See §3.
|
||||
3. **CI does not cover `-DAKBASIC_WITH_AKGL=ON`.** Doing so needs `submodules: recursive`,
|
||||
which pulls SDL, SDL_image, SDL_mixer, SDL_ttf and jansson — a long build on a job the other
|
||||
four do not need. Deliberately deferred, not forgotten: the akgl target is built and tested
|
||||
locally with the two commands in §3, and it will rot silently until somebody decides that
|
||||
cost is worth paying.
|
||||
4. **§6 items 12–17** — the defects the port uncovered. Item 12 is the one that produces a
|
||||
*wrong answer* rather than a refused one, and should be fixed first.
|
||||
4. **Mutation survivors in `src/value.c`.** The harness is in place (`scripts/mutation_test.py`,
|
||||
5. **Mutation survivors in `src/value.c`.** The harness is in place (`scripts/mutation_test.py`,
|
||||
the `mutation` CMake target, and a CI job on `src/convert.c`), and a partial run over
|
||||
`src/value.c` turned up test gaps worth closing. These are *not* equivalent mutants; each
|
||||
is a real bug of that shape the suite would not notice:
|
||||
|
||||
Reference in New Issue
Block a user