Let a caller take keystrokes without owning the event loop
akbasic's GET and GETKEY ask "is there a keystroke waiting, yes or no" and must not require the interpreter to pump SDL events itself. akgl_controller_poll_key drains one key per call from a fixed 32-entry ring that akgl_controller_handle_event fills, so the host keeps pumping and the embedded interpreter reads at its own pace. akgl_controller_flush_keys discards a backlog. The recording happens before the control-map scan, not after: that scan returns as soon as a binding claims the event, so recording afterwards would have dropped exactly the keys a game also acts on. A full buffer refuses the newest keystroke rather than overwriting the oldest, so a caller reading a line gets what was typed first. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -524,6 +524,164 @@ akerr_ErrorContext *test_controller_device_enumeration(void)
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_controller_poll_key(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
SDL_Event event;
|
||||
int keycode = -1;
|
||||
bool available = true;
|
||||
|
||||
ATTEMPT {
|
||||
reset_control_maps();
|
||||
CATCH(e, make_player());
|
||||
CATCH(e, akgl_controller_flush_keys());
|
||||
|
||||
// An empty buffer answers "no key waiting" and succeeds.
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available),
|
||||
"polling an empty key buffer");
|
||||
TEST_ASSERT(e, available == false,
|
||||
"polling an empty buffer reported a key was available");
|
||||
TEST_ASSERT(e, keycode == 0,
|
||||
"polling an empty buffer left keycode at %d, expected 0", keycode);
|
||||
|
||||
// A key press pumped through the handler is drained by the poller,
|
||||
// exactly once.
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_A);
|
||||
TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event),
|
||||
"dispatching a key press with no control maps installed");
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "polling for that key");
|
||||
TEST_ASSERT(e, available == true, "the pressed key was not available to the poller");
|
||||
TEST_ASSERT(e, keycode == SDLK_A,
|
||||
"the poller returned keycode %d, expected %d", keycode, (int)SDLK_A);
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "polling again");
|
||||
TEST_ASSERT(e, available == false, "the same keystroke was delivered twice");
|
||||
|
||||
// Key releases are not keystrokes.
|
||||
make_key_event(&event, SDL_EVENT_KEY_UP, TEST_KBID, SDLK_A);
|
||||
TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event),
|
||||
"dispatching a key release");
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "polling after a release");
|
||||
TEST_ASSERT(e, available == false, "a key release was buffered as a keystroke");
|
||||
|
||||
// Keys come back in the order they were pressed.
|
||||
CATCH(e, akgl_controller_flush_keys());
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_1);
|
||||
CATCH(e, akgl_controller_handle_event(&appstate_placeholder, &event));
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_2);
|
||||
CATCH(e, akgl_controller_handle_event(&appstate_placeholder, &event));
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_3);
|
||||
CATCH(e, akgl_controller_handle_event(&appstate_placeholder, &event));
|
||||
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "draining the first key");
|
||||
TEST_ASSERT(e, keycode == SDLK_1, "the first key out was %d, expected %d",
|
||||
keycode, (int)SDLK_1);
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "draining the second key");
|
||||
TEST_ASSERT(e, keycode == SDLK_2, "the second key out was %d, expected %d",
|
||||
keycode, (int)SDLK_2);
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "draining the third key");
|
||||
TEST_ASSERT(e, keycode == SDLK_3, "the third key out was %d, expected %d",
|
||||
keycode, (int)SDLK_3);
|
||||
|
||||
// A key that a control map also acts on still reaches the poller, so a
|
||||
// game and an embedded interpreter can share one keyboard.
|
||||
CATCH(e, akgl_controller_flush_keys());
|
||||
CATCH(e, akgl_controller_default(0, "player", TEST_KBID, TEST_JSID));
|
||||
player->state = 0;
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_LEFT);
|
||||
TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event),
|
||||
"dispatching a bound key press");
|
||||
TEST_ASSERT(e, AKGL_BITMASK_HAS(player->state, AKGL_ACTOR_STATE_MOVING_LEFT),
|
||||
"the bound key stopped driving the actor (state %d)", player->state);
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available),
|
||||
"polling for a key a control map claimed");
|
||||
TEST_ASSERT(e, available == true, "a bound key never reached the key buffer");
|
||||
TEST_ASSERT(e, keycode == SDLK_LEFT, "the poller returned keycode %d, expected %d",
|
||||
keycode, (int)SDLK_LEFT);
|
||||
|
||||
// Flushing discards the backlog.
|
||||
CATCH(e, akgl_controller_flush_keys());
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_B);
|
||||
CATCH(e, akgl_controller_handle_event(&appstate_placeholder, &event));
|
||||
TEST_EXPECT_OK(e, akgl_controller_flush_keys(), "flushing a buffer with a key in it");
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available), "polling after a flush");
|
||||
TEST_ASSERT(e, available == false, "a flush left a keystroke behind");
|
||||
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_poll_key(NULL, &available),
|
||||
"polling into a NULL keycode");
|
||||
TEST_EXPECT_STATUS(e, AKERR_NULLPOINTER, akgl_controller_poll_key(&keycode, NULL),
|
||||
"polling into a NULL availability flag");
|
||||
} CLEANUP {
|
||||
reset_control_maps();
|
||||
IGNORE(akgl_controller_flush_keys());
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *test_controller_poll_key_overflow(void)
|
||||
{
|
||||
PREPARE_ERROR(e);
|
||||
SDL_Event event;
|
||||
int keycode = -1;
|
||||
bool available = true;
|
||||
bool ordered = true;
|
||||
bool pumped = true;
|
||||
int i = 0;
|
||||
|
||||
ATTEMPT {
|
||||
reset_control_maps();
|
||||
CATCH(e, akgl_controller_flush_keys());
|
||||
|
||||
// Fill the buffer exactly, then press one more. The overflowing key is
|
||||
// the one that is dropped -- what was typed first survives.
|
||||
for ( i = 0; i < AKGL_CONTROLLER_KEY_BUFFER + 1; i++ ) {
|
||||
akerr_ErrorContext *pumpresult = NULL;
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_A + i);
|
||||
pumpresult = akgl_controller_handle_event(&appstate_placeholder, &event);
|
||||
if ( pumpresult != NULL ) {
|
||||
pumpresult->handled = true;
|
||||
pumpresult = akerr_release_error(pumpresult);
|
||||
pumped = false;
|
||||
}
|
||||
}
|
||||
TEST_ASSERT(e, pumped == true, "dispatching the overflow key presses failed");
|
||||
|
||||
for ( i = 0; i < AKGL_CONTROLLER_KEY_BUFFER; i++ ) {
|
||||
akerr_ErrorContext *pollresult = akgl_controller_poll_key(&keycode, &available);
|
||||
if ( pollresult != NULL ) {
|
||||
pollresult->handled = true;
|
||||
pollresult = akerr_release_error(pollresult);
|
||||
ordered = false;
|
||||
}
|
||||
TEST_ASSERT_FLAG(ordered, available == true);
|
||||
TEST_ASSERT_FLAG(ordered, keycode == (int)(SDLK_A + i));
|
||||
}
|
||||
TEST_ASSERT(e, ordered == true,
|
||||
"a full buffer did not return the first %d keys in order",
|
||||
AKGL_CONTROLLER_KEY_BUFFER);
|
||||
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available),
|
||||
"polling after draining a full buffer");
|
||||
TEST_ASSERT(e, available == false,
|
||||
"the key pressed past capacity was buffered anyway (keycode %d)", keycode);
|
||||
|
||||
// The buffer is reusable after an overflow rather than wedged.
|
||||
make_key_event(&event, SDL_EVENT_KEY_DOWN, TEST_KBID, SDLK_Z);
|
||||
TEST_EXPECT_OK(e, akgl_controller_handle_event(&appstate_placeholder, &event),
|
||||
"dispatching a key after an overflow");
|
||||
TEST_EXPECT_OK(e, akgl_controller_poll_key(&keycode, &available),
|
||||
"polling after an overflow");
|
||||
TEST_ASSERT(e, available == true, "the buffer stayed full after being drained");
|
||||
TEST_ASSERT(e, keycode == SDLK_Z, "the poller returned keycode %d, expected %d",
|
||||
keycode, (int)SDLK_Z);
|
||||
} CLEANUP {
|
||||
reset_control_maps();
|
||||
IGNORE(akgl_controller_flush_keys());
|
||||
} PROCESS(e) {
|
||||
} FINISH(e, true);
|
||||
SUCCEED_RETURN(e);
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
@@ -551,6 +709,8 @@ int main(void)
|
||||
CATCH(errctx, test_controller_gamepad_button_handlers());
|
||||
CATCH(errctx, test_controller_device_events());
|
||||
CATCH(errctx, test_controller_device_enumeration());
|
||||
CATCH(errctx, test_controller_poll_key());
|
||||
CATCH(errctx, test_controller_poll_key_overflow());
|
||||
} CLEANUP {
|
||||
SDL_Quit();
|
||||
} PROCESS(errctx) {
|
||||
|
||||
Reference in New Issue
Block a user