All files / src/components/VirtualWalkthrough xr-teleport-gesture.ts

100% Statements 15/15
100% Branches 8/8
100% Functions 2/2
100% Lines 15/15

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57                                                10x 10x       20x 6x 6x   6x     14x 3x     14x 5x   9x           9x 9x 9x   9x      
import { XrInputSource } from 'playcanvas';
 
/** Per-frame aim state for one input source, sampled while its select gesture is in progress. */
export interface TeleportGestureFrame {
  pressed: boolean;
  teleportDisabled: boolean;
  uiBlocked: boolean;
}
 
/**
 * Decides whether the select gesture that just ended is allowed to teleport. The two reasons to
 * refuse have deliberately different lifetimes:
 *
 * - `teleportDisabled` latches for the rest of the press. The React flag behind it re-enables as
 *   soon as the select dismisses the panel, which can land before the selectend that ends the press.
 * - `uiBlocked` keeps only the newest frame, because that frame is the one whose arc hit the base
 *   script has cached and would commit on release. Re-deciding from the release-time ray instead
 *   would let a flick off the UI commit a target that was aimed while over it, and latching the
 *   whole press would refuse an aim that merely swept across UI before coming to rest on floor.
 *
 * Callers must poll every frame for every source they care about; a press and release between polls
 * is missed.
 */
export class TeleportGestureLatch {
  private _teleportDisabled = new WeakSet<XrInputSource>();
  private _uiBlocked = new WeakSet<XrInputSource>();
 
  /** Records a frame of an in-progress press. A source that isn't pressed drops both latches. */
  track(inputSource: XrInputSource, frame: TeleportGestureFrame) {
    if (!frame.pressed) {
      this._teleportDisabled.delete(inputSource);
      this._uiBlocked.delete(inputSource);
 
      return;
    }
 
    if (frame.teleportDisabled) {
      this._teleportDisabled.add(inputSource);
    }
 
    if (frame.uiBlocked) {
      this._uiBlocked.add(inputSource);
    } else {
      this._uiBlocked.delete(inputSource);
    }
  }
 
  /** True when the press may not teleport. Clears both latches so they never outlive the press. */
  consumeBlocked(inputSource: XrInputSource): boolean {
    const blocked = this._teleportDisabled.has(inputSource) || this._uiBlocked.has(inputSource);
    this._teleportDisabled.delete(inputSource);
    this._uiBlocked.delete(inputSource);
 
    return blocked;
  }
}