joymap
Version:
A Gamepad API wrapper and mapping tool.
138 lines (114 loc) • 4.18 kB
text/typescript
import memoize from 'fast-memoize';
import { filter, forEach, assignIn, map, isString } from 'lodash/fp';
import createBaseModule from '../baseModule/base';
import { buttonMap, stickMap } from '../common/utils';
import { eventIsValid, getEventTokens, verifyTokens } from './eventUtils';
import { RawGamepad, InputEvent, InputResult } from '../types';
export type EventModule = ReturnType<typeof createEventModule>;
export default function createEventModule(params = {}) {
const { state, module: baseModule } = createBaseModule(params);
const buttonMapMemoized = memoize(buttonMap);
const stickMapMemoized = memoize(stickMap);
let inputEvents: InputEvent[] = [];
const module = assignIn(baseModule, {
...baseModule,
addEvent: (eventName: string, callback: InputEvent['callback']) => {
const tokens = getEventTokens(eventName);
if (eventIsValid(tokens)) {
inputEvents.push({ name: eventName, callback, tokens });
}
},
removeEvent: (eventName: string, callback: InputEvent['callback']) => {
inputEvents = filter(
(event) => event.name !== eventName || event.callback !== callback,
inputEvents,
);
},
update: (gamepad: RawGamepad) => {
baseModule.update(gamepad);
forEach((event) => {
if (state.buttons[event.name]) {
// simple button event
const result = buttonMapMemoized(
state.pad,
state.prevPad,
state.buttons[event.name],
state.threshold,
state.clampThreshold,
);
if (result.pressed) {
event.callback([result]);
}
} else if (state.sticks[event.name]) {
// simple stick event
const { indexes, inverts } = state.sticks[event.name];
const result = stickMapMemoized(
state.pad,
state.prevPad,
indexes,
inverts,
state.threshold,
state.clampThreshold,
);
if (result.pressed) {
event.callback([result]);
}
} else {
// composite event with operators
const resultCopy: InputResult[] = [];
const results: (string | boolean)[] = map((token) => {
if (isString(token)) {
return token;
}
let result;
if (state.buttons[token.inputName]) {
result = buttonMapMemoized(
state.pad,
state.prevPad,
state.buttons[token.inputName],
state.threshold,
state.clampThreshold,
);
} else if (state.sticks[token.inputName]) {
const { indexes, inverts } = state.sticks[token.inputName];
result = stickMapMemoized(
state.pad,
state.prevPad,
indexes,
inverts,
state.threshold,
state.clampThreshold,
);
}
if (result) {
if (!resultCopy.includes(result)) {
// we save the input result for the callback (avoiding repetition)
// TODO: maybe there's a cleaner way of doing this?
resultCopy.push(result);
}
if (token.inputState === 'pressed') {
return result.pressed;
}
if (token.inputState === 'justPressed') {
return result.pressed && result.justChanged;
}
if (token.inputState === 'justReleased') {
return !result.pressed && result.justChanged;
}
return !result.pressed;
}
return false;
}, event.tokens);
if (verifyTokens(results)) {
event.callback(resultCopy);
}
}
}, inputEvents);
},
destroy() {
baseModule.destroy();
inputEvents = [];
},
});
return module;
}