Skip to content
Snippets Groups Projects
networked-counter.js 2.58 KiB
/**
 * Limits networked interactables to a maximum number at any given time
 * @namespace network
 * @component networked-counter
 */
AFRAME.registerComponent("networked-counter", {
  schema: {
    max: { default: 3 },
    ttl: { default: 0 },
    grab_event: { type: "string", default: "grab-start" },
    release_event: { type: "string", default: "grab-end" }
  },

  init() {
    this.registeredEls = new Map();
  },

  remove() {
    this.registeredEls.forEach(({ onGrabHandler, onReleaseHandler, timeout }, el) => {
      el.removeEventListener(this.data.grab_event, onGrabHandler);
      el.removeEventListener(this.data.release_event, onReleaseHandler);
      clearTimeout(timeout);
    });
    this.registeredEls.clear();
  },

  register(el) {
    if (this.data.max <= 0 || this.registeredEls.has(el)) return;

    const grabEventListener = this._onGrabbed.bind(this, el);
    const releaseEventListener = this._onReleased.bind(this, el);

    this.registeredEls.set(el, {
      ts: Date.now(),
      onGrabHandler: grabEventListener,
      onReleaseHandler: releaseEventListener
    });

    el.addEventListener(this.data.grab_event, grabEventListener);
    el.addEventListener(this.data.release_event, releaseEventListener);

    if (!el.is("grabbed")) {
      this._startTimer(el);
    }

    this._destroyOldest();
  },

  deregister(el) {
    if (this.registeredEls.has(el)) {
      const { onGrabHandler, onReleaseHandler, timeout } = this.registeredEls.get(el);
      el.removeEventListener(this.data.grab_event, onGrabHandler);
      el.removeEventListener(this.data.release_event, onReleaseHandler);
      clearTimeout(timeout);
      this.registeredEls.delete(el);
    }
  },

  _onGrabbed(el) {
    clearTimeout(this.registeredEls.get(el).timeout);
  },

  _onReleased(el) {
    this._startTimer(el);
    this.registeredEls.get(el).ts = Date.now();
  },

  _destroyOldest() {
    if (this.registeredEls.size > this.data.max) {
      let oldestEl = null,
        minTs = Number.MAX_VALUE;
      this.registeredEls.forEach(({ ts }, el) => {
        if (ts < minTs && !el.is("grabbed")) {
          oldestEl = el;
          minTs = ts;
        }
      });
      this._destroy(oldestEl);
    }
  },

  _startTimer(el) {
    if (!this.data.ttl) return;
    clearTimeout(this.registeredEls.get(el).timeout);
    this.registeredEls.get(el).timeout = setTimeout(() => {
      this._destroy(el);
    }, this.data.ttl * 1000);
  },

  _destroy(el) {
    // networked-interactable's remvoe will also call deregister, but it will happen async so we do it here as well.
    this.deregister(el);
    el.parentNode.removeChild(el);
  }
});