300 lines
9.3 KiB
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);
|
|
}
|