ecspresso 0.14.0 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -6
- package/dist/index.js +2 -2
- package/dist/index.js.map +3 -3
- package/dist/plugins/ai/pathfinding.d.ts +163 -0
- package/dist/plugins/ai/pathfinding.js +4 -0
- package/dist/plugins/ai/pathfinding.js.map +10 -0
- package/dist/plugins/input/input.d.ts +105 -27
- package/dist/plugins/input/input.js +2 -2
- package/dist/plugins/input/input.js.map +3 -3
- package/dist/plugins/rendering/renderer3D.d.ts +23 -2
- package/dist/plugins/rendering/renderer3D.js +239 -184
- package/dist/plugins/rendering/renderer3D.js.map +5 -5
- package/dist/plugins/rendering/tilemap.d.ts +230 -0
- package/dist/plugins/rendering/tilemap.js +4 -0
- package/dist/plugins/rendering/tilemap.js.map +11 -0
- package/dist/plugins/spatial/camera3D.d.ts +29 -9
- package/dist/plugins/spatial/camera3D.js +2 -2
- package/dist/plugins/spatial/camera3D.js.map +3 -3
- package/dist/plugins/ui/ui.d.ts +116 -0
- package/dist/plugins/ui/ui.js +4 -0
- package/dist/plugins/ui/ui.js.map +11 -0
- package/dist/system-builder.d.ts +31 -0
- package/package.json +16 -4
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/plugins/input/input.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"/**\n * Input Plugin for ECSpresso\n *\n * Provides frame-accurate keyboard, pointer (mouse + touch via PointerEvent),\n * and action mapping input. Resource-only plugin — input is polled via the\n * `inputState` resource. No ECS components or events.\n *\n * DOM events are accumulated between frames and snapshotted once per frame\n * in the system's process step, so all systems see consistent state.\n */\n\nimport { definePlugin, type BasePluginOptions } from 'ecspresso';\n\n// ==================== Public Types ====================\n\nexport interface Vec2 {\n\tx: number;\n\ty: number;\n}\n\n// Key codes per the UI Events spec (KeyboardEvent.key values)\n// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values\n\ntype LowercaseLetter =\n\t| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'\n\t| 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';\n\ntype Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';\n\ntype Punctuation =\n\t| '`' | '~' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')'\n\t| '-' | '_' | '=' | '+' | '[' | '{' | ']' | '}' | '\\\\' | '|'\n\t| ';' | ':' | \"'\" | '\"' | ',' | '<' | '.' | '>' | '/' | '?';\n\ntype ModifierKey =\n\t| 'Alt' | 'AltGraph' | 'CapsLock' | 'Control' | 'Fn' | 'FnLock'\n\t| 'Hyper' | 'Meta' | 'NumLock' | 'ScrollLock' | 'Shift'\n\t| 'Super' | 'Symbol' | 'SymbolLock';\n\ntype WhitespaceKey = 'Enter' | 'Tab' | ' ';\n\ntype NavigationKey =\n\t| `Arrow${'Down' | 'Left' | 'Right' | 'Up'}`\n\t| 'End' | 'Home' | 'PageDown' | 'PageUp';\n\ntype EditingKey =\n\t| 'Backspace' | 'Clear' | 'Copy' | 'CrSel' | 'Cut' | 'Delete'\n\t| 'EraseEof' | 'ExSel' | 'Insert' | 'Paste' | 'Redo' | 'Undo';\n\ntype UIKey =\n\t| 'Accept' | 'Again' | 'Attn' | 'Cancel' | 'ContextMenu' | 'Escape'\n\t| 'Execute' | 'Find' | 'Finish' | 'Help' | 'Pause' | 'Play'\n\t| 'Props' | 'Select' | 'ZoomIn' | 'ZoomOut';\n\ntype DeviceKey =\n\t| 'BrightnessDown' | 'BrightnessUp' | 'Eject' | 'Hibernate'\n\t| 'LogOff' | 'Power' | 'PowerOff' | 'PrintScreen' | 'Standby' | 'WakeUp';\n\ntype IMEKey =\n\t| 'AllCandidates' | 'Alphanumeric' | 'CodeInput' | 'Compose' | 'Convert'\n\t| 'FinalMode' | 'GroupFirst' | 'GroupLast' | 'GroupNext' | 'GroupPrevious'\n\t| 'ModeChange' | 'NextCandidate' | 'NonConvert' | 'PreviousCandidate'\n\t| 'Process' | 'SingleCandidate'\n\t| 'HangulMode' | 'HanjaMode' | 'JunjaMode'\n\t| 'Eisu' | 'Hankaku' | 'Hiragana' | 'HiraganaKatakana' | 'KanaMode'\n\t| 'KanjiMode' | 'Katakana' | 'Romaji' | 'Zenkaku' | 'ZenkakuHankaku';\n\ntype FunctionKey =\n\t| `F${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24}`\n\t| 'Soft1' | 'Soft2' | 'Soft3' | 'Soft4';\n\ntype PhoneKey =\n\t| 'AppSwitch' | 'Call' | 'Camera' | 'CameraFocus' | 'EndCall'\n\t| 'GoBack' | 'GoHome' | 'HeadsetHook' | 'LastNumberRedial'\n\t| 'Notification' | 'MannerMode' | 'VoiceDial';\n\ntype MultimediaKey =\n\t| 'ChannelDown' | 'ChannelUp'\n\t| `Media${\n\t\t'FastForward' | 'Pause' | 'Play' | 'PlayPause'\n\t\t| 'Record' | 'Rewind' | 'Stop' | 'TrackNext' | 'TrackPrevious'\n\t}`;\n\ntype AudioKey =\n\t| `Audio${\n\t\t'BalanceLeft' | 'BalanceRight' | 'BassDown' | 'BassBoostDown'\n\t\t| 'BassBoostToggle' | 'BassBoostUp' | 'BassUp' | 'FaderFront' | 'FaderRear'\n\t\t| 'SurroundModeNext' | 'TrebleDown' | 'TrebleUp'\n\t\t| 'VolumeDown' | 'VolumeMute' | 'VolumeUp'\n\t}`\n\t| `Microphone${'Toggle' | 'VolumeDown' | 'VolumeMute' | 'VolumeUp'}`;\n\ntype TVKey =\n\t| 'TV'\n\t| `TV${\n\t\t'3DMode' | 'AntennaCable' | 'AudioDescription' | 'AudioDescriptionMixDown'\n\t\t| 'AudioDescriptionMixUp' | 'ContentsMenu' | 'DataService' | 'Input'\n\t\t| 'InputComponent1' | 'InputComponent2' | 'InputComposite1' | 'InputComposite2'\n\t\t| 'InputHDMI1' | 'InputHDMI2' | 'InputHDMI3' | 'InputHDMI4' | 'InputVGA1'\n\t\t| 'MediaContext' | 'Network' | 'NumberEntry' | 'Power' | 'RadioService'\n\t\t| 'Satellite' | 'SatelliteBS' | 'SatelliteCS' | 'SatelliteToggle'\n\t\t| 'TerrestrialAnalog' | 'TerrestrialDigital' | 'Timer'\n\t}`;\n\ntype MediaControllerKey =\n\t| 'AVRInput' | 'AVRPower'\n\t| `Color${'F0Red' | 'F1Green' | 'F2Yellow' | 'F3Blue' | 'F4Grey' | 'F5Brown'}`\n\t| 'ClosedCaptionToggle' | 'Dimmer' | 'DisplaySwap' | 'DVR' | 'Exit'\n\t| `Favorite${'Clear' | 'Recall' | 'Store'}${0 | 1 | 2 | 3}`\n\t| 'Guide' | 'GuideNextDay' | 'GuidePreviousDay' | 'Info' | 'InstantReplay'\n\t| 'Link' | 'ListProgram' | 'LiveContent' | 'Lock'\n\t| `Media${\n\t\t'Apps' | 'AudioTrack' | 'Last' | 'SkipBackward'\n\t\t| 'SkipForward' | 'StepBackward' | 'StepForward' | 'TopMenu'\n\t}`\n\t| `Navigate${'In' | 'Next' | 'Out' | 'Previous'}`\n\t| 'NextFavoriteChannel' | 'NextUserProfile' | 'OnDemand' | 'Pairing'\n\t| `PinP${'Down' | 'Move' | 'Toggle' | 'Up'}`\n\t| `PlaySpeed${'Down' | 'Reset' | 'Up'}`\n\t| 'RandomToggle' | 'RcLowBattery' | 'RecordSpeedNext' | 'RfBypass'\n\t| 'ScanChannelsToggle' | 'ScreenModeNext' | 'Settings' | 'SplitScreenToggle'\n\t| 'STBInput' | 'STBPower' | 'Subtitle' | 'Teletext'\n\t| 'VideoModeNext' | 'Wink' | 'ZoomToggle';\n\ntype SpeechKey = 'SpeechCorrectionList' | 'SpeechInputToggle';\n\ntype DocumentKey =\n\t| 'Close' | 'New' | 'Open' | 'Print' | 'Save' | 'SpellCheck'\n\t| 'MailForward' | 'MailReply' | 'MailSend';\n\ntype LaunchKey = `Launch${\n\t| 'Calculator' | 'Calendar' | 'Contacts' | 'Mail' | 'MediaPlayer'\n\t| 'MusicPlayer' | 'MyComputer' | 'Phone' | 'ScreenSaver' | 'Spreadsheet'\n\t| 'WebBrowser' | 'WebCam' | 'WordProcessor'\n\t| `Application${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16}`\n}`;\n\ntype BrowserKey = `Browser${'Back' | 'Favorites' | 'Forward' | 'Home' | 'Refresh' | 'Search' | 'Stop'}`;\n\ntype NumpadKey = 'Decimal' | 'Key11' | 'Key12' | 'Multiply' | 'Add' | 'Divide' | 'Subtract' | 'Separator';\n\nexport type KeyCode =\n\t| LowercaseLetter | Uppercase<LowercaseLetter> | Digit | Punctuation\n\t| ModifierKey | WhitespaceKey | NavigationKey | EditingKey | UIKey | DeviceKey\n\t| IMEKey | FunctionKey | PhoneKey | MultimediaKey | AudioKey | TVKey\n\t| MediaControllerKey | SpeechKey | DocumentKey | LaunchKey | BrowserKey | NumpadKey\n\t| 'Unidentified' | 'Dead';\n\nexport interface KeyboardState {\n\tisDown(key: KeyCode): boolean;\n\tjustPressed(key: KeyCode): boolean;\n\tjustReleased(key: KeyCode): boolean;\n}\n\nexport interface PointerState {\n\treadonly position: Readonly<Vec2>;\n\treadonly delta: Readonly<Vec2>;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n}\n\nexport interface ActionState<A extends string = string> {\n\tisActive(action: A): boolean;\n\tjustActivated(action: A): boolean;\n\tjustDeactivated(action: A): boolean;\n}\n\nexport interface InputState<A extends string = string> {\n\treadonly keyboard: KeyboardState;\n\treadonly pointer: PointerState;\n\treadonly actions: ActionState<A>;\n\tsetActionMap(actions: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n}\n\nexport interface ActionBinding {\n\tkeys?: KeyCode[];\n\tbuttons?: number[];\n}\n\nexport type ActionMap<A extends string = string> = Record<A, ActionBinding>;\n\nexport interface InputResourceTypes<A extends string = string> {\n\tinputState: InputState<A>;\n}\n\nexport interface InputPluginOptions<A extends string = string, G extends string = 'input'> extends BasePluginOptions<G> {\n\t/** Initial action mappings */\n\tactions?: ActionMap<A>;\n\t/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */\n\ttarget?: EventTarget;\n\t/**\n\t * Optional conversion from raw DOM client coordinates to the space `inputState.pointer.position` should report.\n\t * Renderer-agnostic: wire to `clientToLogical(...)` from renderer2D when using `screenScale`, or to a renderer-specific helper.\n\t * When omitted, pointer coords remain raw `clientX`/`clientY` (not canvas-relative).\n\t */\n\tcoordinateTransform?: (clientX: number, clientY: number) => { x: number; y: number };\n}\n\n// ==================== Helper Functions ====================\n\n/**\n * Create a single action binding.\n *\n * @param binding The binding configuration\n * @returns The same binding object\n */\nexport function createActionBinding(binding: ActionBinding): ActionBinding {\n\treturn binding;\n}\n\n// ==================== Internal Types ====================\n\ninterface RawInputState {\n\tkeysDown: Set<string>;\n\tkeysPressed: string[];\n\tkeysReleased: string[];\n\tbuttonsDown: Set<number>;\n\tbuttonsPressed: number[];\n\tbuttonsReleased: number[];\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tlastPointerX: number;\n\tlastPointerY: number;\n\tpointerMoved: boolean;\n}\n\ninterface FrameSnapshot {\n\tkeysDown: ReadonlySet<string>;\n\tkeysPressed: ReadonlySet<string>;\n\tkeysReleased: ReadonlySet<string>;\n\tbuttonsDown: ReadonlySet<number>;\n\tbuttonsPressed: ReadonlySet<number>;\n\tbuttonsReleased: ReadonlySet<number>;\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n\tactionsActive: ReadonlySet<string>;\n\tprevActionsActive: ReadonlySet<string>;\n}\n\n// ==================== Plugin Factory ====================\n\nfunction createRawInputState(): RawInputState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: [],\n\t\tkeysReleased: [],\n\t\tbuttonsDown: new Set(),\n\t\tbuttonsPressed: [],\n\t\tbuttonsReleased: [],\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tlastPointerX: 0,\n\t\tlastPointerY: 0,\n\t\tpointerMoved: false,\n\t};\n}\n\nconst EMPTY_SET_STRING: ReadonlySet<string> = new Set<string>();\nconst EMPTY_SET_NUMBER: ReadonlySet<number> = new Set<number>();\n\nfunction createEmptySnapshot(): FrameSnapshot {\n\treturn {\n\t\tkeysDown: EMPTY_SET_STRING,\n\t\tkeysPressed: EMPTY_SET_STRING,\n\t\tkeysReleased: EMPTY_SET_STRING,\n\t\tbuttonsDown: EMPTY_SET_NUMBER,\n\t\tbuttonsPressed: EMPTY_SET_NUMBER,\n\t\tbuttonsReleased: EMPTY_SET_NUMBER,\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t\tactionsActive: EMPTY_SET_STRING,\n\t\tprevActionsActive: EMPTY_SET_STRING,\n\t};\n}\n\nfunction computeActiveActions(\n\tactionMap: ActionMap,\n\tkeysDown: ReadonlySet<string>,\n\tbuttonsDown: ReadonlySet<number>,\n): Set<string> {\n\tconst active = new Set<string>();\n\tfor (const [name, binding] of Object.entries(actionMap)) {\n\t\tconst keyActive = binding.keys?.some((k) => keysDown.has(k)) ?? false;\n\t\tconst buttonActive = binding.buttons?.some((b) => buttonsDown.has(b)) ?? false;\n\t\tif (keyActive || buttonActive) {\n\t\t\tactive.add(name);\n\t\t}\n\t}\n\treturn active;\n}\n\nfunction snapshotRaw(raw: RawInputState, prevActionsActive: ReadonlySet<string>, actionMap: ActionMap): FrameSnapshot {\n\tconst keysDown = new Set(raw.keysDown);\n\tconst keysPressed = new Set(raw.keysPressed);\n\tconst keysReleased = new Set(raw.keysReleased);\n\tconst buttonsDown = new Set(raw.buttonsDown);\n\tconst buttonsPressed = new Set(raw.buttonsPressed);\n\tconst buttonsReleased = new Set(raw.buttonsReleased);\n\n\tconst pointerDeltaX = raw.pointerMoved ? raw.pointerX - raw.lastPointerX : 0;\n\tconst pointerDeltaY = raw.pointerMoved ? raw.pointerY - raw.lastPointerY : 0;\n\n\tconst actionsActive = computeActiveActions(actionMap, keysDown, buttonsDown);\n\n\tconst snapshot: FrameSnapshot = {\n\t\tkeysDown,\n\t\tkeysPressed,\n\t\tkeysReleased,\n\t\tbuttonsDown,\n\t\tbuttonsPressed,\n\t\tbuttonsReleased,\n\t\tpointerX: raw.pointerX,\n\t\tpointerY: raw.pointerY,\n\t\tpointerDeltaX,\n\t\tpointerDeltaY,\n\t\tactionsActive,\n\t\tprevActionsActive,\n\t};\n\n\t// Clear accumulation buffers\n\traw.keysPressed = [];\n\traw.keysReleased = [];\n\traw.buttonsPressed = [];\n\traw.buttonsReleased = [];\n\traw.lastPointerX = raw.pointerX;\n\traw.lastPointerY = raw.pointerY;\n\traw.pointerMoved = false;\n\n\treturn snapshot;\n}\n\n/**\n * Create an input plugin for ECSpresso.\n *\n * This plugin provides:\n * - Frame-accurate keyboard state (isDown, justPressed, justReleased)\n * - Pointer position/delta and button state (mouse + touch via PointerEvent)\n * - Named action mapping with runtime remapping\n * - Automatic listener cleanup on detach\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createInputPlugin({\n * actions: {\n * jump: { keys: [' ', 'ArrowUp'] },\n * shoot: { keys: ['z'], buttons: [0] },\n * },\n * }))\n * .build();\n *\n * // In a system:\n * const input = ecs.getResource('inputState');\n * if (input.actions.justActivated('jump')) { ... }\n * if (input.keyboard.isDown('ArrowRight')) { ... }\n * ```\n */\nexport function createInputPlugin<A extends string = string, G extends string = 'input'>(\n\toptions?: InputPluginOptions<A, G>\n) {\n\tconst {\n\t\tsystemGroup = 'input',\n\t\tpriority = 100,\n\t\tphase = 'preUpdate',\n\t\tactions: initialActions = {},\n\t\ttarget = globalThis,\n\t\tcoordinateTransform,\n\t} = options ?? {};\n\n\t// Closure state\n\tconst raw = createRawInputState();\n\tlet snapshot = createEmptySnapshot();\n\tlet actionMap: ActionMap = { ...initialActions };\n\tconst cleanupFns: Array<() => void> = [];\n\n\t// The position/delta objects exposed via the resource.\n\t// Updated in-place each frame to avoid allocations.\n\tconst position: Vec2 = { x: 0, y: 0 };\n\tconst delta: Vec2 = { x: 0, y: 0 };\n\n\t// Build the InputState resource that closes over snapshot\n\tconst keyboard: KeyboardState = {\n\t\tisDown: (key) => snapshot.keysDown.has(key),\n\t\tjustPressed: (key) => snapshot.keysPressed.has(key),\n\t\tjustReleased: (key) => snapshot.keysReleased.has(key),\n\t};\n\n\tconst pointer: PointerState = {\n\t\tposition,\n\t\tdelta,\n\t\tisDown: (button) => snapshot.buttonsDown.has(button),\n\t\tjustPressed: (button) => snapshot.buttonsPressed.has(button),\n\t\tjustReleased: (button) => snapshot.buttonsReleased.has(button),\n\t};\n\n\tconst actionState: ActionState<A> = {\n\t\tisActive: (action) => snapshot.actionsActive.has(action),\n\t\tjustActivated: (action) =>\n\t\t\tsnapshot.actionsActive.has(action) && !snapshot.prevActionsActive.has(action),\n\t\tjustDeactivated: (action) =>\n\t\t\t!snapshot.actionsActive.has(action) && snapshot.prevActionsActive.has(action),\n\t};\n\n\tconst inputState: InputState<A> = {\n\t\tkeyboard,\n\t\tpointer,\n\t\tactions: actionState,\n\t\tsetActionMap(newMap) {\n\t\t\tactionMap = { ...newMap };\n\t\t},\n\t\tgetActionMap() {\n\t\t\treturn { ...actionMap } as ActionMap<A>;\n\t\t},\n\t};\n\n\t// DOM event handlers\n\tfunction onKeyDown(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\tif (ke.repeat) return;\n\t\traw.keysDown.add(ke.key);\n\t\traw.keysPressed.push(ke.key);\n\t}\n\n\tfunction onKeyUp(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\traw.keysDown.delete(ke.key);\n\t\traw.keysReleased.push(ke.key);\n\t}\n\n\tfunction onPointerDown(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.buttonsDown.add(pe.button);\n\t\traw.buttonsPressed.push(pe.button);\n\t}\n\n\tfunction onPointerMove(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\tif (coordinateTransform) {\n\t\t\tconst { x, y } = coordinateTransform(pe.clientX, pe.clientY);\n\t\t\traw.pointerX = x;\n\t\t\traw.pointerY = y;\n\t\t} else {\n\t\t\traw.pointerX = pe.clientX;\n\t\t\traw.pointerY = pe.clientY;\n\t\t}\n\t\traw.pointerMoved = true;\n\t}\n\n\tfunction onPointerUp(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.buttonsDown.delete(pe.button);\n\t\traw.buttonsReleased.push(pe.button);\n\t}\n\n\tfunction addListener(type: string, handler: (e: Event) => void) {\n\t\ttarget.addEventListener(type, handler);\n\t\tcleanupFns.push(() => { target.removeEventListener(type, handler); });\n\t}\n\n\treturn definePlugin('input')\n\t\t.withResourceTypes<InputResourceTypes<A>>()\n\t\t.withLabels<'input-state'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('inputState', inputState);\n\n\t\t\tworld\n\t\t\t\t.addSystem('input-state')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(() => {\n\t\t\t\t\taddListener('keydown', onKeyDown);\n\t\t\t\t\taddListener('keyup', onKeyUp);\n\t\t\t\t\taddListener('pointerdown', onPointerDown);\n\t\t\t\t\taddListener('pointermove', onPointerMove);\n\t\t\t\t\taddListener('pointerup', onPointerUp);\n\t\t\t\t})\n\t\t\t\t.setOnDetach(() => {\n\t\t\t\t\tfor (const cleanup of cleanupFns) {\n\t\t\t\t\t\tcleanup();\n\t\t\t\t\t}\n\t\t\t\t\tcleanupFns.length = 0;\n\t\t\t\t})\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\tconst prevActionsActive = snapshot.actionsActive;\n\t\t\t\t\tsnapshot = snapshotRaw(raw, prevActionsActive, actionMap);\n\n\t\t\t\t\t// Update the exposed position/delta objects in-place\n\t\t\t\t\tposition.x = snapshot.pointerX;\n\t\t\t\t\tposition.y = snapshot.pointerY;\n\t\t\t\t\tdelta.x = snapshot.pointerDeltaX;\n\t\t\t\t\tdelta.y = snapshot.pointerDeltaY;\n\t\t\t\t});\n\t\t});\n}\n"
|
|
5
|
+
"/**\n * Input Plugin for ECSpresso\n *\n * Resource-only plugin — input is polled via the `inputState` resource. Provides\n * frame-accurate keyboard, pointer (mouse + touch via PointerEvent), up to 4\n * gamepads, and unified + per-player action maps.\n *\n * Mutation model: DOM events accumulate into `raw` between frames and are\n * flattened once per frame into a stable `frame` object whose Sets are cleared\n * and refilled in place (no per-frame allocations). Gamepads are polled once\n * per frame via `navigator.getGamepads()` (or an injected poll function).\n * Unified and per-player action states ping-pong two Sets (`active` / `prev`)\n * so edge detection costs nothing beyond one `.add()` per active action.\n */\n\nimport { definePlugin, type BasePluginOptions, type Vector2D } from 'ecspresso';\n\n// ==================== Public Types ====================\n\n// Key codes per the UI Events spec (KeyboardEvent.key values)\n// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values\n\ntype LowercaseLetter =\n\t| 'a' | 'b' | 'c' | 'd' | 'e' | 'f' | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'\n\t| 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't' | 'u' | 'v' | 'w' | 'x' | 'y' | 'z';\n\ntype Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';\n\ntype Punctuation =\n\t| '`' | '~' | '!' | '@' | '#' | '$' | '%' | '^' | '&' | '*' | '(' | ')'\n\t| '-' | '_' | '=' | '+' | '[' | '{' | ']' | '}' | '\\\\' | '|'\n\t| ';' | ':' | \"'\" | '\"' | ',' | '<' | '.' | '>' | '/' | '?';\n\ntype ModifierKey =\n\t| 'Alt' | 'AltGraph' | 'CapsLock' | 'Control' | 'Fn' | 'FnLock'\n\t| 'Hyper' | 'Meta' | 'NumLock' | 'ScrollLock' | 'Shift'\n\t| 'Super' | 'Symbol' | 'SymbolLock';\n\ntype WhitespaceKey = 'Enter' | 'Tab' | ' ';\n\ntype NavigationKey =\n\t| `Arrow${'Down' | 'Left' | 'Right' | 'Up'}`\n\t| 'End' | 'Home' | 'PageDown' | 'PageUp';\n\ntype EditingKey =\n\t| 'Backspace' | 'Clear' | 'Copy' | 'CrSel' | 'Cut' | 'Delete'\n\t| 'EraseEof' | 'ExSel' | 'Insert' | 'Paste' | 'Redo' | 'Undo';\n\ntype UIKey =\n\t| 'Accept' | 'Again' | 'Attn' | 'Cancel' | 'ContextMenu' | 'Escape'\n\t| 'Execute' | 'Find' | 'Finish' | 'Help' | 'Pause' | 'Play'\n\t| 'Props' | 'Select' | 'ZoomIn' | 'ZoomOut';\n\ntype DeviceKey =\n\t| 'BrightnessDown' | 'BrightnessUp' | 'Eject' | 'Hibernate'\n\t| 'LogOff' | 'Power' | 'PowerOff' | 'PrintScreen' | 'Standby' | 'WakeUp';\n\ntype IMEKey =\n\t| 'AllCandidates' | 'Alphanumeric' | 'CodeInput' | 'Compose' | 'Convert'\n\t| 'FinalMode' | 'GroupFirst' | 'GroupLast' | 'GroupNext' | 'GroupPrevious'\n\t| 'ModeChange' | 'NextCandidate' | 'NonConvert' | 'PreviousCandidate'\n\t| 'Process' | 'SingleCandidate'\n\t| 'HangulMode' | 'HanjaMode' | 'JunjaMode'\n\t| 'Eisu' | 'Hankaku' | 'Hiragana' | 'HiraganaKatakana' | 'KanaMode'\n\t| 'KanjiMode' | 'Katakana' | 'Romaji' | 'Zenkaku' | 'ZenkakuHankaku';\n\ntype FunctionKey =\n\t| `F${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24}`\n\t| 'Soft1' | 'Soft2' | 'Soft3' | 'Soft4';\n\ntype PhoneKey =\n\t| 'AppSwitch' | 'Call' | 'Camera' | 'CameraFocus' | 'EndCall'\n\t| 'GoBack' | 'GoHome' | 'HeadsetHook' | 'LastNumberRedial'\n\t| 'Notification' | 'MannerMode' | 'VoiceDial';\n\ntype MultimediaKey =\n\t| 'ChannelDown' | 'ChannelUp'\n\t| `Media${\n\t\t'FastForward' | 'Pause' | 'Play' | 'PlayPause'\n\t\t| 'Record' | 'Rewind' | 'Stop' | 'TrackNext' | 'TrackPrevious'\n\t}`;\n\ntype AudioKey =\n\t| `Audio${\n\t\t'BalanceLeft' | 'BalanceRight' | 'BassDown' | 'BassBoostDown'\n\t\t| 'BassBoostToggle' | 'BassBoostUp' | 'BassUp' | 'FaderFront' | 'FaderRear'\n\t\t| 'SurroundModeNext' | 'TrebleDown' | 'TrebleUp'\n\t\t| 'VolumeDown' | 'VolumeMute' | 'VolumeUp'\n\t}`\n\t| `Microphone${'Toggle' | 'VolumeDown' | 'VolumeMute' | 'VolumeUp'}`;\n\ntype TVKey =\n\t| 'TV'\n\t| `TV${\n\t\t'3DMode' | 'AntennaCable' | 'AudioDescription' | 'AudioDescriptionMixDown'\n\t\t| 'AudioDescriptionMixUp' | 'ContentsMenu' | 'DataService' | 'Input'\n\t\t| 'InputComponent1' | 'InputComponent2' | 'InputComposite1' | 'InputComposite2'\n\t\t| 'InputHDMI1' | 'InputHDMI2' | 'InputHDMI3' | 'InputHDMI4' | 'InputVGA1'\n\t\t| 'MediaContext' | 'Network' | 'NumberEntry' | 'Power' | 'RadioService'\n\t\t| 'Satellite' | 'SatelliteBS' | 'SatelliteCS' | 'SatelliteToggle'\n\t\t| 'TerrestrialAnalog' | 'TerrestrialDigital' | 'Timer'\n\t}`;\n\ntype MediaControllerKey =\n\t| 'AVRInput' | 'AVRPower'\n\t| `Color${'F0Red' | 'F1Green' | 'F2Yellow' | 'F3Blue' | 'F4Grey' | 'F5Brown'}`\n\t| 'ClosedCaptionToggle' | 'Dimmer' | 'DisplaySwap' | 'DVR' | 'Exit'\n\t| `Favorite${'Clear' | 'Recall' | 'Store'}${0 | 1 | 2 | 3}`\n\t| 'Guide' | 'GuideNextDay' | 'GuidePreviousDay' | 'Info' | 'InstantReplay'\n\t| 'Link' | 'ListProgram' | 'LiveContent' | 'Lock'\n\t| `Media${\n\t\t'Apps' | 'AudioTrack' | 'Last' | 'SkipBackward'\n\t\t| 'SkipForward' | 'StepBackward' | 'StepForward' | 'TopMenu'\n\t}`\n\t| `Navigate${'In' | 'Next' | 'Out' | 'Previous'}`\n\t| 'NextFavoriteChannel' | 'NextUserProfile' | 'OnDemand' | 'Pairing'\n\t| `PinP${'Down' | 'Move' | 'Toggle' | 'Up'}`\n\t| `PlaySpeed${'Down' | 'Reset' | 'Up'}`\n\t| 'RandomToggle' | 'RcLowBattery' | 'RecordSpeedNext' | 'RfBypass'\n\t| 'ScanChannelsToggle' | 'ScreenModeNext' | 'Settings' | 'SplitScreenToggle'\n\t| 'STBInput' | 'STBPower' | 'Subtitle' | 'Teletext'\n\t| 'VideoModeNext' | 'Wink' | 'ZoomToggle';\n\ntype SpeechKey = 'SpeechCorrectionList' | 'SpeechInputToggle';\n\ntype DocumentKey =\n\t| 'Close' | 'New' | 'Open' | 'Print' | 'Save' | 'SpellCheck'\n\t| 'MailForward' | 'MailReply' | 'MailSend';\n\ntype LaunchKey = `Launch${\n\t| 'Calculator' | 'Calendar' | 'Contacts' | 'Mail' | 'MediaPlayer'\n\t| 'MusicPlayer' | 'MyComputer' | 'Phone' | 'ScreenSaver' | 'Spreadsheet'\n\t| 'WebBrowser' | 'WebCam' | 'WordProcessor'\n\t| `Application${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16}`\n}`;\n\ntype BrowserKey = `Browser${'Back' | 'Favorites' | 'Forward' | 'Home' | 'Refresh' | 'Search' | 'Stop'}`;\n\ntype NumpadKey = 'Decimal' | 'Key11' | 'Key12' | 'Multiply' | 'Add' | 'Divide' | 'Subtract' | 'Separator';\n\nexport type KeyCode =\n\t| LowercaseLetter | Uppercase<LowercaseLetter> | Digit | Punctuation\n\t| ModifierKey | WhitespaceKey | NavigationKey | EditingKey | UIKey | DeviceKey\n\t| IMEKey | FunctionKey | PhoneKey | MultimediaKey | AudioKey | TVKey\n\t| MediaControllerKey | SpeechKey | DocumentKey | LaunchKey | BrowserKey | NumpadKey\n\t| 'Unidentified' | 'Dead';\n\nexport interface KeyboardState {\n\tisDown(key: KeyCode): boolean;\n\tjustPressed(key: KeyCode): boolean;\n\tjustReleased(key: KeyCode): boolean;\n}\n\nexport interface PointerState {\n\treadonly position: Readonly<Vector2D>;\n\treadonly delta: Readonly<Vector2D>;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n}\n\nexport interface GamepadState {\n\treadonly connected: boolean;\n\treadonly id: string | null;\n\tisDown(button: number): boolean;\n\tjustPressed(button: number): boolean;\n\tjustReleased(button: number): boolean;\n\t/** Analog button value in [0, 1]. Useful for triggers. Returns 0 when disconnected or out of range. */\n\tbuttonValue(button: number): number;\n\t/** Deadzone-applied axis value in [-1, 1]. Sticks use radial deadzone on axis pairs (0,1) and (2,3). */\n\taxis(index: number): number;\n\t/** Raw axis value in [-1, 1] with no deadzone applied. */\n\trawAxis(index: number): number;\n}\n\nexport interface ActionState<A extends string = string> {\n\tisActive(action: A): boolean;\n\tjustActivated(action: A): boolean;\n\tjustDeactivated(action: A): boolean;\n}\n\nexport interface PlayerInput<A extends string = string> {\n\treadonly actions: ActionState<A>;\n\tsetActionMap(map: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n}\n\nexport interface InputState<A extends string = string> {\n\treadonly keyboard: KeyboardState;\n\treadonly pointer: PointerState;\n\t/** Always length 4 (standard web gamepad slot count). Disconnected slots return `connected: false`. */\n\treadonly gamepads: ReadonlyArray<GamepadState>;\n\t/** Unified action state — fires when any bound input (keyboard, pointer, any pad) is active. Intended for menu/shared input. */\n\treadonly actions: ActionState<A>;\n\tsetActionMap(actions: ActionMap<A>): void;\n\tgetActionMap(): Readonly<ActionMap<A>>;\n\t/** Register or replace a player's action map. Per-player states are isolated from the unified `actions`. */\n\tdefinePlayer(id: string, map: ActionMap<A>): void;\n\t/** Returns true if the player existed and was removed. */\n\tremovePlayer(id: string): boolean;\n\t/** Returns a handle to a registered player's input, or undefined if no such player. */\n\tplayer(id: string): PlayerInput<A> | undefined;\n\tplayerIds(): readonly string[];\n}\n\nexport interface GamepadButtonRef {\n\tpad: number;\n\tbutton: number;\n}\n\nexport interface GamepadAxisRef {\n\tpad: number;\n\taxis: number;\n\t/** Which half of the axis counts as \"active\". */\n\tdirection: 1 | -1;\n\t/** Magnitude at which the axis triggers the action. Applied to the deadzone-adjusted axis value. Default: 0.5. */\n\tthreshold?: number;\n}\n\nexport interface ActionBinding {\n\tkeys?: KeyCode[];\n\t/** Pointer (mouse/touch) button indices — 0 = primary, 1 = auxiliary, 2 = secondary, etc. */\n\tpointerButtons?: number[];\n\tgamepadButtons?: GamepadButtonRef[];\n\tgamepadAxes?: GamepadAxisRef[];\n}\n\nexport type ActionMap<A extends string = string> = Record<A, ActionBinding>;\n\nexport interface InputResourceTypes<A extends string = string> {\n\tinputState: InputState<A>;\n}\n\n/**\n * Minimal gamepad shape required by the injectable poll function. A structural\n * subset of the browser `Gamepad` interface — `navigator.getGamepads()` satisfies\n * it directly, and test doubles can supply just these fields.\n */\nexport interface GamepadLike {\n\tid: string;\n\tconnected: boolean;\n\tbuttons: ReadonlyArray<{ pressed: boolean; value: number }>;\n\taxes: ReadonlyArray<number>;\n}\n\nexport interface GamepadOptions {\n\t/** Radial deadzone applied to stick pairs (axes 0,1 and 2,3). Value in [0, 1]. Default: 0.15. */\n\tdeadzone?: number;\n\t/**\n\t * Custom poll function returning up to 4 gamepad slots. Defaults to `navigator.getGamepads()`.\n\t * Primarily an injection point for tests; in the browser the default is correct.\n\t */\n\tpoll?: () => ReadonlyArray<GamepadLike | null>;\n}\n\nexport interface InputPluginOptions<A extends string = string, G extends string = 'input'> extends BasePluginOptions<G> {\n\t/** Initial unified action map. */\n\tactions?: ActionMap<A>;\n\t/** Initial per-player action maps, keyed by player id. */\n\tplayers?: Record<string, ActionMap<A>>;\n\t/** EventTarget to attach listeners to (default: globalThis). Pass a custom target for testability. */\n\ttarget?: EventTarget;\n\t/** Gamepad polling and deadzone configuration. */\n\tgamepad?: GamepadOptions;\n\t/**\n\t * Optional conversion from raw DOM client coordinates to the space `inputState.pointer.position` should report.\n\t * Renderer-agnostic: wire to `clientToLogical(...)` from renderer2D when using `screenScale`, or to a renderer-specific helper.\n\t * When omitted, pointer coords remain raw `clientX`/`clientY` (not canvas-relative).\n\t */\n\tcoordinateTransform?: (clientX: number, clientY: number) => { x: number; y: number };\n}\n\n// ==================== Helper Functions ====================\n\n/** Create a single action binding. Identity function that provides type inference for inline literals. */\nexport function createActionBinding(binding: ActionBinding): ActionBinding {\n\treturn binding;\n}\n\n/** Build an array of gamepad button refs scoped to one pad — `gamepadButtonsOn(0, 0, 1, 9)` = pad 0's buttons 0, 1, 9. */\nexport function gamepadButtonsOn(pad: number, ...buttons: number[]): GamepadButtonRef[] {\n\treturn buttons.map((button) => ({ pad, button }));\n}\n\n/** Build a gamepad axis ref. `threshold` defaults to 0.5 at activation time. */\nexport function gamepadAxisOn(pad: number, axis: number, direction: 1 | -1, threshold?: number): GamepadAxisRef {\n\treturn threshold === undefined ? { pad, axis, direction } : { pad, axis, direction, threshold };\n}\n\n// ==================== Internal Types ====================\n\ninterface RawKeyPointerState {\n\tkeysDown: Set<string>;\n\tkeysPressed: string[];\n\tkeysReleased: string[];\n\tpointerButtonsDown: Set<number>;\n\tpointerButtonsPressed: number[];\n\tpointerButtonsReleased: number[];\n\tpointerX: number;\n\tpointerY: number;\n\tlastPointerX: number;\n\tlastPointerY: number;\n\tpointerMoved: boolean;\n}\n\n/**\n * Stable per-frame view of keyboard + pointer input. Sets are mutated in place\n * each frame (cleared and refilled from raw), so closures over this object see\n * consistent state throughout a frame without per-frame Set allocation.\n */\ninterface FrameState {\n\tkeysDown: Set<string>;\n\tkeysPressed: Set<string>;\n\tkeysReleased: Set<string>;\n\tpointerButtonsDown: Set<number>;\n\tpointerButtonsPressed: Set<number>;\n\tpointerButtonsReleased: Set<number>;\n\tpointerX: number;\n\tpointerY: number;\n\tpointerDeltaX: number;\n\tpointerDeltaY: number;\n}\n\ninterface PadRuntime {\n\tconnected: boolean;\n\tid: string | null;\n\tbuttonsDown: Set<number>;\n\tbuttonsPrev: Set<number>;\n\tbuttonsPressed: Set<number>;\n\tbuttonsReleased: Set<number>;\n\tbuttonValues: number[];\n\taxes: number[];\n\trawAxes: number[];\n}\n\n/** Two ping-ponged Sets backing an ActionState. Each frame we swap `active` ↔ `prev`, clear the new `active`, and refill. */\ninterface ActionSlot {\n\tactive: Set<string>;\n\tprev: Set<string>;\n}\n\n// ==================== Helpers ====================\n\nconst DEFAULT_AXIS_THRESHOLD = 0.5;\nconst DEFAULT_DEADZONE = 0.15;\nconst PAD_COUNT = 4;\n\nfunction createRawKeyPointerState(): RawKeyPointerState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: [],\n\t\tkeysReleased: [],\n\t\tpointerButtonsDown: new Set(),\n\t\tpointerButtonsPressed: [],\n\t\tpointerButtonsReleased: [],\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tlastPointerX: 0,\n\t\tlastPointerY: 0,\n\t\tpointerMoved: false,\n\t};\n}\n\nfunction createFrameState(): FrameState {\n\treturn {\n\t\tkeysDown: new Set(),\n\t\tkeysPressed: new Set(),\n\t\tkeysReleased: new Set(),\n\t\tpointerButtonsDown: new Set(),\n\t\tpointerButtonsPressed: new Set(),\n\t\tpointerButtonsReleased: new Set(),\n\t\tpointerX: 0,\n\t\tpointerY: 0,\n\t\tpointerDeltaX: 0,\n\t\tpointerDeltaY: 0,\n\t};\n}\n\nfunction createPadRuntime(): PadRuntime {\n\treturn {\n\t\tconnected: false,\n\t\tid: null,\n\t\tbuttonsDown: new Set(),\n\t\tbuttonsPrev: new Set(),\n\t\tbuttonsPressed: new Set(),\n\t\tbuttonsReleased: new Set(),\n\t\tbuttonValues: [],\n\t\taxes: [],\n\t\trawAxes: [],\n\t};\n}\n\nfunction createActionSlot(): ActionSlot {\n\treturn { active: new Set(), prev: new Set() };\n}\n\nfunction refillSet<T>(dest: Set<T>, source: Iterable<T>): void {\n\tdest.clear();\n\tfor (const item of source) dest.add(item);\n}\n\nfunction updateFrameStateFromRaw(frame: FrameState, raw: RawKeyPointerState): void {\n\trefillSet(frame.keysDown, raw.keysDown);\n\trefillSet(frame.keysPressed, raw.keysPressed);\n\trefillSet(frame.keysReleased, raw.keysReleased);\n\trefillSet(frame.pointerButtonsDown, raw.pointerButtonsDown);\n\trefillSet(frame.pointerButtonsPressed, raw.pointerButtonsPressed);\n\trefillSet(frame.pointerButtonsReleased, raw.pointerButtonsReleased);\n\n\tframe.pointerDeltaX = raw.pointerMoved ? raw.pointerX - raw.lastPointerX : 0;\n\tframe.pointerDeltaY = raw.pointerMoved ? raw.pointerY - raw.lastPointerY : 0;\n\tframe.pointerX = raw.pointerX;\n\tframe.pointerY = raw.pointerY;\n\n\traw.keysPressed.length = 0;\n\traw.keysReleased.length = 0;\n\traw.pointerButtonsPressed.length = 0;\n\traw.pointerButtonsReleased.length = 0;\n\traw.lastPointerX = raw.pointerX;\n\traw.lastPointerY = raw.pointerY;\n\traw.pointerMoved = false;\n}\n\nfunction defaultPoll(out: Array<GamepadLike | null>): () => ReadonlyArray<GamepadLike | null> {\n\treturn () => {\n\t\tif (typeof navigator === 'undefined' || typeof navigator.getGamepads !== 'function') {\n\t\t\tfor (let i = 0; i < out.length; i++) out[i] = null;\n\t\t\treturn out;\n\t\t}\n\t\tconst pads = navigator.getGamepads();\n\t\tfor (let i = 0; i < out.length; i++) out[i] = pads[i] ?? null;\n\t\treturn out;\n\t};\n}\n\nfunction applyStickDeadzone(x: number, y: number, deadzone: number, out: number[], baseIndex: number): void {\n\tconst mag = Math.sqrt(x * x + y * y);\n\tif (mag < deadzone) {\n\t\tout[baseIndex] = 0;\n\t\tout[baseIndex + 1] = 0;\n\t\treturn;\n\t}\n\tconst scaled = Math.min((mag - deadzone) / (1 - deadzone), 1);\n\tout[baseIndex] = (x / mag) * scaled;\n\tout[baseIndex + 1] = (y / mag) * scaled;\n}\n\nfunction applyAxisDeadzoning(rawAxes: number[], axes: number[], deadzone: number): void {\n\taxes.length = rawAxes.length;\n\tif (rawAxes.length >= 2) {\n\t\tapplyStickDeadzone(rawAxes[0] ?? 0, rawAxes[1] ?? 0, deadzone, axes, 0);\n\t}\n\tif (rawAxes.length >= 4) {\n\t\tapplyStickDeadzone(rawAxes[2] ?? 0, rawAxes[3] ?? 0, deadzone, axes, 2);\n\t}\n\t// Axes beyond the two standard sticks pass through with no deadzone (triggers, dpad-as-axis, etc.)\n\tfor (let i = 4; i < rawAxes.length; i++) {\n\t\taxes[i] = rawAxes[i] ?? 0;\n\t}\n}\n\nfunction pollGamepadsInto(pads: PadRuntime[], pollFn: () => ReadonlyArray<GamepadLike | null>, deadzone: number): void {\n\tconst polled = pollFn();\n\tfor (let i = 0; i < PAD_COUNT; i++) {\n\t\tconst pad = polled[i] ?? null;\n\t\tconst state = pads[i];\n\t\tif (!state) continue;\n\n\t\t// Rotate button sets using the existing `prev` set as scratch, then clear what we'll refill.\n\t\tconst reusedPrev = state.buttonsPrev;\n\t\trefillSet(reusedPrev, state.buttonsDown);\n\t\tstate.buttonsDown.clear();\n\t\tstate.buttonsPressed.clear();\n\t\tstate.buttonsReleased.clear();\n\n\t\tif (!pad || !pad.connected) {\n\t\t\tif (state.connected) {\n\t\t\t\t// Newly disconnected: synthesize justReleased for anything that was held, then clear values.\n\t\t\t\tfor (const b of reusedPrev) state.buttonsReleased.add(b);\n\t\t\t\tstate.connected = false;\n\t\t\t\tstate.id = null;\n\t\t\t\tstate.buttonValues.length = 0;\n\t\t\t\tstate.axes.length = 0;\n\t\t\t\tstate.rawAxes.length = 0;\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tstate.connected = true;\n\t\tstate.id = pad.id;\n\n\t\tstate.buttonValues.length = pad.buttons.length;\n\t\tfor (let b = 0; b < pad.buttons.length; b++) {\n\t\t\tconst info = pad.buttons[b];\n\t\t\tif (!info) {\n\t\t\t\tstate.buttonValues[b] = 0;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tstate.buttonValues[b] = info.value;\n\t\t\tif (info.pressed) state.buttonsDown.add(b);\n\t\t}\n\n\t\tfor (const b of state.buttonsDown) {\n\t\t\tif (!reusedPrev.has(b)) state.buttonsPressed.add(b);\n\t\t}\n\t\tfor (const b of reusedPrev) {\n\t\t\tif (!state.buttonsDown.has(b)) state.buttonsReleased.add(b);\n\t\t}\n\n\t\tstate.rawAxes.length = pad.axes.length;\n\t\tfor (let a = 0; a < pad.axes.length; a++) {\n\t\t\tstate.rawAxes[a] = pad.axes[a] ?? 0;\n\t\t}\n\t\tapplyAxisDeadzoning(state.rawAxes, state.axes, deadzone);\n\t}\n}\n\nfunction isBindingActive(\n\tbinding: ActionBinding,\n\tkeysDown: ReadonlySet<string>,\n\tpointerButtonsDown: ReadonlySet<number>,\n\tpads: ReadonlyArray<PadRuntime>,\n): boolean {\n\tif (binding.keys?.some((k) => keysDown.has(k))) return true;\n\tif (binding.pointerButtons?.some((b) => pointerButtonsDown.has(b))) return true;\n\tif (binding.gamepadButtons?.some(({ pad, button }) => pads[pad]?.buttonsDown.has(button) ?? false)) return true;\n\tif (binding.gamepadAxes?.some(({ pad, axis, direction, threshold = DEFAULT_AXIS_THRESHOLD }) => {\n\t\tconst value = pads[pad]?.axes[axis] ?? 0;\n\t\treturn direction > 0 ? value > threshold : value < -threshold;\n\t})) return true;\n\treturn false;\n}\n\n/**\n * Recompute the slot's `active` set in place from `map` against current input sources.\n * Rotates `active` ↔ `prev` (reusing Set instances) so edge detection works with no allocations.\n */\nfunction advanceActionSlot(\n\tslot: ActionSlot,\n\tmap: ActionMap,\n\tkeysDown: ReadonlySet<string>,\n\tpointerButtonsDown: ReadonlySet<number>,\n\tpads: ReadonlyArray<PadRuntime>,\n): void {\n\tconst nextActive = slot.prev;\n\tslot.prev = slot.active;\n\tslot.active = nextActive;\n\tnextActive.clear();\n\n\tfor (const [name, binding] of Object.entries(map)) {\n\t\tif (isBindingActive(binding, keysDown, pointerButtonsDown, pads)) nextActive.add(name);\n\t}\n}\n\nfunction makeActionState<A extends string>(slot: ActionSlot): ActionState<A> {\n\treturn {\n\t\tisActive: (action) => slot.active.has(action),\n\t\tjustActivated: (action) => slot.active.has(action) && !slot.prev.has(action),\n\t\tjustDeactivated: (action) => !slot.active.has(action) && slot.prev.has(action),\n\t};\n}\n\n// ==================== Plugin Factory ====================\n\n/**\n * Create an input plugin for ECSpresso.\n *\n * Provides:\n * - Frame-accurate keyboard state (isDown, justPressed, justReleased)\n * - Pointer position/delta and button state (mouse + touch via PointerEvent)\n * - Up to 4 gamepads polled per frame, with radial deadzone on sticks and analog button values\n * - Unified action mapping (keyboard + pointer + any pad)\n * - Per-player action maps for local co-op (`definePlayer`, `player(id)`)\n * - Automatic listener cleanup on detach\n *\n * @example\n * ```typescript\n * const ecs = ECSpresso.create()\n * .withPlugin(createInputPlugin({\n * actions: {\n * jump: { keys: [' ', 'ArrowUp'], gamepadButtons: [{ pad: 0, button: 0 }] },\n * shoot: { keys: ['z'], pointerButtons: [0] },\n * },\n * players: {\n * p1: { jump: { keys: [' '] }, shoot: { keys: ['z'] } },\n * p2: {\n * jump: { gamepadButtons: gamepadButtonsOn(0, 0) },\n * shoot: { gamepadButtons: gamepadButtonsOn(0, 2) },\n * },\n * },\n * }))\n * .build();\n *\n * const input = ecs.getResource('inputState');\n * if (input.actions.justActivated('jump')) { ... } // any source\n * if (input.player('p1')?.actions.isActive('jump')) { ... } // just player 1\n * if (input.gamepads[0].isDown(0)) { ... } // raw pad 0 A-button\n * ```\n */\nexport function createInputPlugin<A extends string = string, G extends string = 'input'>(\n\toptions?: InputPluginOptions<A, G>\n) {\n\tconst {\n\t\tsystemGroup = 'input',\n\t\tpriority = 100,\n\t\tphase = 'preUpdate',\n\t\ttarget = globalThis,\n\t\tgamepad: gamepadOpts = {},\n\t\tcoordinateTransform,\n\t} = options ?? {};\n\n\t// Construction-time casts: option defaults of `{}` don't structurally satisfy a narrow `ActionMap<A>`,\n\t// but at this boundary we know the user either supplied a valid map or is using A = string.\n\tconst unifiedActionMap = { ...(options?.actions ?? {}) } as ActionMap<A>;\n\tconst playerMaps = new Map<string, ActionMap<A>>(\n\t\tObject.entries(options?.players ?? {}) as Array<[string, ActionMap<A>]>,\n\t);\n\n\tconst deadzone = gamepadOpts.deadzone ?? DEFAULT_DEADZONE;\n\tconst pollFn = gamepadOpts.poll ?? defaultPoll(new Array<GamepadLike | null>(PAD_COUNT).fill(null));\n\n\tconst raw = createRawKeyPointerState();\n\tconst frame = createFrameState();\n\tconst pads: PadRuntime[] = Array.from({ length: PAD_COUNT }, createPadRuntime);\n\tconst unifiedSlot = createActionSlot();\n\tconst playerSlots = new Map<string, ActionSlot>();\n\tconst playerHandles = new Map<string, PlayerInput<A>>();\n\tconst cleanupFns: Array<() => void> = [];\n\n\t// Vector2Ds exposed via the resource — updated in place each frame.\n\tconst position: Vector2D = { x: 0, y: 0 };\n\tconst delta: Vector2D = { x: 0, y: 0 };\n\n\tlet currentUnifiedMap = unifiedActionMap;\n\n\tconst keyboard: KeyboardState = {\n\t\tisDown: (key) => frame.keysDown.has(key),\n\t\tjustPressed: (key) => frame.keysPressed.has(key),\n\t\tjustReleased: (key) => frame.keysReleased.has(key),\n\t};\n\n\tconst pointer: PointerState = {\n\t\tposition,\n\t\tdelta,\n\t\tisDown: (button) => frame.pointerButtonsDown.has(button),\n\t\tjustPressed: (button) => frame.pointerButtonsPressed.has(button),\n\t\tjustReleased: (button) => frame.pointerButtonsReleased.has(button),\n\t};\n\n\tfunction makeGamepadState(index: number): GamepadState {\n\t\tconst state = pads[index];\n\t\tif (!state) throw new Error(`Invalid gamepad index: ${index}`);\n\t\treturn {\n\t\t\tget connected() { return state.connected; },\n\t\t\tget id() { return state.id; },\n\t\t\tisDown: (button) => state.buttonsDown.has(button),\n\t\t\tjustPressed: (button) => state.buttonsPressed.has(button),\n\t\t\tjustReleased: (button) => state.buttonsReleased.has(button),\n\t\t\tbuttonValue: (button) => state.buttonValues[button] ?? 0,\n\t\t\taxis: (index) => state.axes[index] ?? 0,\n\t\t\trawAxis: (index) => state.rawAxes[index] ?? 0,\n\t\t};\n\t}\n\n\tconst gamepadStates: ReadonlyArray<GamepadState> = Array.from({ length: PAD_COUNT }, (_, i) => makeGamepadState(i));\n\n\tconst unifiedActions = makeActionState<A>(unifiedSlot);\n\n\tfunction ensurePlayerSlot(id: string): ActionSlot {\n\t\tconst existing = playerSlots.get(id);\n\t\tif (existing) return existing;\n\t\tconst slot = createActionSlot();\n\t\tplayerSlots.set(id, slot);\n\t\treturn slot;\n\t}\n\n\tfunction createPlayerHandle(id: string): PlayerInput<A> {\n\t\tconst slot = ensurePlayerSlot(id);\n\t\treturn {\n\t\t\tactions: makeActionState<A>(slot),\n\t\t\tsetActionMap: (map) => {\n\t\t\t\tif (!playerMaps.has(id)) throw new Error(`Player '${id}' was removed`);\n\t\t\t\tplayerMaps.set(id, { ...map });\n\t\t\t},\n\t\t\tgetActionMap: () => {\n\t\t\t\tconst map = playerMaps.get(id);\n\t\t\t\tif (!map) throw new Error(`Player '${id}' was removed`);\n\t\t\t\treturn { ...map };\n\t\t\t},\n\t\t};\n\t}\n\n\tfor (const id of playerMaps.keys()) {\n\t\tplayerHandles.set(id, createPlayerHandle(id));\n\t}\n\n\tconst inputState: InputState<A> = {\n\t\tkeyboard,\n\t\tpointer,\n\t\tgamepads: gamepadStates,\n\t\tactions: unifiedActions,\n\t\tsetActionMap(newMap) {\n\t\t\tcurrentUnifiedMap = { ...newMap };\n\t\t},\n\t\tgetActionMap() {\n\t\t\treturn { ...currentUnifiedMap };\n\t\t},\n\t\tdefinePlayer(id, map) {\n\t\t\tplayerMaps.set(id, { ...map });\n\t\t\tif (!playerHandles.has(id)) playerHandles.set(id, createPlayerHandle(id));\n\t\t},\n\t\tremovePlayer(id) {\n\t\t\tconst existed = playerMaps.delete(id);\n\t\t\tplayerHandles.delete(id);\n\t\t\tplayerSlots.delete(id);\n\t\t\treturn existed;\n\t\t},\n\t\tplayer(id) {\n\t\t\treturn playerHandles.get(id);\n\t\t},\n\t\tplayerIds() {\n\t\t\treturn Array.from(playerMaps.keys());\n\t\t},\n\t};\n\n\tfunction onKeyDown(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\tif (ke.repeat) return;\n\t\traw.keysDown.add(ke.key);\n\t\traw.keysPressed.push(ke.key);\n\t}\n\n\tfunction onKeyUp(e: Event) {\n\t\tconst ke = e as KeyboardEvent;\n\t\traw.keysDown.delete(ke.key);\n\t\traw.keysReleased.push(ke.key);\n\t}\n\n\tfunction onPointerDown(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.pointerButtonsDown.add(pe.button);\n\t\traw.pointerButtonsPressed.push(pe.button);\n\t}\n\n\tfunction onPointerMove(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\tif (coordinateTransform) {\n\t\t\tconst { x, y } = coordinateTransform(pe.clientX, pe.clientY);\n\t\t\traw.pointerX = x;\n\t\t\traw.pointerY = y;\n\t\t} else {\n\t\t\traw.pointerX = pe.clientX;\n\t\t\traw.pointerY = pe.clientY;\n\t\t}\n\t\traw.pointerMoved = true;\n\t}\n\n\tfunction onPointerUp(e: Event) {\n\t\tconst pe = e as PointerEvent;\n\t\traw.pointerButtonsDown.delete(pe.button);\n\t\traw.pointerButtonsReleased.push(pe.button);\n\t}\n\n\tfunction addListener(type: string, handler: (e: Event) => void) {\n\t\ttarget.addEventListener(type, handler);\n\t\tcleanupFns.push(() => { target.removeEventListener(type, handler); });\n\t}\n\n\treturn definePlugin('input')\n\t\t.withResourceTypes<InputResourceTypes<A>>()\n\t\t.withLabels<'input-state'>()\n\t\t.withGroups<G>()\n\t\t.install((world) => {\n\t\t\tworld.addResource('inputState', inputState);\n\n\t\t\tworld\n\t\t\t\t.addSystem('input-state')\n\t\t\t\t.setPriority(priority)\n\t\t\t\t.inPhase(phase)\n\t\t\t\t.inGroup(systemGroup)\n\t\t\t\t.setOnInitialize(() => {\n\t\t\t\t\taddListener('keydown', onKeyDown);\n\t\t\t\t\taddListener('keyup', onKeyUp);\n\t\t\t\t\taddListener('pointerdown', onPointerDown);\n\t\t\t\t\taddListener('pointermove', onPointerMove);\n\t\t\t\t\taddListener('pointerup', onPointerUp);\n\t\t\t\t})\n\t\t\t\t.setOnDetach(() => {\n\t\t\t\t\tfor (const cleanup of cleanupFns) cleanup();\n\t\t\t\t\tcleanupFns.length = 0;\n\t\t\t\t})\n\t\t\t\t.setProcess(() => {\n\t\t\t\t\t// Pads must be polled before action computation so a single frame reflects\n\t\t\t\t\t// both DOM-driven (keyboard/pointer) and polled (gamepad) sources consistently.\n\t\t\t\t\tpollGamepadsInto(pads, pollFn, deadzone);\n\t\t\t\t\tupdateFrameStateFromRaw(frame, raw);\n\n\t\t\t\t\tposition.x = frame.pointerX;\n\t\t\t\t\tposition.y = frame.pointerY;\n\t\t\t\t\tdelta.x = frame.pointerDeltaX;\n\t\t\t\t\tdelta.y = frame.pointerDeltaY;\n\n\t\t\t\t\tadvanceActionSlot(unifiedSlot, currentUnifiedMap, frame.keysDown, frame.pointerButtonsDown, pads);\n\t\t\t\t\tfor (const [id, map] of playerMaps) {\n\t\t\t\t\t\tconst slot = ensurePlayerSlot(id);\n\t\t\t\t\t\tadvanceActionSlot(slot, map, frame.keysDown, frame.pointerButtonsDown, pads);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t});\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": "
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "A22CC6420E47F29C64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
|
@@ -82,8 +82,13 @@ export interface Renderer3DPluginPreInitOptions<G extends string = 'renderer3d'>
|
|
|
82
82
|
}
|
|
83
83
|
/**
|
|
84
84
|
* Camera configuration for managed mode.
|
|
85
|
+
*
|
|
86
|
+
* Discriminated on `projection`. Defaults to `'perspective'` when omitted.
|
|
87
|
+
* Orthographic cameras use `viewSize` (world-unit height at zoom=1) to define
|
|
88
|
+
* the base frustum; `zoom` maps directly to Three.js's `OrthographicCamera.zoom`.
|
|
85
89
|
*/
|
|
86
|
-
export
|
|
90
|
+
export type CameraOptions = {
|
|
91
|
+
projection?: 'perspective';
|
|
87
92
|
fov?: number;
|
|
88
93
|
near?: number;
|
|
89
94
|
far?: number;
|
|
@@ -97,7 +102,23 @@ export interface CameraOptions {
|
|
|
97
102
|
y: number;
|
|
98
103
|
z: number;
|
|
99
104
|
};
|
|
100
|
-
}
|
|
105
|
+
} | {
|
|
106
|
+
projection: 'orthographic';
|
|
107
|
+
viewSize?: number;
|
|
108
|
+
zoom?: number;
|
|
109
|
+
near?: number;
|
|
110
|
+
far?: number;
|
|
111
|
+
position?: {
|
|
112
|
+
x: number;
|
|
113
|
+
y: number;
|
|
114
|
+
z: number;
|
|
115
|
+
};
|
|
116
|
+
lookAt?: {
|
|
117
|
+
x: number;
|
|
118
|
+
y: number;
|
|
119
|
+
z: number;
|
|
120
|
+
};
|
|
121
|
+
};
|
|
101
122
|
/**
|
|
102
123
|
* Options when letting the plugin create and manage Three.js objects.
|
|
103
124
|
*/
|