restructure data to limit indirect links; breaking change
I found that the game list was wiped out (again) and I suspect that the
issue was that some client pushed an update to the game list before
receiving the current data for the top-level object. That would have
caused it to create new objects for the game and metadata lists
containing only the single newly-created game and store links to them in
the 'games' and 'meta' fields of the top-level object. This data would
not be merged because the new objects have no connection to the original
objects which should have been linked from those fields. The top-level
object would be merged, but only to the extent of updating the links.
To fix this, I moved the 'meta' object to the top level with a fixed
identifier (UUID + '/meta'). This identifier _is_ the object's identity
("soul") so even if a client initializes it with an empty object the new
fields should be merged when syncing resumes. I removed the 'games'
object altogether, since the meta object keeps track of which game IDs
are available. Individual games are assigned "souls" based on their game
IDs (UUID + ('/game/' or '/meta/') + game ID). The resulting structure
looks like:
'UUID/meta': {
'12345abcde': <link to 'UUID/meta/12345abcde'>,
'3a2b5c7e1f': <link to 'UUID/meta/3a2b5c7e1f'>,
...
},
'UUID/meta/12345abcde': { ... darkName, lightName, moves, etc. ... },
'UUID/game/12345abcde': { 'board': 'JSON' },
'UUID/meta/3a2b5c7e1f': { ... darkName, lightName, moves, etc. ... },
'UUID/game/3a2b5c7e1f': { 'board': 'JSON' },
...
Besides being more resilient, this structure should also be more
performant since there are fewer links to traverse to access the game
data. So far in my admittedly limited testing I haven't seen any of the
syncronization issues that plagued the older version.
This commit is contained in:
parent
b228f1f626
commit
7ebea1d1fc
|
|
@ -423,14 +423,14 @@ $(function (){
|
||||||
return name.slice(0, 20) + '…';
|
return name.slice(0, 20) + '…';
|
||||||
}
|
}
|
||||||
|
|
||||||
const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3'; /* V2 release */
|
const PacoSakoUUID = 'b425b812-6bdb-11ea-9414-6f946662bac3'; /* V2 & V3 releases */
|
||||||
|
|
||||||
function putState() {
|
function putState() {
|
||||||
const boardElem = $('#cb_board');
|
const boardElem = $('#cb_board');
|
||||||
const gameId = boardElem.data('gameId');
|
const gameId = boardElem.data('gameId');
|
||||||
const moves = { past: currentGame.moves, future: currentGame.redoMoves };
|
const moves = { past: currentGame.moves, future: currentGame.redoMoves };
|
||||||
boardElem.data('last_state', currentGame.moves);
|
boardElem.data('last_state', currentGame.moves);
|
||||||
gun.get(PacoSakoUUID).get('games').get(gameId).put({ board: JSON.stringify(moves) });
|
gun.get(PacoSakoUUID + '/game/' + gameId).get('board').put(JSON.stringify(moves));
|
||||||
putMeta();
|
putMeta();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -439,21 +439,26 @@ $(function (){
|
||||||
const lightName = $('#cb_light_name').val();
|
const lightName = $('#cb_light_name').val();
|
||||||
const darkName = $('#cb_dark_name').val();
|
const darkName = $('#cb_dark_name').val();
|
||||||
const turns = currentGame.countTurns();
|
const turns = currentGame.countTurns();
|
||||||
let meta = null;
|
|
||||||
if (lightName !== '' || darkName !== '' || turns !== 0) {
|
|
||||||
const winner = currentGame.winner;
|
const winner = currentGame.winner;
|
||||||
const lastMove = currentGame.lastMove || {};
|
const lastMove = currentGame.lastMove || {};
|
||||||
const lastMeta = lastMove.meta || {};
|
const lastMeta = lastMove.meta || {};
|
||||||
const status = !winner ? null : (lastMove.took === PS.KING) ? 'mate' : 'ended';
|
const status = !winner ? null : (lastMove.took === PS.KING) ? 'mate' : 'ended';
|
||||||
meta = {
|
|
||||||
lightName: lightName,
|
const meta = {
|
||||||
darkName: darkName,
|
lightName,
|
||||||
|
darkName,
|
||||||
moves: turns,
|
moves: turns,
|
||||||
timestamp: lastMeta.timestamp || new Date(Gun.state()).getTime(),
|
timestamp: lastMeta.timestamp || new Date(Gun.state()).getTime(),
|
||||||
status: status,
|
status,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const metaRef = gun.get(PacoSakoUUID + '/meta/' + gameId).put(meta);
|
||||||
|
|
||||||
|
if (lightName !== '' || darkName !== '' || turns !== 0) {
|
||||||
|
gun.get(PacoSakoUUID + '/meta').get(gameId).put(metaRef);
|
||||||
|
} else {
|
||||||
|
gun.get(PacoSakoUUID + '/meta').get(gameId).put(null);
|
||||||
}
|
}
|
||||||
gun.get(PacoSakoUUID).get('meta').put({ [gameId]: meta });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function switchGameId(newId){
|
function switchGameId(newId){
|
||||||
|
|
@ -489,7 +494,7 @@ $(function (){
|
||||||
$('#cb_light_name').val('');
|
$('#cb_light_name').val('');
|
||||||
$('#cb_dark_name').val('');
|
$('#cb_dark_name').val('');
|
||||||
|
|
||||||
cancelGameCallback = gun.get(PacoSakoUUID).get('games').get(newId).onWithCancel(function(d) {
|
cancelGameCallback = gun.get(PacoSakoUUID + '/game/' + newId).onWithCancel(function(d) {
|
||||||
if (d && d.board) {
|
if (d && d.board) {
|
||||||
try {
|
try {
|
||||||
const received = JSON.parse(d.board);
|
const received = JSON.parse(d.board);
|
||||||
|
|
@ -526,7 +531,7 @@ $(function (){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cancelMetaCallback = gun.get(PacoSakoUUID).get('meta').get(newId).onWithCancel(function(d) {
|
cancelMetaCallback = gun.get(PacoSakoUUID + '/meta').get(newId).onWithCancel(function(d) {
|
||||||
d = d || {};
|
d = d || {};
|
||||||
debug('got meta', d);
|
debug('got meta', d);
|
||||||
$('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light')));
|
$('#cb_board').data('lightName', shortenName(String(d.lightName || 'Light')));
|
||||||
|
|
@ -885,7 +890,7 @@ $(function (){
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancellers = {};
|
let cancellers = {};
|
||||||
let cancelAll = gun.get(PacoSakoUUID).get('meta').onWithCancel(function(meta) {
|
let cancelAll = gun.get(PacoSakoUUID + '/meta').onWithCancel(function(meta) {
|
||||||
for (const gameId in meta) { /* use of 'in' here is deliberate */
|
for (const gameId in meta) { /* use of 'in' here is deliberate */
|
||||||
/* 'gameId' may include extra GUN fields like '_' */
|
/* 'gameId' may include extra GUN fields like '_' */
|
||||||
if (gameId.match(/^[0-9a-f]{16}$/)) {
|
if (gameId.match(/^[0-9a-f]{16}$/)) {
|
||||||
|
|
@ -956,67 +961,22 @@ $(function (){
|
||||||
|
|
||||||
/* Low-level commands to be run from the JS console */
|
/* Low-level commands to be run from the JS console */
|
||||||
window.Admin = {
|
window.Admin = {
|
||||||
convertFromV1: function() {
|
convertFromV2: function() {
|
||||||
const PacoSakoUUIDv1 = '7c38edd4-c931-49c8-9f1a-84de560815db';
|
gun.get(PacoSakoUUID).get('games').map().once(function(d,key){
|
||||||
gun.get(PacoSakoUUIDv1).get('games').map().once(function(d,key){
|
|
||||||
if (d && d.board) {
|
if (d && d.board) {
|
||||||
debug('converting ' + key);
|
debug('converting ' + key);
|
||||||
const game = new PS.Game();
|
gun.get(PacoSakoUUID + '/game/' + key).get('board').put(d.board);
|
||||||
let moves = [];
|
|
||||||
|
|
||||||
try {
|
|
||||||
let board = JSON.parse(d.board);
|
|
||||||
|
|
||||||
while (board.prior) {
|
|
||||||
moves.push(board.move);
|
|
||||||
board = board.prior;
|
|
||||||
}
|
|
||||||
moves.reverse();
|
|
||||||
|
|
||||||
for (const move of moves) {
|
|
||||||
if (move.to) {
|
|
||||||
game.move(move.from, move.to, move.timestamp && { timestamp: move.timestamp });
|
|
||||||
} else if (move.resign) {
|
|
||||||
game.resign();
|
|
||||||
} else {
|
|
||||||
throw { message: 'unknown move', move: move };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
gun.get(PacoSakoUUID).get('games').get(key).put({ board: JSON.stringify({
|
|
||||||
past: game.moves,
|
|
||||||
future: [],
|
|
||||||
})});
|
|
||||||
} catch (err) {
|
|
||||||
debug('conversion of ' + key + ' failed', err, game, moves);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
gun.get(PacoSakoUUIDv1).get('meta').map().once(function(d,key){
|
gun.get(PacoSakoUUID).get('meta').map().once(function(d,key){
|
||||||
if (d) {
|
if (d) {
|
||||||
debug('converting metadata for ' + key);
|
debug('converting metadata for ' + key);
|
||||||
gun.get(PacoSakoUUID).get('meta').get(key).put({
|
gun.get(PacoSakoUUID + '/meta').get(key).put(d);
|
||||||
lightName: d.lightName,
|
|
||||||
darkName: d.darkName,
|
|
||||||
moves: d.moves,
|
|
||||||
timestamp: d.timestamp,
|
|
||||||
status: d.status,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cleanupMissingGames: function() {
|
|
||||||
gun.get(PacoSakoUUID).get('games').once(function(games){
|
|
||||||
gun.get(PacoSakoUUID).get('meta').map().once(function(d,key){
|
|
||||||
if (!(key in games)) {
|
|
||||||
gun.get(PacoSakoUUID).get('meta').get(key).put(null);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrentGame: function() { return currentGame; },
|
getCurrentGame: function() { return currentGame; },
|
||||||
getVisibleGame: function() { return visibleGame; },
|
getVisibleGame: function() { return visibleGame; },
|
||||||
setCurrentGame: setCurrentGame,
|
setCurrentGame: setCurrentGame,
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
mode: 'production',
|
||||||
entry: {
|
entry: {
|
||||||
index: './index.js'
|
index: './index.js'
|
||||||
},
|
},
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue