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
Co-authored-by: AlecM33 <24642328+AlecM33@users.noreply.github.com>
This commit is contained in:
@@ -297,12 +297,11 @@ const Events = [
|
||||
id: EVENT_IDS.RESTART_GAME,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
if (vars.instanceId !== vars.senderInstanceId
|
||||
&& vars.timerManager.timerThreads[game.accessCode]
|
||||
&& vars.timerManager.timers[game.accessCode]
|
||||
) {
|
||||
if (!vars.timerManager.timerThreads[game.accessCode].killed) {
|
||||
vars.timerManager.timerThreads[game.accessCode].kill();
|
||||
}
|
||||
delete vars.timerManager.timerThreads[game.accessCode];
|
||||
const timer = vars.timerManager.timers[game.accessCode];
|
||||
timer.stopTimer();
|
||||
delete vars.timerManager.timers[game.accessCode];
|
||||
}
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
@@ -316,16 +315,62 @@ 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 timer = vars.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
// Handle timer commands directly
|
||||
switch (vars.timerEventSubtype) {
|
||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
timer.stopTimer();
|
||||
game.timerParams.paused = true;
|
||||
game.timerParams.timeRemaining = timer.currentTimeInMillis;
|
||||
await vars.gameManager.refreshGame(game);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
timer.currentTimeInMillis
|
||||
);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: timer.currentTimeInMillis })
|
||||
)
|
||||
);
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
timer.resumeTimer();
|
||||
game.timerParams.paused = false;
|
||||
game.timerParams.timeRemaining = timer.currentTimeInMillis;
|
||||
await vars.gameManager.refreshGame(game);
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
timer.currentTimeInMillis
|
||||
);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: timer.currentTimeInMillis })
|
||||
)
|
||||
);
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
timer.currentTimeInMillis,
|
||||
game.timerParams.paused
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Timer not on this instance, consult another container
|
||||
if (vars.timerEventSubtype === GAME_PROCESS_COMMANDS.GET_TIME_REMAINING) {
|
||||
const socket = vars.gameManager.namespace.sockets.get(vars.requestingSocketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id).emit(
|
||||
@@ -334,50 +379,90 @@ const Events = [
|
||||
game.timerParams.paused
|
||||
);
|
||||
}
|
||||
} 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: vars.requestingSocketId, timerEventSubtype: vars.timerEventSubtype })
|
||||
)
|
||||
);
|
||||
}
|
||||
} 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. */
|
||||
* 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.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
// Handle the timer command on this instance
|
||||
switch (socketArgs.timerEventSubtype) {
|
||||
case GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
timer.stopTimer();
|
||||
game.timerParams.paused = true;
|
||||
game.timerParams.timeRemaining = timer.currentTimeInMillis;
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
GAME_PROCESS_COMMANDS.PAUSE_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: timer.currentTimeInMillis })
|
||||
)
|
||||
);
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
timer.resumeTimer();
|
||||
game.timerParams.paused = false;
|
||||
game.timerParams.timeRemaining = timer.currentTimeInMillis;
|
||||
await vars.gameManager.refreshGame(game);
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
GAME_PROCESS_COMMANDS.RESUME_TIMER,
|
||||
vars.instanceId,
|
||||
JSON.stringify({ timeRemaining: timer.currentTimeInMillis })
|
||||
)
|
||||
);
|
||||
break;
|
||||
case GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
await vars.eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
vars.eventManager.createMessageToPublish(
|
||||
game.accessCode,
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
vars.instanceId,
|
||||
JSON.stringify({
|
||||
socketId: socketArgs.socketId,
|
||||
timeRemaining: timer.currentTimeInMillis
|
||||
})
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Timer not on this instance either, send back stored value
|
||||
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
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -30,11 +30,11 @@ class GameManager {
|
||||
|
||||
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.timerManager.timers[accessCode]) {
|
||||
// Clean up timer reference if game no longer exists
|
||||
const timer = this.timerManager.timers[accessCode];
|
||||
timer.stopTimer();
|
||||
delete this.timerManager.timers[accessCode];
|
||||
}
|
||||
let activeGame;
|
||||
if (r !== null) {
|
||||
@@ -109,43 +109,36 @@ 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
|
||||
});
|
||||
const timer = this.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.debug('Timer found for game ' + game.accessCode);
|
||||
timer.stopTimer();
|
||||
}
|
||||
};
|
||||
|
||||
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
|
||||
});
|
||||
const timer = this.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.debug('Timer found for game ' + game.accessCode);
|
||||
timer.resumeTimer();
|
||||
}
|
||||
};
|
||||
|
||||
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.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.namespace.to(socketId).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
timer.currentTimeInMillis,
|
||||
game.timerParams.paused
|
||||
);
|
||||
} else if (game.timerParams && game.timerParams.timeRemaining === 0) {
|
||||
this.namespace.to(socketId).emit(
|
||||
GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
game.timerParams.timeRemaining,
|
||||
game.timerParams.paused
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -248,15 +241,13 @@ 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];
|
||||
// kill any outstanding timers
|
||||
const timer = this.timerManager.timers[game.accessCode];
|
||||
if (timer) {
|
||||
this.logger.info('Stopping timer for: ' + game.accessCode);
|
||||
timer.stopTimer();
|
||||
this.logger.debug('Deleting reference to timer');
|
||||
delete this.timerManager.timers[game.accessCode];
|
||||
}
|
||||
|
||||
for (let i = 0; i < game.people.length; i ++) {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const { fork } = require('child_process');
|
||||
const path = require('path');
|
||||
const { REDIS_CHANNELS, GAME_PROCESS_COMMANDS } = require('../../config/globals');
|
||||
const ServerTimer = require('../ServerTimer');
|
||||
const { REDIS_CHANNELS, GAME_PROCESS_COMMANDS, PRIMITIVES } = require('../../config/globals');
|
||||
|
||||
class TimerManager {
|
||||
constructor (logger, instanceId) {
|
||||
@@ -8,7 +7,7 @@ class TimerManager {
|
||||
throw new Error('The server tried to instantiate more than one TimerManager');
|
||||
}
|
||||
logger.info('CREATING SINGLETON TIMER MANAGER');
|
||||
this.timerThreads = {};
|
||||
this.timers = {};
|
||||
this.logger = logger;
|
||||
this.subscriber = null;
|
||||
this.instanceId = instanceId;
|
||||
@@ -17,29 +16,52 @@ class TimerManager {
|
||||
|
||||
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
|
||||
|
||||
// Create timer instance directly on main thread
|
||||
const timer = new ServerTimer(
|
||||
game.timerParams.hours,
|
||||
game.timerParams.minutes,
|
||||
PRIMITIVES.CLOCK_TICK_INTERVAL_MILLIS,
|
||||
this.logger
|
||||
);
|
||||
|
||||
this.timers[game.accessCode] = timer;
|
||||
this.logger.debug('game ' + game.accessCode + ' now has timer instance');
|
||||
|
||||
// Start the timer (paused initially as per original logic)
|
||||
timer.runTimer(true).then(async () => {
|
||||
this.logger.debug('Timer finished for ' + game.accessCode);
|
||||
|
||||
// Get fresh game state
|
||||
const currentGame = await gameManager.getActiveGame(game.accessCode);
|
||||
if (currentGame) {
|
||||
// Handle END_TIMER event
|
||||
await eventManager.handleEventById(
|
||||
GAME_PROCESS_COMMANDS.END_TIMER,
|
||||
null,
|
||||
currentGame,
|
||||
null,
|
||||
currentGame.accessCode,
|
||||
{},
|
||||
null,
|
||||
false
|
||||
);
|
||||
await gameManager.refreshGame(currentGame);
|
||||
await eventManager.publisher.publish(
|
||||
REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
eventManager.createMessageToPublish(
|
||||
currentGame.accessCode,
|
||||
GAME_PROCESS_COMMANDS.END_TIMER,
|
||||
this.instanceId,
|
||||
'{}'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Clean up timer reference
|
||||
delete this.timers[game.accessCode];
|
||||
});
|
||||
|
||||
game.startTime = new Date().toJSON();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user