// 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 #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); }