Skip to content

Commit c2ea197

Browse files
saeidalidadinicolodavis
authored andcommitted
make db interface async (#86)
* persist game state inside db * undo some unnecessary changes * fix merge * fix tests * no babel on src/server * reset package-lock.json * remove stage-0 preset
1 parent 9e507ce commit c2ea197

7 files changed

Lines changed: 86 additions & 68 deletions

File tree

.babelrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
"presets": [
33
["es2015", { "modules": false }],
44
"react",
5-
"stage-0"
65
],
76
"env": {
87
"targets": {
@@ -21,5 +20,7 @@
2120
"boardgame.io": "./packages",
2221
}
2322
}],
23+
"transform-object-rest-spread",
24+
"transform-class-properties"
2425
]
2526
}

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,11 @@
6262
"babel-loader": "^7.1.2",
6363
"babel-plugin-external-helpers": "^6.22.0",
6464
"babel-plugin-module-resolver": "^3.0.0",
65+
"babel-plugin-transform-class-properties": "^6.24.1",
6566
"babel-plugin-transform-es2015-modules-commonjs": "^6.26.0",
67+
"babel-plugin-transform-object-rest-spread": "^6.26.0",
6668
"babel-preset-es2015": "^6.24.1",
6769
"babel-preset-react": "^6.24.1",
68-
"babel-preset-stage-0": "^6.24.1",
6970
"babel-watch": "^2.0.7",
7071
"chess.js": "^0.10.2",
7172
"coveralls": "^3.0.0",

rollup.npm.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export default [
4141
output: { file: 'dist/server.js', format: 'cjs' },
4242
name: 'Server',
4343
plugins: [
44-
babel({ exclude: '**/node_modules/**' }),
44+
babel({ exclude: ['**/node_modules/**'] }),
4545
commonjs({
4646
exclude: 'node_modules/**',
4747
}),

src/server/db.js

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,28 +20,27 @@ export class InMemory {
2020
/**
2121
* Write the game state to the in-memory object.
2222
* @param {string} id - The game id.
23-
* @param {object} store - A Redux store to persist.
23+
* @param {object} store - A game state to persist.
2424
*/
25-
set(id, store) {
26-
this.games.set(id, store);
25+
async set(id, state) {
26+
return await this.games.set(id, state);
2727
}
2828

2929
/**
3030
* Read the game state from the in-memory object.
3131
* @param {string} id - The game id.
32-
* @returns {object} - A Redux store with the game state, or undefined
32+
* @returns {object} - A game state, or undefined
3333
* if no game is found with this id.
3434
*/
35-
get(id) {
36-
return this.games.get(id);
35+
async get(id) {
36+
return await this.games.get(id);
3737
}
38-
3938
/**
4039
* Read the game state from the in-memory object.
4140
* @param {string} id - The game id.
4241
* @returns {boolean} - True if a game with this id exists.
4342
*/
44-
has(id) {
45-
return this.games.has(id);
43+
async has(id) {
44+
return await this.games.has(id);
4645
}
4746
}

src/server/db.test.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,22 @@
99
import { InMemory } from './db';
1010
import * as Redux from 'redux';
1111

12-
test('basic', () => {
12+
test('basic', async () => {
1313
const db = new InMemory();
1414
const reducer = () => {};
1515
const store = Redux.createStore(reducer);
1616

1717
// Must return undefined when no game exists.
18-
expect(db.get('gameID')).toEqual(undefined);
18+
let state = await db.get('gameID');
19+
expect(state).toEqual(undefined);
20+
1921
// Create game.
20-
db.set('gameID', store);
22+
await db.set('gameID', store.getState());
2123
// Must return created game.
22-
expect(db.get('gameID')).toEqual(store);
24+
state = await db.get('gameID');
25+
expect(state).toEqual(store.getState());
2326

2427
// Must return true if game exists
25-
expect(db.has('gameID')).toEqual(true);
28+
let has = await db.has('gameID');
29+
expect(has).toEqual(true);
2630
});

src/server/index.js

Lines changed: 27 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,18 @@ function Server({ games, db }) {
2929
const nsp = app._io.of(game.name);
3030

3131
nsp.on('connection', socket => {
32-
socket.on('action', (action, stateID, gameID, playerID) => {
33-
const store = db.get(gameID);
32+
socket.on('action', async (action, stateID, gameID, playerID) => {
33+
let state = await db.get(gameID);
3434

35-
if (store === undefined) {
35+
if (state === undefined) {
3636
return { error: 'game not found' };
3737
}
3838

39-
const state = store.getState();
39+
const reducer = createGameReducer({
40+
game,
41+
numPlayers: state.ctx.numPlayers,
42+
});
43+
const store = Redux.createStore(reducer, state);
4044

4145
// The null player is a view-only player.
4246
if (playerID == null) {
@@ -55,33 +59,30 @@ function Server({ games, db }) {
5559
if (state._id == stateID) {
5660
// Update server's version of the store.
5761
store.dispatch(action);
58-
const state = store.getState();
62+
state = store.getState();
5963

6064
// Get clients connected to this current game.
6165
const roomClients = roomInfo.get(gameID);
6266
for (const client of roomClients.values()) {
6367
const playerID = clientInfo.get(client).playerID;
68+
const newState = Object.assign({}, state, {
69+
G: game.playerView(state.G, state.ctx, playerID),
70+
});
6471

6572
if (client === socket.id) {
66-
socket.emit('sync', gameID, {
67-
...state,
68-
G: game.playerView(state.G, state.ctx, playerID),
69-
});
73+
socket.emit('sync', gameID, newState);
7074
} else {
71-
socket.to(client).emit('sync', gameID, {
72-
...state,
73-
G: game.playerView(state.G, state.ctx, playerID),
74-
});
75+
socket.to(client).emit('sync', gameID, newState);
7576
}
7677
}
7778

78-
db.set(gameID, store);
79+
db.set(gameID, store.getState());
7980
}
8081
});
8182

82-
socket.on('sync', (gameID, playerID, numPlayers) => {
83+
socket.on('sync', async (gameID, playerID, numPlayers) => {
8384
socket.join(gameID);
84-
85+
const reducer = createGameReducer({ game, numPlayers });
8586
let roomClients = roomInfo.get(gameID);
8687
if (roomClients === undefined) {
8788
roomClients = new Set();
@@ -91,18 +92,20 @@ function Server({ games, db }) {
9192

9293
clientInfo.set(socket.id, { gameID, playerID });
9394

94-
let store = db.get(gameID);
95-
if (store === undefined) {
96-
const reducer = createGameReducer({ game, numPlayers });
97-
store = Redux.createStore(reducer);
98-
db.set(gameID, store);
95+
let state = await db.get(gameID);
96+
if (state === undefined) {
97+
const store = Redux.createStore(reducer);
98+
state = store.getState();
99+
await db.set(gameID, state);
99100
}
100101

101-
const state = store.getState();
102-
socket.emit('sync', gameID, {
103-
...state,
102+
const newState = Object.assign({}, state, {
104103
G: game.playerView(state.G, state.ctx, playerID),
105104
});
105+
106+
socket.emit('sync', gameID, newState);
107+
108+
return;
106109
});
107110

108111
socket.on('disconnect', () => {

src/server/index.test.js

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@ jest.mock('koa-socket', () => {
2020
this.broadcast = { emit: jest.fn() };
2121
}
2222

23-
receive(type, ...args) {
24-
this.callbacks[type](args[0], args[1], args[2], args[3], args[4]);
23+
async receive(type, ...args) {
24+
await this.callbacks[type](args[0], args[1], args[2], args[3], args[4]);
25+
return;
2526
}
2627

2728
on(type, callback) {
@@ -65,7 +66,7 @@ test('basic', () => {
6566
io.socket.receive('disconnect');
6667
});
6768

68-
test('sync', () => {
69+
test('sync', async () => {
6970
const server = Server({ games: [game] });
7071
const io = server.context.io;
7172
expect(server).not.toBe(undefined);
@@ -74,40 +75,42 @@ test('sync', () => {
7475

7576
// Sync causes the server to respond.
7677
expect(io.socket.emit).toHaveBeenCalledTimes(0);
77-
io.socket.receive('sync', 'gameID');
78+
await io.socket.receive('sync', 'gameID');
79+
7880
expect(io.socket.emit).toHaveBeenCalledTimes(1);
7981
expect(spy).toHaveBeenCalled();
8082

8183
// Sync a second time does not create a game.
8284
spy.mockReset();
83-
io.socket.receive('sync', 'gameID');
85+
await io.socket.receive('sync', 'gameID');
86+
8487
expect(io.socket.emit).toHaveBeenCalledTimes(2);
8588
expect(spy).not.toHaveBeenCalled();
8689

8790
spy.mockRestore();
8891
});
8992

90-
test('action', () => {
93+
test('action', async () => {
9194
const server = Server({ games: [game] });
9295
const io = server.context.io;
9396
const action = ActionCreators.gameEvent('endTurn');
9497

95-
io.socket.receive('action', action);
98+
await io.socket.receive('action', action);
9699
expect(io.socket.emit).toHaveBeenCalledTimes(0);
97100
io.socket.emit.mockReset();
98101

99-
io.socket.receive('sync', 'gameID');
102+
await io.socket.receive('sync', 'gameID');
100103
io.socket.id = 'second';
101-
io.socket.receive('sync', 'gameID');
104+
await io.socket.receive('sync', 'gameID');
102105
io.socket.emit.mockReset();
103106

104107
// View-only players cannot send actions.
105-
io.socket.receive('action', action, 0, 'gameID', null);
108+
await io.socket.receive('action', action, 0, 'gameID', null);
106109
expect(io.socket.emit).not.toHaveBeenCalled();
107110

108111
// Actions are broadcasted as state updates.
109112
// The playerID parameter is necessary to account for view-only players.
110-
io.socket.receive('action', action, 0, 'gameID', '0');
113+
await io.socket.receive('action', action, 0, 'gameID', '0');
111114
expect(io.socket.emit).lastCalledWith('sync', 'gameID', {
112115
G: {},
113116
_id: 1,
@@ -141,55 +144,55 @@ test('action', () => {
141144
io.socket.emit.mockReset();
142145

143146
// ... but not if the gameID is not known.
144-
io.socket.receive('action', action, 1, 'unknown', '1');
147+
await io.socket.receive('action', action, 1, 'unknown', '1');
145148
expect(io.socket.emit).toHaveBeenCalledTimes(0);
146149

147150
// ... and not if the _id doesn't match the internal state.
148-
io.socket.receive('action', action, 100, 'gameID', '1');
151+
await io.socket.receive('action', action, 100, 'gameID', '1');
149152
expect(io.socket.emit).toHaveBeenCalledTimes(0);
150153

151154
// ... and not if player != currentPlayer
152-
io.socket.receive('action', action, 1, 'gameID', '100');
155+
await io.socket.receive('action', action, 1, 'gameID', '100');
153156
expect(io.socket.emit).toHaveBeenCalledTimes(0);
154157

155158
// Another broadcasted action.
156-
io.socket.receive('action', action, 1, 'gameID', '1');
159+
await io.socket.receive('action', action, 1, 'gameID', '1');
157160
expect(io.socket.emit).toHaveBeenCalledTimes(2);
158161
});
159162

160-
test('playerView (sync)', () => {
163+
test('playerView (sync)', async () => {
161164
// Write the player into G.
162165
const game = Game({
163166
playerView: (G, ctx, player) => {
164-
return { ...G, player };
167+
return Object.assign({}, G, { player });
165168
},
166169
});
167170

168171
const server = Server({ games: [game] });
169172
const io = server.context.io;
170173

171-
io.socket.receive('sync', 'gameID', 0);
174+
await io.socket.receive('sync', 'gameID', 0);
172175
expect(io.socket.emit).toHaveBeenCalledTimes(1);
173176
expect(io.socket.emit.mock.calls[0][2].G).toEqual({ player: 0 });
174177
});
175178

176-
test('playerView (action)', () => {
179+
test('playerView (action)', async () => {
177180
const game = Game({
178181
playerView: (G, ctx, player) => {
179-
return { ...G, player };
182+
return Object.assign({}, G, { player });
180183
},
181184
});
182185
const server = Server({ games: [game] });
183186
const io = server.context.io;
184187
const action = ActionCreators.gameEvent('endTurn');
185188

186189
io.socket.id = 'first';
187-
io.socket.receive('sync', 'gameID', '0', 2);
190+
await io.socket.receive('sync', 'gameID', '0', 2);
188191
io.socket.id = 'second';
189-
io.socket.receive('sync', 'gameID', '1', 2);
192+
await io.socket.receive('sync', 'gameID', '1', 2);
190193
io.socket.emit.mockReset();
191194

192-
io.socket.receive('action', action, 0, 'gameID', '0');
195+
await io.socket.receive('action', action, 0, 'gameID', '0');
193196
expect(io.socket.emit).toHaveBeenCalledTimes(2);
194197

195198
const G_player0 = io.socket.emit.mock.calls[0][2].G;
@@ -199,19 +202,26 @@ test('playerView (action)', () => {
199202
expect(G_player1.player).toBe('1');
200203
});
201204

202-
test('custom db implementation', () => {
205+
test('custom db implementation', async () => {
203206
let getId = null;
207+
204208
class Custom {
205-
get(id) {
209+
constructor() {
210+
this.games = new Map();
211+
}
212+
async get(id) {
206213
getId = id;
214+
return await this.games.get(id);
215+
}
216+
async set(id, state) {
217+
return await this.games.set(id, state);
207218
}
208-
set() {}
209219
}
210220

211221
const game = Game({});
212222
const server = Server({ games: [game], db: new Custom() });
213223
const io = server.context.io;
214224

215-
io.socket.receive('sync', 'gameID');
225+
await io.socket.receive('sync', 'gameID');
216226
expect(getId).toBe('gameID');
217227
});

0 commit comments

Comments
 (0)