Refactoring reconnection: putting it into the GameScene directly.

This commit is contained in:
David Négrier 2020-06-22 15:00:23 +02:00
parent d785a8a1bf
commit f88f28db3f
5 changed files with 101 additions and 58 deletions

View File

@ -124,7 +124,7 @@ export interface StartMapInterface {
} }
export class Connection implements Connection { export class Connection implements Connection {
socket: Socket|null = null; socket: Socket;
token: string|null = null; token: string|null = null;
name: string|null = null; // TODO: drop "name" storage here name: string|null = null; // TODO: drop "name" storage here
character: string|null = null; character: string|null = null;
@ -135,53 +135,59 @@ export class Connection implements Connection {
lastPositionShared: PointInterface|null = null; lastPositionShared: PointInterface|null = null;
lastRoom: string|null = null; lastRoom: string|null = null;
private constructor(GameManager: GameManager) { private constructor(GameManager: GameManager, name: string, character: string, token: string) {
this.GameManager = GameManager; 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<Connection> { public static createConnection(name: string, characterSelected: string): Promise<Connection> {
let connection = new Connection(gameManager);
connection.name = name;
connection.character = characterSelected;
return Axios.post(`${API_URL}/login`, {name: name}) return Axios.post(`${API_URL}/login`, {name: name})
.then((res) => { .then((res) => {
connection.token = res.data.token; let connection = new Connection(gameManager, name, characterSelected, res.data.token);
connection.socket = SocketIo(`${API_URL}`, {
query: {
token: connection.token
}
});
//listen event
connection.disconnectServer();
connection.errorMessage();
// 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(); return connection.connectSocketServer();
}) })
.catch((err) => { .catch((err) => {
console.error(err); // Let's retry in 4-6 seconds
throw err; return new Promise<Connection>((resolve, reject) => {
setTimeout(() => {
resolve(Connection.createConnection(name, characterSelected));
}, 4000 + Math.floor(Math.random() * 2000) );
});
//console.error(err);
//throw err;
}); });
} }
public closeConnection(): void { public closeConnection(): void {
this.socket?.close(); this.socket?.close();
this.socket = null;
this.lastPositionShared = null; this.lastPositionShared = null;
this.lastRoom = 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<Connection>{ connectSocketServer(): Promise<Connection>{
return new Promise<Connection>((resolve, reject) => { return new Promise<Connection>((resolve, reject) => {
this.getSocket().emit(EventMessage.SET_PLAYER_DETAILS, { this.socket.emit(EventMessage.SET_PLAYER_DETAILS, {
name: this.name, name: this.name,
character: this.character character: this.character
} as SetPlayerDetailsMessage, (id: string) => { } as SetPlayerDetailsMessage, (id: string) => {
@ -218,7 +224,7 @@ export class Connection implements Connection {
const point = new Point(startX, startY, direction, moving); const point = new Point(startX, startY, direction, moving);
this.lastPositionShared = point; this.lastPositionShared = point;
let promise = new Promise<MessageUserPositionInterface[]>((resolve, reject) => { let promise = new Promise<MessageUserPositionInterface[]>((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); //this.GameManager.initUsersPosition(userPositions);
resolve(userPositions); resolve(userPositions);
}); });
@ -233,31 +239,31 @@ export class Connection implements Connection {
} }
const point = new Point(x, y, direction, moving); const point = new Point(x, y, direction, moving);
this.lastPositionShared = point; this.lastPositionShared = point;
this.getSocket().emit(EventMessage.USER_POSITION, point); this.socket.emit(EventMessage.USER_POSITION, point);
} }
public onUserJoins(callback: (message: MessageUserJoined) => void): void { 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 { 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 { 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 { 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 { 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) { 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, userId: userId ? userId : this.userId,
receiverId: receiverId ? receiverId : this.userId, receiverId: receiverId ? receiverId : this.userId,
roomId: roomId, roomId: roomId,
@ -266,47 +272,53 @@ export class Connection implements Connection {
} }
receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) { receiveWebrtcStart(callback: (message: WebRtcStartMessageInterface) => void) {
this.getSocket().on(EventMessage.WEBRTC_START, callback); this.socket.on(EventMessage.WEBRTC_START, callback);
} }
receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) { receiveWebrtcSignal(callback: (message: WebRtcSignalMessageInterface) => void) {
return this.getSocket().on(EventMessage.WEBRTC_SIGNAL, callback); return this.socket.on(EventMessage.WEBRTC_SIGNAL, callback);
} }
private errorMessage(): void { public onServerDisconnected(callback: (reason: string) => void): void {
this.getSocket().on(EventMessage.MESSAGE_ERROR, (message: string) => { /*this.socket.on(EventMessage.CONNECT_ERROR, (error: object) => {
console.error(EventMessage.MESSAGE_ERROR, message); callback(error);
}) });*/
}
private disconnectServer(): void { this.socket.on('disconnect', (reason: string) => {
this.getSocket().on(EventMessage.CONNECT_ERROR, () => { if (reason === 'io client disconnect') {
this.GameManager.switchToDisconnectedScene(); // 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'); console.log('Trying to reconnect');
}); });
this.getSocket().on(EventMessage.RECONNECT_ERROR, () => { this.socket.on(EventMessage.RECONNECT_ERROR, () => {
console.log('Error while trying to reconnect.'); 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.'); console.error('Reconnection failed. Giving up.');
}); });
this.getSocket().on(EventMessage.RECONNECT, () => { this.socket.on(EventMessage.RECONNECT, () => {
console.log('Reconnect event triggered'); console.log('Reconnect event triggered');
this.connectSocketServer(); this.connectSocketServer();
if (this.lastPositionShared === null) { if (this.lastPositionShared === null) {
throw new Error('No last position shared found while reconnecting'); throw new Error('No last position shared found while reconnecting');
} }
this.GameManager.reconnectToGameScene(this.lastPositionShared); this.GameManager.reconnectToGameScene(this.lastPositionShared);
}); });*/
} }
disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void { disconnectMessage(callback: (message: WebRtcDisconnectMessageInterface) => void): void {
this.getSocket().on(EventMessage.WEBRTC_DISCONNECT, callback); this.socket.on(EventMessage.WEBRTC_DISCONNECT, callback);
} }
} }

View File

@ -122,7 +122,7 @@ export class GameManager {
this.currentGameScene = null; this.currentGameScene = null;
} }
private timeoutCallback: NodeJS.Timeout|null = null; /*private timeoutCallback: NodeJS.Timeout|null = null;
reconnectToGameScene(lastPositionShared: PointInterface): void { reconnectToGameScene(lastPositionShared: PointInterface): void {
if (this.timeoutCallback !== null) { if (this.timeoutCallback !== null) {
console.log('Reconnect called but setTimeout in progress for the reconnection'); 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); const game : Phaser.Scene = GameScene.createFromUrl(this.oldMapUrlFile, this.oldInstance);
this.reconnectScene.scene.add(this.oldSceneKey, game, true, { initPosition: lastPositionShared }); this.reconnectScene.scene.add(this.oldSceneKey, game, true, { initPosition: lastPositionShared });
this.reconnectScene = null; this.reconnectScene = null;
} }*/
private getCurrentGameScene(): GameScene { private getCurrentGameScene(): GameScene {
if (this.currentGameScene === null) { if (this.currentGameScene === null) {

View File

@ -25,6 +25,7 @@ import {RemotePlayer} from "../Entity/RemotePlayer";
import GameObject = Phaser.GameObjects.GameObject; import GameObject = Phaser.GameObjects.GameObject;
import { Queue } from 'queue-typescript'; import { Queue } from 'queue-typescript';
import {SimplePeer} from "../../WebRtc/SimplePeer"; import {SimplePeer} from "../../WebRtc/SimplePeer";
import {ReconnectingSceneName} from "../Reconnecting/ReconnectingScene";
export enum Textures { export enum Textures {
@ -123,8 +124,7 @@ export class GameScene extends Phaser.Scene {
this.MapKey = MapKey; this.MapKey = MapKey;
this.MapUrlFile = MapUrlFile; this.MapUrlFile = MapUrlFile;
this.RoomId = this.instance + '__' + this.MapKey; this.RoomId = this.instance + '__' + GameScene.getMapKeyByUrl(MapUrlFile);
} }
//hook preload scene //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 // When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection); this.simplePeer = new SimplePeer(this.connection);
if (this.scene.isPaused()) { if (this.scene.isSleeping()) {
this.scene.resume(); this.scene.wake();
this.scene.sleep(ReconnectingSceneName);
} }
return connection; return connection;
@ -326,7 +344,13 @@ export class GameScene extends Phaser.Scene {
// Let's pause the scene if the connection is not established yet // Let's pause the scene if the connection is not established yet
if (this.connection === undefined) { 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);
} }
} }

View File

@ -209,6 +209,12 @@ export class SimplePeer {
} }
} }
public closeAllConnections() {
for (const userId of this.PeerConnectionArray.keys()) {
this.closeConnection(userId);
}
}
/** /**
* *
* @param userId * @param userId

View File

@ -6,6 +6,7 @@
"noImplicitAny": true, "noImplicitAny": true,
"module": "CommonJS", "module": "CommonJS",
"target": "es5", "target": "es5",
"downlevelIteration": true,
"jsx": "react", "jsx": "react",
"allowJs": true, "allowJs": true,