@dcl/ecs
Version:
Decentraland ECS
223 lines (222 loc) • 8.92 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createInputSystem = void 0;
const components = __importStar(require("../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
*/
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
};
}
exports.createInputSystem = createInputSystem;