From c4f28a0a8d213227b2033a5127052306775c9d28 Mon Sep 17 00:00:00 2001 From: Jacem Chaieb Date: Mon, 9 Nov 2020 12:24:45 +0100 Subject: [PATCH 1/7] Add support for QWERTY keyboards --- front/src/Phaser/UserInput/UserInputManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/Phaser/UserInput/UserInputManager.ts b/front/src/Phaser/UserInput/UserInputManager.ts index fa7080b5..636783bc 100644 --- a/front/src/Phaser/UserInput/UserInputManager.ts +++ b/front/src/Phaser/UserInput/UserInputManager.ts @@ -40,7 +40,9 @@ export class UserInputManager { initKeyBoardEvent(){ this.KeysCode = [ {event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Z, false) }, + {event: UserInputEvent.MoveUp, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.W, false) }, {event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q, false) }, + {event: UserInputEvent.MoveLeft, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.A, false) }, {event: UserInputEvent.MoveDown, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.S, false) }, {event: UserInputEvent.MoveRight, keyInstance: this.Scene.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.D, false) }, From 1080328afab761362f06c5fd1ebb55e1c0979c04 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 15:22:30 +0100 Subject: [PATCH 2/7] FIX: cowebsite transitions are now queued to prevent conflicts --- front/src/WebRtc/CoWebsiteManager.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/front/src/WebRtc/CoWebsiteManager.ts b/front/src/WebRtc/CoWebsiteManager.ts index 46d03702..70d171a2 100644 --- a/front/src/WebRtc/CoWebsiteManager.ts +++ b/front/src/WebRtc/CoWebsiteManager.ts @@ -16,6 +16,11 @@ class CoWebsiteManager { private opened: iframeStates = iframeStates.closed; private observers = new Array(); + /** + * Quickly going in and out of an iframe trigger can create conflicts between the iframe states. + * So we use this promise to queue up every cowebsite state transition + */ + private currentOperationPromise: Promise = Promise.resolve(); private close(): HTMLDivElement { const cowebsiteDiv = HtmlUtils.getElementByIdOrFail(cowebsiteDivId); @@ -52,7 +57,7 @@ class CoWebsiteManager { const onTimeoutPromise = new Promise((resolve) => { setTimeout(() => resolve(), 2000); }); - Promise.race([onloadPromise, onTimeoutPromise]).then(() => { + this.currentOperationPromise = this.currentOperationPromise.then(() =>Promise.race([onloadPromise, onTimeoutPromise])).then(() => { this.open(); setTimeout(() => { this.fire(); @@ -65,7 +70,7 @@ class CoWebsiteManager { */ public insertCoWebsite(callback: (cowebsite: HTMLDivElement) => Promise): void { const cowebsiteDiv = this.load(); - callback(cowebsiteDiv).then(() => { + this.currentOperationPromise = this.currentOperationPromise.then(() => callback(cowebsiteDiv)).then(() => { this.open(); setTimeout(() => { this.fire(); @@ -74,14 +79,16 @@ class CoWebsiteManager { } public closeCoWebsite(): Promise { - return new Promise((resolve, reject) => { + this.currentOperationPromise = this.currentOperationPromise.then(() => new Promise((resolve, reject) => { + if(this.opened === iframeStates.closed) resolve(); //this method may be called twice, in case of iframe error for example const cowebsiteDiv = this.close(); this.fire(); setTimeout(() => { resolve(); setTimeout(() => cowebsiteDiv.innerHTML = '', 500) }, animationTime) - }); + })); + return this.currentOperationPromise; } public getGameSize(): {width: number, height: number} { From 5a1147866cffbaba1c92e389cd6fe87ed30222c9 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 15:24:02 +0100 Subject: [PATCH 3/7] FIX: in dev mode, sockets won't be closed to prevent conflicts with live-reloading --- back/src/Enum/EnvironmentVariable.ts | 2 ++ back/src/Services/IoSocketHelpers.ts | 2 ++ docker-compose.yaml | 1 + 3 files changed, 5 insertions(+) diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 55fd1bb7..8be8d54d 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -10,6 +10,7 @@ const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ''; const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; +const DEV_MODE = process.env.DEV_MODE || false; export { SECRET_KEY, @@ -21,6 +22,7 @@ export { GROUP_RADIUS, ALLOW_ARTILLERY, CPU_OVERHEAT_THRESHOLD, + DEV_MODE, JITSI_URL, JITSI_ISS, SECRET_JITSI_KEY diff --git a/back/src/Services/IoSocketHelpers.ts b/back/src/Services/IoSocketHelpers.ts index acaa0bb9..1dbfa0bd 100644 --- a/back/src/Services/IoSocketHelpers.ts +++ b/back/src/Services/IoSocketHelpers.ts @@ -1,5 +1,6 @@ import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; +import {DEV_MODE} from "../Enum/EnvironmentVariable"; export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { socket.batchedMessages.addPayload(payload); @@ -52,6 +53,7 @@ export function emitError(Client: ExSocketInterface, message: string): void { export const pongMaxInterval = 30000; // the maximum duration (in ms) between pongs before we shutdown the connexion. export function refresLogoutTimerOnPong(ws: ExSocketInterface): void { + if (DEV_MODE) return; //this feature is disabled in dev mode as it clashes with live reload. if(ws.pongTimeout) clearTimeout(ws.pongTimeout); ws.pongTimeout = setTimeout(() => { ws.close(); diff --git a/docker-compose.yaml b/docker-compose.yaml index 482dfbcb..5deccb4d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,6 +78,7 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS + DEV_MODE: "1" volumes: - ./back:/usr/src/app labels: From 73fa0ecbfc9eabb5fb6a51386fe64632dad99d25 Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Tue, 10 Nov 2020 17:00:23 +0100 Subject: [PATCH 4/7] Fix init position and trigger layers properties --- front/src/Phaser/Game/GameScene.ts | 68 +++++++++++++++++------------- 1 file changed, 38 insertions(+), 30 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 96648255..d632045f 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -466,35 +466,7 @@ export class GameScene extends ResizableScene implements CenterListener { // From now, this game scene will be notified of reposition events layoutManager.setListener(this); - - this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => { - if (newValue === undefined) { - coWebsiteManager.closeCoWebsite(); - } else { - coWebsiteManager.loadCoWebsite(newValue as string); - } - }); - this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { - if (newValue === undefined) { - this.stopJitsi(); - } else { - if (JITSI_PRIVATE_MODE) { - const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; - - this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag); - } else { - this.startJitsi(newValue as string); - } - } - }) - - this.gameMap.onPropertyChange('silent', (newValue, oldValue) => { - if (newValue === undefined || newValue === false || newValue === '') { - this.connection.setSilent(false); - } else { - this.connection.setSilent(true); - } - }); + this.triggerOnMapLayerPropertyChange(); const camera = this.cameras.main; @@ -627,14 +599,49 @@ export class GameScene extends ResizableScene implements CenterListener { this.gameMap.setPosition(event.x, event.y); }) - this.scene.wake(); this.scene.sleep(ReconnectingSceneName); + //init connection in silent mode + this.connection.setSilent(true); + + //init user position and play trigger to check layers properties + this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y); + return connection; }); } + private triggerOnMapLayerPropertyChange(){ + this.gameMap.onPropertyChange('openWebsite', (newValue, oldValue) => { + if (newValue === undefined) { + coWebsiteManager.closeCoWebsite(); + } else { + coWebsiteManager.loadCoWebsite(newValue as string); + } + }); + this.gameMap.onPropertyChange('jitsiRoom', (newValue, oldValue, allProps) => { + if (newValue === undefined) { + this.stopJitsi(); + } else { + if (JITSI_PRIVATE_MODE) { + const adminTag = allProps.get("jitsiRoomAdminTag") as string|undefined; + + this.connection.emitQueryJitsiJwtMessage(this.instance.replace('/', '-') + "-" + newValue, adminTag); + } else { + this.startJitsi(newValue as string); + } + } + }) + this.gameMap.onPropertyChange('silent', (newValue, oldValue) => { + if (newValue === undefined || newValue === false || newValue === '') { + this.connection.setSilent(false); + } else { + this.connection.setSilent(true); + } + }); + } + private switchLayoutMode(): void { const mode = layoutManager.getLayoutMode(); if (mode === LayoutMode.Presentation) { @@ -713,6 +720,7 @@ export class GameScene extends ResizableScene implements CenterListener { */ //todo: push that into the gameManager private loadNextGame(layer: ITiledMapLayer, mapWidth: number, roomId: string){ + const room = new Room(roomId); gameManager.loadMap(room, this.scene); const exitSceneKey = roomId; From 9b64a970b57d094988073199451b47c857201ee1 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Tue, 10 Nov 2020 18:26:46 +0100 Subject: [PATCH 5/7] FIX: remove the ping and pong overrides server side in favor of and idleTimeout and added a manual ping client side --- back/src/Controller/IoSocketController.ts | 10 +++---- back/src/Enum/EnvironmentVariable.ts | 3 +-- back/src/Model/Websocket/ExSocketInterface.ts | 2 -- back/src/Services/IoSocketHelpers.ts | 26 ------------------- docker-compose.yaml | 1 - front/src/Connexion/RoomConnection.ts | 7 ++++- messages/messages.proto | 4 +++ 7 files changed, 14 insertions(+), 39 deletions(-) diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index c71edd86..85549c03 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -20,9 +20,9 @@ import {parse} from "query-string"; import {jwtTokenManager} from "../Services/JWTTokenManager"; import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Services/AdminApi"; import {SocketManager, socketManager} from "../Services/SocketManager"; -import {emitInBatch, pongMaxInterval, refresLogoutTimerOnPong, resetPing} from "../Services/IoSocketHelpers"; +import {emitInBatch} from "../Services/IoSocketHelpers"; import {clientEventsEmitter} from "../Services/ClientEventsEmitter"; -import {ADMIN_API_TOKEN, ADMIN_API_URL} from "../Enum/EnvironmentVariable"; +import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable"; export class IoSocketController { private nextUserId: number = 1; @@ -110,6 +110,7 @@ export class IoSocketController { this.app.ws('/room', { /* Options */ //compression: uWS.SHARED_COMPRESSOR, + idleTimeout: SOCKET_IDLE_TIMER, maxPayloadLength: 16 * 1024 * 1024, maxBackpressure: 65536, // Maximum 64kB of data in the buffer. //idleTimeout: 10, @@ -239,8 +240,6 @@ export class IoSocketController { // Let's join the room const client = this.initClient(ws); //todo: into the upgrade instead? socketManager.handleJoinRoom(client); - resetPing(client); - refresLogoutTimerOnPong(ws as ExSocketInterface); //get data information and show messages if (ADMIN_API_URL) { @@ -293,9 +292,6 @@ export class IoSocketController { drain: (ws) => { console.log('WebSocket backpressure: ' + ws.getBufferedAmount()); }, - pong(ws) { - refresLogoutTimerOnPong(ws as ExSocketInterface); - }, close: (ws, code, message) => { const Client = (ws as ExSocketInterface); try { diff --git a/back/src/Enum/EnvironmentVariable.ts b/back/src/Enum/EnvironmentVariable.ts index 8be8d54d..3a2ac99e 100644 --- a/back/src/Enum/EnvironmentVariable.ts +++ b/back/src/Enum/EnvironmentVariable.ts @@ -10,7 +10,7 @@ const CPU_OVERHEAT_THRESHOLD = Number(process.env.CPU_OVERHEAT_THRESHOLD) || 80; const JITSI_URL : string|undefined = (process.env.JITSI_URL === '') ? undefined : process.env.JITSI_URL; const JITSI_ISS = process.env.JITSI_ISS || ''; const SECRET_JITSI_KEY = process.env.SECRET_JITSI_KEY || ''; -const DEV_MODE = process.env.DEV_MODE || false; +export const SOCKET_IDLE_TIMER = parseInt(process.env.SOCKET_IDLE_TIMER as string) || 30; // maximum time (in second) without activity before a socket is closed export { SECRET_KEY, @@ -22,7 +22,6 @@ export { GROUP_RADIUS, ALLOW_ARTILLERY, CPU_OVERHEAT_THRESHOLD, - DEV_MODE, JITSI_URL, JITSI_ISS, SECRET_JITSI_KEY diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index e3d19138..c64a4952 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -25,8 +25,6 @@ export interface ExSocketInterface extends WebSocket, Identificable { emitInBatch: (payload: SubMessage) => void; batchedMessages: BatchMessage; batchTimeout: NodeJS.Timeout|null; - pingTimeout: NodeJS.Timeout|null; - pongTimeout: NodeJS.Timeout|null; disconnecting: boolean, tags: string[], textures: CharacterTexture[], diff --git a/back/src/Services/IoSocketHelpers.ts b/back/src/Services/IoSocketHelpers.ts index 1dbfa0bd..9c27c59a 100644 --- a/back/src/Services/IoSocketHelpers.ts +++ b/back/src/Services/IoSocketHelpers.ts @@ -1,6 +1,5 @@ import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {BatchMessage, ErrorMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb"; -import {DEV_MODE} from "../Enum/EnvironmentVariable"; export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): void { socket.batchedMessages.addPayload(payload); @@ -19,22 +18,6 @@ export function emitInBatch(socket: ExSocketInterface, payload: SubMessage): voi socket.batchTimeout = null; }, 100); } - - // If we send a message, we don't need to keep the connection alive - resetPing(socket); -} - -export function resetPing(ws: ExSocketInterface): void { - if (ws.pingTimeout) { - clearTimeout(ws.pingTimeout); - } - ws.pingTimeout = setTimeout(() => { - if (ws.disconnecting) { - return; - } - ws.ping(); - resetPing(ws); - }, 29000); } export function emitError(Client: ExSocketInterface, message: string): void { @@ -50,12 +33,3 @@ export function emitError(Client: ExSocketInterface, message: string): void { console.warn(message); } -export const pongMaxInterval = 30000; // the maximum duration (in ms) between pongs before we shutdown the connexion. - -export function refresLogoutTimerOnPong(ws: ExSocketInterface): void { - if (DEV_MODE) return; //this feature is disabled in dev mode as it clashes with live reload. - if(ws.pongTimeout) clearTimeout(ws.pongTimeout); - ws.pongTimeout = setTimeout(() => { - ws.close(); - }, pongMaxInterval); -} diff --git a/docker-compose.yaml b/docker-compose.yaml index 5deccb4d..482dfbcb 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -78,7 +78,6 @@ services: ADMIN_API_TOKEN: "$ADMIN_API_TOKEN" JITSI_URL: $JITSI_URL JITSI_ISS: $JITSI_ISS - DEV_MODE: "1" volumes: - ./back:/usr/src/app labels: diff --git a/front/src/Connexion/RoomConnection.ts b/front/src/Connexion/RoomConnection.ts index 19d011ff..b25e2d76 100644 --- a/front/src/Connexion/RoomConnection.ts +++ b/front/src/Connexion/RoomConnection.ts @@ -26,6 +26,7 @@ import { QueryJitsiJwtMessage, SendJitsiJwtMessage, CharacterLayerMessage, + PingMessage, SendUserMessage } from "../Messages/generated/messages_pb" @@ -42,6 +43,8 @@ import { } from "./ConnexionModels"; import {BodyResourceDescriptionInterface} from "../Phaser/Entity/body_character"; +const manualPingDelay = 20000; + export class RoomConnection implements RoomConnection { private readonly socket: WebSocket; private userId: number|null = null; @@ -84,7 +87,9 @@ export class RoomConnection implements RoomConnection { this.socket.binaryType = 'arraybuffer'; this.socket.onopen = (ev) => { - //console.log('WS connected'); + //we manually ping every 20s to not be logged out by the server, even when the game is in background. + const pingMessage = new PingMessage(); + setInterval(() => this.socket.send(pingMessage.serializeBinary().buffer), manualPingDelay); }; this.socket.onmessage = (messageEvent) => { diff --git a/messages/messages.proto b/messages/messages.proto index 89353825..6e0b47df 100644 --- a/messages/messages.proto +++ b/messages/messages.proto @@ -38,6 +38,10 @@ message CharacterLayerMessage { /*********** CLIENT TO SERVER MESSAGES *************/ +message PingMessage { + +} + message SetPlayerDetailsMessage { string name = 1; repeated string characterLayers = 2; From b064f01f97cd91aefb76d539d47cfa14058bb3f7 Mon Sep 17 00:00:00 2001 From: kharhamel Date: Thu, 12 Nov 2020 14:51:19 +0100 Subject: [PATCH 6/7] FEAT: added a prometheus gauge for the number of active rooms --- back/src/Services/GaugeManager.ts | 12 ++++++++++++ back/src/Services/SocketManager.ts | 2 ++ 2 files changed, 14 insertions(+) diff --git a/back/src/Services/GaugeManager.ts b/back/src/Services/GaugeManager.ts index f8af822b..80712856 100644 --- a/back/src/Services/GaugeManager.ts +++ b/back/src/Services/GaugeManager.ts @@ -6,8 +6,13 @@ class GaugeManager { private nbClientsPerRoomGauge: Gauge; private nbGroupsPerRoomGauge: Gauge; private nbGroupsPerRoomCounter: Counter; + private nbRoomsGauge: Gauge; constructor() { + this.nbRoomsGauge = new Gauge({ + name: 'workadventure_nb_rooms', + help: 'Number of active rooms' + }); this.nbClientsGauge = new Gauge({ name: 'workadventure_nb_sockets', help: 'Number of connected sockets', @@ -31,6 +36,13 @@ class GaugeManager { }); } + incNbRoomGauge(): void { + this.nbRoomsGauge.inc(); + } + decNbRoomGauge(): void { + this.nbRoomsGauge.dec(); + } + incNbClientPerRoomGauge(roomId: string): void { this.nbClientsGauge.inc(); this.nbClientsPerRoomGauge.inc({ room: roomId }); diff --git a/back/src/Services/SocketManager.ts b/back/src/Services/SocketManager.ts index 4bd26778..97f008c4 100644 --- a/back/src/Services/SocketManager.ts +++ b/back/src/Services/SocketManager.ts @@ -351,6 +351,7 @@ export class SocketManager { world.leave(Client); if (world.isEmpty()) { this.Worlds.delete(Client.roomId); + gaugeManager.decNbRoomGauge(); } } //user leave previous room @@ -383,6 +384,7 @@ export class SocketManager { world.tags = data.tags world.policyType = Number(data.policy_type) } + gaugeManager.incNbRoomGauge(); this.Worlds.set(roomId, world); } return Promise.resolve(world) From 2d0fc1072ff984fa96a91c2cece2222eea35d54f Mon Sep 17 00:00:00 2001 From: Gregoire Parant Date: Mon, 16 Nov 2020 15:05:51 +0100 Subject: [PATCH 7/7] Fix feedback --- front/src/Phaser/Game/GameScene.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index d632045f..7c467862 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -602,9 +602,6 @@ export class GameScene extends ResizableScene implements CenterListener { this.scene.wake(); this.scene.sleep(ReconnectingSceneName); - //init connection in silent mode - this.connection.setSilent(true); - //init user position and play trigger to check layers properties this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);