mirror of
https://github.com/AlecM33/Werewolf.git
synced 2025-12-29 01:07:50 +01:00
part 4 of redis effort
This commit is contained in:
@@ -54,6 +54,7 @@ export const globals = {
|
||||
UPDATE_SPECTATORS: 'updateSpectators',
|
||||
RESTART_GAME: 'restartGame',
|
||||
ASSIGN_DEDICATED_MOD: 'assignDedicatedMod'
|
||||
|
||||
},
|
||||
USER_TYPES: {
|
||||
MODERATOR: 'moderator',
|
||||
|
||||
@@ -52,7 +52,22 @@ export const HTMLFragments = {
|
||||
`<div id='game-header'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
<div id='game-timer'>
|
||||
<div class="lds-spinner lds-spinner-clock">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id='role-info-button' class='app-button'>Roles in This Game <img src='/images/info.svg'/></button>
|
||||
@@ -76,7 +91,22 @@ export const HTMLFragments = {
|
||||
`<div id='game-header'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
<div id='game-timer'>
|
||||
<div class="lds-spinner lds-spinner-clock">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button id='role-info-button' class='app-button'>Roles in This Game <img src='/images/info.svg'/></button>
|
||||
@@ -101,7 +131,22 @@ export const HTMLFragments = {
|
||||
<div id='timer-container-moderator'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
<div id='game-timer'>
|
||||
<div class="lds-spinner lds-spinner-clock">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='play-pause'>
|
||||
<div id="play-pause-placeholder"></div>
|
||||
@@ -131,7 +176,22 @@ export const HTMLFragments = {
|
||||
<div id='timer-container-moderator'>
|
||||
<div>
|
||||
<label for='game-timer'>Time Remaining</label>
|
||||
<div id='game-timer'></div>
|
||||
<div id='game-timer'>
|
||||
<div class="lds-spinner lds-spinner-clock">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id='play-pause'> </div>
|
||||
</div>
|
||||
@@ -180,7 +240,7 @@ export const HTMLFragments = {
|
||||
<div id='game-state-container'></div>`,
|
||||
// via https://loading.io/css/
|
||||
SPINNER:
|
||||
`<div class="lds-spinner">
|
||||
`<div class="lds-spinner lds-spinner-clock">
|
||||
<div></div>
|
||||
<div></div>
|
||||
<div></div>
|
||||
|
||||
@@ -51,6 +51,12 @@ export class InProgress {
|
||||
globals.EVENT_IDS.GET_TIME_REMAINING,
|
||||
this.stateBucket.currentGameState.accessCode
|
||||
);
|
||||
setTimeout(() => {
|
||||
if (this.socket.hasListeners(globals.EVENT_IDS.GET_TIME_REMAINING)) {
|
||||
document.getElementById('game-timer').innerText = 'could not retrieve';
|
||||
document.getElementById('game-timer').classList.add('timer-error');
|
||||
}
|
||||
}, 15000);
|
||||
} else {
|
||||
document.querySelector('#game-timer')?.remove();
|
||||
document.querySelector('#timer-container-moderator')?.remove();
|
||||
|
||||
@@ -118,20 +118,21 @@ export const SharedStateUtil = {
|
||||
const accessCode = splitUrl[1];
|
||||
if (/^[a-zA-Z0-9]+$/.test(accessCode) && accessCode.length === globals.ACCESS_CODE_LENGTH) {
|
||||
socket.emit(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, globals.EVENT_IDS.FETCH_GAME_STATE, accessCode, { personId: cookie }, function (gameState) {
|
||||
if (gameState === null) {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
|
||||
} else {
|
||||
stateBucket.currentGameState = gameState;
|
||||
document.querySelector('.spinner-container')?.remove();
|
||||
document.querySelector('.spinner-background')?.remove();
|
||||
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
|
||||
toast('You are connected.', 'success', true, true, 'short');
|
||||
processGameState(stateBucket.currentGameState, cookie, socket, true, true);
|
||||
}
|
||||
// if (gameState === null) {
|
||||
// window.location = '/not-found?reason=' + encodeURIComponent('game-not-found');
|
||||
// } else {
|
||||
stateBucket.currentGameState = gameState;
|
||||
document.querySelector('.spinner-container')?.remove();
|
||||
document.querySelector('.spinner-background')?.remove();
|
||||
document.getElementById('game-content').innerHTML = HTMLFragments.INITIAL_GAME_DOM;
|
||||
toast('You are connected.', 'success', true, true, 'short');
|
||||
processGameState(stateBucket.currentGameState, cookie, socket, true, true);
|
||||
// }
|
||||
});
|
||||
} else {
|
||||
window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
|
||||
}
|
||||
// else {
|
||||
// window.location = '/not-found?reason=' + encodeURIComponent('invalid-access-code');
|
||||
// }
|
||||
},
|
||||
|
||||
buildSpectatorList (people) {
|
||||
|
||||
@@ -123,17 +123,15 @@ export class GameTimerManager {
|
||||
});
|
||||
}
|
||||
|
||||
if (!socket.hasListeners(globals.COMMANDS.GET_TIME_REMAINING)) {
|
||||
socket.on(globals.COMMANDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
|
||||
if (paused) {
|
||||
this.displayPausedTime(timeRemaining);
|
||||
} else if (timeRemaining === 0) {
|
||||
this.displayExpiredTime();
|
||||
} else {
|
||||
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
|
||||
}
|
||||
});
|
||||
}
|
||||
socket.once(globals.COMMANDS.GET_TIME_REMAINING, (timeRemaining, paused) => {
|
||||
if (paused) {
|
||||
this.displayPausedTime(timeRemaining);
|
||||
} else if (timeRemaining === 0) {
|
||||
this.displayExpiredTime();
|
||||
} else {
|
||||
this.resumeGameTimer(timeRemaining, globals.CLOCK_TICK_INTERVAL_MILLIS, null, timerWorker);
|
||||
}
|
||||
});
|
||||
|
||||
if (!socket.hasListeners(globals.COMMANDS.END_TIMER)) {
|
||||
socket.on(globals.COMMANDS.END_TIMER, () => {
|
||||
|
||||
@@ -710,6 +710,13 @@ input {
|
||||
display: inline-block;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.lds-spinner-clock {
|
||||
position: absolute;
|
||||
transform: scale(0.3);
|
||||
top: -23px;
|
||||
}
|
||||
|
||||
.lds-spinner div {
|
||||
transform-origin: 40px 40px;
|
||||
animation: lds-spinner 1.2s linear infinite;
|
||||
|
||||
@@ -348,12 +348,18 @@ h1 {
|
||||
font-size: 35px;
|
||||
text-shadow: 0 3px 4px rgb(0 0 0 / 85%);
|
||||
border: 1px solid #747474;
|
||||
min-width: 5em;
|
||||
width: 204px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 43px;
|
||||
margin-bottom: 0.5em;
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.timer-error {
|
||||
color: #c51212 !important;
|
||||
font-size: 20px !important;
|
||||
}
|
||||
|
||||
#game-timer.low {
|
||||
@@ -861,6 +867,7 @@ canvas {
|
||||
padding: 5px;
|
||||
font-size: 30px;
|
||||
height: 30px;
|
||||
width: 167px;
|
||||
}
|
||||
|
||||
#role-info-button, #mod-transfer-button {
|
||||
|
||||
1
index.js
1
index.js
@@ -37,7 +37,6 @@ const main = async () => {
|
||||
console.log('Root Redis client connected');
|
||||
await singletons.activeGameRunner.createGameSyncSubscriber(singletons.gameManager, singletons.socketManager);
|
||||
await singletons.socketManager.createRedisPublisher();
|
||||
await singletons.gameManager.createRedisPublisher();
|
||||
|
||||
const socketServer = singletons.socketManager.createSocketServer(webServer, app, port);
|
||||
singletons.gameManager.setGameSocketNamespace(singletons.socketManager.createGameSocketNamespace(socketServer, logger, singletons.gameManager));
|
||||
|
||||
@@ -83,6 +83,7 @@ router.patch('/:code/players', async function (req, res) {
|
||||
gameManager.joinGame(game, req.body.playerName, inUseCookie, req.body.joinAsSpectator).then((data) => {
|
||||
res.status(200).send({ cookie: data, environment: gameManager.environment });
|
||||
}).catch((data) => {
|
||||
console.log(data);
|
||||
res.status(data.status).send(data.reason);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -40,7 +40,10 @@ const globals = {
|
||||
START_GAME: 'startGame',
|
||||
PAUSE_TIMER: 'pauseTimer',
|
||||
RESUME_TIMER: 'resumeTimer',
|
||||
END_TIMER: 'endTimer',
|
||||
GET_TIME_REMAINING: 'getTimeRemaining',
|
||||
SOURCE_TIME_REMAINING: 'sourceTimeRemaining',
|
||||
SHARE_TIME_REMAINING: 'shareTimeRemaining',
|
||||
KILL_PLAYER: 'killPlayer',
|
||||
REVEAL_PLAYER: 'revealPlayer',
|
||||
TRANSFER_MODERATOR: 'transferModerator',
|
||||
@@ -58,9 +61,6 @@ const globals = {
|
||||
return [
|
||||
this.EVENT_IDS.NEW_GAME,
|
||||
this.EVENT_IDS.START_GAME,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.GET_TIME_REMAINING,
|
||||
this.EVENT_IDS.KILL_PLAYER,
|
||||
this.EVENT_IDS.REVEAL_PLAYER,
|
||||
this.EVENT_IDS.TRANSFER_MODERATOR,
|
||||
@@ -68,11 +68,20 @@ const globals = {
|
||||
this.EVENT_IDS.RESTART_GAME,
|
||||
this.EVENT_IDS.PLAYER_JOINED,
|
||||
this.EVENT_IDS.ADD_SPECTATOR,
|
||||
this.EVENT_IDS.REMOVE_SPECTATOR,
|
||||
this.EVENT_IDS.SYNC_GAME_STATE,
|
||||
this.EVENT_IDS.UPDATE_SOCKET,
|
||||
this.EVENT_IDS.FETCH_GAME_STATE,
|
||||
this.EVENT_IDS.ASSIGN_DEDICATED_MOD
|
||||
this.EVENT_IDS.ASSIGN_DEDICATED_MOD,
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.END_TIMER
|
||||
];
|
||||
},
|
||||
TIMER_EVENTS: function () {
|
||||
return [
|
||||
this.EVENT_IDS.RESUME_TIMER,
|
||||
this.EVENT_IDS.PAUSE_TIMER,
|
||||
this.EVENT_IDS.END_TIMER
|
||||
];
|
||||
},
|
||||
MESSAGES: {
|
||||
|
||||
@@ -5,7 +5,7 @@ const EVENT_IDS = globals.EVENT_IDS;
|
||||
const Events = [
|
||||
{
|
||||
id: EVENT_IDS.PLAYER_JOINED,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const toBeAssignedIndex = game.people.findIndex(
|
||||
(person) => person.id === socketArgs.id && person.assigned === false
|
||||
);
|
||||
@@ -14,7 +14,7 @@ const Events = [
|
||||
game.isFull = vars.gameManager.isGameFull(game);
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENTS.PLAYER_JOINED,
|
||||
GameStateCurator.mapPerson(socketArgs),
|
||||
@@ -24,10 +24,10 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.ADD_SPECTATOR,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.people.push(socketArgs);
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
globals.EVENT_IDS.ADD_SPECTATOR,
|
||||
GameStateCurator.mapPerson(socketArgs)
|
||||
@@ -36,14 +36,14 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.FETCH_GAME_STATE,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const matchingPerson = vars.gameManager.findPersonByField(game, 'cookie', socketArgs.personId);
|
||||
if (matchingPerson && matchingPerson.socketId !== vars.socketId) {
|
||||
matchingPerson.socketId = vars.socketId;
|
||||
vars.gameManager.namespace.sockets.get(vars.socketId)?.join(game.accessCode);
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
if (!vars.ackFn) return;
|
||||
const matchingPerson = vars.gameManager.findPersonByField(game, 'cookie', socketArgs.personId);
|
||||
if (matchingPerson && vars.gameManager.namespace.sockets.get(matchingPerson.socketId)) {
|
||||
@@ -64,8 +64,8 @@ const Events = [
|
||||
// }
|
||||
{
|
||||
id: EVENT_IDS.SYNC_GAME_STATE,
|
||||
stateChange: (game, socketArgs, vars) => {},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const matchingPerson = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (matchingPerson && vars.gameManager.namespace.sockets.get(matchingPerson.socketId)) {
|
||||
vars.gameManager.namespace.to(matchingPerson.socketId).emit(globals.EVENTS.SYNC_GAME_STATE);
|
||||
@@ -74,16 +74,16 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.START_GAME,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
if (game.isFull) {
|
||||
game.status = globals.STATUS.IN_PROGRESS;
|
||||
if (game.hasTimer) {
|
||||
game.timerParams.paused = true;
|
||||
// this.activeGameRunner.runGame(game, namespace);
|
||||
await vars.activeGameRunner.runGame(game, vars.gameManager.namespace, vars.socketManager, vars.gameManager);
|
||||
}
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
@@ -92,7 +92,7 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.KILL_PLAYER,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person && !person.out) {
|
||||
person.userType = globals.USER_TYPES.KILLED_PLAYER;
|
||||
@@ -100,7 +100,7 @@ const Events = [
|
||||
person.killed = true;
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person) {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.KILL_PLAYER, person.id);
|
||||
@@ -109,13 +109,13 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.REVEAL_PLAYER,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person && !person.revealed) {
|
||||
person.revealed = true;
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const person = game.people.find((person) => person.id === socketArgs.personId);
|
||||
if (person) {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(
|
||||
@@ -131,7 +131,7 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.END_GAME,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.status = globals.STATUS.ENDED;
|
||||
// if (this.activeGameRunner.timerThreads[game.accessCode]) {
|
||||
// this.logger.trace('KILLING TIMER PROCESS FOR ENDED GAME ' + game.accessCode);
|
||||
@@ -141,7 +141,7 @@ const Events = [
|
||||
person.revealed = true;
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode)
|
||||
.emit(globals.EVENT_IDS.END_GAME, GameStateCurator.mapPeopleForModerator(game.people));
|
||||
if (vars.ackFn) {
|
||||
@@ -151,7 +151,7 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.TRANSFER_MODERATOR,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const currentModerator = vars.gameManager.findPersonByField(game, 'id', game.currentModeratorId);
|
||||
const toTransferTo = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (currentModerator) {
|
||||
@@ -167,7 +167,7 @@ const Events = [
|
||||
game.currentModeratorId = toTransferTo.id;
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
@@ -176,7 +176,7 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.ASSIGN_DEDICATED_MOD,
|
||||
stateChange: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
const currentModerator = vars.gameManager.findPersonByField(game, 'id', game.currentModeratorId);
|
||||
const toTransferTo = vars.gameManager.findPersonByField(game, 'id', socketArgs.personId);
|
||||
if (currentModerator && toTransferTo) {
|
||||
@@ -191,7 +191,7 @@ const Events = [
|
||||
game.currentModeratorId = toTransferTo.id;
|
||||
}
|
||||
},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const moderator = vars.gameManager.findPersonByField(game, 'id', game.currentModeratorId);
|
||||
const moderatorSocket = vars.gameManager.namespace.sockets.get(moderator?.socketId);
|
||||
if (moderator && moderatorSocket) {
|
||||
@@ -208,13 +208,124 @@ const Events = [
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.RESTART_GAME,
|
||||
stateChange: (game, socketArgs, vars) => {},
|
||||
communicate: (game, socketArgs, vars) => {
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
if (vars.ackFn) {
|
||||
vars.ackFn();
|
||||
}
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.EVENT_IDS.RESTART_GAME);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.GET_TIME_REMAINING,
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const thread = vars.activeGameRunner.timerThreads[game.accessCode];
|
||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
||||
thread.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
accessCode: game.accessCode,
|
||||
socketId: vars.socketId,
|
||||
logLevel: vars.logger.logLevel
|
||||
});
|
||||
} else if (thread) {
|
||||
console.log(game.timerParams);
|
||||
if (game.timerParams && game.timerParams.timeRemaining === 0) {
|
||||
vars.gameManager.namespace.to(vars.socketId)
|
||||
.emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
await vars.socketManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.SHARE_TIME_REMAINING + ';' +
|
||||
JSON.stringify({
|
||||
socketId: vars.socketId,
|
||||
timeRemaining: game.timerParams.timeRemaining,
|
||||
paused: game.timerParams.paused
|
||||
}) +
|
||||
';' + vars.instanceId
|
||||
);
|
||||
}
|
||||
} else { // we need to consult another container for the timer data
|
||||
await vars.socketManager.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.SOURCE_TIME_REMAINING + ';' + JSON.stringify({ socketId: vars.socketId }) + ';' + vars.instanceId
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
/* unlike the GET_TIME_REMAINING event, this event is a request from another instance for timer data. In response
|
||||
* to this event, this instance will check if it is home to a particular timer thread. */
|
||||
id: EVENT_IDS.SOURCE_TIME_REMAINING,
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const thread = vars.activeGameRunner.timerThreads[game.accessCode];
|
||||
if (thread && (!thread.killed && thread.exitCode === null)) {
|
||||
thread.send({
|
||||
command: globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING,
|
||||
accessCode: game.accessCode,
|
||||
socketId: socketArgs.socketId,
|
||||
logLevel: vars.logger.logLevel
|
||||
});
|
||||
} else if (thread) {
|
||||
if (game.timerParams && game.timerParams.timeRemaining === 0) {
|
||||
vars.gameManager.namespace.to(vars.socketId)
|
||||
.emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
}
|
||||
await vars.socketManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.SHARE_TIME_REMAINING + ';' +
|
||||
JSON.stringify({
|
||||
socketId: socketArgs.socketId,
|
||||
timeRemaining: game.timerParams.timeRemaining,
|
||||
paused: game.timerParams.paused
|
||||
}) +
|
||||
';' + vars.instanceId
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
/* This is an event fired when an instance receives timer data from another instance. In this case, we should check if the socket id
|
||||
* given in the message is connected to this namespace. If it is, emit the time remaining to them. */
|
||||
id: EVENT_IDS.SHARE_TIME_REMAINING,
|
||||
stateChange: async (game, socketArgs, vars) => {},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
const socket = vars.gameManager.namespace.sockets.get(socketArgs.socketId);
|
||||
if (socket) {
|
||||
vars.gameManager.namespace.to(socket.id)
|
||||
.emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, socketArgs.timeRemaining, socketArgs.paused);
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.END_TIMER,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.timerParams.paused = false;
|
||||
game.timerParams.timeRemaining = 0;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.END_TIMER);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.PAUSE_TIMER,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.timerParams.paused = true;
|
||||
game.timerParams.timeRemaining = socketArgs.timeRemaining;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER, game.timerParams.timeRemaining);
|
||||
}
|
||||
},
|
||||
{
|
||||
id: EVENT_IDS.RESUME_TIMER,
|
||||
stateChange: async (game, socketArgs, vars) => {
|
||||
game.timerParams.paused = false;
|
||||
game.timerParams.timeRemaining = socketArgs.timeRemaining;
|
||||
},
|
||||
communicate: async (game, socketArgs, vars) => {
|
||||
vars.gameManager.namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.RESUME_TIMER, game.timerParams.timeRemaining);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ class ActiveGameRunner {
|
||||
this.timerThreads = {};
|
||||
this.logger = logger;
|
||||
this.client = redis.createClient();
|
||||
this.publisher = null;
|
||||
this.subscriber = null;
|
||||
this.instanceId = instanceId;
|
||||
ActiveGameRunner.instance = this;
|
||||
@@ -56,40 +55,48 @@ class ActiveGameRunner {
|
||||
/* We're only going to fork a child process for games with a timer. They will report back to the parent process whenever
|
||||
the timer is up.
|
||||
*/
|
||||
runGame = (game, namespace) => {
|
||||
runGame = async (game, namespace, socketManager, gameManager) => {
|
||||
this.logger.debug('running game ' + game.accessCode);
|
||||
const gameProcess = fork(path.join(__dirname, '../GameProcess.js'));
|
||||
this.timerThreads[game.accessCode] = gameProcess;
|
||||
console.log(this.timerThreads);
|
||||
this.logger.debug('game ' + game.accessCode + ' now associated with subProcess ' + gameProcess.pid);
|
||||
gameProcess.on('message', (msg) => {
|
||||
gameProcess.on('message', async (msg) => {
|
||||
game = await this.getActiveGame(game.accessCode);
|
||||
switch (msg.command) {
|
||||
case globals.GAME_PROCESS_COMMANDS.END_TIMER:
|
||||
game.timerParams.paused = false;
|
||||
game.timerParams.timeRemaining = 0;
|
||||
namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.END_TIMER);
|
||||
await socketManager.handleEventById(globals.EVENT_IDS.END_TIMER, game, msg.socketId, game.accessCode, msg, null, false);
|
||||
this.logger.trace('PARENT: END TIMER');
|
||||
break;
|
||||
case globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER:
|
||||
game.timerParams.paused = true;
|
||||
this.logger.trace(msg);
|
||||
game.timerParams.timeRemaining = msg.timeRemaining;
|
||||
this.logger.trace('PARENT: PAUSE TIMER');
|
||||
namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.PAUSE_TIMER, game.timerParams.timeRemaining);
|
||||
await socketManager.handleEventById(globals.EVENT_IDS.PAUSE_TIMER, game, msg.socketId, game.accessCode, msg, null, false);
|
||||
break;
|
||||
case globals.GAME_PROCESS_COMMANDS.RESUME_TIMER:
|
||||
game.timerParams.paused = false;
|
||||
this.logger.trace(msg);
|
||||
game.timerParams.timeRemaining = msg.timeRemaining;
|
||||
this.logger.trace('PARENT: RESUME TIMER');
|
||||
namespace.in(game.accessCode).emit(globals.GAME_PROCESS_COMMANDS.RESUME_TIMER, game.timerParams.timeRemaining);
|
||||
await socketManager.handleEventById(globals.EVENT_IDS.RESUME_TIMER, game, msg.socketId, game.accessCode, msg, null, false);
|
||||
break;
|
||||
case globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING:
|
||||
this.logger.trace(msg);
|
||||
game.timerParams.timeRemaining = msg.timeRemaining;
|
||||
this.logger.trace('PARENT: GET TIME REMAINING');
|
||||
namespace.to(msg.socketId).emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
msg.paused = game.timerParams.paused;
|
||||
await socketManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.SHARE_TIME_REMAINING + ';' + JSON.stringify(msg) + ';' + this.instanceId
|
||||
);
|
||||
const socket = namespace.sockets.get(msg.socketId);
|
||||
if (socket) {
|
||||
namespace.to(socket.id).emit(globals.GAME_PROCESS_COMMANDS.GET_TIME_REMAINING, game.timerParams.timeRemaining, game.timerParams.paused);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (globals.SYNCABLE_EVENTS().includes(msg.command)) {
|
||||
await gameManager.refreshGame(game);
|
||||
await socketManager.publisher.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + msg.command + ';' + JSON.stringify(msg) + ';' + this.instanceId
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
gameProcess.on('exit', (code, signal) => {
|
||||
|
||||
@@ -4,7 +4,6 @@ const Person = require('../../model/Person');
|
||||
const GameStateCurator = require('../GameStateCurator');
|
||||
const UsernameGenerator = require('../UsernameGenerator');
|
||||
const GameCreationRequest = require('../../model/GameCreationRequest');
|
||||
const redis = require('redis');
|
||||
|
||||
class GameManager {
|
||||
constructor (logger, environment, instanceId) {
|
||||
@@ -17,17 +16,10 @@ class GameManager {
|
||||
this.activeGameRunner = null;
|
||||
this.socketManager = null;
|
||||
this.namespace = null;
|
||||
this.publisher = null;
|
||||
this.instanceId = instanceId;
|
||||
GameManager.instance = this;
|
||||
}
|
||||
|
||||
createRedisPublisher = async () => {
|
||||
this.publisher = redis.createClient();
|
||||
await this.publisher.connect();
|
||||
this.logger.info('GAME MANAGER - CREATED GAME SYNC PUBLISHER');
|
||||
}
|
||||
|
||||
setGameSocketNamespace = (namespace) => {
|
||||
this.namespace = namespace;
|
||||
};
|
||||
@@ -164,7 +156,7 @@ class GameManager {
|
||||
) {
|
||||
return Promise.reject({ status: 400, reason: 'There are too many people already spectating.' });
|
||||
} else if (joinAsSpectator) {
|
||||
return await addSpectator(game, name, this.logger, this.namespace, this.publisher, this.instanceId, this.refreshGame);
|
||||
return await addSpectator(game, name, this.logger, this.namespace, this.socketManager.publisher, this.instanceId, this.refreshGame);
|
||||
}
|
||||
const unassignedPerson = this.findPersonByField(game, 'id', game.currentModeratorId).assigned === false
|
||||
? this.findPersonByField(game, 'id', game.currentModeratorId)
|
||||
@@ -180,7 +172,7 @@ class GameManager {
|
||||
GameStateCurator.mapPerson(unassignedPerson),
|
||||
game.isFull
|
||||
);
|
||||
await this.publisher?.publish(
|
||||
await this.activeGameRunner.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.PLAYER_JOINED + ';' + JSON.stringify(unassignedPerson) + ';' + this.instanceId
|
||||
);
|
||||
@@ -189,21 +181,21 @@ class GameManager {
|
||||
if (game.people.filter(person => person.userType === globals.USER_TYPES.SPECTATOR).length === globals.MAX_SPECTATORS) {
|
||||
return Promise.reject({ status: 400, reason: 'This game has reached the maximum number of players and spectators.' });
|
||||
}
|
||||
return await addSpectator(game, name, this.logger, this.namespace, this.publisher, this.instanceId, this.refreshGame);
|
||||
return await addSpectator(game, name, this.logger, this.namespace, this.socketManager.publisher, this.instanceId, this.refreshGame);
|
||||
}
|
||||
};
|
||||
|
||||
restartGame = async (game, namespace) => {
|
||||
// kill any outstanding timer threads
|
||||
// const subProcess = this.activeGameRunner.timerThreads[game.accessCode];
|
||||
// if (subProcess) {
|
||||
// if (!subProcess.killed) {
|
||||
// this.logger.info('Killing timer process ' + subProcess.pid + ' for: ' + game.accessCode);
|
||||
// this.activeGameRunner.timerThreads[game.accessCode].kill();
|
||||
// }
|
||||
// this.logger.debug('Deleting reference to subprocess ' + subProcess.pid);
|
||||
// delete this.activeGameRunner.timerThreads[game.accessCode];
|
||||
// }
|
||||
const subProcess = this.activeGameRunner.timerThreads[game.accessCode];
|
||||
if (subProcess) {
|
||||
if (!subProcess.killed) {
|
||||
this.logger.info('Killing timer process ' + subProcess.pid + ' for: ' + game.accessCode);
|
||||
this.activeGameRunner.timerThreads[game.accessCode].kill();
|
||||
}
|
||||
this.logger.debug('Deleting reference to subprocess ' + subProcess.pid);
|
||||
delete this.activeGameRunner.timerThreads[game.accessCode];
|
||||
}
|
||||
|
||||
// re-shuffle the deck
|
||||
const cards = [];
|
||||
@@ -238,11 +230,11 @@ class GameManager {
|
||||
game.status = globals.STATUS.IN_PROGRESS;
|
||||
if (game.hasTimer) {
|
||||
game.timerParams.paused = true;
|
||||
this.activeGameRunner.runGame(game, namespace);
|
||||
await this.activeGameRunner.runGame(game, namespace, this.socketManager, this);
|
||||
}
|
||||
|
||||
await this.refreshGame(game);
|
||||
await this.publisher?.publish(
|
||||
await this.socketManager.publisher?.publish(
|
||||
globals.REDIS_CHANNELS.ACTIVE_GAME_STREAM,
|
||||
game.accessCode + ';' + globals.EVENT_IDS.RESTART_GAME + ';' + JSON.stringify({}) + ';' + this.instanceId
|
||||
);
|
||||
|
||||
@@ -65,14 +65,18 @@ class SocketManager {
|
||||
socket.on(globals.SOCKET_EVENTS.IN_GAME_MESSAGE, async (eventId, accessCode, args = null, ackFn = null) => {
|
||||
const game = await this.activeGameRunner.getActiveGame(accessCode);
|
||||
if (game) {
|
||||
await this.handleAndSyncEvent(eventId, game, socket, args, ackFn);
|
||||
if (globals.TIMER_EVENTS().includes(eventId)) {
|
||||
await this.handleAndSyncTimerEvent(eventId, game, socket, args, ackFn, false);
|
||||
} else {
|
||||
await this.handleAndSyncSocketEvent(eventId, game, socket, args, ackFn, false);
|
||||
}
|
||||
} else {
|
||||
ackFn(null);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleAndSyncEvent = async (eventId, game, socket, socketArgs, ackFn) => {
|
||||
handleAndSyncSocketEvent = async (eventId, game, socket, socketArgs, ackFn) => {
|
||||
await this.handleEventById(eventId, game, socket?.id, game.accessCode, socketArgs, ackFn, false);
|
||||
/* This server should publish events initiated by a connected socket to Redis for consumption by other instances. */
|
||||
if (globals.SYNCABLE_EVENTS().includes(eventId)) {
|
||||
@@ -84,20 +88,7 @@ class SocketManager {
|
||||
}
|
||||
}
|
||||
|
||||
handleEventById = async (eventId, game, socketId, accessCode, socketArgs, ackFn, syncOnly) => {
|
||||
this.logger.trace('ARGS TO HANDLER: ' + JSON.stringify(socketArgs));
|
||||
const event = Events.find((event) => event.id === eventId);
|
||||
const additionalVars = {
|
||||
gameManager: this.gameManager,
|
||||
socketId: socketId,
|
||||
ackFn: ackFn
|
||||
};
|
||||
if (event) {
|
||||
if (!syncOnly) {
|
||||
event.stateChange(game, socketArgs, additionalVars);
|
||||
}
|
||||
event.communicate(game, socketArgs, additionalVars);
|
||||
}
|
||||
handleAndSyncTimerEvent = async (eventId, game, socketId, accessCode, socketArgs, ackFn, syncOnly) => {
|
||||
switch (eventId) {
|
||||
case EVENT_IDS.PAUSE_TIMER:
|
||||
await this.gameManager.pauseTimer(game, this.logger);
|
||||
@@ -105,13 +96,30 @@ class SocketManager {
|
||||
case EVENT_IDS.RESUME_TIMER:
|
||||
await this.gameManager.resumeTimer(game, this.logger);
|
||||
break;
|
||||
case EVENT_IDS.GET_TIME_REMAINING:
|
||||
await this.gameManager.getTimeRemaining(game, socketId);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
handleEventById = async (eventId, game, socketId, accessCode, socketArgs, ackFn, syncOnly) => {
|
||||
this.logger.trace('ARGS TO HANDLER: ' + JSON.stringify(socketArgs));
|
||||
const event = Events.find((event) => event.id === eventId);
|
||||
const additionalVars = {
|
||||
gameManager: this.gameManager,
|
||||
activeGameRunner: this.activeGameRunner,
|
||||
socketManager: this,
|
||||
socketId: socketId,
|
||||
ackFn: ackFn,
|
||||
logger: this.logger,
|
||||
instanceId: this.instanceId
|
||||
};
|
||||
if (event) {
|
||||
if (!syncOnly) {
|
||||
await event.stateChange(game, socketArgs, additionalVars);
|
||||
}
|
||||
await event.communicate(game, socketArgs, additionalVars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function registerRateLimiter (server, logger) {
|
||||
|
||||
Reference in New Issue
Block a user