mirror of
https://github.com/AlecM33/Werewolf.git
synced 2026-02-10 04:03:33 +01:00
Refactor timer system from child processes to main thread (#216)
* Initial plan * Refactor timer system to run on main thread instead of child processes Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove obsolete GameProcess.js and simplify TimerManager to stub Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Address code review feedback: add comments and reduce duplication Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove comments, delete TimerManager, and simplify SOURCE_TIMER_EVENT Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Fix cross-instance timer communication and expired timer handling - GET_TIME_REMAINING now publishes response through Redis for cross-instance communication - SOURCE_TIMER_EVENT handles GET_TIME_REMAINING even when timer is not running locally - Returns 0 time remaining when timer has expired or timerParams is null - Updated test to expect Redis publish instead of direct method call Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Simplify GET_TIME_REMAINING flow and add ended flag - Add ended: true flag to timerParams when timer completes - Simplify TIMER_EVENT to check ended flag first, then local timer, then publish SOURCE_TIMER_EVENT - SOURCE_TIMER_EVENT only publishes GET_TIME_REMAINING if it has the timer - Remove GET_TIME_REMAINING from handleTimerCommand helper - Direct socket emission for local timers instead of Redis publish - Updated test to verify socket emission Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Refactor GET_TIME_REMAINING into handleTimerCommand switch case - Moved GET_TIME_REMAINING logic from TIMER_EVENT handler into handleTimerCommand function - Simplified TIMER_EVENT to just call handleTimerCommand for all timer operations - Maintains same flow: check ended flag, check local timer, publish SOURCE_TIMER_EVENT if needed - All tests passing Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Remove unused getTimeRemaining function from GameManager - Function is no longer called after refactoring GET_TIME_REMAINING into handleTimerCommand - All timer time remaining logic now handled in Events.js handleTimerCommand switch case - Tests still passing Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * Reset timerParams.ended flag when restarting game - Added reset of ended flag to false in restartGame function - Ensures restarted games don't have timer marked as already ended - Placed right after timer cleanup for logical grouping Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> * remuse unused constant --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com> Co-authored-by: AlecM33 <leohfx@gmail.com>
This commit is contained in:
@@ -2,6 +2,95 @@ const GameStateCurator = require('./GameStateCurator');
|
|||||||
const GameCreationRequest = require('../model/GameCreationRequest');
|
const GameCreationRequest = require('../model/GameCreationRequest');
|
||||||
const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS, PRIMITIVES } = require('../config/globals');
|
const { EVENT_IDS, STATUS, USER_TYPES, GAME_PROCESS_COMMANDS, REDIS_CHANNELS, PRIMITIVES } = require('../config/globals');
|
||||||
|
|
||||||
|
async function handleTimerCommand (timerEventSubtype, game, socketId, vars) {
|
||||||
|
switch (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:
|
||||||
|
if (game.timerParams && game.timerParams.ended) {
|
||||||
|
const socket = vars.gameManager.namespace.sockets.get(socketId);
|
||||||
|
if (socket) {
|
||||||
|
vars.gameManager.namespace.to(socket.id).emit(
|
||||||
|
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||||
|
0,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const timer = vars.gameManager.timers[game.accessCode];
|
||||||
|
if (timer) {
|
||||||
|
const socket = vars.gameManager.namespace.sockets.get(socketId);
|
||||||
|
if (socket) {
|
||||||
|
vars.gameManager.namespace.to(socket.id).emit(
|
||||||
|
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||||
|
timer.currentTimeInMillis,
|
||||||
|
game.timerParams ? game.timerParams.paused : false
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
await vars.eventManager.publisher?.publish(
|
||||||
|
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||||
|
vars.eventManager.createMessageToPublish(
|
||||||
|
game.accessCode,
|
||||||
|
EVENT_IDS.SOURCE_TIMER_EVENT,
|
||||||
|
vars.instanceId,
|
||||||
|
JSON.stringify({ socketId: socketId, timerEventSubtype: timerEventSubtype })
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const Events = [
|
const Events = [
|
||||||
{
|
{
|
||||||
id: EVENT_IDS.PLAYER_JOINED,
|
id: EVENT_IDS.PLAYER_JOINED,
|
||||||
@@ -164,7 +253,7 @@ const Events = [
|
|||||||
vars.gameManager.deal(game);
|
vars.gameManager.deal(game);
|
||||||
if (game.hasTimer) {
|
if (game.hasTimer) {
|
||||||
game.timerParams.paused = true;
|
game.timerParams.paused = true;
|
||||||
await vars.timerManager.runTimer(game, vars.gameManager.namespace, vars.eventManager, vars.gameManager);
|
await vars.gameManager.runTimer(game);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -220,9 +309,10 @@ const Events = [
|
|||||||
id: EVENT_IDS.END_GAME,
|
id: EVENT_IDS.END_GAME,
|
||||||
stateChange: async (game, socketArgs, vars) => {
|
stateChange: async (game, socketArgs, vars) => {
|
||||||
game.status = STATUS.ENDED;
|
game.status = STATUS.ENDED;
|
||||||
if (game.hasTimer && vars.timerManager.timerThreads[game.accessCode]) {
|
if (game.hasTimer && vars.gameManager.timers[game.accessCode]) {
|
||||||
vars.logger.trace('KILLING TIMER PROCESS FOR ENDED GAME ' + game.accessCode);
|
vars.logger.trace('STOPPING TIMER FOR ENDED GAME ' + game.accessCode);
|
||||||
vars.timerManager.timerThreads[game.accessCode].kill();
|
vars.gameManager.timers[game.accessCode].stopTimer();
|
||||||
|
delete vars.gameManager.timers[game.accessCode];
|
||||||
}
|
}
|
||||||
for (const person of game.people) {
|
for (const person of game.people) {
|
||||||
person.revealed = true;
|
person.revealed = true;
|
||||||
@@ -297,12 +387,10 @@ const Events = [
|
|||||||
id: EVENT_IDS.RESTART_GAME,
|
id: EVENT_IDS.RESTART_GAME,
|
||||||
stateChange: async (game, socketArgs, vars) => {
|
stateChange: async (game, socketArgs, vars) => {
|
||||||
if (vars.instanceId !== vars.senderInstanceId
|
if (vars.instanceId !== vars.senderInstanceId
|
||||||
&& vars.timerManager.timerThreads[game.accessCode]
|
&& vars.gameManager.timers[game.accessCode]
|
||||||
) {
|
) {
|
||||||
if (!vars.timerManager.timerThreads[game.accessCode].killed) {
|
vars.gameManager.timers[game.accessCode].stopTimer();
|
||||||
vars.timerManager.timerThreads[game.accessCode].kill();
|
delete vars.gameManager.timers[game.accessCode];
|
||||||
}
|
|
||||||
delete vars.timerManager.timerThreads[game.accessCode];
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
communicate: async (game, socketArgs, vars) => {
|
communicate: async (game, socketArgs, vars) => {
|
||||||
@@ -316,68 +404,35 @@ const Events = [
|
|||||||
id: EVENT_IDS.TIMER_EVENT,
|
id: EVENT_IDS.TIMER_EVENT,
|
||||||
stateChange: async (game, socketArgs, vars) => {},
|
stateChange: async (game, socketArgs, vars) => {},
|
||||||
communicate: async (game, socketArgs, vars) => {
|
communicate: async (game, socketArgs, vars) => {
|
||||||
const thread = vars.timerManager.timerThreads[game.accessCode];
|
await handleTimerCommand(vars.timerEventSubtype, game, vars.requestingSocketId, vars);
|
||||||
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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else { // we need to consult another container for the timer data
|
|
||||||
await vars.eventManager.publisher?.publish(
|
|
||||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
|
||||||
vars.eventManager.createMessageToPublish(
|
|
||||||
game.accessCode,
|
|
||||||
EVENT_IDS.SOURCE_TIMER_EVENT,
|
|
||||||
vars.instanceId,
|
|
||||||
JSON.stringify({ socketId: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
/* 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. */
|
|
||||||
id: EVENT_IDS.SOURCE_TIMER_EVENT,
|
id: EVENT_IDS.SOURCE_TIMER_EVENT,
|
||||||
stateChange: async (game, socketArgs, vars) => {},
|
stateChange: async (game, socketArgs, vars) => {},
|
||||||
communicate: async (game, socketArgs, vars) => {
|
communicate: async (game, socketArgs, vars) => {
|
||||||
const thread = vars.timerManager.timerThreads[game.accessCode];
|
if (socketArgs.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
|
||||||
if (thread) {
|
const timer = vars.gameManager.timers[game.accessCode];
|
||||||
if (!thread.killed && thread.exitCode === null) {
|
if (timer) {
|
||||||
thread.send({
|
|
||||||
command: socketArgs.timerEventSubtype,
|
|
||||||
accessCode: game.accessCode,
|
|
||||||
socketId: socketArgs.socketId,
|
|
||||||
logLevel: vars.logger.logLevel
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
await vars.eventManager.publisher.publish(
|
await vars.eventManager.publisher.publish(
|
||||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||||
vars.eventManager.createMessageToPublish(
|
vars.eventManager.createMessageToPublish(
|
||||||
game.accessCode,
|
game.accessCode,
|
||||||
socketArgs.timerEventSubtype,
|
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||||
vars.instanceId,
|
vars.instanceId,
|
||||||
JSON.stringify({
|
JSON.stringify({
|
||||||
socketId: socketArgs.socketId,
|
socketId: socketArgs.socketId,
|
||||||
timeRemaining: game.timerParams.timeRemaining,
|
timeRemaining: timer.currentTimeInMillis,
|
||||||
paused: game.timerParams.paused
|
paused: game.timerParams ? game.timerParams.paused : false
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
const timer = vars.gameManager.timers[game.accessCode];
|
||||||
|
if (timer) {
|
||||||
|
await handleTimerCommand(socketArgs.timerEventSubtype, game, socketArgs.socketId, vars);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -405,6 +460,7 @@ const Events = [
|
|||||||
stateChange: async (game, socketArgs, vars) => {
|
stateChange: async (game, socketArgs, vars) => {
|
||||||
game.timerParams.paused = false;
|
game.timerParams.paused = false;
|
||||||
game.timerParams.timeRemaining = 0;
|
game.timerParams.timeRemaining = 0;
|
||||||
|
game.timerParams.ended = true;
|
||||||
},
|
},
|
||||||
communicate: async (game, socketArgs, vars) => {
|
communicate: async (game, socketArgs, vars) => {
|
||||||
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.END_TIMER);
|
vars.gameManager.namespace.in(game.accessCode).emit(GAME_PROCESS_COMMANDS.END_TIMER);
|
||||||
|
|||||||
@@ -1,53 +0,0 @@
|
|||||||
const { GAME_PROCESS_COMMANDS, PRIMITIVES } = require('../config/globals');
|
|
||||||
const ServerTimer = require('./ServerTimer.js');
|
|
||||||
|
|
||||||
let timer;
|
|
||||||
|
|
||||||
// This is a subprocess spawned by logic in the TimerManager module.
|
|
||||||
process.on('message', (msg) => {
|
|
||||||
const logger = require('./Logger')(msg.logLevel);
|
|
||||||
switch (msg.command) {
|
|
||||||
case GAME_PROCESS_COMMANDS.START_TIMER:
|
|
||||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': START TIMER');
|
|
||||||
timer = new ServerTimer(
|
|
||||||
msg.hours,
|
|
||||||
msg.minutes,
|
|
||||||
PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS,
|
|
||||||
logger
|
|
||||||
);
|
|
||||||
timer.runTimer().then(() => {
|
|
||||||
logger.debug('Timer finished for ' + msg.accessCode);
|
|
||||||
process.send({ command: GAME_PROCESS_COMMANDS.END_TIMER });
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
|
||||||
timer.stopTimer();
|
|
||||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': PAUSE TIMER');
|
|
||||||
process.send({ command: GAME_PROCESS_COMMANDS.PAUSE_TIMER, timeRemaining: timer.currentTimeInMillis });
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
|
||||||
timer.resumeTimer().then(() => {
|
|
||||||
logger.debug('Timer finished for ' + msg.accessCode);
|
|
||||||
process.send({ command: GAME_PROCESS_COMMANDS.END_TIMER });
|
|
||||||
process.exit(0);
|
|
||||||
});
|
|
||||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': RESUME TIMER');
|
|
||||||
process.send({ command: GAME_PROCESS_COMMANDS.RESUME_TIMER, timeRemaining: timer.currentTimeInMillis });
|
|
||||||
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
|
||||||
logger.trace('CHILD PROCESS ' + msg.accessCode + ': GET TIME REMAINING');
|
|
||||||
process.send({
|
|
||||||
command: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
|
||||||
timeRemaining: timer.currentTimeInMillis,
|
|
||||||
socketId: msg.socketId
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -6,14 +6,12 @@ const fs = require('fs');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const EventManager = require('./singletons/EventManager.js');
|
const EventManager = require('./singletons/EventManager.js');
|
||||||
const GameManager = require('./singletons/GameManager.js');
|
const GameManager = require('./singletons/GameManager.js');
|
||||||
const TimerManager = require('./singletons/TimerManager.js');
|
|
||||||
const rateLimit = require('express-rate-limit').default;
|
const rateLimit = require('express-rate-limit').default;
|
||||||
|
|
||||||
const ServerBootstrapper = {
|
const ServerBootstrapper = {
|
||||||
|
|
||||||
singletons: (logger, instanceId) => {
|
singletons: (logger, instanceId) => {
|
||||||
return {
|
return {
|
||||||
timerManager: new TimerManager(logger, instanceId),
|
|
||||||
eventManager: new EventManager(logger, instanceId),
|
eventManager: new EventManager(logger, instanceId),
|
||||||
gameManager: process.env.NODE_ENV.trim() === 'development'
|
gameManager: process.env.NODE_ENV.trim() === 'development'
|
||||||
? new GameManager(logger, ENVIRONMENTS.LOCAL, instanceId)
|
? new GameManager(logger, ENVIRONMENTS.LOCAL, instanceId)
|
||||||
@@ -22,12 +20,9 @@ const ServerBootstrapper = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
injectDependencies: (singletons) => {
|
injectDependencies: (singletons) => {
|
||||||
const timerManager = require('./singletons/TimerManager').instance;
|
|
||||||
const gameManager = require('./singletons/GameManager').instance;
|
const gameManager = require('./singletons/GameManager').instance;
|
||||||
const eventManager = require('./singletons/EventManager').instance;
|
const eventManager = require('./singletons/EventManager').instance;
|
||||||
singletons.gameManager.timerManager = timerManager;
|
|
||||||
singletons.gameManager.eventManager = eventManager;
|
singletons.gameManager.eventManager = eventManager;
|
||||||
singletons.eventManager.timerManager = timerManager;
|
|
||||||
singletons.eventManager.gameManager = gameManager;
|
singletons.eventManager.gameManager = gameManager;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ class EventManager {
|
|||||||
this.io = null;
|
this.io = null;
|
||||||
this.publisher = null;
|
this.publisher = null;
|
||||||
this.subscriber = null;
|
this.subscriber = null;
|
||||||
this.timerManager = null;
|
|
||||||
this.gameManager = null;
|
this.gameManager = null;
|
||||||
this.instanceId = instanceId;
|
this.instanceId = instanceId;
|
||||||
EventManager.instance = this;
|
EventManager.instance = this;
|
||||||
@@ -185,7 +184,6 @@ class EventManager {
|
|||||||
const event = Events.find((event) => event.id === eventId);
|
const event = Events.find((event) => event.id === eventId);
|
||||||
const additionalVars = {
|
const additionalVars = {
|
||||||
gameManager: this.gameManager,
|
gameManager: this.gameManager,
|
||||||
timerManager: this.timerManager,
|
|
||||||
eventManager: this,
|
eventManager: this,
|
||||||
requestingSocketId: requestingSocketId,
|
requestingSocketId: requestingSocketId,
|
||||||
ackFn: ackFn,
|
ackFn: ackFn,
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ const {
|
|||||||
STATUS,
|
STATUS,
|
||||||
PRIMITIVES,
|
PRIMITIVES,
|
||||||
ERROR_MESSAGES,
|
ERROR_MESSAGES,
|
||||||
GAME_PROCESS_COMMANDS,
|
|
||||||
USER_TYPES,
|
USER_TYPES,
|
||||||
EVENT_IDS,
|
EVENT_IDS,
|
||||||
REDIS_CHANNELS
|
REDIS_CHANNELS
|
||||||
@@ -12,6 +11,7 @@ const Person = require('../../model/Person');
|
|||||||
const GameStateCurator = require('../GameStateCurator');
|
const GameStateCurator = require('../GameStateCurator');
|
||||||
const UsernameGenerator = require('../UsernameGenerator');
|
const UsernameGenerator = require('../UsernameGenerator');
|
||||||
const GameCreationRequest = require('../../model/GameCreationRequest');
|
const GameCreationRequest = require('../../model/GameCreationRequest');
|
||||||
|
const ServerTimer = require('../ServerTimer');
|
||||||
|
|
||||||
class GameManager {
|
class GameManager {
|
||||||
constructor (logger, environment, instanceId) {
|
constructor (logger, environment, instanceId) {
|
||||||
@@ -21,20 +21,18 @@ class GameManager {
|
|||||||
logger.info('CREATING SINGLETON GAME MANAGER');
|
logger.info('CREATING SINGLETON GAME MANAGER');
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
this.environment = environment;
|
this.environment = environment;
|
||||||
this.timerManager = null;
|
|
||||||
this.eventManager = null;
|
this.eventManager = null;
|
||||||
this.namespace = null;
|
this.namespace = null;
|
||||||
this.instanceId = instanceId;
|
this.instanceId = instanceId;
|
||||||
|
this.timers = {};
|
||||||
GameManager.instance = this;
|
GameManager.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
getActiveGame = async (accessCode) => {
|
getActiveGame = async (accessCode) => {
|
||||||
const r = await this.eventManager.publisher.get(accessCode);
|
const r = await this.eventManager.publisher.get(accessCode);
|
||||||
if (r === null && this.timerManager.timerThreads[accessCode]) {
|
if (r === null && this.timers[accessCode]) {
|
||||||
if (!this.timerManager.timerThreads[accessCode].killed) {
|
this.timers[accessCode].stopTimer();
|
||||||
this.timerManager.timerThreads[accessCode].kill();
|
delete this.timers[accessCode];
|
||||||
}
|
|
||||||
delete this.timerManager.timerThreads[accessCode];
|
|
||||||
}
|
}
|
||||||
let activeGame;
|
let activeGame;
|
||||||
if (r !== null) {
|
if (r !== null) {
|
||||||
@@ -108,46 +106,64 @@ class GameManager {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
pauseTimer = async (game, logger) => {
|
runTimer = async (game) => {
|
||||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
this.logger.debug('running timer for game ' + game.accessCode);
|
||||||
if (thread && !thread.killed) {
|
const timer = new ServerTimer(
|
||||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
game.timerParams.hours,
|
||||||
thread.send({
|
game.timerParams.minutes,
|
||||||
command: GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS,
|
||||||
accessCode: game.accessCode,
|
this.logger
|
||||||
logLevel: this.logger.logLevel
|
);
|
||||||
});
|
this.timers[game.accessCode] = timer;
|
||||||
}
|
|
||||||
};
|
timer.runTimer(true).then(async () => {
|
||||||
|
this.logger.debug('Timer finished for ' + game.accessCode);
|
||||||
resumeTimer = async (game, logger) => {
|
game = await this.getActiveGame(game.accessCode);
|
||||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
if (game) {
|
||||||
if (thread && !thread.killed) {
|
await this.eventManager.handleEventById(
|
||||||
this.logger.debug('Timer thread found for game ' + game.accessCode);
|
EVENT_IDS.END_TIMER,
|
||||||
thread.send({
|
null,
|
||||||
command: GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
game,
|
||||||
accessCode: game.accessCode,
|
null,
|
||||||
logLevel: this.logger.logLevel
|
game.accessCode,
|
||||||
});
|
{},
|
||||||
}
|
null,
|
||||||
};
|
false
|
||||||
|
);
|
||||||
getTimeRemaining = async (game, socketId) => {
|
await this.refreshGame(game);
|
||||||
if (socketId) {
|
await this.eventManager.publisher.publish(
|
||||||
const thread = this.timerManager.timerThreads[game.accessCode];
|
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
this.eventManager.createMessageToPublish(
|
||||||
thread.send({
|
game.accessCode,
|
||||||
command: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
EVENT_IDS.END_TIMER,
|
||||||
accessCode: game.accessCode,
|
this.instanceId,
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
delete this.timers[game.accessCode];
|
||||||
|
});
|
||||||
|
game.startTime = new Date().toJSON();
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
checkAvailability = async (code) => {
|
checkAvailability = async (code) => {
|
||||||
@@ -248,15 +264,15 @@ class GameManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
restartGame = async (game, namespace) => {
|
restartGame = async (game, namespace) => {
|
||||||
// kill any outstanding timer threads
|
const timer = this.timers[game.accessCode];
|
||||||
const subProcess = this.timerManager.timerThreads[game.accessCode];
|
if (timer) {
|
||||||
if (subProcess) {
|
this.logger.info('Stopping timer for: ' + game.accessCode);
|
||||||
if (!subProcess.killed) {
|
timer.stopTimer();
|
||||||
this.logger.info('Killing timer process ' + subProcess.pid + ' for: ' + game.accessCode);
|
delete this.timers[game.accessCode];
|
||||||
this.timerManager.timerThreads[game.accessCode].kill();
|
}
|
||||||
}
|
|
||||||
this.logger.debug('Deleting reference to subprocess ' + subProcess.pid);
|
if (game.timerParams) {
|
||||||
delete this.timerManager.timerThreads[game.accessCode];
|
game.timerParams.ended = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < game.people.length; i ++) {
|
for (let i = 0; i < game.people.length; i ++) {
|
||||||
|
|||||||
@@ -1,47 +0,0 @@
|
|||||||
const { fork } = require('child_process');
|
|
||||||
const path = require('path');
|
|
||||||
const { REDIS_CHANNELS, GAME_PROCESS_COMMANDS } = require('../../config/globals');
|
|
||||||
|
|
||||||
class TimerManager {
|
|
||||||
constructor (logger, instanceId) {
|
|
||||||
if (TimerManager.instance) {
|
|
||||||
throw new Error('The server tried to instantiate more than one TimerManager');
|
|
||||||
}
|
|
||||||
logger.info('CREATING SINGLETON TIMER MANAGER');
|
|
||||||
this.timerThreads = {};
|
|
||||||
this.logger = logger;
|
|
||||||
this.subscriber = null;
|
|
||||||
this.instanceId = instanceId;
|
|
||||||
TimerManager.instance = this;
|
|
||||||
}
|
|
||||||
|
|
||||||
runTimer = async (game, namespace, eventManager, gameManager) => {
|
|
||||||
this.logger.debug('running timer for game ' + game.accessCode);
|
|
||||||
const gameProcess = fork(path.join(__dirname, '../GameProcess.js'));
|
|
||||||
this.timerThreads[game.accessCode] = gameProcess;
|
|
||||||
this.logger.debug('game ' + game.accessCode + ' now associated with subProcess ' + gameProcess.pid);
|
|
||||||
gameProcess.on('message', async (msg) => {
|
|
||||||
game = await gameManager.getActiveGame(game.accessCode);
|
|
||||||
await eventManager.handleEventById(msg.command, null, game, msg.socketId, game.accessCode, msg, null, false);
|
|
||||||
await gameManager.refreshGame(game);
|
|
||||||
await eventManager.publisher.publish(
|
|
||||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
|
||||||
eventManager.createMessageToPublish(game.accessCode, msg.command, this.instanceId, JSON.stringify(msg))
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
gameProcess.on('exit', (code, signal) => {
|
|
||||||
this.logger.debug('Game timer thread ' + gameProcess.pid + ' exiting with code ' + code + ' - game ' + game.accessCode);
|
|
||||||
});
|
|
||||||
gameProcess.send({
|
|
||||||
command: GAME_PROCESS_COMMANDS.START_TIMER,
|
|
||||||
accessCode: game.accessCode,
|
|
||||||
logLevel: this.logger.logLevel,
|
|
||||||
hours: game.timerParams.hours,
|
|
||||||
minutes: game.timerParams.minutes
|
|
||||||
});
|
|
||||||
game.startTime = new Date().toJSON();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = TimerManager;
|
|
||||||
@@ -1,15 +1,14 @@
|
|||||||
// TODO: clean up these deep relative paths? jsconfig.json is not working...
|
// TODO: clean up these deep relative paths? jsconfig.json is not working...
|
||||||
const Game = require('../../../../server/model/Game');
|
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 GameManager = require('../../../../server/modules/singletons/GameManager.js');
|
||||||
const TimerManager = require('../../../../server/modules/singletons/TimerManager.js');
|
|
||||||
const EventManager = require('../../../../server/modules/singletons/EventManager.js');
|
const EventManager = require('../../../../server/modules/singletons/EventManager.js');
|
||||||
const Events = require('../../../../server/modules/Events.js');
|
const Events = require('../../../../server/modules/Events.js');
|
||||||
const GameStateCurator = require('../../../../server/modules/GameStateCurator.js');
|
const GameStateCurator = require('../../../../server/modules/GameStateCurator.js');
|
||||||
const logger = require('../../../../server/modules/Logger.js')(false);
|
const logger = require('../../../../server/modules/Logger.js')(false);
|
||||||
|
|
||||||
describe('Events', () => {
|
describe('Events', () => {
|
||||||
let gameManager, namespace, socket, game, timerManager, eventManager;
|
let gameManager, namespace, socket, game, eventManager;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
spyOn(logger, 'debug');
|
spyOn(logger, 'debug');
|
||||||
@@ -19,7 +18,6 @@ describe('Events', () => {
|
|||||||
namespace = { in: () => { return inObj; }, to: () => { return toObj; }, sockets: new Map() };
|
namespace = { in: () => { return inObj; }, to: () => { return toObj; }, sockets: new Map() };
|
||||||
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
||||||
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, ENVIRONMENTS.PRODUCTION, 'test');
|
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, ENVIRONMENTS.PRODUCTION, 'test');
|
||||||
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
|
|
||||||
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
||||||
gameManager.setGameSocketNamespace(namespace);
|
gameManager.setGameSocketNamespace(namespace);
|
||||||
eventManager.publisher = { publish: (...args) => {} };
|
eventManager.publisher = { publish: (...args) => {} };
|
||||||
@@ -55,7 +53,7 @@ describe('Events', () => {
|
|||||||
spyOn(eventManager.publisher, 'publish').and.callThrough();
|
spyOn(eventManager.publisher, 'publish').and.callThrough();
|
||||||
spyOn(eventManager, 'createMessageToPublish').and.stub();
|
spyOn(eventManager, 'createMessageToPublish').and.stub();
|
||||||
namespace.sockets = new Map();
|
namespace.sockets = new Map();
|
||||||
timerManager.timerThreads = {};
|
gameManager.timers = {};
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(EVENT_IDS.PLAYER_JOINED, () => {
|
describe(EVENT_IDS.PLAYER_JOINED, () => {
|
||||||
@@ -272,12 +270,12 @@ describe('Events', () => {
|
|||||||
game.isStartable = true;
|
game.isStartable = true;
|
||||||
game.hasTimer = true;
|
game.hasTimer = true;
|
||||||
game.timerParams = {};
|
game.timerParams = {};
|
||||||
spyOn(timerManager, 'runTimer').and.callFake((a, b) => {});
|
spyOn(gameManager, 'runTimer').and.callFake(() => {});
|
||||||
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
|
await Events.find((e) => e.id === EVENT_IDS.START_GAME)
|
||||||
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager });
|
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager });
|
||||||
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
expect(game.status).toEqual(STATUS.IN_PROGRESS);
|
||||||
expect(game.timerParams.paused).toEqual(true);
|
expect(game.timerParams.paused).toEqual(true);
|
||||||
expect(timerManager.runTimer).toHaveBeenCalled();
|
expect(gameManager.runTimer).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('communicate', () => {
|
describe('communicate', () => {
|
||||||
@@ -363,13 +361,15 @@ describe('Events', () => {
|
|||||||
});
|
});
|
||||||
it('should end the game and kill the associated timer thread', async () => {
|
it('should end the game and kill the associated timer thread', async () => {
|
||||||
game.hasTimer = true;
|
game.hasTimer = true;
|
||||||
timerManager.timerThreads = { ABCD: { kill: () => {} } };
|
const mockTimer = { stopTimer: () => {} };
|
||||||
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
|
const stopTimerSpy = spyOn(mockTimer, 'stopTimer').and.callThrough();
|
||||||
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
|
await Events.find((e) => e.id === EVENT_IDS.END_GAME)
|
||||||
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, timerManager: timerManager, logger: { trace: () => {} } });
|
.stateChange(game, { id: 'b', assigned: true }, { gameManager: gameManager, logger: { trace: () => {} } });
|
||||||
expect(game.status).toEqual(STATUS.ENDED);
|
expect(game.status).toEqual(STATUS.ENDED);
|
||||||
expect(game.people.find(p => p.id === 'b').revealed).toBeTrue();
|
expect(game.people.find(p => p.id === 'b').revealed).toBeTrue();
|
||||||
expect(timerManager.timerThreads.ABCD.kill).toHaveBeenCalled();
|
expect(stopTimerSpy).toHaveBeenCalled();
|
||||||
|
expect(gameManager.timers.ABCD).toBeUndefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('communicate', () => {
|
describe('communicate', () => {
|
||||||
@@ -530,22 +530,22 @@ describe('Events', () => {
|
|||||||
describe(EVENT_IDS.RESTART_GAME, () => {
|
describe(EVENT_IDS.RESTART_GAME, () => {
|
||||||
describe('stateChange', () => {
|
describe('stateChange', () => {
|
||||||
it('should kill any alive timer thread if the instance is home to it', async () => {
|
it('should kill any alive timer thread if the instance is home to it', async () => {
|
||||||
const mockThread = { kill: () => {}, killed: false };
|
const mockTimer = { stopTimer: () => {} };
|
||||||
timerManager.timerThreads = { ABCD: mockThread };
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
|
spyOn(gameManager.timers.ABCD, 'stopTimer').and.callThrough();
|
||||||
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
||||||
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '222' });
|
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, instanceId: '111', senderInstanceId: '222' });
|
||||||
expect(mockThread.kill).toHaveBeenCalled();
|
expect(mockTimer.stopTimer).toHaveBeenCalled();
|
||||||
expect(Object.keys(timerManager.timerThreads).length).toEqual(0);
|
expect(Object.keys(gameManager.timers).length).toEqual(0);
|
||||||
});
|
});
|
||||||
it('should not kill the timer thread if the instance sent the event', async () => {
|
it('should not kill the timer thread if the instance sent the event', async () => {
|
||||||
const mockThread = { kill: () => {}, killed: false };
|
const mockTimer = { stopTimer: () => {} };
|
||||||
timerManager.timerThreads = { ABCD: mockThread };
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
spyOn(timerManager.timerThreads.ABCD, 'kill').and.callThrough();
|
spyOn(gameManager.timers.ABCD, 'stopTimer').and.callThrough();
|
||||||
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
await Events.find((e) => e.id === EVENT_IDS.RESTART_GAME)
|
||||||
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, timerManager: timerManager, instanceId: '111', senderInstanceId: '111' });
|
.stateChange(game, { personId: 'b' }, { gameManager: gameManager, instanceId: '111', senderInstanceId: '111' });
|
||||||
expect(mockThread.kill).not.toHaveBeenCalled();
|
expect(mockTimer.stopTimer).not.toHaveBeenCalled();
|
||||||
expect(Object.keys(timerManager.timerThreads).length).toEqual(1);
|
expect(Object.keys(gameManager.timers).length).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('communicate', () => {
|
describe('communicate', () => {
|
||||||
@@ -570,29 +570,31 @@ describe('Events', () => {
|
|||||||
describe(EVENT_IDS.TIMER_EVENT, () => {
|
describe(EVENT_IDS.TIMER_EVENT, () => {
|
||||||
describe('communicate', () => {
|
describe('communicate', () => {
|
||||||
it('should publish an event to source timer data if the timer thread is not found', async () => {
|
it('should publish an event to source timer data if the timer thread is not found', async () => {
|
||||||
|
game.timerParams = { hours: 1, minutes: 0, paused: true, timeRemaining: 3600000 };
|
||||||
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
||||||
.communicate(game, {}, { gameManager: gameManager, timerManager: timerManager, eventManager: eventManager });
|
.communicate(game, {}, {
|
||||||
|
gameManager: gameManager,
|
||||||
|
eventManager: eventManager,
|
||||||
|
instanceId: 'test',
|
||||||
|
timerEventSubtype: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||||
|
requestingSocketId: '2'
|
||||||
|
});
|
||||||
expect(eventManager.publisher.publish).toHaveBeenCalled();
|
expect(eventManager.publisher.publish).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it('should send a message to the thread if it is found', async () => {
|
it('should send a message to the thread if it is found', async () => {
|
||||||
const mockThread = { exitCode: null, kill: () => {}, send: (...args) => {}, killed: false };
|
const mockTimer = { currentTimeInMillis: 5000 };
|
||||||
timerManager.timerThreads = { ABCD: mockThread };
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
spyOn(timerManager.timerThreads.ABCD, 'send').and.callThrough();
|
namespace.sockets.set('2', { id: '2' });
|
||||||
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
await Events.find((e) => e.id === EVENT_IDS.TIMER_EVENT)
|
||||||
.communicate(game, {}, {
|
.communicate(game, {}, {
|
||||||
gameManager: gameManager,
|
gameManager: gameManager,
|
||||||
timerManager: timerManager,
|
|
||||||
eventManager: eventManager,
|
eventManager: eventManager,
|
||||||
timerEventSubtype: EVENT_IDS.GET_TIME_REMAINING,
|
timerEventSubtype: GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||||
requestingSocketId: '2',
|
requestingSocketId: '2',
|
||||||
logger: { logLevel: 'trace' }
|
logger: { logLevel: 'trace' },
|
||||||
|
instanceId: 'test'
|
||||||
});
|
});
|
||||||
expect(mockThread.send).toHaveBeenCalledWith({
|
expect(namespace.to).toHaveBeenCalledWith('2');
|
||||||
command: EVENT_IDS.GET_TIME_REMAINING,
|
|
||||||
accessCode: 'ABCD',
|
|
||||||
socketId: '2',
|
|
||||||
logLevel: 'trace'
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,12 +4,11 @@ const globals = require('../../../../../server/config/globals');
|
|||||||
const USER_TYPES = globals.USER_TYPES;
|
const USER_TYPES = globals.USER_TYPES;
|
||||||
const STATUS = globals.STATUS;
|
const STATUS = globals.STATUS;
|
||||||
const GameManager = require('../../../../../server/modules/singletons/GameManager.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');
|
const EventManager = require('../../../../../server/modules/singletons/EventManager.js');
|
||||||
const logger = require('../../../../../server/modules/Logger.js')(false);
|
const logger = require('../../../../../server/modules/Logger.js')(false);
|
||||||
|
|
||||||
describe('GameManager', () => {
|
describe('GameManager', () => {
|
||||||
let gameManager, timerManager, eventManager, namespace, socket, game;
|
let gameManager, eventManager, namespace, socket, game;
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
spyOn(logger, 'debug');
|
spyOn(logger, 'debug');
|
||||||
@@ -19,11 +18,9 @@ describe('GameManager', () => {
|
|||||||
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
|
namespace = { in: () => { return inObj; }, to: () => { return inObj; } };
|
||||||
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
socket = { id: '123', emit: () => {}, to: () => { return { emit: () => {} }; } };
|
||||||
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENTS.PRODUCTION, 'test');
|
gameManager = GameManager.instance ? GameManager.instance : new GameManager(logger, globals.ENVIRONMENTS.PRODUCTION, 'test');
|
||||||
timerManager = TimerManager.instance ? TimerManager.instance : new TimerManager(logger, 'test');
|
|
||||||
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
eventManager = EventManager.instance ? EventManager.instance : new EventManager(logger, 'test');
|
||||||
eventManager.publisher = { publish: async (...a) => {} };
|
eventManager.publisher = { publish: async (...a) => {} };
|
||||||
gameManager.eventManager = eventManager;
|
gameManager.eventManager = eventManager;
|
||||||
gameManager.timerManager = timerManager;
|
|
||||||
gameManager.setGameSocketNamespace(namespace);
|
gameManager.setGameSocketNamespace(namespace);
|
||||||
spyOn(gameManager, 'refreshGame').and.callFake(async () => {});
|
spyOn(gameManager, 'refreshGame').and.callFake(async () => {});
|
||||||
spyOn(eventManager.publisher, 'publish').and.callFake(async () => {});
|
spyOn(eventManager.publisher, 'publish').and.callFake(async () => {});
|
||||||
@@ -32,7 +29,7 @@ describe('GameManager', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
spyOn(namespace, 'to').and.callThrough();
|
spyOn(namespace, 'to').and.callThrough();
|
||||||
spyOn(socket, 'to').and.callThrough();
|
spyOn(socket, 'to').and.callThrough();
|
||||||
timerManager.timerThreads = {};
|
gameManager.timers = {};
|
||||||
game = new Game(
|
game = new Game(
|
||||||
'ABCD',
|
'ABCD',
|
||||||
STATUS.LOBBY,
|
STATUS.LOBBY,
|
||||||
@@ -91,16 +88,17 @@ describe('GameManager', () => {
|
|||||||
it('should reset all relevant game parameters, including when the game has a timer', async () => {
|
it('should reset all relevant game parameters, including when the game has a timer', async () => {
|
||||||
game.timerParams = { hours: 2, minutes: 2, paused: false };
|
game.timerParams = { hours: 2, minutes: 2, paused: false };
|
||||||
game.hasTimer = true;
|
game.hasTimer = true;
|
||||||
timerManager.timerThreads = { ABCD: { kill: () => {} } };
|
const mockTimer = { stopTimer: () => {} };
|
||||||
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
game.status = STATUS.ENDED;
|
game.status = STATUS.ENDED;
|
||||||
|
|
||||||
const threadKillSpy = spyOn(timerManager.timerThreads.ABCD, 'kill');
|
const stopTimerSpy = spyOn(gameManager.timers.ABCD, 'stopTimer');
|
||||||
const emitSpy = spyOn(namespace.in(), 'emit');
|
const emitSpy = spyOn(namespace.in(), 'emit');
|
||||||
|
|
||||||
await gameManager.restartGame(game, namespace);
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
expect(game.status).toEqual(STATUS.LOBBY);
|
expect(game.status).toEqual(STATUS.LOBBY);
|
||||||
expect(threadKillSpy).toHaveBeenCalled();
|
expect(stopTimerSpy).toHaveBeenCalled();
|
||||||
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
expect(emitSpy).toHaveBeenCalledWith(globals.EVENT_IDS.RESTART_GAME);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -111,6 +109,9 @@ describe('GameManager', () => {
|
|||||||
game.moderator = game.people[0];
|
game.moderator = game.people[0];
|
||||||
game.people.find(p => p.id === 'b').userType = USER_TYPES.MODERATOR;
|
game.people.find(p => p.id === 'b').userType = USER_TYPES.MODERATOR;
|
||||||
game.hasDedicatedModerator = false;
|
game.hasDedicatedModerator = false;
|
||||||
|
// Add a mock timer
|
||||||
|
const mockTimer = { stopTimer: () => {} };
|
||||||
|
gameManager.timers = { ABCD: mockTimer };
|
||||||
|
|
||||||
await gameManager.restartGame(game, namespace);
|
await gameManager.restartGame(game, namespace);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user