internal/gamepad: port the implementation for Android

This commit is contained in:
Hajime Hoshi 2022-02-04 23:29:09 +09:00
parent 8d9937801b
commit 1570c506ae
8 changed files with 184 additions and 335 deletions

View File

@ -15,22 +15,14 @@
//go:build ios
// +build ios
package mobile
package gamepad
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework Foundation -framework GameController
//
// #import <GameController/GameController.h>
//
// static NSMutableArray* controllers = nil;
// static NSMutableArray* controllerProperties = nil;
// static NSString* GCInputXboxShareButton = @"Button Share";
// static uint32_t currentId = 0;
//
// static uint32_t genNextId() {
// currentId++;
// return currentId;
// }
//
// enum ControllerButton {
// kControllerButtonInvalid = -1,
@ -85,18 +77,20 @@ package mobile
// };
//
// struct ControllerProperty {
// uint32_t id;
// uint8_t nButtons;
// uint8_t nAxes;
// uint8_t nButtons;
// uint8_t nHats;
// uint16_t buttonMask;
// uint8_t guid[16];
// uint8_t name[256];
// char guid[16];
// char name[256];
// bool hasDualshockTouchpad;
// bool hasXboxPaddles;
// bool hasXboxShareButton;
// };
//
// void ebitenAddGamepad(uintptr_t controller, struct ControllerProperty* prop);
// void ebitenRemoveGamepad(uintptr_t controller);
//
// static size_t min(size_t a, size_t b) {
// return a < b ? a : b;
// }
@ -272,25 +266,13 @@ package mobile
// return;
// }
//
// [controllers addObject:controller];
//
// struct ControllerProperty property = {};
// getControllerPropertyFromController(controller, &property);
// property.id = genNextId();
// NSValue* value = [NSValue valueWithBytes:&property objCType:@encode(struct ControllerProperty)];
// [controllerProperties addObject:value];
// ebitenAddGamepad((uintptr_t)(controller), &property);
// }
//
// static void removeController(GCController* controller) {
// int index = [controllers indexOfObject:controller];
// [controllers removeObjectAtIndex:index];
// // NSValue should be released by the autorelease pool.
// [controllerProperties removeObjectAtIndex:index];
// }
//
// static void getControllerPropertyFromIndex(int index, struct ControllerProperty* property) {
// NSValue* value = [controllerProperties objectAtIndex:index];
// [value getValue:property];
// ebitenRemoveGamepad((uintptr_t)(controller));
// }
//
// struct ControllerState {
@ -314,12 +296,11 @@ package mobile
// return hat;
// }
//
// static void getControllerStateFromIndex(int index, struct ControllerState* controllerState) {
// static void getControllerState(uintptr_t controller_ptr, struct ControllerState* controllerState,
// uint16_t buttonMask, uint8_t nHats,
// bool hasDualshockTouchpad, bool hasXboxPaddles, bool hasXboxShareButton) {
// GCController* controller = (GCController*)(controller_ptr);
// @autoreleasepool {
// GCController* controller = [controllers objectAtIndex:index];
// struct ControllerProperty property = {};
// getControllerPropertyFromIndex(index, &property);
//
// if (controller.extendedGamepad) {
// GCExtendedGamepad* gamepad = controller.extendedGamepad;
//
@ -341,46 +322,46 @@ package mobile
// #pragma clang diagnostic push
// #pragma clang diagnostic ignored "-Wunguarded-availability-new"
//
// if (property.buttonMask & (1 << kControllerButtonLeftStick)) {
// if (buttonMask & (1 << kControllerButtonLeftStick)) {
// controllerState->buttons[buttonCount++] = gamepad.leftThumbstickButton.isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonRightStick)) {
// if (buttonMask & (1 << kControllerButtonRightStick)) {
// controllerState->buttons[buttonCount++] = gamepad.rightThumbstickButton.isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonBack)) {
// if (buttonMask & (1 << kControllerButtonBack)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonOptions.isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonGuide)) {
// if (buttonMask & (1 << kControllerButtonGuide)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonHome.isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonStart)) {
// if (buttonMask & (1 << kControllerButtonStart)) {
// controllerState->buttons[buttonCount++] = gamepad.buttonMenu.isPressed;
// }
//
// if (property.hasDualshockTouchpad) {
// if (hasDualshockTouchpad) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
// }
// if (property.hasXboxPaddles) {
// if (property.buttonMask & (1 << kControllerButtonPaddle1)) {
// if (hasXboxPaddles) {
// if (buttonMask & (1 << kControllerButtonPaddle1)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonPaddle2)) {
// if (buttonMask & (1 << kControllerButtonPaddle2)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonPaddle3)) {
// if (buttonMask & (1 << kControllerButtonPaddle3)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
// }
// if (property.buttonMask & (1 << kControllerButtonPaddle4)) {
// if (buttonMask & (1 << kControllerButtonPaddle4)) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
// }
// }
// if (property.hasXboxShareButton) {
// if (hasXboxShareButton) {
// controllerState->buttons[buttonCount++] = controller.physicalInputProfile.buttons[GCInputXboxShareButton].isPressed;
// }
//
// #pragma clang diagnostic pop
//
// if (property.nHats) {
// if (nHats) {
// controllerState->hat = getHatState(gamepad.dpad);
// }
// }
@ -388,9 +369,6 @@ package mobile
// }
//
// static void initializeGamepads(void) {
// controllers = [NSMutableArray array];
// controllerProperties = [NSMutableArray array];
//
// @autoreleasepool {
// for (GCController* controller in [GCController controllers]) {
// addController(controller);
@ -410,77 +388,73 @@ package mobile
// }];
// }
// }
//
// static int getControllersNum(void) {
// return [controllers count];
// }
import "C"
import (
"encoding/hex"
//export ebitenAddGamepad
func ebitenAddGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
theGamepads.addIOSGamepad(controller, prop)
}
"github.com/hajimehoshi/ebiten/v2/internal/driver"
)
//export ebitenRemoveGamepad
func ebitenRemoveGamepad(controller C.uintptr_t) {
theGamepads.removeIOSGamepad(controller)
}
func init() {
func (g *gamepads) addIOSGamepad(controller C.uintptr_t, prop *C.struct_ControllerProperty) {
g.m.Lock()
defer g.m.Unlock()
name := C.GoString(&prop.name[0])
sdlID := C.GoStringN(&prop.guid[0], 16)
gp := g.add(name, sdlID)
gp.controller = uintptr(controller)
gp.axes = make([]float64, prop.nAxes)
gp.buttons = make([]bool, prop.nButtons+prop.nHats*4)
gp.hats = make([]int, prop.nHats)
gp.buttonMask = uint16(prop.buttonMask)
gp.hasDualshockTouchpad = bool(prop.hasDualshockTouchpad)
gp.hasXboxPaddles = bool(prop.hasXboxPaddles)
gp.hasXboxShareButton = bool(prop.hasXboxShareButton)
}
func (g *gamepads) removeIOSGamepad(controller C.uintptr_t) {
g.m.Lock()
defer g.m.Unlock()
g.remove(func(gamepad *Gamepad) bool {
return gamepad.controller == uintptr(controller)
})
}
func initializeIOSGamepads() {
C.initializeGamepads()
}
func (i *Input) updateGamepads() {
i.gamepads = i.gamepads[:0]
func (g *nativeGamepad) updateIOSGamepad() {
var state C.struct_ControllerState
C.getControllerState(C.uintptr_t(g.controller), &state, C.uint16_t(g.buttonMask), C.uint8_t(len(g.hats)),
C.bool(g.hasDualshockTouchpad), C.bool(g.hasXboxPaddles), C.bool(g.hasXboxShareButton))
num := int(C.getControllersNum())
for idx := 0; idx < num; idx++ {
var property C.struct_ControllerProperty
C.getControllerPropertyFromIndex(C.int(idx), &property)
var guid [16]byte
for i := range property.guid {
guid[i] = byte(property.guid[i])
}
nButtons := len(g.buttons) - len(g.hats)*4
for i := 0; i < nButtons; i++ {
g.buttons[i] = state.buttons[i] != 0
}
name := make([]byte, 0, len(property.name))
for _, c := range property.name {
if c == 0 {
break
}
name = append(name, byte(c))
}
// Follow the GLFW way to process hats.
// See _glfwInputJoystickHat.
if len(g.hats) > 0 {
base := len(g.buttons) - len(g.hats)*4
g.buttons[base] = state.hat&0x01 != 0
g.buttons[base+1] = state.hat&0x02 != 0
g.buttons[base+2] = state.hat&0x04 != 0
g.buttons[base+3] = state.hat&0x08 != 0
}
gamepad := Gamepad{
ID: driver.GamepadID(property.id),
SDLID: hex.EncodeToString(guid[:]),
Name: string(name),
ButtonNum: int(property.nButtons),
AxisNum: int(property.nAxes),
HatNum: int(property.nHats),
}
for i := range g.axes {
g.axes[i] = float64(state.axes[i])
}
var state C.struct_ControllerState
C.getControllerStateFromIndex(C.int(idx), &state)
for j := 0; j < gamepad.ButtonNum; j++ {
gamepad.Buttons[j] = state.buttons[j] != 0
}
// Follow the GLFW way to process hats.
// See _glfwInputJoystickHat.
if property.nHats > 0 {
base := gamepad.ButtonNum
gamepad.ButtonNum += 4
gamepad.Buttons[base] = state.hat&0x01 != 0
gamepad.Buttons[base+1] = state.hat&0x02 != 0
gamepad.Buttons[base+2] = state.hat&0x04 != 0
gamepad.Buttons[base+3] = state.hat&0x08 != 0
}
for j := 0; j < gamepad.AxisNum; j++ {
gamepad.Axes[j] = float32(state.axes[j])
}
if gamepad.HatNum > 0 {
gamepad.Hats[0] = int(state.hat)
}
i.gamepads = append(i.gamepads, gamepad)
if len(g.hats) > 0 {
g.hats[0] = int(state.hat)
}
}

View File

@ -0,0 +1,95 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ios
// +build ios
package gamepad
import (
"time"
)
type nativeGamepads struct{}
func (*nativeGamepads) init(gamepads *gamepads) error {
initializeIOSGamepads()
return nil
}
func (*nativeGamepads) update(gamepads *gamepads) error {
return nil
}
type nativeGamepad struct {
controller uintptr
buttonMask uint16
hasDualshockTouchpad bool
hasXboxPaddles bool
hasXboxShareButton bool
axes []float64
buttons []bool
hats []int
}
func (g *nativeGamepad) update(gamepad *gamepads) error {
g.updateIOSGamepad()
return nil
}
func (*nativeGamepad) hasOwnStandardLayoutMapping() bool {
return false
}
func (g *nativeGamepad) axisCount() int {
return len(g.axes)
}
func (g *nativeGamepad) buttonCount() int {
return len(g.buttons)
}
func (g *nativeGamepad) hatCount() int {
return len(g.hats)
}
func (g *nativeGamepad) axisValue(axis int) float64 {
if axis < 0 || axis >= len(g.axes) {
return 0
}
return g.axes[axis]
}
func (g *nativeGamepad) isButtonPressed(button int) bool {
if button < 0 || button >= len(g.buttons) {
return false
}
return g.buttons[button]
}
func (*nativeGamepad) buttonValue(button int) float64 {
panic("gamepad: buttonValue is not implemented")
}
func (g *nativeGamepad) hatState(hat int) int {
if hat < 0 || hat >= len(g.hats) {
return 0
}
return g.hats[hat]
}
func (g *nativeGamepad) vibrate(duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -12,11 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build (!darwin || ios) && !js && !linux && !windows
// +build !darwin ios
// +build !js
// +build !linux
// +build !windows
//go:build !darwin && !js && !linux && !windows
// +build !darwin,!js,!linux,!windows
package gamepad

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build android
// +build android
//go:build android || ios
// +build android ios
package mobile
@ -115,10 +115,3 @@ func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, stro
}
g.Vibrate(duration, strongMagnitude, weakMagnitude)
}
// TODO: Remove this struct after porting the gamepad part of iOS to internal/gamepad.
type Gamepad struct{}
// TODO: Remove this function after porting the gamepad part of iOS to internal/gamepad.
func (i *Input) updateGamepads() {
}

View File

@ -26,9 +26,6 @@ type Input struct {
runes []rune
touches []Touch
ui *UserInterface
// gamepads is used only on iOS.
gamepads []Gamepad
}
func (i *Input) CursorPosition() (x, y int) {

View File

@ -1,206 +0,0 @@
// Copyright 2022 The Ebiten Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build ios
// +build ios
package mobile
import (
"time"
"github.com/hajimehoshi/ebiten/v2/internal/driver"
"github.com/hajimehoshi/ebiten/v2/internal/gamepaddb"
)
type Gamepad struct {
ID driver.GamepadID
SDLID string
Name string
Buttons [driver.GamepadButtonNum]bool
ButtonNum int
Axes [32]float32
AxisNum int
Hats [16]int
HatNum int
}
type gamepadState struct {
g *Gamepad
}
func (s gamepadState) Axis(index int) float64 {
return float64(s.g.Axes[index])
}
func (s gamepadState) Button(index int) bool {
return s.g.Buttons[index]
}
func (s gamepadState) Hat(index int) int {
return s.g.Hats[index]
}
func (i *Input) AppendGamepadIDs(gamepadIDs []driver.GamepadID) []driver.GamepadID {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
gamepadIDs = append(gamepadIDs, g.ID)
}
return gamepadIDs
}
func (i *Input) GamepadSDLID(id driver.GamepadID) string {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.SDLID
}
return ""
}
func (i *Input) GamepadName(id driver.GamepadID) string {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if name := gamepaddb.Name(g.SDLID); name != "" {
return name
}
return g.Name
}
return ""
}
func (i *Input) GamepadAxisNum(id driver.GamepadID) int {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.AxisNum
}
return 0
}
func (i *Input) GamepadAxisValue(id driver.GamepadID, axis int) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.AxisNum <= int(axis) {
return 0
}
return float64(g.Axes[axis])
}
return 0
}
func (i *Input) GamepadButtonNum(id driver.GamepadID) int {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return g.ButtonNum
}
return 0
}
func (i *Input) IsGamepadButtonPressed(id driver.GamepadID, button driver.GamepadButton) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
if g.ButtonNum <= int(button) {
return false
}
return g.Buttons[button]
}
return false
}
func (i *Input) IsStandardGamepadLayoutAvailable(id driver.GamepadID) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.HasStandardLayoutMapping(g.SDLID)
}
return false
}
func (i *Input) IsStandardGamepadButtonPressed(id driver.GamepadID, button driver.StandardGamepadButton) bool {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.IsButtonPressed(g.SDLID, button, gamepadState{&g})
}
return false
}
func (i *Input) StandardGamepadButtonValue(id driver.GamepadID, button driver.StandardGamepadButton) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.ButtonValue(g.SDLID, button, gamepadState{&g})
}
return 0
}
func (i *Input) StandardGamepadAxisValue(id driver.GamepadID, axis driver.StandardGamepadAxis) float64 {
i.ui.m.RLock()
defer i.ui.m.RUnlock()
for _, g := range i.gamepads {
if g.ID != id {
continue
}
return gamepaddb.AxisValue(g.SDLID, axis, gamepadState{&g})
}
return 0
}
func (i *Input) VibrateGamepad(id driver.GamepadID, duration time.Duration, strongMagnitude float64, weakMagnitude float64) {
// TODO: Implement this (#1452)
}

View File

@ -84,8 +84,6 @@ func (u *UserInterface) Update() error {
return nil
}
// TODO: Remove this call after porting the gamepad part of iOS to internal/gamepad.
u.input.updateGamepads()
gamepad.Update()
renderCh <- struct{}{}

View File

@ -17,7 +17,8 @@
package mobile
// #cgo LDFLAGS: -framework AVFoundation -framework CoreHaptics
// #cgo CFLAGS: -x objective-c
// #cgo LDFLAGS: -framework AVFoundation -framework CoreHaptics -framework Foundation
//
// #import <AVFoundation/AVFoundation.h>
// #import <CoreHaptics/CoreHaptics.h>