From bf301ad79ae9b080bcb6a1ba6090143703dd92ac Mon Sep 17 00:00:00 2001 From: Jesse McDonald Date: Wed, 29 Apr 2020 00:27:10 -0500 Subject: [PATCH] merge consecutive updates into a single request --- js/pacosako_ui.js | 164 +++++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 68 deletions(-) diff --git a/js/pacosako_ui.js b/js/pacosako_ui.js index 2ad798d..ea4b729 100644 --- a/js/pacosako_ui.js +++ b/js/pacosako_ui.js @@ -426,16 +426,6 @@ $(function (){ return name.slice(0, 20) + '…'; } - const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3'; /* V2 & V3 releases */ - - function putState() { - const boardElem = $('#cb_board'); - const gameId = boardElem.data('gameId'); - const moves = { past: currentGame.moves, future: currentGame.redoMoves }; - boardElem.data('last_state', currentGame.moves); - putMeta({ board: moves }); - } - let noticeBox = null; function openNoticeBox(content) { @@ -465,16 +455,13 @@ $(function (){ const updateQueue = { head: 0, /* next item to be sent */ tail: 0, /* ID to assign to the next update added to the queue */ - lastGameId: null, - lastModified: null, - newModified: 0, sending: false, add(gameId, data, modified) { - const wasEmpty = this.head === this.tail; + const wasEmpty = this.isEmpty(); data = Object.assign({}, data, { modified }); - this[this.tail] = { gameId, data, modified }; + this[this.tail] = { gameId, data }; this.tail += 1; if (!this.sending) { @@ -484,37 +471,70 @@ $(function (){ } }, + isEmpty() { + return this.head === this.tail; + }, + + peek() { + if (!this.isEmpty()) { + return this[this.head]; + } else { + return undefined; + } + }, + + remove() { + if (!this.isEmpty()) { + const first = this[this.head]; + delete this[this.head]; + this.head += 1; + return first; + } else { + return undefined; + } + }, + sendNext() { const queue = this; + const update = queue.remove(); - if (queue.head === queue.tail) { + if (update === undefined) { return; } - const update = queue[queue.head]; - delete queue[queue.head]; - queue.head += 1; - - if (update.gameId === queue.lastGameId && update.modified === queue.lastModified) { - update.data.modified = queue.newModified; + /* merge updates for the same game with the same baseline into a single request */ + let peek; + while ((peek = queue.peek()) !== undefined && peek.gameId === update.gameId + && peek.data.modified === update.data.modified) { + Object.assign(update.data, peek.data); + queue.remove(); } IO.sendUpdate(update.gameId, update.data).then((response) => { - queue.lastGameId = update.gameId; if (response && response.data && 'modified' in response.data) { /* * If we queued two or more updates with the same .modified (thus - * based on the same server data), send the next update with the + * based on the same server data), send the later update(s) with the * new .modified assigned by the server as a result of this update. */ - queue.lastModified = update.modified; /* original, not adjusted */ - queue.newModified = response.data.modified; - } else { - queue.lastModified = null; - queue.newModified = 0; + for (let i = queue.head; i !== queue.tail; i += 1) { + if (queue[i].gameId === update.gameId && queue[i].data.modified === update.data.modified) { + queue[i].data.modified = response.data.modified; + } + } + + /* + * Send future updates with the new modified time, and prevent loading + * older data from the server in case the connection is lagging. + */ + const cbBoard = $('#cb_board'); + if (cbBoard.data('gameId') === update.gameId + && cbBoard.data('modified') === update.data.modified) { + cbBoard.data('modified', response.data.modified); + } } - if (queue.head === queue.tail) { + if (queue.isEmpty()) { queue.sending = false; /* close the Saving... notice*/ noticeBox.close({ ignoreDelay: true }); @@ -527,9 +547,8 @@ $(function (){ debug('update error', err); /* additional updates are unlikely to succeed, so empty the queue */ - while (queue.head < queue.tail) { - delete queue[queue.head]; - queue.head += 1; + while (!queue.isEmpty()) { + queue.remove(); } queue.sending = false; @@ -542,6 +561,14 @@ $(function (){ }, }; + function putState() { + const boardElem = $('#cb_board'); + const gameId = boardElem.data('gameId'); + const moves = { past: currentGame.moves, future: currentGame.redoMoves }; + boardElem.data('last_state', currentGame.moves); + putMeta({ board: moves }); + } + function putMeta(extra) { const gameId = $('#cb_board').data('gameId'); const lightName = $('#cb_light_name').val(); @@ -606,53 +633,55 @@ $(function (){ $('#cb_dark_name').val(''); cancelGameCallback = IO.onGameUpdate(newId, function(data, gameId) { - const board = data.board || { past: [], future: [] }; + if (data.modified > $('#cb_board').data('modified')) { + const board = data.board || { past: [], future: [] }; - try { - const moves = { past: [...board.past], future: [...board.future] }; - const oldState = { past: currentGame.moves, future: currentGame.redoMoves }; + try { + const moves = { past: [...board.past], future: [...board.future] }; + const oldState = { past: currentGame.moves, future: currentGame.redoMoves }; - if (!deepEqual(moves, oldState)) { - debug('got board', moves); + if (!deepEqual(moves, oldState)) { + debug('got board', moves); - const newGame = new PS.Game(); - - try { - for (const move of moves.past) { - newGame.replayMove(move); - } - - let n = 0; + const newGame = new PS.Game(); try { - for (const move of moves.future.slice().reverse()) { + for (const move of moves.past) { newGame.replayMove(move); - n += 1; + } + + let n = 0; + + try { + for (const move of moves.future.slice().reverse()) { + newGame.replayMove(move); + n += 1; + } + } catch (err) { + debug('Error replaying board redo state', err); + } + + for (let i = 0; i < n; ++i) { + newGame.undo(); } } catch (err) { - debug('Error replaying board redo state', err); + debug('Error replaying board state', err); } - for (let i = 0; i < n; ++i) { - newGame.undo(); - } - } catch (err) { - debug('Error replaying board state', err); + setCurrentGame(newGame, newGame.moves.length > currentGame.moves.length); } - - setCurrentGame(newGame, newGame.moves.length > currentGame.moves.length); + } catch (err) { + debug('Error parsing board data', err); } - } catch (err) { - debug('Error parsing board data', err); + + const d = data || {}; + $('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light'))); + $('#cb_board').data('darkName', shortenName(String(d.darkName || 'Dark'))); + $('#cb_light_name').val(String(d.lightName || '')); + $('#cb_dark_name').val(String(d.darkName || '')); + + $('#cb_board').data('modified', data.modified); } - - const d = data || {}; - $('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light'))); - $('#cb_board').data('darkName', shortenName(String(d.darkName || 'Dark'))); - $('#cb_light_name').val(String(d.lightName || '')); - $('#cb_dark_name').val(String(d.darkName || '')); - - $('#cb_board').data('modified', data.modified); }); let selOpt = $('#cb_game_' + newId); @@ -1104,7 +1133,6 @@ $(function (){ renderBoard, putState, putMeta, - PacoSakoUUID, IO, };