From f88f28db3f6ba637b4c0b174a531390c999bb282 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Mon, 22 Jun 2020 15:00:23 +0200 Subject: [PATCH] Refactoring reconnection: putting it into the GameScene directly. --- front/src/Connection.ts | 114 +++++++++++++++------------ front/src/Phaser/Game/GameManager.ts | 4 +- front/src/Phaser/Game/GameScene.ts | 34 ++++++-- front/src/WebRtc/SimplePeer.ts | 6 ++ front/tsconfig.json | 1 + 5 files changed, 101 insertions(+), 58 deletions(-) diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 931f7737..ea382365 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -124,7 +124,7 @@ export interface StartMapInterface { } export class Connection implements Connection { - socket: Socket|null = null; + socket: Socket; token: string|null = null; name: string|null = null; // TODO: drop "name" storage here character: string|null = null; @@ -135,53 +135,59 @@ export class Connection implements Connection { lastPositionShared: PointInterface|null = null; lastRoom: string|null = null; - private constructor(GameManager: GameManager) { + private constructor(GameManager: GameManager, name: string, character: string, token: string) { this.GameManager = GameManager; + this.name = name; + this.character = character; + this.token = token; + + this.socket = SocketIo(`${API_URL}`, { + query: { + token: this.token + }, + reconnection: false // Reconnection is handled by the application itself + }); + + this.socket.on(EventMessage.CONNECT_ERROR, () => { + console.error("Connection failed") + }); + + this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => { + console.error(EventMessage.MESSAGE_ERROR, message); + }) } public static createConnection(name: string, characterSelected: string): Promise { - let connection = new Connection(gameManager); - connection.name = name; - connection.character = characterSelected; return Axios.post(`${API_URL}/login`, {name: name}) .then((res) => { - connection.token = res.data.token; - connection.socket = SocketIo(`${API_URL}`, { - query: { - token: connection.token - } - }); - - //listen event - connection.disconnectServer(); - connection.errorMessage(); + let connection = new Connection(gameManager, name, characterSelected, res.data.token); + // FIXME: we should wait for the complete connexion here (i.e. the "connected" message from socket.io)! + // Otherwise, the connection MAY fail and we will never know! return connection.connectSocketServer(); }) .catch((err) => { - console.error(err); - throw err; + // Let's retry in 4-6 seconds + return new Promise((resolve, reject) => { + setTimeout(() => { + resolve(Connection.createConnection(name, characterSelected)); + }, 4000 + Math.floor(Math.random() * 2000) ); + }); + + //console.error(err); + //throw err; }); } public closeConnection(): void { this.socket?.close(); - this.socket = null; this.lastPositionShared = null; this.lastRoom = null; - - } - - private getSocket(): Socket { - if (this.socket === null) { - throw new Error('Socket not initialized while using Connection') - } - return this.socket; } connectSocketServer(): Promise{ return new Promise((resolve, reject) => { - this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, { + this.socket.emit(EventMessage.SET_PLAYER_DETAILS, { name: this.name, character: this.character } as SetPlayerDetailsMessage, (id: string) => { @@ -218,7 +224,7 @@ export class Connection implements Connection { const point = new Point(startX, startY, direction, moving); this.lastPositionShared = point; let promise = new Promise((resolve, reject) => { - this.getSocket().emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { + this.socket.emit(EventMessage.JOIN_ROOM, { roomId, position: {x: startX, y: startY, direction, moving }}, (userPositions: MessageUserPositionInterface[]) => { //this.GameManager.initUsersPosition(userPositions); resolve(userPositions); }); @@ -233,31 +239,31 @@ export class Connection implements Connection { } const point = new Point(x, y, direction, moving); this.lastPositionShared = point; - this.getSocket().emit(EventMessage.USER_POSITION, point); + this.socket.emit(EventMessage.USER_POSITION, point); } public onUserJoins(callback: (message: MessageUserJoined) => void): void { - this.getSocket().on(EventMessage.JOIN_ROOM, callback); + this.socket.on(EventMessage.JOIN_ROOM, callback); } public onUserMoved(callback: (message: MessageUserMovedInterface) => void): void { - this.getSocket().on(EventMessage.USER_MOVED, callback); + this.socket.on(EventMessage.USER_MOVED, callback); } public onUserLeft(callback: (userId: string) => void): void { - this.getSocket().on(EventMessage.USER_LEFT, callback); + this.socket.on(EventMessage.USER_LEFT, callback); } public onGroupUpdatedOrCreated(callback: (groupCreateUpdateMessage: GroupCreatedUpdatedMessageInterface) => void): void { - this.getSocket().on(EventMessage.GROUP_CREATE_UPDATE, callback); + this.socket.on(EventMessage.GROUP_CREATE_UPDATE, callback); } public onGroupDeleted(callback: (groupId: string) => void): void { - this.getSocket().on(EventMessage.GROUP_DELETE, callback) + this.socket.on(EventMessage.GROUP_DELETE, callback) } sendWebrtcSignal(signal: unknown, roomId: string, userId? : string|null, receiverId? : string) { - return this.getSocket().emit(EventMessage.WEBRTC_SIGNAL, { + return this.socket.emit(EventMessage.WEBRTC_SIGNAL, { userId: userId ? userId : this.userId, receiverId: receiverId ? receiverId : this.userId, roomId: roomId, @@ -266,47 +272,53 @@ export class Connection implements Connection { } receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { - this.getSocket().on(EventMessage.WEBRTC_START, callback); + this.socket.on(EventMessage.WEBRTC_START, callback); } receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { - return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback); + return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback); } - private errorMessage(): void { - this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { - console.error(EventMessage.MESSAGE_ERROR, message); - }) - } + public onServerDisconnected(callback: (reason: string) => void): void { + /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { + callback(error); + });*/ - private disconnectServer(): void { - this.getSocket().on(EventMessage.CONNECT_ERROR, () => { - this.GameManager.switchToDisconnectedScene(); + this.socket.on('disconnect', (reason: string) => { + if (reason === 'io client disconnect') { + // The client asks for disconnect, let's not trigger any event. + return; + } + callback(reason); }); - this.getSocket().on(EventMessage.RECONNECTING, () => { + /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => { + this.GameManager.switchToDisconnectedScene(); + });*/ + + /*this.socket.on(EventMessage.RECONNECTING, () => { console.log('Trying to reconnect'); }); - this.getSocket().on(EventMessage.RECONNECT_ERROR, () => { + this.socket.on(EventMessage.RECONNECT_ERROR, () => { console.log('Error while trying to reconnect.'); }); - this.getSocket().on(EventMessage.RECONNECT_FAILED, () => { + this.socket.on(EventMessage.RECONNECT_FAILED, () => { console.error('Reconnection failed. Giving up.'); }); - this.getSocket().on(EventMessage.RECONNECT, () => { + this.socket.on(EventMessage.RECONNECT, () => { console.log('Reconnect event triggered'); this.connectSocketServer(); if (this.lastPositionShared === null) { throw new Error('No last position shared found while reconnecting'); } this.GameManager.reconnectToGameScene(this.lastPositionShared); - }); + });*/ } disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { - this.getSocket().on(EventMessage.WEBRTC_DISCONNECT, callback); + this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback); } } diff --git a/front/src/Phaser/Game/GameManager.ts b/front/src/Phaser/Game/GameManager.ts index 6c91972f..5d77e6a7 100644 --- a/front/src/Phaser/Game/GameManager.ts +++ b/front/src/Phaser/Game/GameManager.ts @@ -122,7 +122,7 @@ export class GameManager { this.currentGameScene = null; } - private timeoutCallback: NodeJS.Timeout|null = null; + /*private timeoutCallback: NodeJS.Timeout|null = null; reconnectToGameScene(lastPositionShared: PointInterface): void { if (this.timeoutCallback !== null) { console.log('Reconnect called but setTimeout in progress for the reconnection'); @@ -150,7 +150,7 @@ export class GameManager { const game : Phaser.Scene = GameScene.createFromUrl(this.oldMapUrlFile, this.oldInstance); this.reconnectScene.scene.add(this.oldSceneKey, game, true, { initPosition: lastPositionShared }); this.reconnectScene = null; - } + }*/ private getCurrentGameScene(): GameScene { if (this.currentGameScene === null) { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index a1181c0e..3374fc08 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -25,6 +25,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer"; import GameObject = Phaser.GameObjects.GameObject; import { Queue } from 'queue-typescript'; import {SimplePeer} from "../../WebRtc/SimplePeer"; +import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene"; export enum Textures { @@ -123,8 +124,7 @@ export class GameScene extends Phaser.Scene { this.MapKey = MapKey; this.MapUrlFile = MapUrlFile; - this.RoomId = this.instance + '__' + this.MapKey; - + this.RoomId = this.instance + '__' + GameScene.getMapKeyByUrl(MapUrlFile); } //hook preload scene @@ -186,11 +186,29 @@ export class GameScene extends Phaser.Scene { } }) + connection.onServerDisconnected(() => { + console.log('Player disconnected from server. Reloading scene.'); + + this.simplePeer.closeAllConnections(); + + let key = 'somekey'+Math.round(Math.random()*10000); + const game : Phaser.Scene = GameScene.createFromUrl(this.MapUrlFile, this.instance, key); + this.scene.add(key, game, false, + { + initPosition: { + x: this.CurrentPlayer.x, + y: this.CurrentPlayer.y + } + }); + this.scene.start(key); + }) + // When connection is performed, let's connect SimplePeer this.simplePeer = new SimplePeer(this.connection); - if (this.scene.isPaused()) { - this.scene.resume(); + if (this.scene.isSleeping()) { + this.scene.wake(); + this.scene.sleep(ReconnectingSceneName); } return connection; @@ -326,7 +344,13 @@ export class GameScene extends Phaser.Scene { // Let's pause the scene if the connection is not established yet if (this.connection === undefined) { - this.scene.pause(); + // Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking + setTimeout(() => { + if (this.connection === undefined) { + this.scene.sleep(); + this.scene.launch(ReconnectingSceneName); + } + }, 500); } } diff --git a/front/src/WebRtc/SimplePeer.ts b/front/src/WebRtc/SimplePeer.ts index 4adb1b18..381b3ac2 100644 --- a/front/src/WebRtc/SimplePeer.ts +++ b/front/src/WebRtc/SimplePeer.ts @@ -209,6 +209,12 @@ export class SimplePeer { } } + public closeAllConnections() { + for (const userId of this.PeerConnectionArray.keys()) { + this.closeConnection(userId); + } + } + /** * * @param userId diff --git a/front/tsconfig.json b/front/tsconfig.json index 84882e74..1661efa2 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -6,6 +6,7 @@ "noImplicitAny": true, "module": "CommonJS", "target": "es5", + "downlevelIteration": true, "jsx": "react", "allowJs": true,