Implement controller rumble support in Gamepad (#1739)

* Add SDL rumble

* Fix rumble

* whitespace fixins real (#2)

* whitespacing real.. please!

* nativecffi rumble thing fix lol

* Remove trailing whitespace.

* Use SDL's argument names and order.

* Standardize formatting.

* Make `SDLGamepad` fully static again, for simplicity.

Also, consistently use `find()` instead of array access, to avoid accidentally creating entries.

Also also, consistently use guard clauses instead of indenting.

* Make another guard clause.

* Update CFFI function signature.

* Use `clamp()` instead of `if` statements.

* Include required header for `std::clamp()`.

* Revert "Use `clamp()` instead of `if` statements."

`std::clamp()` was not available until C++17, and we'd like to continue supporting older versions.

This reverts commit 715a270f79.

* Revert "Include required header for `std::clamp()`."

This reverts commit f47aebf640.

* Tidy up.

* Document `Gamepad.rumble()`'s arguments.

* Don't limit rumble duration.

SDL apparently supports the full Uint32 range, so there's no reason for Lime to restrict it.

* Fix whitespace.

* Add rumble support in HTML5 (experimental).

---------

Co-authored-by: Cameron Taylor <cameron.taylor.ninja@gmail.com>
Co-authored-by: player-03 <player3.14@gmail.com>
This commit is contained in:
Regan Green
2025-09-01 00:29:58 -04:00
committed by GitHub
parent 5b05c3f77e
commit c8beb396f6
6 changed files with 129 additions and 46 deletions

View File

@@ -12,6 +12,7 @@ namespace lime {
static void AddMapping (const char* content); static void AddMapping (const char* content);
static const char* GetDeviceGUID (int id); static const char* GetDeviceGUID (int id);
static const char* GetDeviceName (int id); static const char* GetDeviceName (int id);
static void Rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration);
}; };

View File

