From 1570c506aef4d5af3b7a12b4f4286736794ea6b4 Mon Sep 17 00:00:00 2001 From: Hajime Hoshi Date: Fri, 4 Feb 2022 23:29:09 +0900 Subject: [PATCH] internal/gamepad: port the implementation for Android --- .../gamepad_ios.go => gamepad/api_ios.go} | 192 +++++++--------- internal/gamepad/gamepad_ios.go | 95 ++++++++ internal/gamepad/gamepad_null.go | 7 +- internal/uidriver/mobile/gamepad.go | 11 +- internal/uidriver/mobile/input.go | 3 - internal/uidriver/mobile/inputgamepad_ios.go | 206 ------------------ internal/uidriver/mobile/ui.go | 2 - internal/uidriver/mobile/vibrate_ios.go | 3 +- 8 files changed, 184 insertions(+), 335 deletions(-) rename internal/{uidriver/mobile/gamepad_ios.go => gamepad/api_ios.go} (78%) create mode 100644 internal/gamepad/gamepad_ios.go delete mode 100644 internal/uidriver/mobile/inputgamepad_ios.go diff --git a/internal/uidriver/mobile/gamepad_ios.go b/internal/gamepad/api_ios.go similarity index 78% rename from internal/uidriver/mobile/gamepad_ios.go rename to internal/gamepad/api_ios.go index f47a96358..0fdbd9fed 100644 --- a/internal/uidriver/mobile/gamepad_ios.go +++ b/internal/gamepad/api_ios.go @@ -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 // -// 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) } } diff --git a/internal/gamepad/gamepad_ios.go b/internal/gamepad/gamepad_ios.go new file mode 100644 index 000000000..96b1493e7 --- /dev/null +++ b/internal/gamepad/gamepad_ios.go @@ -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) +} diff --git a/internal/gamepad/gamepad_null.go b/internal/gamepad/gamepad_null.go index eab84e239..4cfb8f354 100644 --- a/internal/gamepad/gamepad_null.go +++ b/internal/gamepad/gamepad_null.go @@ -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 diff --git a/internal/uidriver/mobile/gamepad.go b/internal/uidriver/mobile/gamepad.go index 3bffdf77f..e403d27fc 100644 --- a/internal/uidriver/mobile/gamepad.go +++ b/internal/uidriver/mobile/gamepad.go @@ -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() { -} diff --git a/internal/uidriver/mobile/input.go b/internal/uidriver/mobile/input.go index 96158c60e..115f3682e 100644 --- a/internal/uidriver/mobile/input.go +++ b/internal/uidriver/mobile/input.go @@ -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) { diff --git a/internal/uidriver/mobile/inputgamepad_ios.go b/internal/uidriver/mobile/inputgamepad_ios.go deleted file mode 100644 index dea8b0a1f..000000000 --- a/internal/uidriver/mobile/inputgamepad_ios.go +++ /dev/null @@ -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) -} diff --git a/internal/uidriver/mobile/ui.go b/internal/uidriver/mobile/ui.go index 23aa28f32..4b9886098 100644 --- a/internal/uidriver/mobile/ui.go +++ b/internal/uidriver/mobile/ui.go @@ -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{}{} diff --git a/internal/uidriver/mobile/vibrate_ios.go b/internal/uidriver/mobile/vibrate_ios.go index b9392c840..bd3c72d81 100644 --- a/internal/uidriver/mobile/vibrate_ios.go +++ b/internal/uidriver/mobile/vibrate_ios.go @@ -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 // #import