-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(platform): add Keyboard Input device
- Loading branch information
1 parent
8fc00ef
commit 8bb496e
Showing
9 changed files
with
227 additions
and
55 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { ISystem } from "./ISystem"; | ||
import { InputComponent } from '../components'; | ||
import { KeyboardDevice } from "../../platform"; | ||
|
||
export class InputSystem implements ISystem { | ||
public readonly components: InputComponent[] = []; | ||
|
||
constructor(private readonly inputDevice: KeyboardDevice) {} | ||
|
||
public update(): void { | ||
this.components.forEach(inputComponent => inputComponent.update(this.inputDevice)); | ||
this.inputDevice.update(); | ||
} | ||
|
||
public registerComponent(component: InputComponent): void { | ||
this.components.push(component); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
export * from './ISystem'; | ||
export * from './RenderSystem'; | ||
export * from './PhysicsSystem'; | ||
export * from './PhysicsSystem'; | ||
export * from './InputSystem'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
export * from './gfx'; | ||
export * from './gfx'; | ||
export * from './inputs'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
export enum KeyStatus { | ||
Up, | ||
Down | ||
}; | ||
|
||
export interface KeyEvent { | ||
status: KeyStatus, | ||
code: string | ||
} | ||
|
||
type InputListenerCallback = (event: KeyEvent) => void; | ||
|
||
export class KeyboardDevice { | ||
private _listeners: InputListenerCallback[] = []; | ||
private _keyStatusMap: Record<string, KeyStatus> = {}; | ||
|
||
get listeners(): InputListenerCallback[] { | ||
// Copy to avoid modifying existing items from getter | ||
return [ | ||
...this._listeners | ||
]; | ||
} | ||
|
||
public constructor() { | ||
window.addEventListener("keydown", (e) => this.onKeyDown(e), true); | ||
window.addEventListener("keyup", (e) => this.onKeyUp(e), true); | ||
} | ||
|
||
public pushInputListener(callback: InputListenerCallback): void { | ||
this._listeners.push(callback); | ||
} | ||
|
||
public update(): void { | ||
Object.entries(this._keyStatusMap).forEach(([key, value]) => { | ||
this._listeners.forEach(listener => listener({ | ||
code: key, | ||
status: value | ||
})) | ||
}) | ||
|
||
// Empty listeners after every update | ||
this._listeners = []; | ||
} | ||
|
||
private onKeyDown(e: KeyboardEvent): void { | ||
this._keyStatusMap[e.code] = KeyStatus.Down; | ||
} | ||
|
||
private onKeyUp(e: KeyboardEvent): void { | ||
this._keyStatusMap[e.code] = KeyStatus.Up; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from './KeyboardDevice'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,37 +1,22 @@ | ||
import { InputComponent, KeyStatus } from "../../../../src"; | ||
import { InputComponent, KeyEvent, KeyStatus, KeyboardDevice } from "../../../../src"; | ||
|
||
describe('ecs/components/InputComponent', () => { | ||
const inputComponent = new InputComponent(); | ||
let inputDevice = new KeyboardDevice(); | ||
let inputComponent = new InputComponent(); | ||
|
||
beforeEach(() => { | ||
inputDevice = new KeyboardDevice(); | ||
inputComponent = new InputComponent(); | ||
}) | ||
|
||
describe('.update()', () => { | ||
it('Should invoke the onInputEventCb when a key is pressed down', () => { | ||
const onInputCb = jest.fn(); | ||
|
||
inputComponent.onInputEventCb = onInputCb; | ||
|
||
const event = new KeyboardEvent('keydown', { code: 'KeyA' }); | ||
window.dispatchEvent(event); | ||
it('Should push a listener in the inputDevice', () => { | ||
const onInputCb = jest.fn((e: KeyEvent) => { }); | ||
|
||
expect(onInputCb).toHaveBeenCalledWith({ | ||
status: KeyStatus.Down, | ||
code: 'KeyA' | ||
}); | ||
}); | ||
|
||
it('Should invoke the onInputEventCb when a key is released', () => { | ||
const onInputCb = jest.fn(); | ||
|
||
inputComponent.onInputEventCb = onInputCb; | ||
inputComponent.update(inputDevice); | ||
|
||
const event = new KeyboardEvent('keyup', { code: 'KeyA' }); | ||
window.dispatchEvent(event); | ||
|
||
expect(onInputCb).toHaveBeenCalledWith({ | ||
status: KeyStatus.Up, | ||
code: 'KeyA' | ||
}); | ||
expect(inputDevice.listeners.length).toBe(1); | ||
}); | ||
|
||
it.todo('Should push an InputListener into the InputSystem'); | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import { InputComponent, InputSystem, KeyStatus, KeyboardDevice } from "../../../../src"; | ||
|
||
describe('ecs/systems/InputSystem', () => { | ||
const inputDevice = new KeyboardDevice(); | ||
let inputSystem = new InputSystem(inputDevice); | ||
|
||
beforeEach(() => { | ||
inputSystem = new InputSystem(inputDevice); | ||
}) | ||
|
||
describe('.registerComponent()', () => { | ||
it('Should register the component into the InputSystem components list', () => { | ||
const testInputComponent = new InputComponent(); | ||
inputSystem.registerComponent(testInputComponent); | ||
|
||
expect(inputSystem.components).toContain(testInputComponent); | ||
}) | ||
}); | ||
|
||
describe('.update()', () => { | ||
it('Should update each component registered into the system', () => { | ||
const inputComponent = new InputComponent(); | ||
const spyUpdate = jest.spyOn(inputComponent, 'update'); | ||
|
||
inputSystem.registerComponent(inputComponent); | ||
inputSystem.update(); | ||
|
||
expect(spyUpdate).toHaveBeenCalled(); | ||
}); | ||
|
||
it('Should trigger the inputDevice update', () => { | ||
const fakeCb = jest.fn(); | ||
const inputComponent = new InputComponent(); | ||
inputComponent.onInputEventCb = fakeCb; | ||
|
||
const event = new KeyboardEvent('keydown', { code: 'KeyA' }); | ||
window.dispatchEvent(event); | ||
|
||
inputSystem.registerComponent(inputComponent); | ||
inputSystem.update(); | ||
|
||
expect(fakeCb).toHaveBeenCalledWith({ | ||
code: 'KeyA', | ||
status: KeyStatus.Down | ||
}); | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
import { KeyEvent, KeyStatus, KeyboardDevice } from "../../../../src"; | ||
|
||
describe('platform/inputs/KeyboardDevice', () => { | ||
let keyboardDevice = new KeyboardDevice(); | ||
|
||
beforeEach(() => { | ||
keyboardDevice = new KeyboardDevice(); | ||
}) | ||
|
||
describe('.pushInputListener()', () => { | ||
it('Should register a new inputListener in the listener list', () => { | ||
const callback = jest.fn((event: KeyEvent) => {}); | ||
|
||
keyboardDevice.pushInputListener(callback); | ||
|
||
expect(keyboardDevice.listeners).toContain(callback); | ||
}); | ||
}) | ||
|
||
describe('.update()', () => { | ||
it('Should invoke the listener when a button is pressed down', () => { | ||
const callback = jest.fn((event: KeyEvent) => {}); | ||
|
||
keyboardDevice.pushInputListener(callback); | ||
|
||
const event = new KeyboardEvent('keydown', { code: 'KeyA' }); | ||
window.dispatchEvent(event); | ||
|
||
keyboardDevice.update(); | ||
|
||
expect(callback).toHaveBeenCalledWith({ | ||
code: 'KeyA', | ||
status: KeyStatus.Down | ||
}); | ||
}); | ||
|
||
|
||
it('Should invoke the listener when a button is released', () => { | ||
const callback = jest.fn((event: KeyEvent) => {}); | ||
|
||
keyboardDevice.pushInputListener(callback); | ||
|
||
const event = new KeyboardEvent('keyup', { code: 'KeyA' }); | ||
window.dispatchEvent(event); | ||
|
||
keyboardDevice.update(); | ||
|
||
expect(callback).toHaveBeenCalledWith({ | ||
code: 'KeyA', | ||
status: KeyStatus.Up | ||
}); | ||
}); | ||
|
||
it('Should invoke the listener with only the latest status of the key', () => { | ||
const callback = jest.fn((event: KeyEvent) => { }); | ||
|
||
keyboardDevice.pushInputListener(callback); | ||
|
||
const keyDownEvent = new KeyboardEvent('keydown', { code: 'KeyA' }); | ||
const keyUpEvent = new KeyboardEvent('keyup', { code: 'KeyA' }); | ||
|
||
window.dispatchEvent(keyDownEvent); | ||
window.dispatchEvent(keyUpEvent); | ||
|
||
keyboardDevice.update(); | ||
|
||
expect(callback).toHaveBeenCalledWith({ | ||
code: 'KeyA', | ||
status: KeyStatus.Up | ||
}); | ||
|
||
expect(callback).not.toHaveBeenCalledWith({ | ||
code: 'KeyA', | ||
status: KeyStatus.Down | ||
}) | ||
}); | ||
|
||
it('Should cleanup all listeners after an update', () => { | ||
const callback = jest.fn((event: KeyEvent) => {}); | ||
|
||
keyboardDevice.pushInputListener(callback); | ||
keyboardDevice.update(); | ||
|
||
expect(keyboardDevice.listeners).toBeEmpty(); | ||
}) | ||
}) | ||
}) |