@@ -1765,6 +1765,20 @@ namespace lime {
} }
void lime_gamepad_rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) {
Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration);
}
HL_PRIM void HL_NAME(hl_gamepad_rumble) (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) {
Gamepad::Rumble (id, lowFrequencyRumble, highFrequencyRumble, duration);
}
value lime_gzip_compress (value buffer, value bytes) { value lime_gzip_compress (value buffer, value bytes) {
#ifdef LIME_ZLIB #ifdef LIME_ZLIB
@@ -4062,6 +4076,7 @@ namespace lime {
DEFINE_PRIME2v (lime_gamepad_event_manager_register); DEFINE_PRIME2v (lime_gamepad_event_manager_register);
DEFINE_PRIME1 (lime_gamepad_get_device_guid); DEFINE_PRIME1 (lime_gamepad_get_device_guid);
DEFINE_PRIME1 (lime_gamepad_get_device_name); DEFINE_PRIME1 (lime_gamepad_get_device_name);
DEFINE_PRIME4v (lime_gamepad_rumble);
DEFINE_PRIME2 (lime_gzip_compress); DEFINE_PRIME2 (lime_gzip_compress);
DEFINE_PRIME2 (lime_gzip_decompress); DEFINE_PRIME2 (lime_gzip_decompress);
DEFINE_PRIME2v (lime_haptic_vibrate); DEFINE_PRIME2v (lime_haptic_vibrate);
@@ -4255,6 +4270,7 @@ namespace lime {
DEFINE_HL_PRIM (_VOID, hl_gamepad_event_manager_register, _FUN(_VOID, _NO_ARG) _TGAMEPAD_EVENT); DEFINE_HL_PRIM (_VOID, hl_gamepad_event_manager_register, _FUN(_VOID, _NO_ARG) _TGAMEPAD_EVENT);
DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_guid, _I32); DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_guid, _I32);
DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_name, _I32); DEFINE_HL_PRIM (_BYTES, hl_gamepad_get_device_name, _I32);
DEFINE_HL_PRIM (_VOID, hl_gamepad_rumble, _I32 _I32 _F64 _F64);
DEFINE_HL_PRIM (_TBYTES, hl_gzip_compress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_gzip_compress, _TBYTES _TBYTES);
DEFINE_HL_PRIM (_TBYTES, hl_gzip_decompress, _TBYTES _TBYTES); DEFINE_HL_PRIM (_TBYTES, hl_gzip_decompress, _TBYTES _TBYTES);
DEFINE_HL_PRIM (_VOID, hl_haptic_vibrate, _I32 _I32); DEFINE_HL_PRIM (_VOID, hl_haptic_vibrate, _I32 _I32);

View File

@@ -4,48 +4,40 @@
namespace lime { namespace lime {
std::map<int, SDL_GameController*> gameControllers = std::map<int, SDL_GameController*> (); std::map<int, SDL_GameController*> gameControllers;
std::map<int, int> gameControllerIDs = std::map<int, int> (); std::map<int, int> gameControllerIDs;
bool SDLGamepad::Connect (int deviceID) { bool SDLGamepad::Connect (int deviceID) {
if (SDL_IsGameController (deviceID)) { if (!SDL_IsGameController (deviceID))
return false;
SDL_GameController *gameController = SDL_GameControllerOpen (deviceID); SDL_GameController *gameController = SDL_GameControllerOpen (deviceID);
if (gameController == nullptr)
return false;
if (gameController) { SDL_Joystick *joystick = SDL_GameControllerGetJoystick (gameController);
int id = SDL_JoystickInstanceID (joystick);
SDL_Joystick *joystick = SDL_GameControllerGetJoystick (gameController); gameControllers[id] = gameController;
int id = SDL_JoystickInstanceID (joystick); gameControllerIDs[deviceID] = id;
gameControllers[id] = gameController; return true;
gameControllerIDs[deviceID] = id;
return true;
}
}
return false;
} }
bool SDLGamepad::Disconnect (int id) { bool SDLGamepad::Disconnect (int id) {
if (gameControllers.find (id) != gameControllers.end ()) { auto it = gameControllers.find (id);
if (it == gameControllers.end ())
return false;
SDL_GameController *gameController = gameControllers[id]; SDL_GameControllerClose (it->second);
SDL_GameControllerClose (gameController); gameControllers.erase (id);
gameControllers.erase (id);
return true; return true;
}
return false;
} }
@@ -66,26 +58,51 @@ namespace lime {
const char* Gamepad::GetDeviceGUID (int id) { const char* Gamepad::GetDeviceGUID (int id) {
SDL_Joystick* joystick = SDL_GameControllerGetJoystick (gameControllers[id]); auto it = gameControllers.find (id);
if (it == gameControllers.end ())
return nullptr;
if (joystick) { SDL_Joystick* joystick = SDL_GameControllerGetJoystick (it->second);
if (joystick == nullptr)
return nullptr;
char* guid = new char[64]; char* guid = new char[64];
SDL_JoystickGetGUIDString (SDL_JoystickGetGUID (joystick), guid, 64); SDL_JoystickGetGUIDString (SDL_JoystickGetGUID (joystick), guid, 64);
return guid; return guid;
}
return 0;
} }
const char* Gamepad::GetDeviceName (int id) { const char* Gamepad::GetDeviceName (int id) {
return SDL_GameControllerName (gameControllers[id]); auto it = gameControllers.find (id);
if (it == gameControllers.end ())
return nullptr;
return SDL_GameControllerName (it->second);
} }
} void Gamepad::Rumble (int id, double lowFrequencyRumble, double highFrequencyRumble, int duration) {
auto it = gameControllers.find (id);
if (it == gameControllers.end ())
return;
if (highFrequencyRumble < 0.0f)
highFrequencyRumble = 0.0f;
else if (highFrequencyRumble > 1.0f)
highFrequencyRumble = 1.0f;
if (lowFrequencyRumble < 0.0f)
lowFrequencyRumble = 0.0f;
else if (lowFrequencyRumble > 1.0f)
lowFrequencyRumble = 1.0f;
SDL_GameControllerRumble (it->second, lowFrequencyRumble * 0xFFFF, highFrequencyRumble * 0xFFFF, duration);
}
}

View File

@@ -161,6 +161,8 @@ class NativeCFFI
@:cffi private static function lime_gamepad_get_device_name(id:Int):Dynamic; @:cffi private static function lime_gamepad_get_device_name(id:Int):Dynamic;
@:cffi private static function lime_gamepad_rumble(id:Int, lowFrequencyRumble:Float, highFrequencyRumble:Float, duration:Int):Void;
@:cffi private static function lime_gamepad_event_manager_register(callback:Dynamic, eventObject:Dynamic):Void; @:cffi private static function lime_gamepad_event_manager_register(callback:Dynamic, eventObject:Dynamic):Void;
@:cffi private static function lime_gzip_compress(data:Dynamic, bytes:Dynamic):Dynamic; @:cffi private static function lime_gzip_compress(data:Dynamic, bytes:Dynamic):Dynamic;
@@ -458,6 +460,8 @@ class NativeCFFI
false)); false));
private static var lime_gamepad_get_device_name = new cpp.Callable<Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_gamepad_get_device_name", "io", private static var lime_gamepad_get_device_name = new cpp.Callable<Int->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_gamepad_get_device_name", "io",
false)); false));
private static var lime_gamepad_rumble = new cpp.Callable<Int->Float->Float->Int->cpp.Void>(cpp.Prime._loadPrime("lime", "lime_gamepad_rumble", "iddiv",
false));
private static var lime_gamepad_event_manager_register = new cpp.Callable<cpp.Object->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime", private static var lime_gamepad_event_manager_register = new cpp.Callable<cpp.Object->cpp.Object->cpp.Void>(cpp.Prime._loadPrime("lime",
"lime_gamepad_event_manager_register", "oov", false)); "lime_gamepad_event_manager_register", "oov", false));
private static var lime_gzip_compress = new cpp.Callable<cpp.Object->cpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_gzip_compress", "ooo", private static var lime_gzip_compress = new cpp.Callable<cpp.Object->cpp.Object->cpp.Object>(cpp.Prime._loadPrime("lime", "lime_gzip_compress", "ooo",
@@ -693,6 +697,7 @@ class NativeCFFI
private static var lime_gamepad_add_mappings = CFFI.load("lime", "lime_gamepad_add_mappings", 1); private static var lime_gamepad_add_mappings = CFFI.load("lime", "lime_gamepad_add_mappings", 1);
private static var lime_gamepad_get_device_guid = CFFI.load("lime", "lime_gamepad_get_device_guid", 1); private static var lime_gamepad_get_device_guid = CFFI.load("lime", "lime_gamepad_get_device_guid", 1);
private static var lime_gamepad_get_device_name = CFFI.load("lime", "lime_gamepad_get_device_name", 1); private static var lime_gamepad_get_device_name = CFFI.load("lime", "lime_gamepad_get_device_name", 1);
private static var lime_gamepad_rumble = CFFI.load("lime", "lime_gamepad_rumble", 4);
private static var lime_gamepad_event_manager_register = CFFI.load("lime", "lime_gamepad_event_manager_register", 2); private static var lime_gamepad_event_manager_register = CFFI.load("lime", "lime_gamepad_event_manager_register", 2);
private static var lime_gzip_compress = CFFI.load("lime", "lime_gzip_compress", 2); private static var lime_gzip_compress = CFFI.load("lime", "lime_gzip_compress", 2);
private static var lime_gzip_decompress = CFFI.load("lime", "lime_gzip_decompress", 2); private static var lime_gzip_decompress = CFFI.load("lime", "lime_gzip_decompress", 2);
@@ -1036,6 +1041,8 @@ class NativeCFFI
return null; return null;
} }
@:hlNative("lime", "hl_gamepad_rumble") private static function lime_gamepad_rumble(id:Int, lowFrequencyRumble:Float, highFrequencyRumble:Float, duration:Int):Void {}
@:hlNative("lime", "hl_gamepad_event_manager_register") private static function lime_gamepad_event_manager_register(callback:Void->Void, @:hlNative("lime", "hl_gamepad_event_manager_register") private static function lime_gamepad_event_manager_register(callback:Void->Void,
eventObject:GamepadEventInfo):Void {} eventObject:GamepadEventInfo):Void {}

