@dcl/ecs
Version:
Decentraland ECS
196 lines (195 loc) • 7.73 kB
JavaScript
import * as components from '../components';
const InputCommands = [
0 /* InputAction.IA_POINTER */,
1 /* InputAction.IA_PRIMARY */,
2 /* InputAction.IA_SECONDARY */,
4 /* InputAction.IA_FORWARD */,
5 /* InputAction.IA_BACKWARD */,
6 /* InputAction.IA_RIGHT */,
7 /* InputAction.IA_LEFT */,
8 /* InputAction.IA_JUMP */,
9 /* InputAction.IA_WALK */,
10 /* InputAction.IA_ACTION_3 */,
11 /* InputAction.IA_ACTION_4 */,
12 /* InputAction.IA_ACTION_5 */,
13 /* InputAction.IA_ACTION_6 */
];
const InputStateUpdateSystemPriority = 1 << 20;
/**
* @public
* ____DO NOT USE ____ use inputSystem instead
*/
export function createInputSystem(engine) {
const PointerEventsResult = components.PointerEventsResult(engine);
const globalState = {
previousFrameMaxTimestamp: 0,
currentFrameMaxTimestamp: 0,
buttonState: new Map(),
thisFrameCommands: []
};
function findLastAction(pointerEventType, inputAction, entity) {
const ascendingTimestampIterator = PointerEventsResult.get(entity);
for (const command of Array.from(ascendingTimestampIterator).reverse()) {
if (command.button === inputAction && command.state === pointerEventType) {
return command;
}
}
}
function* findCommandsByActionDescending(inputAction, entity) {
const ascendingTimestampIterator = PointerEventsResult.get(entity);
for (const command of Array.from(ascendingTimestampIterator).reverse()) {
if (command.button === inputAction) {
yield command;
}
}
}
function buttonStateUpdateSystem() {
// first store the previous' frame timestamp
let maxTimestamp = globalState.currentFrameMaxTimestamp;
globalState.previousFrameMaxTimestamp = maxTimestamp;
if (globalState.thisFrameCommands.length) {
globalState.thisFrameCommands = [];
}
// then iterate over all new commands
for (const [, commands] of engine.getEntitiesWith(PointerEventsResult)) {
// TODO: adapt the gset component to have a cached "reversed" option by default
const arrayCommands = Array.from(commands);
for (let i = arrayCommands.length - 1; i >= 0; i--) {
const command = arrayCommands[i];
if (command.timestamp > maxTimestamp) {
maxTimestamp = command.timestamp;
}
if (command.timestamp > globalState.previousFrameMaxTimestamp) {
globalState.thisFrameCommands.push(command);
}
if (command.state === 0 /* PointerEventType.PET_UP */ || command.state === 1 /* PointerEventType.PET_DOWN */) {
const prevCommand = globalState.buttonState.get(command.button);
if (!prevCommand || command.timestamp > prevCommand.timestamp) {
globalState.buttonState.set(command.button, command);
}
else {
// since we are iterating a descending array, we can early finish the
// loop
break;
}
}
}
}
// update current frame's max timestamp
globalState.currentFrameMaxTimestamp = maxTimestamp;
}
engine.addSystem(buttonStateUpdateSystem, InputStateUpdateSystemPriority, '@dcl/ecs#inputSystem');
function timestampIsCurrentFrame(timestamp) {
if (timestamp > globalState.previousFrameMaxTimestamp && timestamp <= globalState.currentFrameMaxTimestamp) {
return true;
}
else {
return false;
}
}
function getClick(inputAction, entity) {
if (inputAction !== 3 /* InputAction.IA_ANY */) {
return findClick(inputAction, entity);
}
for (const input of InputCommands) {
const cmd = findClick(input, entity);
if (cmd)
return cmd;
}
return null;
}
function findClick(inputAction, entity) {
let down = null;
let up = null;
// We search the last UP & DOWN command sorted by timestamp descending
for (const it of findCommandsByActionDescending(inputAction, entity)) {
if (!up) {
if (it.state === 0 /* PointerEventType.PET_UP */) {
up = it;
continue;
}
}
else if (!down) {
if (it.state === 1 /* PointerEventType.PET_DOWN */) {
down = it;
break;
}
}
}
if (!up || !down)
return null;
// If the DOWN command has happen before the UP commands, it means that that a clicked has happen
if (down.timestamp < up.timestamp && timestampIsCurrentFrame(up.timestamp)) {
return { up, down };
}
return null;
}
function getInputCommandFromEntity(inputAction, pointerEventType, entity) {
if (inputAction !== 3 /* InputAction.IA_ANY */) {
return findInputCommand(inputAction, pointerEventType, entity);
}
for (const input of InputCommands) {
const cmd = findInputCommand(input, pointerEventType, entity);
if (cmd)
return cmd;
}
return null;
}
function getInputCommand(inputAction, pointerEventType, entity) {
if (entity) {
return getInputCommandFromEntity(inputAction, pointerEventType, entity);
}
else {
for (const command of globalState.thisFrameCommands) {
if ((command.button === inputAction || inputAction === 3 /* InputAction.IA_ANY */) &&
command.state === pointerEventType) {
return command;
}
}
return null;
}
}
function findInputCommand(inputAction, pointerEventType, entity) {
// We search the last pointer Event command sorted by timestamp
const command = findLastAction(pointerEventType, inputAction, entity);
if (!command)
return null;
if (timestampIsCurrentFrame(command.timestamp)) {
return command;
}
else {
return null;
}
}
// returns true if there was a DOWN (in any past frame), and then an UP in the last frame
function isClicked(inputAction, entity) {
return getClick(inputAction, entity) !== null;
}
// returns true if the provided last action was triggered in the last frame
function isTriggered(inputAction, pointerEventType, entity) {
if (entity) {
const command = findLastAction(pointerEventType, inputAction, entity);
return (command && timestampIsCurrentFrame(command.timestamp)) || false;
}
else {
for (const command of globalState.thisFrameCommands) {
if ((command.button === inputAction || inputAction === 3 /* InputAction.IA_ANY */) &&
command.state === pointerEventType) {
return true;
}
}
return false;
}
}
// returns the global state of the input. This global state is updated from the system
function isPressed(inputAction) {
return globalState.buttonState.get(inputAction)?.state === 1 /* PointerEventType.PET_DOWN */;
}
return {
isPressed,
getClick,
getInputCommand,
isClicked,
isTriggered
};
}