Skip to content

Commit

Permalink
MongoDB connector
Browse files Browse the repository at this point in the history
  • Loading branch information
darthfiddler committed Feb 28, 2018
1 parent e3f7e91 commit 003fe46
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 21 deletions.
106 changes: 104 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@
"koa-helmet": "^3.2.0",
"koa-webpack": "^1.0.0",
"lint-staged": "^6.0.1",
"mongo-mock": "^3.1.0",
"open-browser-webpack-plugin": "0.0.5",
"prettier": "^1.10.2",
"react": "^16.0.0",
Expand All @@ -113,6 +114,8 @@
"koa-router": "^7.2.1",
"koa-socket": "^4.4.0",
"koa-static": "^4.0.1",
"lru-native": "^0.4.0",
"mongodb": "^3.0.3",
"mousetrap": "^1.6.1",
"prop-types": "^15.5.10",
"react-json-view": "^1.13.0",
Expand Down
2 changes: 1 addition & 1 deletion src/client/multiplayer/multiplayer.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export class Multiplayer {
this.socket.emit(
'action',
action,
state._id,
state._stateID,
this.gameID,
this.playerID
);
Expand Down
6 changes: 3 additions & 3 deletions src/core/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function createGameReducer({ game, numPlayers, multiplayer }) {
// A monotonically non-decreasing ID to ensure that
// state updates are only allowed from clients that
// are at the same version that the server.
_id: 0,
_stateID: 0,

// A snapshot of this object so that actions can be
// replayed over it to view old snapshots.
Expand Down Expand Up @@ -84,7 +84,7 @@ export function createGameReducer({ game, numPlayers, multiplayer }) {
ctx = { ...ctx, _random: PRNGState.get() };

const log = [...state.log, action];
return { ...state, G, ctx, log, _id: state._id + 1 };
return { ...state, G, ctx, log, _stateID: state._stateID + 1 };
}

case Actions.MAKE_MOVE: {
Expand All @@ -110,7 +110,7 @@ export function createGameReducer({ game, numPlayers, multiplayer }) {
}

const log = [...state.log, action];
state = { ...state, G, ctx, log, _id: state._id + 1 };
state = { ...state, G, ctx, log, _stateID: state._stateID + 1 };

// If we're on the client, just process the move
// and no triggers in multiplayer mode.
Expand Down
6 changes: 3 additions & 3 deletions src/core/reducer.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ const game = Game({

const endTurn = () => gameEvent('endTurn');

test('_id is incremented', () => {
test('_stateID is incremented', () => {
const reducer = createGameReducer({ game });

let state = undefined;

state = reducer(state, makeMove('unknown'));
expect(state._id).toBe(1);
expect(state._stateID).toBe(1);
state = reducer(state, endTurn());
expect(state._id).toBe(2);
expect(state._stateID).toBe(2);
});

test('makeMove', () => {
Expand Down
97 changes: 96 additions & 1 deletion src/server/db.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* https://opensource.org/licenses/MIT.
*/

const MongoClient = require('mongodb').MongoClient;
const LRUCache = require('lru-native');

/**
* InMemory data storage.
*/
Expand All @@ -17,6 +20,14 @@ export class InMemory {
this.games = new Map();
}

/**
* Connect.
* No-op for the InMemory instance.
*/
async connect() {
return;
}

/**
* Write the game state to the in-memory object.
* @param {string} id - The game id.
Expand All @@ -35,12 +46,96 @@ export class InMemory {
async get(id) {
return await this.games.get(id);
}

/**
* Read the game state from the in-memory object.
* Check if a particular game id exists.
* @param {string} id - The game id.
* @returns {boolean} - True if a game with this id exists.
*/
async has(id) {
return await this.games.has(id);
}
}

/**
* MongoDB connector.
*/
export class Mongo {
/**
* Creates a new Mongo connector object.
*/
constructor({ url, dbname, cacheSize, mockClient }) {
if (cacheSize === undefined) cacheSize = 1000;

this.client = mockClient || MongoClient;
this.url = url;
this.dbname = dbname;
this.cache = new LRUCache({ maxElements: cacheSize });
}

/**
* Connect to the instance.
*/
async connect() {
const c = await this.client.connect(this.url);
this.db = c.db(this.dbname);
return;
}

/**
* Write the game state.
* @param {string} id - The game id.
* @param {object} store - A game state to persist.
*/
async set(id, state) {
this.cache.set(id, state);

const col = this.db.collection(id);
delete state._id;
await col.insert(state);

return;
}

/**
* Read the game state.
* @param {string} id - The game id.
* @returns {object} - A game state, or undefined
* if no game is found with this id.
*/
async get(id) {
const item = this.cache.get(id);
if (item !== undefined) {
return item;
}

const col = this.db.collection(id);
const docs = await col
.find()
.sort({ _id: -1 })
.limit(1)
.toArray();

this.cache.set(id, docs[0]);
return docs[0];
}

/**
* Check if a particular game exists.
* @param {string} id - The game id.
* @returns {boolean} - True if a game with this id exists.
*/
async has(id) {
const item = this.cache.get(id);
if (item !== undefined) {
return true;
}

const col = this.db.collection(id);
const docs = await col
.find()
.limit(1)
.toArray();
return docs.length > 0;
}
}
Loading

0 comments on commit 003fe46

Please sign in to comment.