feat: decouple input normalization from action registry

This commit is contained in:
cha0s 2019-04-28 23:30:56 -05:00
parent ebf3d2dc28
commit 7fc3935f05
4 changed files with 171 additions and 126 deletions

View File

@ -17,4 +17,5 @@
- ✔ EventEmitter::emit is too hot - ✔ EventEmitter::emit is too hot
- ❌ entityList.fromJSON() - ❌ entityList.fromJSON()
- ❌ Socket WebWorker can't connect in Firefox - ❌ Socket WebWorker can't connect in Firefox
- ❌ Transients - ✔ Entity packets
- ✔ Decouple input normalization from action registry

View File

@ -0,0 +1,79 @@
import * as I from 'immutable';
import {compose} from '@avocado/core';
import {EventEmitter} from '@avocado/mixins';
const decorate = compose(
EventEmitter,
);
export class ActionRegistry extends decorate(class {}) {
constructor() {
super();
this.mapActionToKey = new Map();
this.mapKeyToAction = {};
this.normalizer = undefined;
this._state = I.Map();
}
actionForKey(key) {
return this.mapKeyToAction[key];
}
listen(normalizer) {
// Only listen once.
if (this.normalizer) {
return;
}
this.normalizer = normalizer;
this.normalizer.on('keyUp', this.onKeyUp, this);
this.normalizer.on('keyDown', this.onKeyDown, this);
}
keyForAction(action) {
return this.mapActionToKey.get(action);
}
mapKeysToActions(map) {
for (const key in map) {
const action = map[key];
this.mapKeyToAction[key] = action;
this.mapActionToKey.set(action, key);
}
}
onKeyDown(key) {
if (this.mapKeyToAction[key]) {
const action = this.mapKeyToAction[key];
this._state = this._state.set(action, true);
}
}
onKeyUp(key) {
if (this.mapKeyToAction[key]) {
const action = this.mapKeyToAction[key];
this._state = this._state.delete(action);
}
}
get state() {
return this._state;
}
set state(state) {
this._state = state;
}
stopListening(normalizer) {
if (!this.normalizer) {
return;
}
this.normalizer.off('keyUp', this.onKeyUp);
this.normalizer.off('keyDown', this.onKeyDown);
this.normalizer = undefined;
}
}
export {InputPacket} from './packet/input.packet';

View File

@ -1,126 +1,3 @@
import * as I from 'immutable'; export {ActionRegistry} from './action-registry';
export {InputNormalizer} from './normalizer';
import {compose} from '@avocado/core';
import {EventEmitter} from '@avocado/mixins';
const decorate = compose(
EventEmitter,
);
export class ActionRegistry extends decorate(class {}) {
static normalizeKey(key) {
return key.toLowerCase();
}
constructor() {
super();
// Track events.
this.target = undefined;
this.mapActionToKey = new Map();
this.mapKeyToAction = {};
// Track actions.
this._state = I.Map();
// Handle lame OS input event behavior. See: https://mzl.la/2Ob0WQE
this.keysDown = {};
this.keyUpDelays = {};
// Bind event handlers.
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
}
actionForKey(key) {
return this.mapKeyToAction[key];
}
listen(target = window.document) {
// Only listen once.
if (this.target) {
return;
}
this.target = target;
this.target.addEventListener('blur', this.onBlur);
this.target.addEventListener('keydown', this.onKeyDown);
this.target.addEventListener('keyup', this.onKeyUp);
}
keyForAction(action) {
return this.mapActionToKey.get(action);
}
mapKeysToActions(map) {
for (const key in map) {
const action = map[key];
this.mapKeyToAction[key] = action;
this.mapActionToKey.set(action, key);
}
}
onBlur(event) {
event = event || window.event;
this.setAllKeysUp();
}
onKeyDown(event) {
event = event || window.event;
const key = this.constructor.normalizeKey(event.key);
if (this.keysDown[key]) {
if (this.keyUpDelays[key]) {
clearTimeout(this.keyUpDelays[key]);
delete this.keyUpDelays[key];
}
return;
}
this.keysDown[key] = true;
if (this.mapKeyToAction[key]) {
const action = this.mapKeyToAction[key];
this._state = this._state.set(action, true);
}
}
onKeyUp(event) {
event = event || window.event;
const key = this.constructor.normalizeKey(event.key);
this.keyUpDelays[key] = setTimeout(() => {
delete this.keyUpDelays[key];
delete this.keysDown[key];
if (this.mapKeyToAction[key]) {
const action = this.mapKeyToAction[key];
this._state = this._state.delete(action);
}
}, 20);
}
setAllKeysUp() {
this.keysDown = {};
for (const key in this.keyUpDelays) {
const handle = this.keyUpDelays[key];
clearTimeout(handle);
}
this.keyUpDelays = {};
this._state = I.Map();
}
get state() {
return this._state;
}
set state(state) {
this._state = state;
}
stopListening() {
this.setAllKeysUp();
if (!this.target) {
return;
}
this.target.removeEventListener('blur', this.onBlur);
this.target.removeEventListener('keydown', this.onKeyDown);
this.target.removeEventListener('keyup', this.onKeyUp);
this.target = undefined;
}
}
export {InputPacket} from './packet/input.packet'; export {InputPacket} from './packet/input.packet';

View File

@ -0,0 +1,88 @@
import {compose} from '@avocado/core';
import {EventEmitter} from '@avocado/mixins';
const decorate = compose(
EventEmitter,
);
export class InputNormalizer extends decorate(class{}) {
constructor() {
super();
// Track events.
this.target = undefined;
this.targetForKeyUp = undefined;
// Handle lame OS input event behavior. See: https://mzl.la/2Ob0WQE
this.keysDown = {};
this.keyUpDelays = {};
// Bind event handlers.
this.onBlur = this.onBlur.bind(this);
this.onKeyDown = this.onKeyDown.bind(this);
this.onKeyUp = this.onKeyUp.bind(this);
}
actionForKey(key) {
return this.mapKeyToAction[key];
}
listen(target = window.document, targetForKeyUp = window.document) {
// Only listen once.
if (this.target) {
return;
}
this.target = target;
this.targetForKeyUp = targetForKeyUp;
this.target.addEventListener('blur', this.onBlur);
this.target.addEventListener('keydown', this.onKeyDown);
this.targetForKeyUp.addEventListener('keyup', this.onKeyUp);
}
onBlur(event) {
this.setAllKeysUp();
}
onKeyDown(event) {
const {key} = event;
if (this.keysDown[key]) {
if (this.keyUpDelays[key]) {
clearTimeout(this.keyUpDelays[key]);
delete this.keyUpDelays[key];
}
return;
}
this.keysDown[key] = true;
this.emit('keyDown', key);
}
onKeyUp(event) {
const {key} = event;
this.keyUpDelays[key] = setTimeout(() => {
delete this.keyUpDelays[key];
delete this.keysDown[key];
this.emit('keyUp', key);
}, 20);
}
setAllKeysUp() {
this.keysDown = {};
for (const key in this.keyUpDelays) {
const handle = this.keyUpDelays[key];
clearTimeout(handle);
this.emit('keyUp', key);
}
this.keyUpDelays = {};
}
stopListening() {
this.setAllKeysUp();
if (!this.target) {
return;
}
this.target.removeEventListener('blur', this.onBlur);
this.target.removeEventListener('keydown', this.onKeyDown);
this.targetForKeyUp.removeEventListener('keyup', this.onKeyUp);
this.target = undefined;
this.targetForKeyUp = undefined;
}
}