Refactor gamepad polling to use Strudel's signal system

- Replace requestAnimationFrame polling with signal
- individual signals for buttons and axes from baseSignal
- Remove window message-based gamepad state handling for toggleButtons
- Add module-level state storage to keep button state across strudel code update
This commit is contained in:
nkymut 2025-01-26 02:04:13 +08:00
parent 0be9f439bc
commit bb56ed479f

View file

@ -76,6 +76,8 @@ class ButtonSequenceDetector {
? targetSequence.toLowerCase().split('') ? targetSequence.toLowerCase().split('')
: targetSequence.map((s) => s.toString().toLowerCase()); : targetSequence.map((s) => s.toString().toLowerCase());
//console.log(this.sequence);
// Get the last n inputs where n is the target sequence length // Get the last n inputs where n is the target sequence length
const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input); const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input);
@ -103,7 +105,6 @@ class GamepadHandler {
this._axes = [0, 0, 0, 0]; this._axes = [0, 0, 0, 0];
this._buttons = Array(16).fill(0); this._buttons = Array(16).fill(0);
this.setupEventListeners(); this.setupEventListeners();
this.startPolling();
} }
setupEventListeners() { setupEventListeners() {
@ -122,20 +123,16 @@ class GamepadHandler {
}); });
} }
startPolling() { poll() {
const poll = () => { if (this._activeGamepad !== null) {
if (this._activeGamepad !== null) { const gamepad = navigator.getGamepads()[this._activeGamepad];
const gamepad = navigator.getGamepads()[this._activeGamepad]; if (gamepad) {
if (gamepad) { // Update axes (normalized to 0-1 range)
// Update axes (normalized to 0-1 range) this._axes = gamepad.axes.map((axis) => (axis + 1) / 2);
this._axes = gamepad.axes.map((axis) => (axis + 1) / 2); // Update buttons
// Update buttons this._buttons = gamepad.buttons.map((button) => button.value);
this._buttons = gamepad.buttons.map((button) => button.value);
}
} }
requestAnimationFrame(poll); }
};
poll();
} }
getAxes() { getAxes() {
@ -146,25 +143,33 @@ class GamepadHandler {
} }
} }
// Store gamepadValues globally // Module-level state store for toggle states
export const gamepadValues = {}; const gamepadStates = new Map();
// Replace singleton with factory function
export const gamepad = (index = 0) => { export const gamepad = (index = 0) => {
const handler = new GamepadHandler(index); const handler = new GamepadHandler(index);
const sequenceDetector = new ButtonSequenceDetector(2000); const sequenceDetector = new ButtonSequenceDetector(2000);
// Initialize state for this gamepad if it doesn't exist // Base signal that polls gamepad state and handles sequence detection
if (!gamepadValues[index]) { const baseSignal = signal((t) => {
gamepadValues[index] = Array(16).fill(0); handler.poll();
} const axes = handler.getAxes();
const buttons = handler.getButtons();
// Create signals for this specific gamepad instance // Add all button inputs to sequence detector
buttons.forEach((value, i) => {
sequenceDetector.addInput(i, value);
});
return { axes, buttons, t };
});
// Create axes patterns
const axes = { const axes = {
x1: signal(() => handler.getAxes()[0]), x1: baseSignal.fmap((state) => state.axes[0]),
y1: signal(() => handler.getAxes()[1]), y1: baseSignal.fmap((state) => state.axes[1]),
x2: signal(() => handler.getAxes()[2]), x2: baseSignal.fmap((state) => state.axes[2]),
y2: signal(() => handler.getAxes()[3]), y2: baseSignal.fmap((state) => state.axes[3]),
}; };
// Add bipolar versions // Add bipolar versions
@ -173,39 +178,48 @@ export const gamepad = (index = 0) => {
axes.x2_2 = axes.x2.toBipolar(); axes.x2_2 = axes.x2.toBipolar();
axes.y2_2 = axes.y2.toBipolar(); axes.y2_2 = axes.y2.toBipolar();
// Create button signals // Create button patterns
const buttons = Array(16) const buttons = Array(16)
.fill(null) .fill(null)
.map((_, i) => { .map((_, i) => {
const btn = signal(() => { // Create unique key for this gamepad+button combination
const value = handler.getButtons()[i]; const stateKey = `gamepad${index}_btn${i}`;
sequenceDetector.addInput(i, value);
return value; // Initialize toggle state if it doesn't exist
}); if (!gamepadStates.has(stateKey)) {
let lastButtonState = 0; gamepadStates.set(stateKey, {
const toggle = signal(() => { lastButtonState: 0,
const currentState = handler.getButtons()[i]; toggleState: 0,
if (currentState === 1 && lastButtonState === 0) { });
// Toggle the state }
const newValue = gamepadValues[index][i] === 0 ? 1 : 0;
gamepadValues[index][i] = newValue; // Direct button value pattern (no longer needs to call addInput)
// Broadcast the change const btn = baseSignal.fmap((state) => state.buttons[i]);
window.postMessage({
type: 'gamepad-toggle', // Button toggle pattern with persistent state
gamepadIndex: index, const toggle = baseSignal.fmap((state) => {
buttonIndex: i, const currentState = state.buttons[i];
value: newValue, const buttonState = gamepadStates.get(stateKey);
});
if (currentState === 1 && buttonState.lastButtonState === 0) {
// Toggle the state on rising edge
buttonState.toggleState = buttonState.toggleState === 0 ? 1 : 0;
} }
lastButtonState = currentState;
return gamepadValues[index][i]; buttonState.lastButtonState = currentState;
return buttonState.toggleState;
}); });
return { value: btn, toggle }; return { value: btn, toggle };
}); });
const checkSequence = (sequence) => { // Create sequence checker pattern
return signal(() => sequenceDetector.checkSequence(sequence)); const btnSequence = (sequence) => {
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
}; };
const checkSequence = btnSequence;
const btnSeq = btnSequence;
const btnseq = btnSeq;
// Return an object with all controls // Return an object with all controls
return { return {
@ -220,18 +234,13 @@ export const gamepad = (index = 0) => {
]), ]),
), ),
checkSequence, checkSequence,
btnSequence,
btnSeq,
btnseq,
raw: baseSignal,
}; };
}; };
// Message-based state updates (add this at the bottom of the file) // Optional: Export for debugging or state management
if (typeof window !== 'undefined') { export const getGamepadStates = () => Object.fromEntries(gamepadStates);
window.addEventListener('message', (e) => { export const clearGamepadStates = () => gamepadStates.clear();
if (e.data.type === 'gamepad-toggle') {
const { gamepadIndex, buttonIndex, value } = e.data;
if (!gamepadValues[gamepadIndex]) {
gamepadValues[gamepadIndex] = Array(16).fill(0);
}
gamepadValues[gamepadIndex][buttonIndex] = value;
}
});
}