diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift index 1660c8cf6..34e022536 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/NativeController.swift @@ -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.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) } diff --git a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/VirtualController.swift b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/VirtualController.swift index e35e6f603..7fa5c33c1 100644 --- a/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/VirtualController.swift +++ b/src/MeloNX/MeloNX/App/Core/Ryujinx/Controller/VirtualController.swift @@ -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 } diff --git a/src/MeloNX/MeloNX/App/Views/ContentView.swift b/src/MeloNX/MeloNX/App/Views/ContentView.swift index 1f39d2c0f..9e365c251 100644 --- a/src/MeloNX/MeloNX/App/Views/ContentView.swift +++ b/src/MeloNX/MeloNX/App/Views/ContentView.swift @@ -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() }