From c9655a37c34659c47fb0fdd7e7961950ab6667ca Mon Sep 17 00:00:00 2001
From: Kevin Lee <kevin@infinite-lee.com>
Date: Fri, 26 Jan 2018 17:24:28 -0800
Subject: [PATCH] Adding "network-counter" component- keeps track of networked
 entities and limits how many you can have, and how long they can live for.

---
 src/components/networked-counter.js   | 127 ++++++++++++++++++++++++++
 src/components/remote-dynamic-body.js |  38 +++++---
 src/room.js                           |   1 +
 templates/room.hbs                    |   9 +-
 4 files changed, 160 insertions(+), 15 deletions(-)
 create mode 100644 src/components/networked-counter.js

diff --git a/src/components/networked-counter.js b/src/components/networked-counter.js
new file mode 100644
index 000000000..7b31fdb31
--- /dev/null
+++ b/src/components/networked-counter.js
@@ -0,0 +1,127 @@
+AFRAME.registerComponent("networked-counter", {
+  schema: {
+    max: { default: 3 },
+    ttl: { default: 120 }
+  },
+
+  init: function() {
+    this.count = 0;
+    this.queue = {};
+    this.timeouts = {};
+  },
+
+  getCount: function() {
+    return queue.length;
+  },
+
+  getMax: function() {
+    return this.data.max;
+  },
+
+  getTtl: function() {
+    return this.data.ttl;
+  },
+
+  register: function(networkedEl) {
+    if (this.data.max <= 0) {
+      return;
+    }
+
+    const id = this._getNetworkId(networkedEl);
+    if (this.queue.hasOwnProperty(id)) {
+      return;
+    }
+
+    const now = Date.now();
+    const onGrabHandler = this._onGrabbed.bind(this, id);
+    const onReleaseHandler = this._onReleased.bind(this, id);
+    this.queue[id] = {
+      ts: now,
+      el: networkedEl,
+      onGrabHandler: onGrabHandler,
+      onReleaseHandler: onReleaseHandler
+    };
+
+    networkedEl.addEventListener("grab-start", onGrabHandler);
+    networkedEl.addEventListener("grab-end", onReleaseHandler);
+
+    this.count++;
+
+    if (!this._isCurrentlyGrabbed(id)) {
+      this._addTimeout(id);
+    }
+
+    this._removeOldest();
+  },
+
+  deregister: function(networkedEl) {
+    const id = this._getNetworkId(networkedEl);
+    if (this.queue.hasOwnProperty(id)) {
+      const item = this.queue[id];
+      networkedEl.removeEventListener("grab-start", item.onGrabHandler);
+      networkedEl.removeEventListener("grab-end", item.onReleaseHandler);
+
+      delete this.queue[id];
+
+      this._removeTimeout(id);
+      delete this.timeouts[id];
+
+      this.count--;
+    }
+  },
+
+  _onGrabbed: function(id, e) {
+    this._removeTimeout(id);
+  },
+
+  _onReleased: function(id, e) {
+    this._removeTimeout(id);
+    this._addTimeout(id);
+  },
+
+  _removeOldest: function() {
+    if (this.count > this.data.max) {
+      let oldest = null, ts = Number.MAX_VALUE;
+      Object.keys(this.queue).forEach(function(id) {
+        const expiration = this.queue[id].ts + this.data.ttl * 1000;
+        if (this.queue[id].ts < ts && !this._isCurrentlyGrabbed(id)) {
+          oldest = this.queue[id];
+          ts = this.queue[id].ts;
+        }
+      }, this);
+      if (ts > 0) {
+        this.deregister(oldest.el);
+        this._destroy(oldest.el);
+      }
+    }
+  },
+
+  _isCurrentlyGrabbed: function(id) {
+    const networkedEl = this.queue[id].el;
+    return networkedEl.is("grabbed");
+  },
+
+  _addTimeout: function(id) {
+    const timeout = this.data.ttl * 1000;
+    this.timeouts[id] = setTimeout(() => {
+
+      const el = this.queue[id].el;
+      this.deregister(el);
+      this._destroy(el);
+    }, timeout);
+  },
+
+  _removeTimeout: function(id) {
+    if (this.timeouts.hasOwnProperty(id)) {
+      clearTimeout(this.timeouts[id]);
+    }
+  },
+
+  _destroy: function(networkedEl) {
+    networkedEl.parentNode.removeChild(networkedEl);
+  },
+
+  _getNetworkId: function(networkedEl) {
+    return networkedEl.components.networked.data.networkId;
+  }
+});
diff --git a/src/components/remote-dynamic-body.js b/src/components/remote-dynamic-body.js
index de7482606..09ebc617a 100644
--- a/src/components/remote-dynamic-body.js
+++ b/src/components/remote-dynamic-body.js
@@ -1,36 +1,48 @@
 AFRAME.registerComponent("remote-dynamic-body", {
+  schema: {
+    mass: { default: 1 },
+    counter: { type: "selector" }
+  },
 
   init: function() {
+    this.counter = this.data.counter.components["networked-counter"];
+    this.timer = 0;
+
     this.networkedEl = NAF.utils.getNetworkedEntity(this.el);
 
     if (!this._isMine()) {
       this.networkedEl.setAttribute("dynamic-body", "mass: 0;");
       this.networkedEl.setAttribute("grabbable", "");
       this.networkedEl.setAttribute("stretchable", "");
-      this.el.setAttribute("color", "white");
+    } else {
+      this.counter.register(this.networkedEl);
+      this.timer = Date.now();
     }
 
     this.wasMine = this._isMine();
 
-    const self = this;
-
-    this.networkedEl.addEventListener('grab-start', function(e){
-      if (!self._isMine()) {
-        if(self.networkedEl.components.networked.takeOwnership()) {
-          self.networkedEl.components["dynamic-body"].updateMass(1);
-          self.el.setAttribute("color", "green");
-          self.wasMine = true;
-        }
-      }
+    this.networkedEl.addEventListener("grab-start", e => {
+      this._onGrabStart(e);
     });
   },
 
   tick: function(t) {
-    if(this.wasMine && !this._isMine()) {
+    if (this.wasMine && !this._isMine()) {
       this.wasMine = false;
       this.networkedEl.components["dynamic-body"].updateMass(0);
-      this.el.setAttribute("color", "white");
       this.networkedEl.components["grabbable"].resetGrabber();
+      this.counter.deregister(this.networkedEl);
+      this.timer = 0;
+    }
+  },
+
+  _onGrabStart: function(e) {
+    if (!this._isMine()) {
+      if (this.networkedEl.components.networked.takeOwnership()) {
+        this.networkedEl.components["dynamic-body"].updateMass(this.data.mass);
+        this.wasMine = true;
+        this.counter.register(this.networkedEl);
+      }
     }
   },
 
diff --git a/src/room.js b/src/room.js
index c836acaf4..7840ccff6 100644
--- a/src/room.js
+++ b/src/room.js
@@ -41,6 +41,7 @@ import "./components/skybox";
 import "./components/layers";
 import "./components/spawn-controller";
 import "./components/remote-dynamic-body";
+import "./components/networked-counter";
 import "./systems/personal-space-bubble";
 
 import { promptForName, getCookie, parseJwt } from "./utils/identity";
diff --git a/templates/room.hbs b/templates/room.hbs
index 342cb1f82..97b6b1cb6 100644
--- a/templates/room.hbs
+++ b/templates/room.hbs
@@ -117,12 +117,17 @@
                     scale="6 6 6"
                 ></a-entity>
             </script>
-
             <script id="physics-cube" type="text/html">
-                <a-box class="box" remote-dynamic-body physics-collider scale="0.25 0.25 0.25"  material="color: green"></a-box>
+                <a-box class="box" remote-dynamic-body="counter: #counter" physics-collider scale="0.25 0.25 0.25"  material="color: green"></a-box>
             </script>
         </a-assets>
 
+        <a-entity
+            id="counter"
+            networked-counter="max: 3; ttl: 120"
+        >
+        </a-entity>
+
         <!-- Player Rig -->
         <a-entity
             id="player-rig"
-- 
GitLab