internal/gamepad: make the APIs concurrent-safe again

This commit is contained in:
Hajime Hoshi 2022-01-29 03:41:47 +09:00
parent 2e9ce94ed0
commit 1d59023649
3 changed files with 72 additions and 60 deletions

View File

@ -40,6 +40,7 @@ const (
type gamepads struct {
inited bool
gamepads []*Gamepad
m sync.Mutex
nativeGamepads
}
@ -50,22 +51,22 @@ func init() {
theGamepads.nativeGamepads.gamepads = &theGamepads
}
// AppendGamepadIDs must be called on the main thread.
func AppendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
return theGamepads.appendGamepadIDs(ids)
}
// Update must be called on the main thread.
func Update() {
theGamepads.update()
}
// Get must be called on the main thread.
func Get(id driver.GamepadID) *Gamepad {
return theGamepads.get(id)
}
func (g *gamepads) appendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
g.m.Lock()
defer g.m.Unlock()
for i, gp := range g.gamepads {
if gp != nil && gp.present() {
ids = append(ids, driver.GamepadID(i))
@ -75,6 +76,9 @@ func (g *gamepads) appendGamepadIDs(ids []driver.GamepadID) []driver.GamepadID {
}
func (g *gamepads) update() {
g.m.Lock()
defer g.m.Unlock()
if !g.inited {
g.nativeGamepads.init()
g.inited = true
@ -89,14 +93,15 @@ func (g *gamepads) update() {
}
func (g *gamepads) get(id driver.GamepadID) *Gamepad {
g.m.Lock()
defer g.m.Unlock()
if id < 0 || int(id) >= len(g.gamepads) {
return nil
}
return g.gamepads[id]
}
// find can be invoked from callbacks on the OS's main thread.
// As a callback can be called synchronously from update, using a mutex is not a good idea.
func (g *gamepads) find(cond func(*Gamepad) bool) *Gamepad {
for _, gp := range g.gamepads {
if gp == nil {
@ -109,8 +114,6 @@ func (g *gamepads) find(cond func(*Gamepad) bool) *Gamepad {
return nil
}
// add can be invoked from callbacks on the OS's main thread.
// As a callback can be called synchronously from update, using a mutex is not a good idea.
func (g *gamepads) add(name, sdlID string) *Gamepad {
for i, gp := range g.gamepads {
if gp == nil {
@ -131,8 +134,6 @@ func (g *gamepads) add(name, sdlID string) *Gamepad {
return gp
}
// remove can be invoked from callbacks on the OS's main thread.
// As a callback can be called synchronously from update, using a mutex is not a good idea.
func (g *gamepads) remove(cond func(*Gamepad) bool) {
for i, gp := range g.gamepads {
if gp == nil {

View File

@ -20,6 +20,7 @@ package gamepad
import (
"fmt"
"sort"
"sync"
"time"
"unsafe"
)
@ -60,7 +61,10 @@ import "C"
type nativeGamepads struct {
gamepads *gamepads
hidManager C.IOHIDManagerRef
hidManager C.IOHIDManagerRef
devicesToAdd []C.IOHIDDeviceRef
devicesToRemove []C.IOHIDDeviceRef
devicesM sync.Mutex
}
type nativeGamepad struct {
@ -285,7 +289,36 @@ func (g *nativeGamepads) init() {
//export ebitenGamepadMatchingCallback
func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res C.IOReturn, sender unsafe.Pointer, device C.IOHIDDeviceRef) {
if theGamepads.find(func(g *Gamepad) bool {
theGamepads.devicesM.Lock()
defer theGamepads.devicesM.Unlock()
theGamepads.devicesToAdd = append(theGamepads.devicesToAdd, device)
}
//export ebitenGamepadRemovalCallback
func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res C.IOReturn, sender unsafe.Pointer, device C.IOHIDDeviceRef) {
theGamepads.devicesM.Lock()
defer theGamepads.devicesM.Unlock()
theGamepads.devicesToRemove = append(theGamepads.devicesToRemove, device)
}
func (g *nativeGamepads) update() {
theGamepads.devicesM.Lock()
defer theGamepads.devicesM.Unlock()
for _, device := range g.devicesToAdd {
g.addDevice(device)
}
for _, device := range g.devicesToRemove {
g.gamepads.remove(func(g *Gamepad) bool {
return g.device == device
})
}
g.devicesToAdd = g.devicesToAdd[:0]
g.devicesToRemove = g.devicesToRemove[:0]
}
func (g *nativeGamepads) addDevice(device C.IOHIDDeviceRef) {
if g.gamepads.find(func(g *Gamepad) bool {
return g.device == device
}) != nil {
return
@ -331,8 +364,8 @@ func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res C.IOReturn, sender un
elements := C.IOHIDDeviceCopyMatchingElements(device, 0, C.kIOHIDOptionsTypeNone)
defer C.CFRelease(C.CFTypeRef(elements))
g := theGamepads.add(name, sdlID)
g.device = device
gp := g.gamepads.add(name, sdlID)
gp.device = device
for i := C.CFIndex(0); i < C.CFArrayGetCount(elements); i++ {
native := (C.IOHIDElementRef)(C.CFArrayGetValueAtIndex(elements, i))
@ -356,27 +389,27 @@ func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res C.IOReturn, sender un
case C.kHIDUsage_GD_X, C.kHIDUsage_GD_Y, C.kHIDUsage_GD_Z,
C.kHIDUsage_GD_Rx, C.kHIDUsage_GD_Ry, C.kHIDUsage_GD_Rz,
C.kHIDUsage_GD_Slider, C.kHIDUsage_GD_Dial, C.kHIDUsage_GD_Wheel:
g.axes = append(g.axes, element{
gp.axes = append(gp.axes, element{
native: native,
usage: int(usage),
index: len(g.axes),
index: len(gp.axes),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
case C.kHIDUsage_GD_Hatswitch:
g.hats = append(g.hats, element{
gp.hats = append(gp.hats, element{
native: native,
usage: int(usage),
index: len(g.hats),
index: len(gp.hats),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
case C.kHIDUsage_GD_DPadUp, C.kHIDUsage_GD_DPadRight, C.kHIDUsage_GD_DPadDown, C.kHIDUsage_GD_DPadLeft,
C.kHIDUsage_GD_SystemMainMenu, C.kHIDUsage_GD_Select, C.kHIDUsage_GD_Start:
g.buttons = append(g.buttons, element{
gp.buttons = append(gp.buttons, element{
native: native,
usage: int(usage),
index: len(g.buttons),
index: len(gp.buttons),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
@ -384,36 +417,26 @@ func ebitenGamepadMatchingCallback(ctx unsafe.Pointer, res C.IOReturn, sender un
case C.kHIDPage_Simulation:
switch usage {
case C.kHIDUsage_Sim_Accelerator, C.kHIDUsage_Sim_Brake, C.kHIDUsage_Sim_Throttle, C.kHIDUsage_Sim_Rudder, C.kHIDUsage_Sim_Steering:
g.axes = append(g.axes, element{
gp.axes = append(gp.axes, element{
native: native,
usage: int(usage),
index: len(g.axes),
index: len(gp.axes),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
}
case C.kHIDPage_Button, C.kHIDPage_Consumer:
g.buttons = append(g.buttons, element{
gp.buttons = append(gp.buttons, element{
native: native,
usage: int(usage),
index: len(g.buttons),
index: len(gp.buttons),
minimum: int(C.IOHIDElementGetLogicalMin(native)),
maximum: int(C.IOHIDElementGetLogicalMax(native)),
})
}
}
sort.Stable(g.axes)
sort.Stable(g.buttons)
sort.Stable(g.hats)
}
//export ebitenGamepadRemovalCallback
func ebitenGamepadRemovalCallback(ctx unsafe.Pointer, res C.IOReturn, sender unsafe.Pointer, device C.IOHIDDeviceRef) {
theGamepads.remove(func(g *Gamepad) bool {
return g.device == device
})
}
func (g *nativeGamepads) update() {
sort.Stable(gp.axes)
sort.Stable(gp.buttons)
sort.Stable(gp.hats)
}

View File

@ -32,23 +32,11 @@ func (i *Input) updateGamepads() {
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
var gs []driver.GamepadID
i.ui.t.Call(func() {
gs = gamepadpkg.AppendGamepadIDs(gamepadIDs)
})
return gs
}
func (i *Input) gamepad(id driver.GamepadID) *gamepadpkg.Gamepad {
var g *gamepadpkg.Gamepad
i.ui.t.Call(func() {
g = gamepadpkg.Get(id)
})
return g
return gamepadpkg.AppendGamepadIDs(gamepadIDs)
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return ""
}
@ -56,7 +44,7 @@ func (i *Input) GamepadSDLID(id driver.GamepadID) string {
}
func (i *Input) GamepadName(id driver.GamepadID) string {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return ""
}
@ -64,7 +52,7 @@ func (i *Input) GamepadName(id driver.GamepadID) string {
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
@ -72,7 +60,7 @@ func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
@ -80,7 +68,7 @@ func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
@ -90,7 +78,7 @@ func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return false
}
@ -110,7 +98,7 @@ func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.Gamepa
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return false
}
@ -118,7 +106,7 @@ func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
@ -126,7 +114,7 @@ func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.Standa
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return 0
}
@ -134,7 +122,7 @@ func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.St
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return false
}
@ -142,7 +130,7 @@ func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button drive
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
g := i.gamepad(id)
g := gamepadpkg.Get(id)
if g == nil {
return
}