Reach hardware through backend records, and take the time from the host
Groups G, I and E are unblocked but cannot be written yet: the core library is free of SDL and builds with no libakgl present, so a graphics verb cannot call akgl_draw_* and a sound verb cannot call akgl_audio_*. This adds what they call instead. Three records of function pointers -- akbasic_GraphicsBackend, _AudioBackend and _InputBackend -- in the same shape as akbasic_TextSink, and the same shape libakgl uses for akgl_RenderBackend. akbasic_runtime_set_devices() attaches any subset; all three may be NULL and that is the standalone driver's normal state, so a runtime with no backends still comes up and still prints. A verb that needs one it was not given raises the new AKBASIC_ERR_DEVICE rather than dereferencing a NULL vtable. Two decisions worth stating. The graphics record has no circle entry point: BASIC 7.0's CIRCLE takes two radii, an arc range, a rotation and a degree increment, which makes it a polygon by definition, so it will be built from line calls rather than from akgl_draw_circle. And coordinates are double rather than an integer pixel address, because SCALE makes them fractional and rounding at each verb rather than once at the backend accumulates drift along a polyline. akbasic_runtime_settime() is how SOUND, PLAY and TEMPO get a clock without the library reading one. Section 1.6 forbids blocking or owning a loop, so the caller that owns the frame owns the time -- which is what libakgl already does, since akgl_actor_logic_changeframe takes curtimems as an argument. Left unset it is zero and every duration expires immediately: audible, but never a hang. AKBASIC_ERR_LAST is a sentinel rather than a status, so tests/error_codes.c walks every code looking for an unnamed one without anybody remembering to widen the loop when a code is added. tests/mockdevice.h records every backend call as a formatted line. The graphics and audio verbs emit nothing a golden file can compare, so that log is where their assertions have to live -- and since it needs no SDL, the whole of groups G, I and E stays testable in the default build. 62/62 ctest, clean under -Wall -Wextra, doxygen clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -152,6 +152,41 @@ akerr_ErrorContext *akbasic_runtime_init(akbasic_Runtime *obj, akbasic_TextSink
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_runtime_set_devices(akbasic_Runtime *obj, akbasic_GraphicsBackend *graphics, akbasic_AudioBackend *audio, akbasic_InputBackend *input)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||||
"NULL runtime in set_devices");
|
||||
|
||||
/*
|
||||
* No validation of the records themselves. A backend with a NULL entry point
|
||||
* is caught at the call site by the verb that needs it, which can say which
|
||||
* verb wanted what -- checking every pointer here would only be able to say
|
||||
* that something, somewhere, was incomplete.
|
||||
*/
|
||||
obj->graphics = graphics;
|
||||
obj->audio = audio;
|
||||
obj->input = input;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *akbasic_runtime_settime(akbasic_Runtime *obj, int64_t timems)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
FAIL_ZERO_RETURN(errctx, (obj != NULL), AKERR_NULLPOINTER,
|
||||
"NULL runtime in settime");
|
||||
|
||||
/*
|
||||
* Deliberately not rejecting a time that moves backwards. A host is free to
|
||||
* drive this from a paused, scrubbed or replayed clock, and the only thing
|
||||
* that happens is a note holding longer than it asked to.
|
||||
*/
|
||||
obj->timems = timems;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------- output -- */
|
||||
|
||||
akerr_ErrorContext *akbasic_runtime_write(akbasic_Runtime *obj, const char *text)
|
||||
|
||||
Reference in New Issue
Block a user