Rollback last (bad) improving attempt
authorBenjamin Auder <benjamin.auder@somewhere>
Sun, 30 May 2021 22:58:12 +0000 (00:58 +0200)
committerBenjamin Auder <benjamin.auder@somewhere>
Sun, 30 May 2021 22:58:12 +0000 (00:58 +0200)
client/src/views/Game.vue
client/src/views/Hall.vue
client/src/views/MyGames.vue
server/sockets.js

index 8c5e7c2..1071ece 100644 (file)
@@ -231,13 +231,17 @@ export default {
       conn: null,
       roomInitialized: false,
       // If asklastate got no reply, ask again:
+      gotLastate: false,
       gotMoveIdx: -1, //last move index received
-      opponentGotMove: false, //used to freeze clock
+      // If newmove got no pingback, send again:
+      opponentGotMove: false,
       connexionString: "",
-      reopenTimeout: 0,
-      reconnectTimeout: 0,
-      // Incomplete info games: show move played:
+      socketCloseListener: 0,
+      // Incomplete info games: show move played
       moveNotation: "",
+      // Intervals from setInterval():
+      askLastate: null,
+      retrySendmove: null,
       clockUpdate: null,
       // Related to (killing of) self multi-connects:
       newConnect: {}
@@ -285,11 +289,14 @@ export default {
   },
   methods: {
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       document.removeEventListener('visibilitychange', this.visibilityChange);
       window.removeEventListener('focus', this.onFocus);
       window.removeEventListener('blur', this.onBlur);
+      if (!!this.askLastate) clearInterval(this.askLastate);
+      if (!!this.retrySendmove) clearInterval(this.retrySendmove);
       if (!!this.clockUpdate) clearInterval(this.clockUpdate);
-      clearTimeout(this.reopenTimeout);
+      this.conn.removeEventListener("message", this.socketMessageListener);
       this.send("disconnect");
       this.conn = null;
     },
@@ -331,6 +338,23 @@ export default {
       //const oppSid =
       //  this.game.players.find(p => p.sid != this.st.user.sid).sid;
       this.send("asklastate", { target: sid });
+      let counter = 1;
+      this.askLastate = setInterval(
+        () => {
+          // Ask at most 3 times:
+          // if no reply after that there should be a network issue.
+          if (
+            counter < 3 &&
+            !this.gotLastate &&
+            !!this.people[sid]
+          ) {
+            this.send("asklastate", { target: sid });
+            counter++;
+          }
+          else clearInterval(this.askLastate);
+        },
+        1500
+      );
     },
     atCreation: function() {
       document.addEventListener('visibilitychange', this.visibilityChange);
@@ -369,8 +393,11 @@ export default {
       this.rematchOffer = "";
       this.lastate = undefined;
       this.roomInitialized = false;
+      this.gotLastate = false;
       this.gotMoveIdx = -1;
       this.opponentGotMove = false;
+      this.askLastate = null;
+      this.retrySendmove = null;
       this.clockUpdate = null;
       this.newConnect = {};
       // 1] Initialize connection
@@ -382,41 +409,30 @@ export default {
         "&page=" +
         // Discard potential "/?next=[...]" for page indication:
         encodeURIComponent(this.$route.path.match(/\/game\/[a-zA-Z0-9]+/)[0]);
-      this.openConnection();
-    },
-    openConnection: function() {
       this.conn = new WebSocket(this.connexionString);
-      const onOpen = () => {
-        this.reconnectTimeout = 250;
-        const oppSid = this.getOppsid();
-        if (!!oppSid) this.send("asklastate", { target: oppSid });
-      };
-      this.conn.onopen = onOpen;
-      this.conn.onmessage = this.socketMessageListener;
-      const closeConnection = () => {
-        this.reopenTimeout = setTimeout(
-          () => {
-            this.openConnection();
-            this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000);
-          },
-          this.reconnectTimeout
-        );
-      };
-      this.conn.onerror = closeConnection;
-      this.conn.onclose = closeConnection;
+      this.conn.addEventListener("message", this.socketMessageListener);
+      this.socketCloseListener = setInterval(
+        () => {
+          if (this.conn.readyState == 3) {
+            this.conn.removeEventListener(
+              "message", this.socketMessageListener);
+            this.conn = new WebSocket(this.connexionString);
+            this.conn.addEventListener("message", this.socketMessageListener);
+            const oppSid = this.getOppsid();
+            if (!!oppSid) this.requestLastate(oppSid); //in case of
+          }
+        },
+        1000
+      );
       // Socket init required before loading remote game:
       const socketInit = callback => {
         if (this.conn.readyState == 1)
           // 1 == OPEN state
           callback();
-        else {
+        else
           // Socket not ready yet (initial loading)
           // NOTE: first arg is Websocket object, unused here:
-          this.conn.onopen = () => {
-            onOpen();
-            callback();
-          };
-        }
+          this.conn.onopen = () => callback();
       };
       this.fetchGame((game) => {
         if (!!game) {
@@ -447,13 +463,8 @@ export default {
       }
     },
     send: function(code, obj) {
-      let timeout = 0;
-      const trySend = () => {
-        if (!!this.conn && this.conn.readyState == 1)
-          this.conn.send(JSON.stringify(Object.assign({ code: code }, obj)));
-        else setTimeout(trySend, timeout = (timeout > 0 ? 2 * timeout : 250));
-      }
-      setTimeout(trySend, timeout);
+      if (!!this.conn && this.conn.readyState == 1)
+        this.conn.send(JSON.stringify(Object.assign({ code: code }, obj)));
     },
     isConnected: function(index) {
       const player = this.game.players[index];
@@ -681,11 +692,12 @@ export default {
           }
           // Ask potentially missed last state, if opponent and I play
           if (
+            !this.gotLastate &&
             !!this.game.mycolor &&
             this.game.type == "live" &&
             this.game.players.some(p => p.sid == user.sid)
           ) {
-            this.send("asklastate", { target: user.sid });
+            this.requestLastate(user.sid);
           }
           break;
         }
@@ -745,6 +757,7 @@ export default {
         // Confirm scenario? Fix?
         case "lastate": {
           // Got opponent infos about last move
+          this.gotLastate = true;
           this.lastate = data.data;
           if (this.lastate.movesCount - 1 > this.gotMoveIdx)
             this.gotMoveIdx = this.lastate.movesCount - 1;
@@ -1300,7 +1313,6 @@ export default {
           ).replace(/(fen:)([^:]*):/g, replaceByDiag);
       };
       let variant = undefined;
-      // TODO: avoid setInterval() here
       const trySetVname = setInterval(
         () => {
           // this.st.variants might be uninitialized (variant == null)
@@ -1529,7 +1541,33 @@ export default {
             sendMove["clock"] = this.game.clocks[colorIdx];
           // (Live) Clocks will re-start when the opponent pingback arrive
           this.opponentGotMove = false;
-          this.send("newmove", { data: sendMove });
+          this.send("newmove", {data: sendMove});
+          // If the opponent doesn't reply gotmove soon enough, re-send move:
+          // Do this at most 2 times, because more would mean network issues,
+          // opponent would then be expected to disconnect/reconnect.
+          let counter = 1;
+          const currentUrl = document.location.href;
+          this.retrySendmove = setInterval(
+            () => {
+              if (
+                counter >= 3 ||
+                this.opponentGotMove ||
+                document.location.href != currentUrl //page change
+              ) {
+                clearInterval(this.retrySendmove);
+                return;
+              }
+              const oppsid = this.getOppsid();
+              if (!oppsid)
+                // Opponent is disconnected: he'll ask last state
+                clearInterval(this.retrySendmove);
+              else {
+                this.send("newmove", { data: sendMove, target: oppsid });
+                counter++;
+              }
+            },
+            1500
+          );
         }
         else
           // Not my move or I'm an observer: just start other player's clock
index 4f27f79..94a7bba 100644 (file)
@@ -282,8 +282,7 @@ export default {
       presetChalls: JSON.parse(localStorage.getItem("presetChalls") || "[]"),
       conn: null,
       connexionString: "",
-      reopenTimeout: 0,
-      reconnectTimeout: 0,
+      socketCloseListener: 0,
       // Related to (killing of) self multi-connects:
       newConnect: {}
     };
@@ -322,31 +321,35 @@ export default {
         }
       }
     );
-    if (!!this.$route.query["challenge"]) {
-      // Automatic challenge sending, for tournaments
-      this.loadNewchallVariant(
-        () => {
-          Object.assign(
-            this.newchallenge,
-            {
-              fen: "",
-              vid:
-                this.st.variants
-                .find(v => v.name == this.$route.query["variant"])
-                .id,
-              to: this.$route.query["challenge"],
-              color: this.$route.query["color"] || '',
-              cadence: this.$route.query["cadence"],
-              options: {},
-              memorize: false
-            }
-          );
-          window.doClick("modalNewgame");
-        },
-        this.$route.query["variant"]
-      );
-    }
-    // Connexion string won't change if disconnect/reconnect
+    const connectAndPoll = () => {
+      this.send("connect");
+      this.send("pollclientsandgamers");
+      if (!!this.$route.query["challenge"]) {
+        // Automatic challenge sending, for tournaments
+        this.loadNewchallVariant(
+          () => {
+            Object.assign(
+              this.newchallenge,
+              {
+                fen: "",
+                vid:
+                  this.st.variants
+                  .find(v => v.name == this.$route.query["variant"])
+                  .id,
+                to: this.$route.query["challenge"],
+                color: this.$route.query["color"] || '',
+                cadence: this.$route.query["cadence"],
+                options: {},
+                memorize: false
+              }
+            );
+            window.doClick("modalNewgame");
+          },
+          this.$route.query["variant"]
+        );
+      }
+    };
+    // Initialize connection
     this.connexionString =
       params.socketUrl +
       "/?sid=" + this.st.user.sid +
@@ -355,7 +358,19 @@ export default {
       "&page=" +
       // Hall: path is "/" (TODO: could be hard-coded as well)
       encodeURIComponent(this.$route.path);
-    this.openConnection();
+    this.conn = new WebSocket(this.connexionString);
+    this.conn.onopen = connectAndPoll;
+    this.conn.addEventListener("message", this.socketMessageListener);
+    this.socketCloseListener = setInterval(
+      () => {
+        if (this.conn.readyState == 3) {
+          this.conn.removeEventListener("message", this.socketMessageListener);
+          this.conn = new WebSocket(this.connexionString);
+          this.conn.addEventListener("message", this.socketMessageListener);
+        }
+      },
+      1000
+    );
   },
   mounted: function() {
     document.getElementById("peopleWrap")
@@ -451,36 +466,13 @@ export default {
     this.cleanBeforeDestroy();
   },
   methods: {
-    openConnection: function() {
-      const connectAndPoll = () => {
-        this.send("connect");
-        this.send("pollclientsandgamers");
-      };
-      // Initialize connection
-      this.conn = new WebSocket(this.connexionString);
-      this.conn.onopen = () => {
-        this.reconnectTimeout = 250;
-        connectAndPoll();
-      };
-      this.conn.onmessage = this.socketMessageListener;
-      const closeConnection = () => {
-        this.reopenTimeout = setTimeout(
-          () => {
-            this.openConnection();
-            this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000);
-          },
-          this.reconnectTimeout
-        );
-      };
-      this.conn.onerror = closeConnection;
-      this.conn.onclose = closeConnection;
-    },
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       document.removeEventListener('visibilitychange', this.visibilityChange);
       window.removeEventListener('focus', this.onFocus);
       window.removeEventListener('blur', this.onBlur);
       window.removeEventListener("beforeunload", this.cleanBeforeDestroy);
-      clearTimeout(this.reopenTimeout);
+      this.conn.removeEventListener("message", this.socketMessageListener);
       this.send("disconnect");
       this.conn = null;
     },
index ad330ae..d12fff4 100644 (file)
@@ -78,8 +78,7 @@ export default {
       },
       conn: null,
       connexionString: "",
-      reopenTimeout: 0,
-      reconnectTimeout: 0
+      socketCloseListener: 0
     };
   },
   watch: {
@@ -97,6 +96,7 @@ export default {
   },
   created: function() {
     window.addEventListener("beforeunload", this.cleanBeforeDestroy);
+    // Initialize connection
     this.connexionString =
       params.socketUrl +
       "/?sid=" + this.st.user.sid +
@@ -104,7 +104,19 @@ export default {
       "&tmpId=" + getRandString() +
       "&page=" +
       encodeURIComponent(this.$route.path);
-    this.openConnection();
+    this.conn = new WebSocket(this.connexionString);
+    this.conn.onmessage = this.socketMessageListener;
+    this.socketCloseListener = setInterval(
+      () => {
+        if (this.conn.readyState == 3) {
+          // Connexion is closed: re-open
+          this.conn.removeEventListener("message", this.socketMessageListener);
+          this.conn = new WebSocket(this.connexionString);
+          this.conn.addEventListener("message", this.socketMessageListener);
+        }
+      },
+      1000
+    );
   },
   mounted: function() {
     const adjustAndSetDisplay = () => {
@@ -170,26 +182,10 @@ export default {
     this.cleanBeforeDestroy();
   },
   methods: {
-    openConnection: function() {
-      // Initialize connection
-      this.conn = new WebSocket(this.connexionString);
-      this.conn.onopen = () => { this.reconnectTimeout = 250; };
-      this.conn.onmessage = this.socketMessageListener;
-      const closeConnection = () => {
-        this.reopenTimeout = setTimeout(
-          () => {
-            this.openConnection();
-            this.reconnectTimeout = Math.min(2*this.reconnectTimeout, 30000);
-          },
-          this.reconnectTimeout
-        );
-      };
-      this.conn.onerror = closeConnection;
-      this.conn.onclose = closeConnection;
-    },
     cleanBeforeDestroy: function() {
+      clearInterval(this.socketCloseListener);
       window.removeEventListener("beforeunload", this.cleanBeforeDestroy);
-      clearTimeout(this.reopenTimeout);
+      this.conn.removeEventListener("message", this.socketMessageListener);
       this.conn.send(JSON.stringify({code: "disconnect"}));
       this.conn = null;
     },
index 520b8b7..c2b41e8 100644 (file)
@@ -18,12 +18,6 @@ function send(socket, message) {
     socket.send(JSON.stringify(message));
 }
 
-// https://www.npmjs.com/package/ws - detect lost connections...
-function noop() {}
-function heartbeat() {
-  this.isAlive = true;
-}
-
 module.exports = function(wss) {
   // Associative array page --> sid --> tmpId --> socket
   // "page" is either "/" for hall or "/game/some_gid" for Game,
@@ -42,8 +36,6 @@ module.exports = function(wss) {
     });
   }
   wss.on("connection", (socket, req) => {
-    socket.isAlive = true;
-    socket.on('pong', heartbeat);
     const query = getJsonFromUrl(req.url);
     const sid = query["sid"];
     const id = query["id"];
@@ -376,15 +368,4 @@ module.exports = function(wss) {
     socket.on("message", messageListener);
     socket.on("close", closeListener);
   });
-  const interval = setInterval(
-    () => {
-      wss.clients.forEach(ws => {
-        if (ws.isAlive === false) return ws.terminate();
-        ws.isAlive = false;
-        ws.ping(noop);
-      });
-    },
-    30000
-  );
-  wss.on('close', () => clearInterval(interval));
 }