View File

@@ -24,10 +24,19 @@ class Gamepad
public var onButtonUp = new Event<GamepadButton->Void>(); public var onButtonUp = new Event<GamepadButton->Void>();
public var onDisconnect = new Event<Void->Void>(); public var onDisconnect = new Event<Void->Void>();
#if (js && html5)
private var __jsGamepad:js.html.Gamepad;
#end
public function new(id:Int) public function new(id:Int)
{ {
this.id = id; this.id = id;
connected = true; connected = true;
#if (js && html5)
var devices = Joystick.__getDeviceData();
__jsGamepad = devices[this.id];
#end
} }
public static function addMappings(mappings:Array<String>):Void public static function addMappings(mappings:Array<String>):Void
@@ -43,6 +52,41 @@ class Gamepad
#end #end
} }
/**
@param lowFrequencyRumble The intensity of the low frequency (strong)
rumble motor, from 0 to 1.
@param highFrequencyRumble The intensity of the high frequency (weak)
rumble motor, from 0 to 1. Will be ignored in situations where only one
motor is available.
@param duration The duration of the rumble effect, in milliseconds.
**/
public inline function rumble(lowFrequencyRumble:Float, highFrequencyRumble:Float, duration:Int):Void
{
#if (lime_cffi && !macro)
NativeCFFI.lime_gamepad_rumble(this.id, lowFrequencyRumble, highFrequencyRumble, duration);
#elseif (js && html5)
var actuator:Dynamic = (untyped __jsGamepad.vibrationActuator) ? untyped __jsGamepad.vibrationActuator
: (untyped __jsGamepad.hapticActuators) ? untyped __jsGamepad.hapticActuators[0]
: null;
if (actuator == null) return;
if (untyped actuator.playEffect)
{
untyped actuator.playEffect("dual-rumble", {
duration: duration,
strongMagnitude: lowFrequencyRumble,
weakMagnitude: highFrequencyRumble
});
}
else if (untyped actuator.pulse)
{
untyped actuator.pulse(lowFrequencyRumble, duration);
}
#else
return;
#end
}
@:noCompletion private static function __connect(id:Int):Void @:noCompletion private static function __connect(id:Int):Void
{ {
if (!devices.exists(id)) if (!devices.exists(id))
@@ -67,8 +111,7 @@ class Gamepad
#if (lime_cffi && !macro) #if (lime_cffi && !macro)
return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_guid(this.id)); return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_guid(this.id));
#elseif (js && html5) #elseif (js && html5)
var devices = Joystick.__getDeviceData(); return __jsGamepad.id;
return devices[this.id].id;
#else #else
return null; return null;
#end #end
@@ -79,8 +122,7 @@ class Gamepad
#if (lime_cffi && !macro) #if (lime_cffi && !macro)
return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_name(this.id)); return CFFI.stringValue(NativeCFFI.lime_gamepad_get_device_name(this.id));
#elseif (js && html5) #elseif (js && html5)
var devices = Joystick.__getDeviceData(); return __jsGamepad.id;
return devices[this.id].id;
#else #else
return null; return null;
#end #end

View File

@@ -52,11 +52,11 @@ class Joystick
} }
#if (js && html5) #if (js && html5)
@:noCompletion private static function __getDeviceData():Array<Dynamic> @:noCompletion private static function __getDeviceData():Array<js.html.Gamepad>
{ {
var res:Dynamic = null; var res:Array<js.html.Gamepad> = null;
try try
{ {
res = (untyped navigator.getGamepads) ? untyped navigator.getGamepads() : (untyped navigator.webkitGetGamepads) ? untyped navigator.webkitGetGamepads() : null; res = (untyped navigator.getGamepads) ? untyped navigator.getGamepads() : (untyped navigator.webkitGetGamepads) ? untyped navigator.webkitGetGamepads() : null;
} }
@@ -65,7 +65,7 @@ class Joystick
// if something went wrong, treat it the same as when navigator.getGamepads doesn't exist // if something went wrong, treat it the same as when navigator.getGamepads doesn't exist
// we probably don't have permission to use this feature // we probably don't have permission to use this feature
} }
return res; return res;
} }
#end #end