mirror of
https://github.com/AlecM33/Werewolf.git
synced 2026-02-10 04:03:33 +01:00
Refactor timer system to run on main thread instead of child processes
Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com>
This commit is contained in:
@@ -164,7 +164,7 @@ const Events = [
|
||||
vars.gameManager.deal(game);
|
||||
if (game.hasTimer) {
|
||||
game.timerParams.paused = true;
|
||||
await vars.timerManager.runTimer(game, vars.gameManager.namespace, vars.eventManager, vars.gameManager);
|
||||
await vars.gameManager.runTimer(game);
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -297,12 +297,10 @@ const Events = [
|
||||
id: EVENT_IDS.RESTART_GAME,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
if (vars.instanceId !== vars.senderInstanceId
|
||||
&& vars.timerManager.timerThreads[game.accessCode]
|
||||
&& vars.gameManager.timers[game.accessCode]
|
||||
) {
|
||||
if (!vars.timerManager.timerThreads[game.accessCode].killed) {
|
||||
vars.timerManager.timerThreads[game.accessCode].kill();
|
||||
}
|
||||
delete vars.timerManager.timerThreads[game.accessCode];
|
||||
vars.gameManager.timers[game.accessCode].stopTimer();
|
||||
delete vars.gameManager.timers[game.accessCode];
|
||||
}
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
@@ -316,24 +314,65 @@ const Events = [
|
||||
id: EVENT_IDS.TIMER_EVENT,
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const thread = vars.timerManager.timerThreads[game.accessCode];
|
||||
if (thread) {
|
||||
if (!thread.killed && thread.exitCode === null) {
|
||||
thread.send({
|
||||
command: vars.timerEventSubtype,
|
||||
accessCode: game.accessCode,
|
||||
socketId: vars.requestingSocketId,
|
||||
logLevel: vars.logger.logLevel
|
||||
});
|
||||
} else {
|
||||
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
game.timerParams.timeRemaining,
|
||||
game.timerParams.paused
|
||||
);
|
||||
}
|
||||
const timer = vars.gameManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
// Timer is running on this instance, handle the request directly
|
||||
switch (vars.timerEventSubtype) {
|
||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
const pauseTimeRemaining = await vars.gameManager.pauseTimer(game);
|
||||
if (pauseTimeRemaining !== null) {
|
||||
// Trigger PAUSE_TIMER event to update state and notify clients
|
||||
await vars.eventManager.handleEventById(
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
null,
|
||||
game,
|
||||
null,
|
||||
game.accessCode,
|
||||
{ timeRemaining: pauseTimeRemaining },
|
||||
null,
|
||||
false
|
||||
);
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: pauseTimeRemaining })
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
const resumeTimeRemaining = await vars.gameManager.resumeTimer(game);
|
||||
if (resumeTimeRemaining !== null) {
|
||||
// Trigger RESUME_TIMER event to update state and notify clients
|
||||
await vars.eventManager.handleEventById(
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
null,
|
||||
game,
|
||||
null,
|
||||
game.accessCode,
|
||||
{ timeRemaining: resumeTimeRemaining },
|
||||
null,
|
||||
false
|
||||
);
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: resumeTimeRemaining })
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
await vars.gameManager.getTimeRemaining(game, vars.requestingSocketId);
|
||||
break;
|
||||
}
|
||||
} else { // we need to consult another container for the timer data
|
||||
await vars.eventManager.publisher?.publish(
|
||||
@@ -350,34 +389,83 @@ const Events = [
|
||||
},
|
||||
{
|
||||
/* This event is a request from another instance to consult its timer data. In response
|
||||
* to this event, this instance will check if it is home to a particular timer thread. */
|
||||
* to this event, this instance will check if it is home to a particular timer. */
|
||||
id: EVENT_IDS.SOURCE_TIMER_EVENT,
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const thread = vars.timerManager.timerThreads[game.accessCode];
|
||||
if (thread) {
|
||||
if (!thread.killed && thread.exitCode === null) {
|
||||
thread.send({
|
||||
command: socketArgs.timerEventSubtype,
|
||||
accessCode: game.accessCode,
|
||||
socketId: socketArgs.socketId,
|
||||
logLevel: vars.logger.logLevel
|
||||
});
|
||||
} else {
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
socketArgs.timerEventSubtype,
|
||||
vars.instanceId,
|
||||
JSON.stringify({
|
||||
socketId: socketArgs.socketId,
|
||||
timeRemaining: game.timerParams.timeRemaining,
|
||||
paused: game.timerParams.paused
|
||||
})
|
||||
)
|
||||
);
|
||||
const timer = vars.gameManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
// Timer is running on this instance, handle the request
|
||||
switch (socketArgs.timerEventSubtype) {
|
||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
const pauseTimeRemaining = await vars.gameManager.pauseTimer(game);
|
||||
if (pauseTimeRemaining !== null) {
|
||||
await vars.eventManager.handleEventById(
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
null,
|
||||
game,
|
||||
null,
|
||||
game.accessCode,
|
||||
{ timeRemaining: pauseTimeRemaining },
|
||||
null,
|
||||
false
|
||||
);
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
EVENT_IDS.PAUSE_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: pauseTimeRemaining })
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
const resumeTimeRemaining = await vars.gameManager.resumeTimer(game);
|
||||
if (resumeTimeRemaining !== null) {
|
||||
await vars.eventManager.handleEventById(
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
null,
|
||||
game,
|
||||
null,
|
||||
game.accessCode,
|
||||
{ timeRemaining: resumeTimeRemaining },
|
||||
null,
|
||||
false
|
||||
);
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
EVENT_IDS.RESUME_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: resumeTimeRemaining })
|
||||
)
|
||||
);
|
||||
}
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
await vars.gameManager.getTimeRemaining(game, socketArgs.socketId);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Timer not running here, publish stored timer state
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
socketArgs.timerEventSubtype,
|
||||
vars.instanceId,
|
||||
JSON.stringify({
|
||||
socketId: socketArgs.socketId,
|
||||
timeRemaining: game.timerParams.timeRemaining,
|
||||
paused: game.timerParams.paused
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -12,6 +12,7 @@ const Person = require('../../model/Person');
|
||||
const GameStateCurator = require('../GameStateCurator');
|
||||
const UsernameGenerator = require('../UsernameGenerator');
|
||||
const GameCreationRequest = require('../../model/GameCreationRequest');
|
||||
const ServerTimer = require('../ServerTimer');
|
||||
|
||||
class GameManager {
|
||||
constructor (logger, environment, instanceId) {
|
||||
@@ -25,16 +26,16 @@ class GameManager {
|
||||
this.eventManager = null;
|
||||
this.namespace = null;
|
||||
this.instanceId = instanceId;
|
||||
this.timers = {}; // Map of accessCode -> ServerTimer instance
|
||||
GameManager.instance = this;
|
||||
}
|
||||
|
||||
getActiveGame = async (accessCode) => {
|
||||
const r = await this.eventManager.publisher.get(accessCode);
|
||||
if (r === null && this.timerManager.timerThreads[accessCode]) {
|
||||
if (!this.timerManager.timerThreads[accessCode].killed) {
|
||||
this.timerManager.timerThreads[accessCode].kill();
|
||||
}
|
||||
delete this.timerManager.timerThreads[accessCode];
|
||||
if (r === null && this.timers[accessCode]) {
|
||||
// Clean up orphaned timer
|
||||
this.timers[accessCode].stopTimer();
|
||||
delete this.timers[accessCode];
|
||||
}
|
||||
let activeGame;
|
||||
if (r !== null) {
|
||||
@@ -108,43 +109,87 @@ class GameManager {
|
||||
});
|
||||
};
|
||||
|
||||
pauseTimer = async (game, logger) => {
|
||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
||||
if (thread && !thread.killed) {
|
||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
||||
thread.send({
|
||||
command: GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
accessCode: game.accessCode,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
}
|
||||
runTimer = async (game) => {
|
||||
this.logger.debug('running timer for game ' + game.accessCode);
|
||||
const timer = new ServerTimer(
|
||||
game.timerParams.hours,
|
||||
game.timerParams.minutes,
|
||||
PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS,
|
||||
this.logger
|
||||
);
|
||||
this.timers[game.accessCode] = timer;
|
||||
|
||||
// Start timer in paused state initially (pausedInitially = true)
|
||||
timer.runTimer(true).then(async () => {
|
||||
this.logger.debug('Timer finished for ' + game.accessCode);
|
||||
// Trigger END_TIMER event
|
||||
game = await this.getActiveGame(game.accessCode);
|
||||
if (game) {
|
||||
await this.eventManager.handleEventById(
|
||||
EVENT_IDS.END_TIMER,
|
||||
null,
|
||||
game,
|
||||
null,
|
||||
game.accessCode,
|
||||
{},
|
||||
null,
|
||||
false
|
||||
);
|
||||
await this.refreshGame(game);
|
||||
await this.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
this.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
EVENT_IDS.END_TIMER,
|
||||
this.instanceId,
|
||||
'{}'
|
||||
)
|
||||
);
|
||||
}
|
||||
// Clean up timer instance
|
||||
delete this.timers[game.accessCode];
|
||||
});
|
||||
game.startTime = new Date().toJSON();
|
||||
};
|
||||
|
||||
resumeTimer = async (game, logger) => {
|
||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
||||
if (thread && !thread.killed) {
|
||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
||||
thread.send({
|
||||
command: GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
accessCode: game.accessCode,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
pauseTimer = async (game) => {
|
||||
const timer = this.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.debug('Timer found for game ' + game.accessCode);
|
||||
timer.stopTimer();
|
||||
return timer.currentTimeInMillis;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
resumeTimer = async (game) => {
|
||||
const timer = this.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.debug('Timer found for game ' + game.accessCode);
|
||||
timer.resumeTimer();
|
||||
return timer.currentTimeInMillis;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
getTimeRemaining = async (game, socketId) => {
|
||||
if (socketId) {
|
||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
||||
thread.send({
|
||||
command: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
accessCode: game.accessCode,
|
||||
socketId: socketId,
|
||||
logLevel: this.logger.logLevel
|
||||
});
|
||||
} else if (thread) {
|
||||
if (game.timerParams && game.timerParams.timeRemaining === 0) {
|
||||
this.namespace.to(socketId).emit(GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
const timer = this.timers[game.accessCode];
|
||||
if (timer) {
|
||||
// Timer is running on this instance, emit directly
|
||||
this.namespace.to(socketId).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
timer.currentTimeInMillis,
|
||||
game.timerParams.paused
|
||||
);
|
||||
} else {
|
||||
// Timer not running on this instance, return stored value
|
||||
if (game.timerParams) {
|
||||
this.namespace.to(socketId).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
game.timerParams.timeRemaining,
|
||||
game.timerParams.paused
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -248,15 +293,12 @@ class GameManager {
|
||||
}
|
||||
|
||||
restartGame = async (game, namespace) => {
|
||||
// kill any outstanding timer threads
|
||||
const subProcess = this.timerManager.timerThreads[game.accessCode];
|
||||
if (subProcess) {
|
||||
if (!subProcess.killed) {
|
||||
this.logger.info('Killing timer process ' + subProcess.pid + ' for: ' + game.accessCode);
|
||||
this.timerManager.timerThreads[game.accessCode].kill();
|
||||
}
|
||||
this.logger.debug('Deleting reference to subprocess ' + subProcess.pid);
|
||||
delete this.timerManager.timerThreads[game.accessCode];
|
||||
// stop any outstanding timers
|
||||
const timer = this.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.info('Stopping timer for: ' + game.accessCode);
|
||||
timer.stopTimer();
|
||||
delete this.timers[game.accessCode];
|
||||
}
|
||||
|
||||
for (let i = 0; i < game.people.length; i ++) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// TODO: clean up these deep relative paths? jsconfig.json is not working...
|
||||
const Game = require('../../../../server/model/Game');
|
||||
const { ENVIRONMENTS, EVENT_IDS, USER_TYPES, STATUS } = require('../../../../server/config/globals.js');
|
||||
const { ENVIRONMENTS, EVENT_IDS, USER_TYPES, STATUS, GAME_PROCESS_COMMANDS } = require('../../../../server/config/globals.js');
|
||||
const GameManager = require('../../../../server/modules/singletons/GameManager.js');
|
||||
const TimerManager = require('../../../../server/modules/singletons/TimerManager.js');
|
||||
const EventManager = require('../../../../server/modules/singletons/EventManager.js');
|
||||
@@ -272,12 +272,12 @@ describe('Events', () => {
|
||||
game.isStartable = true;
|
||||
game.hasTimer = true;
|
||||
game.timerParams = {};
|
||||
spyOn(timerManager, 'runTimer').and.callFake((a, b) => {});
|
||||
spyOn(gameManager, 'runTimer').and.callFake(() => {});
|
||||
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
|
||||
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager });
|
||||
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||
expect(game.timerParams.paused).toEqual(true);
|
||||
expect(timerManager.runTimer).toHaveBeenCalled();
|
||||
expect(gameManager.runTimer).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('communicate', () => {
|
||||
@@ -530,22 +530,22 @@ describe('Events', () => {
|
||||
describe(EVENT_IDS.RESTART_GAME, () => {
|
||||
describe('stateChange', () => {
|
||||
it('should kill any alive timer thread if the instance is home to it', async () => {
|
||||
const mockThread = { kill: () => {}, killed: false };
|
||||
timerManager.timerThreads = { ABCD: mockThread };
|
||||
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
|
||||
const mockTimer = { stopTimer: () => {} };
|
||||
gameManager.timers = { ABCD: mockTimer };
|
||||
spyOn(gameManager.timers.ABCD, 'stopTimer').and.callThrough();
|
||||
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
||||
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '222' });
|
||||
expect(mockThread.kill).toHaveBeenCalled();
|
||||
expect(Object.keys(timerManager.timerThreads).length).toEqual(0);
|
||||
expect(mockTimer.stopTimer).toHaveBeenCalled();
|
||||
expect(Object.keys(gameManager.timers).length).toEqual(0);
|
||||
});
|
||||
it('should not kill the timer thread if the instance sent the event', async () => {
|
||||
const mockThread = { kill: () => {}, killed: false };
|
||||
timerManager.timerThreads = { ABCD: mockThread };
|
||||
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
|
||||
const mockTimer = { stopTimer: () => {} };
|
||||
gameManager.timers = { ABCD: mockTimer };
|
||||
spyOn(gameManager.timers.ABCD, 'stopTimer').and.callThrough();
|
||||
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
||||
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '111' });
|
||||
expect(mockThread.kill).not.toHaveBeenCalled();
|
||||
expect(Object.keys(timerManager.timerThreads).length).toEqual(1);
|
||||
expect(mockTimer.stopTimer).not.toHaveBeenCalled();
|
||||
expect(Object.keys(gameManager.timers).length).toEqual(1);
|
||||
});
|
||||
});
|
||||
describe('communicate', () => {
|
||||
@@ -571,28 +571,30 @@ describe('Events', () => {
|
||||
describe('communicate', () => {
|
||||
it('should publish an event to source timer data if the timer thread is not found', async () => {
|
||||
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
||||
.communicate(game, {}, { gameManager: gameManager, timerManager: timerManager, eventManager: eventManager });
|
||||
.communicate(game, {}, {
|
||||
gameManager: gameManager,
|
||||
timerManager: timerManager,
|
||||
eventManager: eventManager,
|
||||
instanceId: 'test',
|
||||
timerEventSubtype: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
requestingSocketId: '2'
|
||||
});
|
||||
expect(eventManager.publisher.publish).toHaveBeenCalled();
|
||||
});
|
||||
it('should send a message to the thread if it is found', async () => {
|
||||
const mockThread = { exitCode: null, kill: () => {}, send: (...args) => {}, killed: false };
|
||||
timerManager.timerThreads = { ABCD: mockThread };
|
||||
spyOn(timerManager.timerThreads.ABCD, 'send').and.callThrough();
|
||||
const mockTimer = { currentTimeInMillis: 5000 };
|
||||
gameManager.timers = { ABCD: mockTimer };
|
||||
spyOn(gameManager, 'getTimeRemaining').and.returnValue(Promise.resolve());
|
||||
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
||||
.communicate(game, {}, {
|
||||
gameManager: gameManager,
|
||||
timerManager: timerManager,
|
||||
eventManager: eventManager,
|
||||
timerEventSubtype: EVENT_IDS.GET_TIME_REMAINING,
|
||||
timerEventSubtype: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
requestingSocketId: '2',
|
||||
logger: { logLevel: 'trace' }
|
||||
});
|
||||
expect(mockThread.send).toHaveBeenCalledWith({
|
||||
command: EVENT_IDS.GET_TIME_REMAINING,
|
||||
accessCode: 'ABCD',
|
||||
socketId: '2',
|
||||
logLevel: 'trace'
|
||||
});
|
||||
expect(gameManager.getTimeRemaining).toHaveBeenCalledWith(game, '2');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -91,16 +91,17 @@ describe('GameManager', () => {
|
||||
it('should reset all relevant game parameters, including when the game has a timer', async () => {
|
||||
game.timerParams = { hours: 2, minutes: 2, paused: false };
|
||||
game.hasTimer = true;
|
||||
timerManager.timerThreads = { ABCD: { kill: () => {} } };
|
||||
const mockTimer = { stopTimer: () => {} };
|
||||
gameManager.timers = { ABCD: mockTimer };
|
||||
game.status = STATUS.ENDED;
|
||||
|
||||
const threadKillSpy = spyOn(timerManager.timerThreads.ABCD, 'kill');
|
||||
const stopTimerSpy = spyOn(gameManager.timers.ABCD, 'stopTimer');
|
||||
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||
|
||||
await gameManager.restartGame(game, namespace);
|
||||
|
||||
expect(game.status).toEqual(STATUS.LOBBY);
|
||||
expect(threadKillSpy).toHaveBeenCalled();
|
||||
expect(stopTimerSpy).toHaveBeenCalled();
|
||||
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user