joeylib2/src/core/input.c

300 lines
9.3 KiB
C

// Public input API. Maintains current/previous state buffers for the
// keyboard (gKeyState/gKeyPrev), mouse buttons (gMouseButtonState/
// gMouseButtonPrev), and joystick buttons (gJoyButtonState/
// gJoyButtonPrev). jlInputPoll snapshots the previous state, then
// asks the port HAL to refresh the current state; the predicates
// derive held/edge values from those two buffers.
//
// Mouse position and joystick axes are plain current state with no
// edge predicates -- games that want deltas track the previous values
// themselves.
//
// The typed-character queue (gCharQueue) is a separate path for text
// entry: ports push OS/firmware-translated ASCII via jlInputCharPush
// during jlpInputPoll, and apps pop with jlInputGetChar. The queue is
// a classic ring with one empty slot (head == tail means empty), so
// no element count is stored and push/pop touch a single index each.
#include <string.h>
#include "joey/input.h"
#include "port.h"
#include "inputInternal.h"
// See inputInternal.h for why these are uint8_t and not bool.
uint8_t gKeyState [KEY_COUNT];
uint8_t gKeyPrev [KEY_COUNT];
int16_t gMouseX = 0;
bool gMouseAttached = false; // set by jlpInputInit where the HAL confirms a mouse
int16_t gMouseY = 0;
uint8_t gMouseButtonState[MOUSE_BUTTON_COUNT];
uint8_t gMouseButtonPrev [MOUSE_BUTTON_COUNT];
uint8_t gJoyConnected [JOYSTICK_COUNT];
int8_t gJoyAxisX [JOYSTICK_COUNT];
int8_t gJoyAxisY [JOYSTICK_COUNT];
uint8_t gJoyButtonState[JOYSTICK_COUNT][JOY_BUTTON_COUNT];
uint8_t gJoyButtonPrev [JOYSTICK_COUNT][JOY_BUTTON_COUNT];
uint8_t gJoyDeadZone [JOYSTICK_COUNT];
uint8_t gCharQueue [JL_CHAR_QUEUE_SIZE];
uint8_t gCharQueueHead = 0;
uint8_t gCharQueueTail = 0;
// Build-time check: the ring-index wrap masks with the size minus one,
// which only wraps correctly for a power-of-two size.
typedef int joey_charqueue_pow2_check[((JL_CHAR_QUEUE_SIZE & (JL_CHAR_QUEUE_SIZE - 1)) == 0) ? 1 : -1];
#ifdef JOEYLIB_PLATFORM_IIGS
extern void iigsInputSnapshot(void);
// Build-time checks: iigsInputSnapshot's asm hard-codes KEY_COUNT=60
// and the small button counts, and walks every array one byte per
// element. If a future change adds/removes keys or buttons the asm
// must be updated; if anyone re-types the arrays back to bool the
// per-element size could grow past one byte and the asm reads
// the wrong bytes. Either condition declares a zero-size array
// below, which is a compile error.
typedef int joey_keycount_check [(KEY_COUNT == 60) ? 1 : -1];
typedef int joey_mousebtn_check [(MOUSE_BUTTON_COUNT == 4) ? 1 : -1];
typedef int joey_joybtn_check [(JOYSTICK_COUNT * JOY_BUTTON_COUNT == 4) ? 1 : -1];
typedef int joey_keystate_size_check [(sizeof(gKeyState) == KEY_COUNT) ? 1 : -1];
typedef int joey_mousebtn_size_check [(sizeof(gMouseButtonState) == MOUSE_BUTTON_COUNT) ? 1 : -1];
typedef int joey_joybtn_size_check [(sizeof(gJoyButtonState) == JOYSTICK_COUNT * JOY_BUTTON_COUNT) ? 1 : -1];
#endif
void jlInputCharPush(uint8_t ch) {
uint8_t head;
uint8_t next;
// 0x7F is the FORWARD delete, not junk: it is the one code above the printable range the queue
// carries, because a text editor needs a key that removes the character under the caret.
if (ch > 0x7E && ch != JL_CHAR_DELETE) {
return;
}
if (ch < 0x20 &&
ch != JL_CHAR_BACKSPACE && ch != JL_CHAR_TAB &&
ch != JL_CHAR_RETURN && ch != JL_CHAR_ESCAPE) {
return;
}
head = gCharQueueHead;
next = (uint8_t)((head + 1u) & (JL_CHAR_QUEUE_SIZE - 1u));
if (next == gCharQueueTail) {
return;
}
gCharQueue[head] = ch;
gCharQueueHead = next;
}
void jlInputCharReset(void) {
gCharQueueHead = 0;
gCharQueueTail = 0;
}
int jlInputGetChar(void) {
uint8_t tail;
int ch;
tail = gCharQueueTail;
if (tail == gCharQueueHead) {
return -1;
}
ch = gCharQueue[tail];
gCharQueueTail = (uint8_t)((tail + 1u) & (JL_CHAR_QUEUE_SIZE - 1u));
return ch;
}
void jlInputPoll(void) {
#ifdef JOEYLIB_PLATFORM_IIGS
// One asm pass for: TTL decrement + key snapshot + mouse/joy
// button snapshots. Replaces 3 memcpys + the C TTL loop
// that used to live in jlpInputPoll. ~0.6 ms saved per frame.
iigsInputSnapshot();
#else
memcpy(gKeyPrev, gKeyState, sizeof(gKeyState));
memcpy(gMouseButtonPrev, gMouseButtonState, sizeof(gMouseButtonState));
memcpy(gJoyButtonPrev, gJoyButtonState, sizeof(gJoyButtonState));
#endif
jlpInputPoll();
}
void jlWaitForAnyKey(void) {
int16_t i;
// Prime the previous-state snapshot so a key already held when the
// wait starts has to be released and re-pressed (rising edge) to
// satisfy the wait. Otherwise auto-repeat or a key still down from
// the previous frame would exit instantly.
jlInputPoll();
while (1) {
jlInputPoll();
for (i = (int16_t)(KEY_NONE + 1); i < (int16_t)KEY_COUNT; i++) {
if (jlKeyPressed((jlKeyE)i)) {
// The dismissing keystroke (and anything typed during
// the wait) was consumed by this wait, not typed at a
// text field -- drop it from the character queue so a
// following jlInputGetChar never sees a stray char.
jlInputCharReset();
return;
}
}
}
}
/* All six key/mouse predicates fold the lower-bound check (`<= NONE`)
* and upper-bound check (`>= COUNT`) into a single unsigned compare.
* Index 0 (KEY_NONE / MOUSE_BUTTON_NONE) is a sentinel that no HAL
* ever writes, so reading gKeyState[0] / gMouseButtonState[0] is
* always 0 -- the predicate result is unchanged.
*
* DECIDED (PERF-AUDIT #40, Phase 8): these stay out-of-line functions.
* Macro/inline forms in joey/input.h would expose the state arrays in
* a public header and multi-evaluate args, to shave call overhead off
* predicates already measured at 70K+ ops/sec post-clock-fix -- input
* is nowhere near any real game's frame profile. Revisit only if a
* profiled game shows the predicates in its frame budget. */
bool jlKeyDown(jlKeyE key) {
if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
return false;
}
return gKeyState[key];
}
bool jlKeyPressed(jlKeyE key) {
if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
return false;
}
return gKeyState[key] && !gKeyPrev[key];
}
bool jlKeyReleased(jlKeyE key) {
if ((uint16_t)key >= (uint16_t)KEY_COUNT) {
return false;
}
return !gKeyState[key] && gKeyPrev[key];
}
bool jlMouseDown(jlMouseButtonE button) {
if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
return false;
}
return gMouseButtonState[button];
}
bool jlMousePressed(jlMouseButtonE button) {
if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
return false;
}
return gMouseButtonState[button] && !gMouseButtonPrev[button];
}
bool jlMouseReleased(jlMouseButtonE button) {
if ((uint16_t)button >= (uint16_t)MOUSE_BUTTON_COUNT) {
return false;
}
return !gMouseButtonState[button] && gMouseButtonPrev[button];
}
bool jlMousePresent(void) {
return gMouseAttached;
}
int16_t jlMouseX(void) {
return gMouseX;
}
int16_t jlMouseY(void) {
return gMouseY;
}
bool jlJoystickConnected(jlJoystickE js) {
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return false;
}
return gJoyConnected[js];
}
int8_t jlJoystickX(jlJoystickE js) {
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return 0;
}
return gJoyAxisX[js];
}
int8_t jlJoystickY(jlJoystickE js) {
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return 0;
}
return gJoyAxisY[js];
}
/* Joystick button predicates: compute the 1D byte index once and read
* via an explicit (uint8_t *) cast so the 2D-array indexing avoids a
* per-access multiply helper. */
bool jlJoyDown(jlJoystickE js, jlJoyButtonE button) {
uint16_t idx;
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return false;
}
if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
return false;
}
idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
return ((const uint8_t *)gJoyButtonState)[idx] != 0;
}
bool jlJoyPressed(jlJoystickE js, jlJoyButtonE button) {
uint16_t idx;
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return false;
}
if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
return false;
}
idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
return (((const uint8_t *)gJoyButtonState)[idx] != 0) &&
(((const uint8_t *)gJoyButtonPrev) [idx] == 0);
}
bool jlJoyReleased(jlJoystickE js, jlJoyButtonE button) {
uint16_t idx;
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return false;
}
if ((uint16_t)button >= (uint16_t)JOY_BUTTON_COUNT) {
return false;
}
idx = (uint16_t)((uint16_t)js * JOY_BUTTON_COUNT + (uint16_t)button);
return (((const uint8_t *)gJoyButtonState)[idx] == 0) &&
(((const uint8_t *)gJoyButtonPrev) [idx] != 0);
}
void jlJoystickReset(jlJoystickE js, uint8_t deadZone) {
if ((uint16_t)js >= (uint16_t)JOYSTICK_COUNT) {
return;
}
gJoyDeadZone[js] = deadZone;
jlpJoystickReset(js);
}