Fix for virtual controller detach | pass gamepad haptic engine

This commit is contained in:
Daniil Vinogradov 2025-02-16 11:06:45 +01:00
parent ac4e5d394e
commit 500f3d5b9e
3 changed files with 35 additions and 12 deletions

View File

@ -11,15 +11,22 @@ import GameController
class NativeController: Hashable {
private var instanceID: SDL_JoystickID = -1
private var controller: OpaquePointer?
private let nativeController: GCController
private var nativeController: GCController
private let controllerHaptics: CHHapticEngine?
public var controllername: String { "GC - \(nativeController.vendorName ?? "Unknown")" }
init(_ controller: GCController) {
nativeController = controller
controllerHaptics = nativeController.haptics?.createEngine(withLocality: .default)
try? controllerHaptics?.start()
setupHandheldController()
}
deinit {
cleanup()
}
private func setupHandheldController() {
if SDL_WasInit(Uint32(SDL_INIT_GAMECONTROLLER)) == 0 {
SDL_InitSubSystem(Uint32(SDL_INIT_GAMECONTROLLER))
@ -37,7 +44,7 @@ class NativeController: Hashable {
button_mask: 0,
axis_mask: 0,
name: (controllername as NSString).utf8String,
userdata: nil,
userdata: Unmanaged.passUnretained(self).toOpaque(),
Update: { userdata in
// Update joystick state here
},
@ -46,7 +53,9 @@ class NativeController: Hashable {
},
Rumble: { userdata, lowFreq, highFreq in
print("Rumble with \(lowFreq), \(highFreq)")
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq))
guard let userdata else { return 0 }
let _self = Unmanaged<NativeController>.fromOpaque(userdata).takeUnretainedValue()
VirtualController.rumble(lowFreq: Float(lowFreq), highFreq: Float(highFreq), engine: _self.controllerHaptics)
return 0
},
RumbleTriggers: { userdata, leftRumble, rightRumble in
@ -211,16 +220,13 @@ class NativeController: Hashable {
}
func cleanup() {
if let controller = controller {
if let controller {
SDL_JoystickDetachVirtual(instanceID)
SDL_GameControllerClose(controller)
self.controller = nil
}
}
deinit {
cleanup()
}
func hash(into hasher: inout Hasher) {
hasher.combine(nativeController)
}

View File

@ -78,7 +78,7 @@ class VirtualController {
}
}
static func rumble(lowFreq: Float, highFreq: Float) {
static func rumble(lowFreq: Float, highFreq: Float, engine: CHHapticEngine? = nil) {
do {
// Low-frequency haptic pattern
let lowFreqPattern = try CHHapticPattern(events: [
@ -96,9 +96,23 @@ class VirtualController {
], relativeTime: 0.2, duration: 0.2)
], parameters: [])
// Create and start the haptic engine
let engine = try CHHapticEngine()
try engine.start()
// Mutable engine
var engine = engine
// If no engine passed, use device engine
if engine == nil {
// Create and start the haptic engine
if hapticEngine == nil {
hapticEngine = try CHHapticEngine()
try hapticEngine?.start()
}
engine = hapticEngine
}
guard let engine else {
return print("Error creating haptic patterns: hapticEngine is nil")
}
// Create and play the low-frequency player
let lowFreqPlayer = try engine.makePlayer(with: lowFreqPattern)
@ -113,6 +127,8 @@ class VirtualController {
}
}
private static var hapticEngine: CHHapticEngine?
func updateAxisValue(value: Sint16, forAxis axis: SDL_GameControllerAxis) {
guard controller != nil else { return }

View File

@ -165,6 +165,7 @@ struct ContentView: View {
queue: .main) { notification in
if let controller = notification.object as? GCController {
print("Controller disconnected: \(controller.productCategory)")
nativeControllers[controller]?.cleanup()
nativeControllers[controller] = nil
refreshControllersList()
}