From f296e66d142a14d4e52c1e0b6aa5910106a3595b Mon Sep 17 00:00:00 2001 From: johnshaughnessy <johnfshaughnessy@gmail.com> Date: Mon, 5 Nov 2018 15:52:39 -0800 Subject: [PATCH] Sort bindings by dependency. Fix keyboard+mouse bindings. --- .../userinput/bindings/keyboard-mouse-user.js | 119 +++++---- .../userinput/devices/app-aware-mouse.js | 9 +- src/systems/userinput/userinput-debug.js | 117 +++------ src/systems/userinput/userinput.js | 246 +++++++++++------- 4 files changed, 252 insertions(+), 239 deletions(-) diff --git a/src/systems/userinput/bindings/keyboard-mouse-user.js b/src/systems/userinput/bindings/keyboard-mouse-user.js index 2ff3297c9..351b9e3e0 100644 --- a/src/systems/userinput/bindings/keyboard-mouse-user.js +++ b/src/systems/userinput/bindings/keyboard-mouse-user.js @@ -9,25 +9,11 @@ const arrows_vec2 = "/var/mouse-and-keyboard/arrows_vec2"; const dropWithRMB = "/vars/mouse-and-keyboard/drop_with_RMB"; const dropWithEsc = "/vars/mouse-and-keyboard/drop_with_esc"; -const dropWithRMBorEscBindings = [ - { - src: { value: paths.device.mouse.buttonRight }, - dest: { value: dropWithRMB }, - xform: xforms.falling, - root: "rmb", - priority: 200 - }, - { - src: { value: paths.device.keyboard.key("Escape") }, - dest: { value: dropWithEsc }, - xform: xforms.falling - }, - { - src: [dropWithRMB, dropWithEsc], - dest: { value: paths.actions.cursor.drop }, - xform: xforms.any - } -]; +const k = name => { + return `/keyboard-mouse-user/keyboard-var/${name}`; +}; + +const dropWithRMBorEscBindings = []; export const keyboardMouseUserBindings = addSetsToBindings({ [sets.global]: [ @@ -81,16 +67,12 @@ export const keyboardMouseUserBindings = addSetsToBindings({ { src: { value: paths.device.keyboard.key("q") }, dest: { value: paths.actions.snapRotateLeft }, - xform: xforms.rising, - root: "q", - priority: 100 + xform: xforms.rising }, { src: { value: paths.device.keyboard.key("e") }, dest: { value: paths.actions.snapRotateRight }, - xform: xforms.rising, - root: "e", - priority: 100 + xform: xforms.rising }, { src: { value: paths.device.hud.penButton }, @@ -148,7 +130,6 @@ export const keyboardMouseUserBindings = addSetsToBindings({ value: paths.actions.startGazeTeleport }, xform: xforms.rising, - root: "rmb", priority: 100 }, { @@ -202,7 +183,6 @@ export const keyboardMouseUserBindings = addSetsToBindings({ src: { value: "/var/notshift+q" }, dest: { value: paths.actions.snapRotateLeft }, xform: xforms.rising, - root: "q", priority: 200 }, { @@ -217,92 +197,116 @@ export const keyboardMouseUserBindings = addSetsToBindings({ src: { value: "/var/notshift+e" }, dest: { value: paths.actions.snapRotateRight }, xform: xforms.rising, - root: "e", priority: 200 }, { src: { value: paths.device.mouse.buttonLeft }, dest: { value: paths.actions.cursor.startDrawing }, xform: xforms.rising, - priority: 200 + priority: 3 }, { src: { value: paths.device.mouse.buttonLeft }, dest: { value: paths.actions.cursor.stopDrawing }, xform: xforms.falling, - priority: 200, - root: "lmb" + priority: 3 }, { src: { - bool: paths.device.keyboard.key("shift"), - value: paths.device.mouse.wheel + value: k("wheelWithShift") }, dest: { value: "/var/cursorScalePenTipWheel" }, - xform: xforms.copyIfTrue, - priority: 200, - root: "wheel" + xform: xforms.copy, + priority: 200 }, { src: { value: "/var/cursorScalePenTipWheel" }, dest: { value: paths.actions.cursor.scalePenTip }, xform: xforms.scale(0.12) }, - ...dropWithRMBorEscBindings + { + src: { value: paths.device.mouse.buttonRight }, + dest: { value: dropWithRMB }, + xform: xforms.falling, + priority: 200 + }, + { + src: { value: paths.device.keyboard.key("Escape") }, + dest: { value: dropWithEsc }, + xform: xforms.falling + }, + { + src: [dropWithRMB, dropWithEsc], + dest: { value: paths.actions.cursor.drop }, + xform: xforms.any + } ], [sets.cursorHoldingCamera]: [ { src: { value: paths.device.mouse.buttonLeft }, dest: { value: paths.actions.cursor.takeSnapshot }, - xform: xforms.rising + xform: xforms.rising, + priority: 3 }, { - src: { value: paths.device.mouse.buttonLeft }, - xform: xforms.noop, - dest: { value: paths.noop }, - priority: 200, - root: "lmb" + src: { value: paths.device.mouse.buttonRight }, + dest: { value: dropWithRMB }, + xform: xforms.falling, + priority: 200 + }, + { + src: { value: paths.device.keyboard.key("Escape") }, + dest: { value: dropWithEsc }, + xform: xforms.falling }, - ...dropWithRMBorEscBindings + { + src: [dropWithRMB, dropWithEsc], + dest: { value: paths.actions.cursor.drop }, + xform: xforms.any + } ], [sets.cursorHoldingInteractable]: [ { src: { + bool: paths.device.keyboard.key("shift"), value: paths.device.mouse.wheel }, dest: { - value: paths.actions.cursor.modDelta + value: k("wheelWithShift") }, - xform: xforms.copy, - root: "wheel", - priority: 100 + xform: xforms.copyIfTrue }, { src: { bool: paths.device.keyboard.key("shift"), value: paths.device.mouse.wheel }, - dest: { value: paths.actions.cursor.modDelta }, + dest: { + value: k("wheelWithoutShift") + }, xform: xforms.copyIfFalse }, { src: { - bool: paths.device.keyboard.key("shift"), - value: paths.device.mouse.wheel + value: k("wheelWithoutShift") + }, + dest: { value: paths.actions.cursor.modDelta }, + xform: xforms.copy + }, + { + src: { + value: k("wheelWithShift") }, dest: { value: paths.actions.cursor.scaleGrabbedGrabbable }, - xform: xforms.copyIfTrue, - priority: 150, - root: "wheel" + xform: xforms.copy }, { src: { value: paths.device.mouse.buttonLeft }, dest: { value: paths.actions.cursor.drop }, xform: xforms.falling, - priority: 100, - root: "lmb" + priority: 2 } ], @@ -310,7 +314,8 @@ export const keyboardMouseUserBindings = addSetsToBindings({ { src: { value: paths.device.mouse.buttonLeft }, dest: { value: paths.actions.cursor.grab }, - xform: xforms.rising + xform: xforms.rising, + priority: 1 } ], [sets.inputFocused]: [ diff --git a/src/systems/userinput/devices/app-aware-mouse.js b/src/systems/userinput/devices/app-aware-mouse.js index 66e006d44..de5f4acec 100644 --- a/src/systems/userinput/devices/app-aware-mouse.js +++ b/src/systems/userinput/devices/app-aware-mouse.js @@ -1,3 +1,4 @@ +import { sets } from "../sets"; import { paths } from "../paths"; import { Pose } from "../pose"; @@ -35,9 +36,13 @@ export class AppAwareMouseDevice { const rawIntersections = []; this.cursorController.raycaster.intersectObjects(this.cursorController.targets, true, rawIntersections); const intersection = rawIntersections.find(x => x.object.el); + const userinput = AFRAME.scenes[0].systems.userinput; this.clickedOnAnything = - intersection && - intersection.object.el.matches(".pen, .pen *, .video, .video *, .interactable, .interactable *"); + (intersection && + intersection.object.el.matches(".pen, .pen *, .video, .video *, .interactable, .interactable *")) || + userinput.activeSets.has(sets.cursorHoldingPen) || + userinput.activeSets.has(sets.cursorHoldingInteractable) || + userinput.activeSets.has(sets.cursorHoldingCamera); } this.prevButtonLeft = buttonLeft; diff --git a/src/systems/userinput/userinput-debug.js b/src/systems/userinput/userinput-debug.js index fe0362c2f..453bcdaf8 100644 --- a/src/systems/userinput/userinput-debug.js +++ b/src/systems/userinput/userinput-debug.js @@ -1,96 +1,45 @@ import { paths } from "./paths"; -const line = "__________________________________________________________________"; -const bindingToString = b => { - const sb = []; - sb.push("{\n"); - sb.push(" "); - sb.push("src: "); - sb.push("\n"); - for (const s of Object.keys(b.src)) { - sb.push(" "); - sb.push(" "); - sb.push(s); - sb.push(" : "); - sb.push(b.src[s]); - sb.push("\n"); - } - sb.push(" "); - sb.push("dest: "); - sb.push("\n"); - for (const s of Object.keys(b.dest)) { - sb.push(" "); - sb.push(" "); - sb.push(s); - sb.push(" : "); - sb.push(b.dest[s]); - sb.push("\n"); - } - sb.push(" "); - sb.push("priority"); - sb.push(" : "); - sb.push(b.priority || 0); - for (const s of b.sets) { - sb.push("\n"); - sb.push(" "); - sb.push("in set"); - sb.push(" : "); - sb.push(s); - sb.push("\n"); - } - sb.push("\n"); - sb.push("}\n"); - return sb.join(""); -}; AFRAME.registerSystem("userinput-debug", { tick() { const userinput = AFRAME.scenes[0].systems.userinput; if (userinput.get(paths.actions.logDebugFrame) || userinput.get(paths.actions.log)) { - const sb = []; - sb.push("\n"); - sb.push(line); - sb.push("\n"); - sb.push("actives:"); - sb.push("\n"); - sb.push(line); - sb.push("\n"); - for (let i = 0; i < userinput.runners.length; i++) { - if (userinput.actives[i]) { - sb.push(bindingToString(userinput.runners[i])); - } - } - sb.push("\n"); - sb.push(line); - sb.push("\n"); - sb.push("inactives:"); - sb.push("\n"); - sb.push(line); - sb.push("\n"); - for (let i = 0; i < userinput.runners.length; i++) { - if (!userinput.actives[i]) { - sb.push("\n"); - sb.push("The inactive binding:\n"); - sb.push(bindingToString(userinput.runners[i])); - sb.push("\n"); - sb.push("is overridden by the following "); - sb.push(userinput.overrides[i].length); - sb.push(" bindings.\n"); - for (const o of userinput.overrides[i]) { - sb.push("\n"); - sb.push("Override:\n"); - sb.push(bindingToString(o)); - sb.push("\n"); - } - } - } - console.log("active and inactive bindings"); - console.log(sb.join("")); - console.log("runners", userinput.runners); + console.log(userinput); + console.log("sorted", userinput.sortedBindings); console.log("actives", userinput.actives); - console.log("xformStates", userinput.xformStates); + console.log("masked", userinput.masked); console.log("devices", userinput.activeDevices); - console.log("map", userinput.map); console.log("activeSets", userinput.activeSets); console.log("frame", userinput.frame); + console.log("xformStates", userinput.xformStates); + const { sortedBindings, actives, masked, xformStates } = userinput; + for (const i in sortedBindings) { + const sb = []; + if (masked[i].length > 0) { + const xform = xformStates[sortedBindings[i]]; + for (const j of masked[i]) { + sb.push(JSON.stringify(sortedBindings[j])); + } + } + + console.log( + "binding: ", + i, + "\n", + sortedBindings[i], + "\n", + "dest: ", + Object.values(sortedBindings[i].dest), + "\n", + "active: ", + actives[i], + "\n", + "maskedBy: ", + masked[i], + "\n", + sb.join("\n"), + "\n" + ); + } } } }); diff --git a/src/systems/userinput/userinput.js b/src/systems/userinput/userinput.js index a00a277b1..45a255197 100644 --- a/src/systems/userinput/userinput.js +++ b/src/systems/userinput/userinput.js @@ -25,32 +25,125 @@ import { resolveActionSets } from "./resolve-action-sets"; import { GamepadDevice } from "./devices/gamepad"; import { gamepadBindings } from "./bindings/generic-gamepad"; -function buildBindingsForSrcs(registeredMappings) { - const map = new Map(); - const add = (path, binding) => { - if (!map.has(path)) { - map.set(path, [binding]); - } else { - map.get(path).push(binding); +const satisfiesPath = (binding, path) => { + return Object.values(binding.dest).indexOf(path) !== -1; +}; + +const satisfyPath = (bindings, path) => { + for (const binding of bindings) { + if (satisfiesPath(binding, path)) { + return true; } - }; - for (const mapping of registeredMappings) { + } + return false; +}; + +const satisfiedBy = (binding, bindings) => { + for (const path of Object.values(binding.src)) { + if (path.startsWith("/device/")) continue; + if (!satisfyPath(bindings, path)) return false; + } + return true; +}; + +function dependencySort(mappings) { + const unsorted = []; + for (const mapping of mappings) { for (const setName in mapping) { for (const binding of mapping[setName]) { - if (Array.isArray(binding.src)) { - for (const path of binding.src) { - add(path, binding); - } - } else { - for (const srcKey in binding.src) { - const path = binding.src[srcKey]; - add(path, binding); - } + unsorted.push(binding); + } + } + } + + const sorted = []; + while (unsorted.length > 0) { + const binding = unsorted.shift(); + if (satisfiedBy(binding, sorted)) { + sorted.push(binding); + } else { + unsorted.push(binding); + } + } + + return sorted; +} + +function computeDepsDAG(bindings) { + const dag = []; + for (const row in bindings) { + for (const col in bindings) { + for (const path of bindings[row].src) { + dag[Number(row) * bindings.length + Number(col)] = satisfiesPath(bindings[col], path) ? 1 : 0; + } + } + } + return dag; +} + +function canMask(masker, masked) { + if (masker.priority === undefined) { + console.warn("priority undefined", masker); + masker.priority = 0; + } + if (masked.priority === undefined) { + console.warn("priority undefined", masked); + masked.priority = 0; + } + if (masked.priority >= masker.priority) return false; + for (const maskerPath of Object.values(masker.src)) { + for (const maskedPath of Object.values(masked.src)) { + if (maskedPath.indexOf(maskerPath) !== -1) { + return true; + } + } + } + return false; +} + +function computeMasks(bindings) { + const masks = []; + for (const row in bindings) { + for (const col in bindings) { + let ColCanMaskRow = false; + for (const path of Object.values(bindings[row].src)) { + if (canMask(bindings[col], bindings[row])) { + ColCanMaskRow = true; } } + masks[Number(row) * bindings.length + Number(col)] = ColCanMaskRow; } } - return map; + return masks; +} + +function isActive(binding, sets) { + for (const s of binding.sets) { + if (sets.has(s)) { + return true; + } + } + return false; +} + +function computeExecutionStrategy(sortedBindings, masks, activeSets) { + const actives = []; + for (const row in sortedBindings) { + actives[row] = isActive(sortedBindings[row], activeSets); + } + + const masked = []; + for (const row in sortedBindings) { + for (const col in sortedBindings) { + let rowMask = masked[row] || []; + if (masks[Number(row) * sortedBindings.length + Number(col)] && isActive(sortedBindings[col], activeSets)) { + rowMask.push(col); + } + masked[row] = rowMask; + } + } + + return { actives, masked }; } AFRAME.registerSystem("userinput", { @@ -64,14 +157,12 @@ AFRAME.registerSystem("userinput", { init() { this.frame = {}; - this.activeSets = new Set([sets.global]); this.pendingSetChanges = []; + this.xformStates = new Map(); this.activeDevices = new Set([new MouseDevice(), new AppAwareMouseDevice(), new KeyboardDevice(), new HudDevice()]); - this.registeredMappings = new Set([keyboardDebuggingBindings]); - this.bindingsForSrc = buildBindingsForSrcs(this.registeredMappings); - this.xformStates = new Map(); + this.registeredMappingsChanged = true; const appAwareTouchscreenDevice = new AppAwareTouchscreenDevice(); const updateBindingsForVRMode = () => { @@ -91,7 +182,7 @@ AFRAME.registerSystem("userinput", { this.registeredMappings.add(keyboardMouseUserBindings); } } - this.bindingsForSrc = buildBindingsForSrcs(this.registeredMappings); + this.registeredMappingsChanged = true; }; this.el.sceneEl.addEventListener("enter-vr", updateBindingsForVRMode); this.el.sceneEl.addEventListener("exit-vr", updateBindingsForVRMode); @@ -128,7 +219,7 @@ AFRAME.registerSystem("userinput", { this.registeredMappings.add(gamepadBindings); } this.activeDevices.add(gamepadDevice); - this.bindingsForSrc = buildBindingsForSrcs(this.registeredMappings); + this.registeredMappingsChanged = true; }, false ); @@ -137,8 +228,9 @@ AFRAME.registerSystem("userinput", { e => { for (const device of this.activeDevices) { if (device.gamepad === e.gamepad) { + console.warn("NEED TO UPDATE REGISTERED MAPPINGS WHEN GAMEPAD DISCONNECTED!"); this.activeDevices.delete(device); - this.bindingsForSrc = buildBindingsForSrcs(this.registeredMappings); + this.registeredMappingsChanged = true; return; } } @@ -148,75 +240,29 @@ AFRAME.registerSystem("userinput", { }, tick() { - resolveActionSets(); + const registeredMappingsChanged = this.registeredMappingsChanged; + if (registeredMappingsChanged) { + this.registeredMappingsChanged = false; + this.prevSortedBindings = this.sortedBindings; + this.sortedBindings = dependencySort(this.registeredMappings); + if (!this.prevSortedBindings) { + this.prevSortedBindings = this.sortedBindings; + } + this.masks = computeMasks(this.sortedBindings); + } + resolveActionSets(); for (const { set, value } of this.pendingSetChanges) { this.activeSets[value ? "add" : "delete"](set); } - const runners = this.pendingSetChanges.length ? [] : this.runners; - if (this.pendingSetChanges.length) { - this.pendingSetChanges.length = 0; - this.actives = []; - this.overrides = []; - for (const mapping of this.registeredMappings) { - for (const setName in mapping) { - if (!this.activeSets.has(setName) || !mapping[setName]) continue; - for (const binding of mapping[setName]) { - let active = false; - for (const set of binding.sets) { - if (this.activeSets.has(set)) { - active = true; - } - } - this.actives.push(active); - runners.push(binding); - this.overrides.push([]); - } - } - } - - const maxAmongActive = (path, map) => { - let max = { priority: -1 }; - const bindings = map.get(path); - if (!bindings) { - return -1; - } - for (const binding of bindings) { - let active = false; - for (const set of binding.sets) { - if (this.activeSets.has(set)) { - active = true; - } - } - if (active && binding.priority && binding.priority > max.priority) { - max = binding; - } - } - return max; - }; - - for (const i in runners) { - if (!this.actives[i]) continue; - const binding = runners[i]; - let active = true; - for (const p in binding.src) { - const path = binding.src[p]; - const subpaths = String.split(path, "/"); - while (subpaths.length > 1) { - const highestPriorityBindingForSubpath = maxAmongActive( - Array.join(subpaths, "/"), - this.bindingsForSrc, - this.activeSets - ); - if ((binding.priority || 0) < highestPriorityBindingForSubpath.priority) { - this.overrides[i].push(highestPriorityBindingForSubpath); - active = false; - } - subpaths.pop(); - } - } - this.actives[i] = active; - } + const activeSetsChanged = this.pendingSetChanges.length; // TODO: correct this + this.pendingSetChanges.length = 0; + if (registeredMappingsChanged || activeSetsChanged || (!this.actives && !this.masked)) { + this.prevActives = this.actives; + this.prevMasked = this.masked; + const { actives, masked } = computeExecutionStrategy(this.sortedBindings, this.masks, this.activeSets); + this.actives = actives; + this.masked = masked; } this.frame = {}; @@ -224,11 +270,18 @@ AFRAME.registerSystem("userinput", { device.write(this.frame); } - for (const i in runners) { - const binding = runners[i]; - if (!this.actives[i]) continue; - const bindingExistedLastFrame = this.runners && this.runners.includes(binding); + for (const i in this.sortedBindings) { + if (!this.actives[i] || this.masked[i].length > 0) continue; + + const binding = this.sortedBindings[i]; + + let bindingExistedLastFrame = true; + if (!registeredMappingsChanged && activeSetsChanged && this.prevSortedBindings) { + const j = this.prevSortedBindings.indexOf(binding); + bindingExistedLastFrame = j > -1 && this.prevActives[j] && this.prevMasked[j].length === 0; + } if (!bindingExistedLastFrame) { + console.log("deleting xform state for ", binding); this.xformStates.delete(binding); } @@ -239,6 +292,7 @@ AFRAME.registerSystem("userinput", { } } - this.runners = runners; + this.prevSortedBindings = this.sortedBindings; + this.prevFrame = this.frame; } }); -- GitLab