Add input cache for retropad, keyboard and mouse

This commit is contained in:
sergystepanov 2025-12-14 13:53:21 +03:00
parent f708fce112
commit 671e875f12
3 changed files with 163 additions and 63 deletions

View file

@ -6,8 +6,16 @@ import (
"sync/atomic"
)
//#include <stdint.h>
//#include "libretro.h"
/*
#include <stdint.h>
#include "libretro.h"
void input_cache_set_port(unsigned port, uint32_t buttons,
int16_t axis0, int16_t axis1, int16_t axis2, int16_t axis3);
void input_cache_set_keyboard_key(unsigned id, uint8_t pressed);
void input_cache_set_mouse(int16_t dx, int16_t dy, uint8_t buttons);
void input_cache_clear(void);
*/
import "C"
const (
@ -84,6 +92,19 @@ func (s *InputState) IsDpadTouched(port uint, axis uint) (shift C.int16_t) {
return C.int16_t(atomic.LoadInt32(&s[port].axes[axis]))
}
// SyncToCache syncs the entire input state to the C-side cache.
// Call this once before each Run() instead of having C call back into Go.
func (s *InputState) SyncToCache() {
for port := uint(0); port < maxPort; port++ {
buttons := atomic.LoadUint32(&s[port].keys)
axis0 := C.int16_t(atomic.LoadInt32(&s[port].axes[0]))
axis1 := C.int16_t(atomic.LoadInt32(&s[port].axes[1]))
axis2 := C.int16_t(atomic.LoadInt32(&s[port].axes[2]))
axis3 := C.int16_t(atomic.LoadInt32(&s[port].axes[3]))
C.input_cache_set_port(C.uint(port), C.uint32_t(buttons), axis0, axis1, axis2, axis3)
}
}
// SetKey sets keyboard state.
//
// 0 1 2 3 4 5 6
@ -120,6 +141,15 @@ func (ks *KeyboardState) Pressed(key uint) C.int16_t {
return Released
}
// SyncToCache syncs keyboard state to the C-side cache.
func (ks *KeyboardState) SyncToCache() {
ks.mu.Lock()
defer ks.mu.Unlock()
for id, pressed := range ks.keys {
C.input_cache_set_keyboard_key(C.uint(id), C.uint8_t(pressed))
}
}
// ShiftPos sets mouse relative position state.
//
// 0 1 2 3
@ -148,3 +178,12 @@ func (ms *MouseState) Buttons() (l, r, m bool) {
m = mbs&MouseMiddle != 0
return
}
// SyncToCache syncs mouse state to the C-side cache.
// This consumes the delta values (swaps to 0).
func (ms *MouseState) SyncToCache() {
dx := C.int16_t(ms.dx.Swap(0))
dy := C.int16_t(ms.dy.Swap(0))
buttons := C.uint8_t(ms.buttons.Load())
C.input_cache_set_mouse(dx, dy, buttons)
}

View file

@ -3,17 +3,18 @@
#include <stdbool.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#define RETRO_ENVIRONMENT_GET_CLEAR_ALL_THREAD_WAITS_CB (3 | 0x800000)
int initialized = 0;
typedef struct {
int type;
void* fn;
void* arg1;
void* arg2;
void* result;
int type;
void* fn;
void* arg1;
void* arg2;
void* result;
} call_def_t;
call_def_t call;
@ -26,6 +27,57 @@ enum call_type {
void *same_thread_with_args(void *f, int type, ...);
// Input State Cache
#define INPUT_MAX_PORTS 4
#define INPUT_MAX_KEYS 512
typedef struct {
// Retropad: store raw button bitmask and analog axes per port
uint32_t buttons[INPUT_MAX_PORTS];
int16_t analog[INPUT_MAX_PORTS][4]; // 4 axes per port
// Keyboard
uint8_t keyboard[INPUT_MAX_KEYS];
// Mouse
int16_t mouse_x;
int16_t mouse_y;
uint8_t mouse_buttons; // bit 0=left, bit 1=right, bit 2=middle
} input_cache_t;
static input_cache_t input_cache = {0};
// Update entire port state at once
void input_cache_set_port(unsigned port, uint32_t buttons,
int16_t axis0, int16_t axis1, int16_t axis2, int16_t axis3) {
if (port < INPUT_MAX_PORTS) {
input_cache.buttons[port] = buttons;
input_cache.analog[port][0] = axis0;
input_cache.analog[port][1] = axis1;
input_cache.analog[port][2] = axis2;
input_cache.analog[port][3] = axis3;
}
}
// Keyboard update
void input_cache_set_keyboard_key(unsigned id, uint8_t pressed) {
if (id < INPUT_MAX_KEYS) {
input_cache.keyboard[id] = pressed;
}
}
// Mouse update
void input_cache_set_mouse(int16_t dx, int16_t dy, uint8_t buttons) {
input_cache.mouse_x = dx;
input_cache.mouse_y = dy;
input_cache.mouse_buttons = buttons;
}
void input_cache_clear(void) {
memset(&input_cache, 0, sizeof(input_cache));
}
void core_log_cgo(enum retro_log_level level, const char *fmt, ...) {
char msg[2048] = {0};
va_list va;
@ -144,8 +196,61 @@ void core_input_poll_cgo() {
}
int16_t core_input_state_cgo(unsigned port, unsigned device, unsigned index, unsigned id) {
int16_t coreInputState(unsigned, unsigned, unsigned, unsigned);
return coreInputState(port, device, index, id);
if (port >= INPUT_MAX_PORTS) {
return 0;
}
switch (device) {
case RETRO_DEVICE_JOYPAD:
// Extract button bit from cached bitmask
return (int16_t)((input_cache.buttons[port] >> id) & 1);
case RETRO_DEVICE_ANALOG:
switch (index) {
case RETRO_DEVICE_INDEX_ANALOG_LEFT:
// id: 0=X, 1=Y
if (id < 2) {
return input_cache.analog[port][id];
}
break;
case RETRO_DEVICE_INDEX_ANALOG_RIGHT:
// id: 0=X, 1=Y -> stored in axes[2], axes[3]
if (id < 2) {
return input_cache.analog[port][2 + id];
}
break;
}
break;
case RETRO_DEVICE_KEYBOARD:
if (id < INPUT_MAX_KEYS) {
return input_cache.keyboard[id] ? 1 : 0;
}
break;
case RETRO_DEVICE_MOUSE:
switch (id) {
case RETRO_DEVICE_ID_MOUSE_X: {
int16_t x = input_cache.mouse_x;
input_cache.mouse_x = 0; // Consume delta
return x;
}
case RETRO_DEVICE_ID_MOUSE_Y: {
int16_t y = input_cache.mouse_y;
input_cache.mouse_y = 0; // Consume delta
return y;
}
case RETRO_DEVICE_ID_MOUSE_LEFT:
return (input_cache.mouse_buttons & 0x01) ? 1 : 0;
case RETRO_DEVICE_ID_MOUSE_RIGHT:
return (input_cache.mouse_buttons & 0x02) ? 1 : 0;
case RETRO_DEVICE_ID_MOUSE_MIDDLE:
return (input_cache.mouse_buttons & 0x04) ? 1 : 0;
}
break;
}
return 0;
}
size_t core_audio_sample_batch_cgo(const int16_t *data, size_t frames) {

View file

@ -396,12 +396,21 @@ func (n *Nanoarch) Reset() {
C.bridge_call(retroReset)
}
func (n *Nanoarch) syncInputToCache() {
n.retropad.SyncToCache()
if n.keyboardCb != nil {
n.keyboard.SyncToCache()
}
n.mouse.SyncToCache()
}
func (n *Nanoarch) Run() {
n.syncInputToCache()
if n.LibCo {
C.same_thread(retroRun)
} else {
if n.Video.gl.enabled {
// running inside a go routine, lock the thread to make sure the OpenGL context stays current
runtime.LockOSThread()
if err := n.sdlCtx.BindContext(); err != nil {
n.log.Error().Err(err).Msg("ctx bind fail")
@ -672,59 +681,6 @@ func coreVideoRefresh(data unsafe.Pointer, width, height uint, packed uint) {
Nan0.Handlers.OnVideo(data_, int32(dt), FrameInfo{W: width, H: height, Stride: packed})
}
//export coreInputState
func coreInputState(port C.unsigned, device C.unsigned, index C.unsigned, id C.unsigned) C.int16_t {
//Nan0.log.Debug().Msgf("%v %v %v %v", port, device, index, id)
// something like PCSX-ReArmed has 8 ports
if port >= maxPort {
return Released
}
switch device {
case C.RETRO_DEVICE_JOYPAD:
return Nan0.retropad.IsKeyPressed(uint(port), int(id))
case C.RETRO_DEVICE_ANALOG:
switch index {
case C.RETRO_DEVICE_INDEX_ANALOG_LEFT:
return Nan0.retropad.IsDpadTouched(uint(port), uint(index*2+id))
case C.RETRO_DEVICE_INDEX_ANALOG_RIGHT:
case C.RETRO_DEVICE_INDEX_ANALOG_BUTTON:
}
case C.RETRO_DEVICE_KEYBOARD:
return Nan0.keyboard.Pressed(uint(id))
case C.RETRO_DEVICE_MOUSE:
switch id {
case C.RETRO_DEVICE_ID_MOUSE_X:
x := Nan0.mouse.PopX()
return x
case C.RETRO_DEVICE_ID_MOUSE_Y:
y := Nan0.mouse.PopY()
return y
case C.RETRO_DEVICE_ID_MOUSE_LEFT:
if l, _, _ := Nan0.mouse.Buttons(); l {
return Pressed
}
case C.RETRO_DEVICE_ID_MOUSE_RIGHT:
if _, r, _ := Nan0.mouse.Buttons(); r {
return Pressed
}
case C.RETRO_DEVICE_ID_MOUSE_WHEELUP:
case C.RETRO_DEVICE_ID_MOUSE_WHEELDOWN:
case C.RETRO_DEVICE_ID_MOUSE_MIDDLE:
if _, _, m := Nan0.mouse.Buttons(); m {
return Pressed
}
case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELUP:
case C.RETRO_DEVICE_ID_MOUSE_HORIZ_WHEELDOWN:
case C.RETRO_DEVICE_ID_MOUSE_BUTTON_4:
case C.RETRO_DEVICE_ID_MOUSE_BUTTON_5:
}
}
return Released
}
//export coreAudioSampleBatch
func coreAudioSampleBatch(data unsafe.Pointer, frames C.size_t) C.size_t {
if Nan0.Stopped.Load() {