mirror of
https://github.com/giongto35/cloud-game.git
synced 2026-01-23 02:34:42 +00:00
Swap mutex to atomics in keyboard input
This commit is contained in:
parent
58a19affcb
commit
368bae8c07
3 changed files with 407 additions and 139 deletions
|
|
@ -2,7 +2,6 @@ package nanoarch
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
|
|
@ -19,36 +18,11 @@ void input_cache_clear(void);
|
|||
import "C"
|
||||
|
||||
const (
|
||||
Released C.int16_t = iota
|
||||
Pressed
|
||||
maxPort = 4
|
||||
numAxes = 4
|
||||
RetrokLast = int(C.RETROK_LAST)
|
||||
)
|
||||
|
||||
const RetrokLast = int(C.RETROK_LAST)
|
||||
|
||||
// InputState stores full controller state.
|
||||
// It consists of:
|
||||
// - uint16 button values
|
||||
// - int16 analog stick values
|
||||
type InputState [maxPort]RetroPadState
|
||||
|
||||
type (
|
||||
RetroPadState struct {
|
||||
keys uint32
|
||||
axes [dpadAxes]int32
|
||||
}
|
||||
KeyboardState struct {
|
||||
keys [RetrokLast]byte
|
||||
mod uint16
|
||||
mu sync.Mutex
|
||||
}
|
||||
MouseState struct {
|
||||
dx, dy atomic.Int32
|
||||
buttons atomic.Int32
|
||||
}
|
||||
)
|
||||
|
||||
type MouseBtnState int32
|
||||
|
||||
type Device byte
|
||||
|
||||
const (
|
||||
|
|
@ -62,128 +36,110 @@ const (
|
|||
MouseButton
|
||||
)
|
||||
|
||||
type MouseBtnState int32
|
||||
|
||||
const (
|
||||
MouseLeft MouseBtnState = 1 << iota
|
||||
MouseRight
|
||||
MouseMiddle
|
||||
)
|
||||
|
||||
const (
|
||||
maxPort = 4
|
||||
dpadAxes = 4
|
||||
)
|
||||
// InputState stores controller state for all ports.
|
||||
// - uint16 button bitmask
|
||||
// - int16 analog axes x4
|
||||
type InputState [maxPort]struct {
|
||||
keys uint32
|
||||
axes [numAxes]int32
|
||||
}
|
||||
|
||||
// Input sets input state for some player in a game session.
|
||||
func (s *InputState) Input(port int, data []byte) {
|
||||
atomic.StoreUint32(&s[port].keys, uint32(uint16(data[1])<<8+uint16(data[0])))
|
||||
for i, axes := 0, len(data); i < dpadAxes && i<<1+3 < axes; i++ {
|
||||
axis := i<<1 + 2
|
||||
atomic.StoreInt32(&s[port].axes[i], int32(data[axis+1])<<8+int32(data[axis]))
|
||||
// SetInput sets input state for a player.
|
||||
//
|
||||
// [BTN:2][AX0:2][AX1:2][AX2:2][AX3:2]
|
||||
func (s *InputState) SetInput(port int, data []byte) {
|
||||
atomic.StoreUint32(&s[port].keys, uint32(binary.LittleEndian.Uint16(data)))
|
||||
for i := 0; i < numAxes && i*2+3 < len(data); i++ {
|
||||
atomic.StoreInt32(&s[port].axes[i], int32(int16(binary.LittleEndian.Uint16(data[i*2+2:]))))
|
||||
}
|
||||
}
|
||||
|
||||
// IsKeyPressed checks if some button is pressed by any player.
|
||||
func (s *InputState) IsKeyPressed(port uint, key int) C.int16_t {
|
||||
return C.int16_t((atomic.LoadUint32(&s[port].keys) >> uint(key)) & 1)
|
||||
// Button check
|
||||
func (s *InputState) Button(port, key uint) C.int16_t {
|
||||
return C.int16_t((atomic.LoadUint32(&s[port].keys) >> key) & 1)
|
||||
}
|
||||
|
||||
// IsDpadTouched checks if D-pad is used by any player.
|
||||
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.
|
||||
// SyncToCache syncs input state to C-side cache before Run().
|
||||
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)
|
||||
for p := uint(0); p < maxPort; p++ {
|
||||
a := &s[p].axes
|
||||
C.input_cache_set_port(C.uint(p), C.uint32_t(atomic.LoadUint32(&s[p].keys)),
|
||||
C.int16_t(atomic.LoadInt32(&a[0])), C.int16_t(atomic.LoadInt32(&a[1])),
|
||||
C.int16_t(atomic.LoadInt32(&a[2])), C.int16_t(atomic.LoadInt32(&a[3])))
|
||||
}
|
||||
}
|
||||
|
||||
// KeyboardState tracks keys of the keyboard.
|
||||
type KeyboardState struct {
|
||||
keys [6]atomic.Uint64 // 342 keys packed into 6 uint64s (384 bits)
|
||||
mod atomic.Uint32
|
||||
}
|
||||
|
||||
// SetKey sets keyboard state.
|
||||
//
|
||||
// 0 1 2 3 4 5 6
|
||||
// [ KEY ] P MOD
|
||||
// [KEY:4][P:1][MOD:2]
|
||||
//
|
||||
// KEY contains Libretro code of the keyboard key (4 bytes).
|
||||
// P contains 0 or 1 if the key is pressed (1 byte).
|
||||
// MOD contains bitmask for Alt | Ctrl | Meta | Shift keys press state (2 bytes).
|
||||
//
|
||||
// Returns decoded state from the input bytes.
|
||||
// KEY - Libretro key code, P - pressed (0/1), MOD - modifier bitmask
|
||||
func (ks *KeyboardState) SetKey(data []byte) (pressed bool, key uint, mod uint16) {
|
||||
if len(data) != 7 {
|
||||
return
|
||||
}
|
||||
|
||||
press := data[4]
|
||||
pressed = press == 1
|
||||
key = uint(binary.BigEndian.Uint32(data))
|
||||
mod = binary.BigEndian.Uint16(data[5:])
|
||||
ks.mu.Lock()
|
||||
ks.keys[key] = press
|
||||
ks.mod = mod
|
||||
ks.mu.Unlock()
|
||||
pressed = data[4] == 1
|
||||
|
||||
idx, bit := key/64, uint64(1)<<(key%64)
|
||||
if pressed {
|
||||
ks.keys[idx].Or(bit)
|
||||
} else {
|
||||
ks.keys[idx].And(^bit)
|
||||
}
|
||||
ks.mod.Store(uint32(mod))
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (ks *KeyboardState) Pressed(key uint) C.int16_t {
|
||||
ks.mu.Lock()
|
||||
press := ks.keys[key]
|
||||
ks.mu.Unlock()
|
||||
if press == 1 {
|
||||
return Pressed
|
||||
}
|
||||
return Released
|
||||
}
|
||||
|
||||
// SyncToCache syncs keyboard state to the C-side cache.
|
||||
// SyncToCache syncs keyboard state to C-side cache.
|
||||
func (ks *KeyboardState) SyncToCache() {
|
||||
ks.mu.Lock()
|
||||
defer ks.mu.Unlock()
|
||||
for id, pressed := range ks.keys {
|
||||
for id := 0; id < RetrokLast; id++ {
|
||||
pressed := (ks.keys[id/64].Load() >> (id % 64)) & 1
|
||||
C.input_cache_set_keyboard_key(C.uint(id), C.uint8_t(pressed))
|
||||
}
|
||||
}
|
||||
|
||||
// ShiftPos sets mouse relative position state.
|
||||
// MouseState tracks mouse delta and buttons.
|
||||
type MouseState struct {
|
||||
dx, dy atomic.Int32
|
||||
buttons atomic.Int32
|
||||
}
|
||||
|
||||
// ShiftPos adds relative mouse movement.
|
||||
//
|
||||
// 0 1 2 3
|
||||
// [dx] [dy]
|
||||
//
|
||||
// dx and dy are relative mouse coordinates
|
||||
// [dx:2][dy:2]
|
||||
func (ms *MouseState) ShiftPos(data []byte) {
|
||||
if len(data) != 4 {
|
||||
return
|
||||
}
|
||||
dxy := binary.BigEndian.Uint32(data)
|
||||
ms.dx.Add(int32(int16(dxy >> 16)))
|
||||
ms.dy.Add(int32(int16(dxy)))
|
||||
ms.dx.Add(int32(int16(binary.BigEndian.Uint16(data[:2]))))
|
||||
ms.dy.Add(int32(int16(binary.BigEndian.Uint16(data[2:]))))
|
||||
}
|
||||
|
||||
func (ms *MouseState) PopX() C.int16_t { return C.int16_t(ms.dx.Swap(0)) }
|
||||
func (ms *MouseState) PopY() C.int16_t { return C.int16_t(ms.dy.Swap(0)) }
|
||||
|
||||
// SetButtons sets the state MouseBtnState of mouse buttons.
|
||||
func (ms *MouseState) SetButtons(data byte) { ms.buttons.Store(int32(data)) }
|
||||
func (ms *MouseState) SetButtons(b byte) { ms.buttons.Store(int32(b)) }
|
||||
|
||||
func (ms *MouseState) Buttons() (l, r, m bool) {
|
||||
mbs := MouseBtnState(ms.buttons.Load())
|
||||
l = mbs&MouseLeft != 0
|
||||
r = mbs&MouseRight != 0
|
||||
m = mbs&MouseMiddle != 0
|
||||
return
|
||||
b := MouseBtnState(ms.buttons.Load())
|
||||
return b&MouseLeft != 0, b&MouseRight != 0, b&MouseMiddle != 0
|
||||
}
|
||||
|
||||
// SyncToCache syncs mouse state to the C-side cache.
|
||||
// This consumes the delta values (swaps to 0).
|
||||
// SyncToCache syncs mouse state to C-side cache, consuming deltas.
|
||||
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)
|
||||
C.input_cache_set_mouse(C.int16_t(ms.dx.Swap(0)), C.int16_t(ms.dy.Swap(0)), C.uint8_t(ms.buttons.Load()))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,21 +7,215 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestConcurrentInput(t *testing.T) {
|
||||
func TestInputState_SetInput(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
port int
|
||||
data []byte
|
||||
keys uint32
|
||||
axes [4]int32
|
||||
}{
|
||||
{
|
||||
name: "buttons only",
|
||||
port: 0,
|
||||
data: []byte{0xFF, 0x01},
|
||||
keys: 0x01FF,
|
||||
},
|
||||
{
|
||||
name: "buttons and axes",
|
||||
port: 1,
|
||||
data: []byte{0x03, 0x00, 0x10, 0x27, 0xF0, 0xD8, 0x00, 0x80, 0xFF, 0x7F},
|
||||
keys: 0x0003,
|
||||
axes: [4]int32{10000, -10000, -32768, 32767},
|
||||
},
|
||||
{
|
||||
name: "partial axes",
|
||||
port: 2,
|
||||
data: []byte{0x01, 0x00, 0x64, 0x00},
|
||||
keys: 0x0001,
|
||||
axes: [4]int32{100, 0, 0, 0},
|
||||
},
|
||||
{
|
||||
name: "max port",
|
||||
port: 3,
|
||||
data: []byte{0xFF, 0xFF},
|
||||
keys: 0xFFFF,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
state := InputState{}
|
||||
state.SetInput(test.port, test.data)
|
||||
|
||||
if state[test.port].keys != test.keys {
|
||||
t.Errorf("keys: got %v, want %v", state[test.port].keys, test.keys)
|
||||
}
|
||||
for i, want := range test.axes {
|
||||
if state[test.port].axes[i] != want {
|
||||
t.Errorf("axes[%d]: got %v, want %v", i, state[test.port].axes[i], want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInputState_Concurrent(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
state := InputState{}
|
||||
events := 1000
|
||||
wg.Add(2 * events)
|
||||
wg.Add(events)
|
||||
|
||||
for range events {
|
||||
player := rand.Intn(maxPort)
|
||||
go func() { state.Input(player, []byte{0, 1}); wg.Done() }()
|
||||
go func() { state.IsKeyPressed(uint(player), 100); wg.Done() }()
|
||||
go func() {
|
||||
state.SetInput(player, []byte{0, 1, 0, 0, 0, 0, 0, 0, 0, 0})
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestMousePos(t *testing.T) {
|
||||
func TestKeyboardState_SetKey(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data []byte
|
||||
pressed bool
|
||||
key uint
|
||||
mod uint16
|
||||
}{
|
||||
{
|
||||
name: "key pressed",
|
||||
data: []byte{0, 0, 0, 42, 1, 0, 3},
|
||||
pressed: true,
|
||||
key: 42,
|
||||
mod: 3,
|
||||
},
|
||||
{
|
||||
name: "key released",
|
||||
data: []byte{0, 0, 0, 100, 0, 0, 0},
|
||||
pressed: false,
|
||||
key: 100,
|
||||
mod: 0,
|
||||
},
|
||||
{
|
||||
name: "high key code",
|
||||
data: []byte{0, 0, 1, 50, 1, 0xFF, 0xFF},
|
||||
pressed: true,
|
||||
key: 306,
|
||||
mod: 0xFFFF,
|
||||
},
|
||||
{
|
||||
name: "invalid length",
|
||||
data: []byte{0, 0, 0},
|
||||
pressed: false,
|
||||
key: 0,
|
||||
mod: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
ks := KeyboardState{}
|
||||
pressed, key, mod := ks.SetKey(test.data)
|
||||
|
||||
if pressed != test.pressed {
|
||||
t.Errorf("pressed: got %v, want %v", pressed, test.pressed)
|
||||
}
|
||||
if key != test.key {
|
||||
t.Errorf("key: got %v, want %v", key, test.key)
|
||||
}
|
||||
if mod != test.mod {
|
||||
t.Errorf("mod: got %v, want %v", mod, test.mod)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyboardState_IsPressed(t *testing.T) {
|
||||
ks := KeyboardState{}
|
||||
|
||||
// Initially not pressed
|
||||
if ks.keys[0].Load() != 0 {
|
||||
t.Error("key should not be pressed initially")
|
||||
}
|
||||
|
||||
// Press key
|
||||
ks.SetKey([]byte{0, 0, 0, 42, 1, 0, 0})
|
||||
if (ks.keys[42/64].Load()>>(42%64))&1 != 1 {
|
||||
t.Error("key should be pressed")
|
||||
}
|
||||
|
||||
// Release key
|
||||
ks.SetKey([]byte{0, 0, 0, 42, 0, 0, 0})
|
||||
if (ks.keys[42/64].Load()>>(42%64))&1 != 0 {
|
||||
t.Error("key should be released")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyboardState_MultipleBits(t *testing.T) {
|
||||
ks := KeyboardState{}
|
||||
|
||||
// Press keys in different uint64 slots
|
||||
keys := []uint{0, 63, 64, 127, 128, 200, 300, 341}
|
||||
for _, k := range keys {
|
||||
data := make([]byte, 7)
|
||||
binary.BigEndian.PutUint32(data, uint32(k))
|
||||
data[4] = 1
|
||||
ks.SetKey(data)
|
||||
}
|
||||
|
||||
// Check all pressed
|
||||
for _, k := range keys {
|
||||
if (ks.keys[k/64].Load()>>(k%64))&1 != 1 {
|
||||
t.Errorf("key %d should be pressed", k)
|
||||
}
|
||||
}
|
||||
|
||||
// Release some
|
||||
for _, k := range []uint{0, 128, 341} {
|
||||
data := make([]byte, 7)
|
||||
binary.BigEndian.PutUint32(data, uint32(k))
|
||||
data[4] = 0
|
||||
ks.SetKey(data)
|
||||
}
|
||||
|
||||
// Check states
|
||||
expected := map[uint]uint64{
|
||||
0: 0, 63: 1, 64: 1, 127: 1, 128: 0, 200: 1, 300: 1, 341: 0,
|
||||
}
|
||||
for k, want := range expected {
|
||||
got := (ks.keys[k/64].Load() >> (k % 64)) & 1
|
||||
if got != want {
|
||||
t.Errorf("key %d: got %v, want %v", k, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyboardState_Concurrent(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
ks := KeyboardState{}
|
||||
events := 1000
|
||||
wg.Add(events * 2)
|
||||
|
||||
for range events {
|
||||
key := uint(rand.Intn(RetrokLast))
|
||||
go func() {
|
||||
data := make([]byte, 7)
|
||||
binary.BigEndian.PutUint32(data, uint32(key))
|
||||
data[4] = byte(rand.Intn(2))
|
||||
ks.SetKey(data)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
_ = (ks.keys[key/64].Load() >> (key % 64)) & 1
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestMouseState_ShiftPos(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
dx int16
|
||||
|
|
@ -30,42 +224,109 @@ func TestMousePos(t *testing.T) {
|
|||
ry int16
|
||||
b func(dx, dy int16) []byte
|
||||
}{
|
||||
{name: "normal", dx: -10123, dy: 5678, rx: -10123, ry: 5678, b: func(dx, dy int16) []byte {
|
||||
data := []byte{0, 0, 0, 0}
|
||||
binary.BigEndian.PutUint16(data, uint16(dx))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
}},
|
||||
{name: "wrong endian", dx: -1234, dy: 5678, rx: 12027, ry: 11798, b: func(dx, dy int16) []byte {
|
||||
data := []byte{0, 0, 0, 0}
|
||||
binary.LittleEndian.PutUint16(data, uint16(dx))
|
||||
binary.LittleEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
}},
|
||||
{
|
||||
name: "positive values",
|
||||
dx: 100,
|
||||
dy: 200,
|
||||
rx: 100,
|
||||
ry: 200,
|
||||
b: func(dx, dy int16) []byte {
|
||||
data := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(data, uint16(dx))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "negative values",
|
||||
dx: -10123,
|
||||
dy: 5678,
|
||||
rx: -10123,
|
||||
ry: 5678,
|
||||
b: func(dx, dy int16) []byte {
|
||||
data := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(data, uint16(dx))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "wrong endian",
|
||||
dx: -1234,
|
||||
dy: 5678,
|
||||
rx: 12027,
|
||||
ry: 11798,
|
||||
b: func(dx, dy int16) []byte {
|
||||
data := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint16(data, uint16(dx))
|
||||
binary.LittleEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "max values",
|
||||
dx: 32767,
|
||||
dy: -32768,
|
||||
rx: 32767,
|
||||
ry: -32768,
|
||||
b: func(dx, dy int16) []byte {
|
||||
data := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(data, uint16(dx))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(dy))
|
||||
return data
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
data := test.b(test.dx, test.dy)
|
||||
|
||||
ms := MouseState{}
|
||||
ms.ShiftPos(data)
|
||||
ms.ShiftPos(test.b(test.dx, test.dy))
|
||||
|
||||
x := int16(ms.PopX())
|
||||
y := int16(ms.PopY())
|
||||
x, y := int16(ms.dx.Swap(0)), int16(ms.dy.Swap(0))
|
||||
|
||||
if x != test.rx || y != test.ry {
|
||||
t.Errorf("invalid state, %v = %v, %v = %v", test.rx, x, test.ry, y)
|
||||
t.Errorf("got (%v, %v), want (%v, %v)", x, y, test.rx, test.ry)
|
||||
}
|
||||
|
||||
if ms.dx.Load() != 0 || ms.dy.Load() != 0 {
|
||||
t.Errorf("coordinates weren't cleared")
|
||||
t.Error("coordinates weren't cleared")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouseButtons(t *testing.T) {
|
||||
func TestMouseState_ShiftPosAccumulates(t *testing.T) {
|
||||
ms := MouseState{}
|
||||
|
||||
data := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(data, uint16(10))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(20))
|
||||
|
||||
ms.ShiftPos(data)
|
||||
ms.ShiftPos(data)
|
||||
ms.ShiftPos(data)
|
||||
|
||||
if got := ms.dx.Load(); got != 30 {
|
||||
t.Errorf("dx: got %v, want 30", got)
|
||||
}
|
||||
if got := ms.dy.Load(); got != 60 {
|
||||
t.Errorf("dy: got %v, want 60", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouseState_ShiftPosInvalidLength(t *testing.T) {
|
||||
ms := MouseState{}
|
||||
|
||||
ms.ShiftPos([]byte{1, 2, 3})
|
||||
ms.ShiftPos([]byte{1, 2, 3, 4, 5})
|
||||
|
||||
if ms.dx.Load() != 0 || ms.dy.Load() != 0 {
|
||||
t.Error("invalid data should be ignored")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouseState_Buttons(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
data byte
|
||||
|
|
@ -73,10 +334,13 @@ func TestMouseButtons(t *testing.T) {
|
|||
r bool
|
||||
m bool
|
||||
}{
|
||||
{name: "l+r+m+", data: 1 + 2 + 4, l: true, r: true, m: true},
|
||||
{name: "l-r-m-", data: 0},
|
||||
{name: "l-r+m-", data: 2, r: true},
|
||||
{name: "l+r-m+", data: 1 + 4, l: true, m: true},
|
||||
{name: "none", data: 0},
|
||||
{name: "left", data: 1, l: true},
|
||||
{name: "right", data: 2, r: true},
|
||||
{name: "middle", data: 4, m: true},
|
||||
{name: "left+right", data: 3, l: true, r: true},
|
||||
{name: "all", data: 7, l: true, r: true, m: true},
|
||||
{name: "left+middle", data: 5, l: true, m: true},
|
||||
}
|
||||
|
||||
ms := MouseState{}
|
||||
|
|
@ -86,8 +350,56 @@ func TestMouseButtons(t *testing.T) {
|
|||
ms.SetButtons(test.data)
|
||||
l, r, m := ms.Buttons()
|
||||
if l != test.l || r != test.r || m != test.m {
|
||||
t.Errorf("wrong button state: %v -> %v, %v, %v", test.data, l, r, m)
|
||||
t.Errorf("got (%v, %v, %v), want (%v, %v, %v)", l, r, m, test.l, test.r, test.m)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMouseState_Concurrent(t *testing.T) {
|
||||
var wg sync.WaitGroup
|
||||
ms := MouseState{}
|
||||
events := 1000
|
||||
wg.Add(events * 3)
|
||||
|
||||
for range events {
|
||||
go func() {
|
||||
data := make([]byte, 4)
|
||||
binary.BigEndian.PutUint16(data, uint16(rand.Int31n(100)-50))
|
||||
binary.BigEndian.PutUint16(data[2:], uint16(rand.Int31n(100)-50))
|
||||
ms.ShiftPos(data)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
ms.SetButtons(byte(rand.Intn(8)))
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
ms.Buttons()
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestConstants(t *testing.T) {
|
||||
// MouseBtnState
|
||||
if MouseLeft != 1 || MouseRight != 2 || MouseMiddle != 4 {
|
||||
t.Error("invalid MouseBtnState constants")
|
||||
}
|
||||
|
||||
// Device
|
||||
if RetroPad != 0 || Keyboard != 1 || Mouse != 2 {
|
||||
t.Error("invalid Device constants")
|
||||
}
|
||||
|
||||
// Mouse events
|
||||
if MouseMove != 0 || MouseButton != 1 {
|
||||
t.Error("invalid mouse event constants")
|
||||
}
|
||||
|
||||
// Limits
|
||||
if maxPort != 4 || numAxes != 4 || RetrokLast != 342 {
|
||||
t.Error("invalid limit constants")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -426,7 +426,7 @@ func (n *Nanoarch) Run() {
|
|||
func (n *Nanoarch) IsSupported() error { return graphics.TryInit() }
|
||||
func (n *Nanoarch) IsGL() bool { return n.Video.gl.enabled }
|
||||
func (n *Nanoarch) IsStopped() bool { return n.Stopped.Load() }
|
||||
func (n *Nanoarch) InputRetropad(port int, data []byte) { n.retropad.Input(port, data) }
|
||||
func (n *Nanoarch) InputRetropad(port int, data []byte) { n.retropad.SetInput(port, data) }
|
||||
func (n *Nanoarch) InputKeyboard(_ int, data []byte) {
|
||||
if n.keyboardCb == nil {
|
||||
return
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue