Skip to content

Commit ace1144

Browse files
authored
fix(local): Use shared local master with bots (#838)
* fix(local): Use shared local master with bots This returns a shared local master from the Local transport even when `bots` is passed in the options to `Local`. #553 changed this to avoid out-of-date bots persisting, but in the process broke scenarios where several clients should remain in sync on a single page (see #800). This implements a more careful check, returning a shared master as long as the `bots` options passed are *equal* to each other (via simple object equality). The reliance on object identity could still represent a confusing gotcha, so in the future it might be better to offer a more explicit API for getting a new clean transport. Moving the global map of master instances *inside* the function scope of `Local`, could do this — calling `Local` would always return a new transport layer that could be shared around — but would potentially break current usage where repeatedly calling `Local` would still connect to the same master. Fix #800
1 parent 2daf122 commit ace1144

2 files changed

Lines changed: 52 additions & 11 deletions

File tree

src/client/transport/local.test.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { createStore } from 'redux';
1010
import { LocalTransport, LocalMaster, Local, GetBotPlayer } from './local';
1111
import { makeMove, gameEvent } from '../../core/action-creators';
1212
import { CreateGameReducer } from '../../core/reducer';
13+
import { ProcessGameConfig } from '../../core/game';
1314
import { InitializeGame } from '../../core/initialize';
1415
import { Client } from '../client';
1516
import { RandomBot } from '../../ai/random-bot';
@@ -121,6 +122,33 @@ describe('GetBotPlayer', () => {
121122
});
122123
});
123124

125+
describe('Local', () => {
126+
test('transports for same game use shared master', () => {
127+
const gameKey = {};
128+
const game = ProcessGameConfig(gameKey);
129+
const transport1 = Local()({ game, gameKey });
130+
const transport2 = Local()({ game, gameKey });
131+
expect(transport1.master).toBe(transport2.master);
132+
});
133+
134+
test('transports use shared master with bots', () => {
135+
const gameKey = {};
136+
const game = ProcessGameConfig(gameKey);
137+
const bots = {};
138+
const transport1 = Local({ bots })({ game, gameKey });
139+
const transport2 = Local({ bots })({ game, gameKey });
140+
expect(transport1.master).toBe(transport2.master);
141+
});
142+
143+
test('transports use different master for different bots', () => {
144+
const gameKey = {};
145+
const game = ProcessGameConfig(gameKey);
146+
const transport1 = Local({ bots: {} })({ game, gameKey });
147+
const transport2 = Local({ bots: {} })({ game, gameKey });
148+
expect(transport1.master).not.toBe(transport2.master);
149+
});
150+
});
151+
124152
describe('LocalMaster', () => {
125153
const game = {};
126154
const master = new LocalMaster({ game });

src/client/transport/local.ts

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ export function GetBotPlayer(state: State, bots: Record<PlayerID, any>) {
4343

4444
interface LocalMasterOpts {
4545
game: Game;
46-
bots: Record<PlayerID, any>;
46+
bots?: Record<PlayerID, any>;
4747
}
4848

4949
/**
@@ -234,19 +234,32 @@ export class LocalTransport extends Transport {
234234
}
235235
}
236236

237-
const localMasters = new Map();
238-
export function Local(opts?: Pick<LocalMasterOpts, 'bots'>) {
237+
/**
238+
* Global map storing local master instances.
239+
*/
240+
const localMasters: Map<
241+
Game,
242+
{ master: LocalMaster; bots: LocalMasterOpts['bots'] }
243+
> = new Map();
244+
245+
/**
246+
* Create a local transport.
247+
*/
248+
export function Local({ bots }: Pick<LocalMasterOpts, 'bots'> = {}) {
239249
return (transportOpts: TransportOpts) => {
250+
const { gameKey, game } = transportOpts;
240251
let master: LocalMaster;
241252

242-
if (localMasters.has(transportOpts.gameKey) && !opts) {
243-
master = localMasters.get(transportOpts.gameKey);
244-
} else {
245-
master = new LocalMaster({
246-
game: transportOpts.game,
247-
bots: opts && opts.bots,
248-
});
249-
localMasters.set(transportOpts.gameKey, master);
253+
if (localMasters.has(gameKey)) {
254+
const instance = localMasters.get(gameKey);
255+
if (instance.bots === bots) {
256+
master = instance.master;
257+
}
258+
}
259+
260+
if (!master) {
261+
master = new LocalMaster({ game, bots });
262+
localMasters.set(gameKey, { master, bots });
250263
}
251264

252265
return new LocalTransport({ master, ...transportOpts });

0 commit comments

Comments
 (0)