UNPKG

joymap

Version:

A Gamepad API wrapper and mapping tool.

153 lines (127 loc) 4.15 kB
import { noop, map, isFunction, find, filter, difference, forEach, includes, compact, } from 'lodash/fp'; import { getRawGamepads, gamepadIsValid } from './common/utils'; import { RawGamepad, JoymapParams } from './types'; import { BaseModule } from './baseModule/base'; import { QueryModule } from './queryModule/query'; import { StreamModule } from './streamModule/stream'; import { EventModule } from './eventModule/event'; interface JoymapState { onPoll: () => void; autoConnect: boolean; gamepads: RawGamepad[]; modules: AnyModule[]; } export type AnyModule = BaseModule['module'] | QueryModule | StreamModule | EventModule; export type Joymap = ReturnType<typeof createJoymap>; export default function createJoymap(params: JoymapParams = {}) { let animationFrameRequestId: number | null = null; const isSupported = navigator && isFunction(navigator.getGamepads); const state: JoymapState = { onPoll: params.onPoll || noop, autoConnect: params.autoConnect !== false, gamepads: [], modules: [], }; const joymap = { isSupported: () => isSupported, start: () => { if (isSupported && animationFrameRequestId === null) { joymap.poll(); if (state.autoConnect) { forEach((module) => { if (!module.isConnected()) { const padId = joymap.getUnusedPadId(); if (padId) { module.connect(padId); } } }, state.modules); } const step = () => { joymap.poll(); animationFrameRequestId = window.requestAnimationFrame(step); }; animationFrameRequestId = window.requestAnimationFrame(step); } }, stop: () => { if (animationFrameRequestId !== null) { window.cancelAnimationFrame(animationFrameRequestId); animationFrameRequestId = null; } }, setOnPoll: (onPoll: () => void) => { state.onPoll = onPoll; }, setAutoConnect: (autoConnect: boolean) => { state.autoConnect = autoConnect; }, getGamepads: () => state.gamepads, getModules: () => state.modules, getUnusedPadIds: () => compact( difference( map('id', state.gamepads), map((module) => module.getPadId(), state.modules), ), ), getUnusedPadId: () => { const usedIds = map((module) => module.getPadId(), state.modules); const gamepadIds = map('id', state.gamepads); return find((id) => !includes(id, usedIds), gamepadIds); }, addModule: (module: AnyModule) => { state.modules.push(module); if (state.autoConnect && !module.getPadId()) { const padId = joymap.getUnusedPadId(); if (padId) { module.connect(padId); } } }, removeModule: (module: AnyModule) => { state.modules = filter((m) => m !== module, state.modules); module.destroy(); }, clearModules: () => { forEach((module) => joymap.removeModule(module), state.modules); }, poll: () => { state.gamepads = filter(gamepadIsValid, getRawGamepads()) as RawGamepad[]; forEach((module) => { if (state.autoConnect && !module.getPadId()) { const padId = joymap.getUnusedPadId(); if (padId) { module.connect(padId); const pad = find({ id: module.getPadId() }, state.gamepads) as RawGamepad | undefined; if (pad) { module.update(pad); } } } else { const gamepad = find({ id: module.getPadId() }, state.gamepads) as RawGamepad | undefined; if (gamepad) { if (!module.isConnected()) { module.connect(); } module.update(gamepad); } else if (module.isConnected()) { module.disconnect(); } } }, state.modules); state.onPoll(); }, }; return joymap; }