Compare commits

...

15 Commits

Author SHA1 Message Date
Gregoire Parant 3c0bd9da6c Merge branch 'develop' into usersLimit
# Conflicts:
#	back/src/Controller/IoSocketController.ts
#	front/src/Connexion/RoomConnection.ts
#	front/src/Phaser/Game/GameScene.ts
2020-11-21 19:01:25 +01:00
David Négrier 6f10a48d39
Typo fix 2020-11-04 16:37:15 +01:00
Gregoire Parant 1f7f94ea9c Feedback review MR
- Extend the ResizableScene instead.
- Remove ErrorScene
- Add git ignore
2020-11-04 12:39:28 +01:00
Gregoire Parant 00ce899a74 Merge branch 'develop' into usersLimit
# Conflicts:
#	back/src/Services/SocketManager.ts
2020-11-01 12:00:31 +01:00
Gregoire Parant 1cf2f19e47 Merge branch 'develop' into usersLimit
# Conflicts:
#	front/src/Phaser/Game/GameScene.ts
2020-10-24 10:50:37 +02:00
Gregoire Parant bd4b6e468e Fix conflict 2020-10-22 02:03:58 +02:00
Gregoire Parant 7df605e771 remove useless 2020-10-22 01:58:53 +02:00
Gregoire Parant 4f8b315727 Merge branch 'develop' into usersLimit
# Conflicts:
#	back/src/Controller/IoSocketController.ts
#	front/src/Phaser/Game/GameScene.ts
2020-10-22 01:56:57 +02:00
Gregoire Parant fd1904c246 delete canjoin function 2020-10-22 01:55:38 +02:00
grégoire parant f002170ed5
Merge pull request #355 from thecodingmachine/user-limit-grp
Create and send close message
2020-10-22 01:47:53 +02:00
Gregoire Parant ff9f5cfc32 Revert "Create and send close message"
This reverts commit 7ac4a2b849.

# Conflicts:
#	back/src/Controller/IoSocketController.ts
#	back/src/Services/SocketManager.ts
#	front/src/Phaser/Game/GameScene.ts
2020-10-22 01:46:41 +02:00
Gregoire Parant f9bb749c56 Redirect user on wait scene
- Create wait scene
- Load wait scne with status close message
2020-10-22 01:33:36 +02:00
Gregoire Parant 7ac4a2b849 Create and send close message
# TODO
- Show error or wait room
2020-10-21 19:30:02 +02:00
arp 326c2e4183 WIP: how to respond to too many users 2020-10-21 17:43:00 +02:00
arp 664e699fd3 added a users limit per room 2020-10-21 11:33:57 +02:00
9 changed files with 194 additions and 11 deletions

View File

@ -22,7 +22,7 @@ import {adminApi, CharacterTexture, FetchMemberDataByUuidResponse} from "../Serv
import {SocketManager, socketManager} from "../Services/SocketManager";
import {emitInBatch} from "../Services/IoSocketHelpers";
import {clientEventsEmitter} from "../Services/ClientEventsEmitter";
import {ADMIN_API_TOKEN, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
import {ADMIN_API_TOKEN, MAX_USERS_PER_ROOM, ADMIN_API_URL, SOCKET_IDLE_TIMER} from "../Enum/EnvironmentVariable";
export class IoSocketController {
private nextUserId: number = 1;
@ -164,6 +164,7 @@ export class IoSocketController {
let memberTags: string[] = [];
let memberTextures: CharacterTexture[] = [];
const room = await socketManager.getOrCreateRoom(roomId);
if(room.isFull){
throw new Error('Room is full');
@ -240,7 +241,13 @@ export class IoSocketController {
open: (ws) => {
// Let's join the room
const client = this.initClient(ws); //todo: into the upgrade instead?
socketManager.handleJoinRoom(client);
const room = socketManager.getRoomById(client.roomId);
if (room && room.isFull) {
socketManager.emitCloseMessage(client, 302);
}else {
socketManager.handleJoinRoom(client);
}
//get data information and show messages
if (ADMIN_API_URL) {
@ -262,7 +269,14 @@ export class IoSocketController {
}
},
message: (ws, arrayBuffer, isBinary): void => {
const client = ws as ExSocketInterface;
const room = socketManager.getRoomById(client.roomId);
if (room && room.isFull) {
return;
}
const message = ClientToServerMessage.deserializeBinary(new Uint8Array(arrayBuffer));
if (message.hasViewportmessage()) {
@ -286,7 +300,6 @@ export class IoSocketController {
} else if (message.hasQueryjitsijwtmessage()){
socketManager.handleQueryJitsiJwtMessage(client, message.getQueryjitsijwtmessage() as QueryJitsiJwtMessage);
}
/* Ok is false if backpressure was built up, wait for drain */
//let ok = ws.send(message, isBinary);
},

View File

@ -23,7 +23,9 @@ import {
WebRtcStartMessage,
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
SendUserMessage
CharacterLayerMessage,
SendUserMessage,
CloseMessage
} from "../Messages/generated/messages_pb";
import {PointInterface} from "../Model/Websocket/PointInterface";
import {User} from "../Model/User";
@ -57,7 +59,7 @@ export interface AdminSocketData {
export class SocketManager {
private Worlds: Map<string, GameRoom> = new Map<string, GameRoom>();
private sockets: Map<number, ExSocketInterface> = new Map<number, ExSocketInterface>();
constructor() {
clientEventsEmitter.registerToClientJoin((clientUUid: string, roomId: string) => {
gaugeManager.incNbClientPerRoomGauge(roomId);
@ -390,6 +392,10 @@ export class SocketManager {
return Promise.resolve(world)
}
getRoomById(roomId: string) {
return this.Worlds.get(roomId);
}
private joinRoom(client : ExSocketInterface, position: PointInterface): GameRoom {
const roomId = client.roomId;
@ -677,6 +683,19 @@ export class SocketManager {
return socket;
}
public emitCloseMessage(socket: ExSocketInterface, status: number): ExSocketInterface {
const closeMessage = new CloseMessage();
closeMessage.setStatus(status);
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setClosemessage(closeMessage);
if (!socket.disconnecting) {
socket.send(serverToClientMessage.serializeBinary().buffer, true);
}
return socket;
}
/**
* Merges the characterLayers received from the front (as an array of string) with the custom textures from the back.
*/

1
front/dist/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
tests/*

View File

@ -9,6 +9,11 @@ import {Room} from "./Room";
const URL_ROOM_STARTED = '/Floor0/floor0.json';
export enum connexionErrorTypes {
serverError = 1,
tooManyUsers,
}
class ConnectionManager {
private localUser!:LocalUser;
@ -92,8 +97,11 @@ class ConnectionManager {
return new Promise<RoomConnection>((resolve, reject) => {
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
connection.onConnectError((error: object) => {
console.log('An error occurred while connecting to socket server. Retrying');
reject(error);
if (error) { //todo: how to check error type?
reject(connexionErrorTypes.tooManyUsers);
} else {
reject(connexionErrorTypes.serverError);
}
});
connection.onConnect(() => {
resolve(connection);
@ -101,6 +109,8 @@ class ConnectionManager {
}).catch((err) => {
// Let's retry in 4-6 seconds
return new Promise<RoomConnection>((resolve, reject) => {
if (err === connexionErrorTypes.tooManyUsers) return reject(err);
console.log('An error occurred while connecting to socket server. Retrying');
setTimeout(() => {
//todo: allow a way to break recurrsion?
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));

View File

@ -30,6 +30,8 @@ export enum EventMessage{
TELEPORT = "teleport",
USER_MESSAGE = "user-message",
START_JITSI_ROOM = "start-jitsi-room",
CLOSE_MESSAGE = "close-message",
}
export interface PointInterface {

View File

@ -26,6 +26,8 @@ import {
QueryJitsiJwtMessage,
SendJitsiJwtMessage,
CharacterLayerMessage,
SendUserMessage,
CloseMessage,
PingMessage,
SendUserMessage
} from "../Messages/generated/messages_pb"
@ -162,10 +164,11 @@ export class RoomConnection implements RoomConnection {
this.dispatch(EventMessage.START_JITSI_ROOM, message.getSendjitsijwtmessage());
} else if (message.hasSendusermessage()) {
this.dispatch(EventMessage.USER_MESSAGE, message.getSendusermessage());
} else if (message.hasClosemessage()) {
this.dispatch(EventMessage.CLOSE_MESSAGE, message.getClosemessage());
} else {
throw new Error('Unknown message received');
}
}
}
@ -546,6 +549,12 @@ export class RoomConnection implements RoomConnection {
});
}
public onCloseMessage(callback: (status: number) => void): void {
return this.onMessage(EventMessage.CLOSE_MESSAGE, (message: CloseMessage) => {
callback(message.getStatus());
});
}
public hasTag(tag: string): boolean {
return this.tags.includes(tag);
}

View File

@ -46,7 +46,7 @@ import {ActionableItem} from "../Items/ActionableItem";
import {UserInputManager} from "../UserInput/UserInputManager";
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
import {connectionManager} from "../../Connexion/ConnectionManager";
import {connectionManager, connexionErrorTypes} from "../../Connexion/ConnectionManager";
import {RoomConnection} from "../../Connexion/RoomConnection";
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
import {UserMessageManager} from "../../Administration/UserMessageManager";
@ -54,6 +54,13 @@ import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMes
import {ResizableScene} from "../Login/ResizableScene";
import {Room} from "../../Connexion/Room";
import {jitsiFactory} from "../../WebRtc/JitsiFactory";
import {MessageUI} from "../../Logger/MessageUI";
import {ErrorScene} from "../Reconnecting/ErrorScene";
export enum Textures {
Player = "male1"
}
export interface GameSceneInitInterface {
initPosition: PointInterface|null
@ -117,6 +124,7 @@ export class GameScene extends ResizableScene implements CenterListener {
// A promise that will resolve when the "create" method is called (signaling loading is ended)
private createPromise: Promise<void>;
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
private offlineMode: boolean = false;
MapKey: string;
MapUrlFile: string;
@ -395,7 +403,7 @@ export class GameScene extends ResizableScene implements CenterListener {
if (this.connection === undefined) {
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
setTimeout(() => {
if (this.connection === undefined) {
if (this.connection === undefined && !this.offlineMode) {
this.scene.sleep();
this.scene.launch(ReconnectingSceneName);
}
@ -539,6 +547,46 @@ export class GameScene extends ResizableScene implements CenterListener {
this.startJitsi(room, jwt);
});
connection.onCloseMessage((status: number) => {
this.connection.closeConnection();
this.simplePeer.unregister();
connection.closeConnection();
const waitGameSceneKey = 'somekey' + Math.round(Math.random() * 10000);
//show wait scene
setTimeout(() => {
/**
* Note: the ErrorScene could then become a singleton. In the future,
* an error message could originate from the server directly.
* **/
const game: Phaser.Scene = new ErrorScene(waitGameSceneKey);
this.scene.add(waitGameSceneKey, game, true, {
initPosition: {
x: this.CurrentPlayer.x,
y: this.CurrentPlayer.y
}
});
this.scene.stop(this.scene.key);
this.scene.start(waitGameSceneKey, {
status: status,
text : 'Oops! WorkAdventure is too popular, ' +
'\n' +
'\n' +
'the maximum number of players has been reached!' +
'\n' +
'\n' +
`Reconnect in 30 secondes ...`
});
}, 500);
//trying to reload map
setTimeout(() => {
this.scene.stop(waitGameSceneKey);
this.scene.remove(waitGameSceneKey);
this.scene.start(this.scene.key);
}, 30000);
});
// When connection is performed, let's connect SimplePeer
this.simplePeer = new SimplePeer(this.connection, !this.room.isPublic, this.GameManager.getPlayerName());
this.GlobalMessageManager = new GlobalMessageManager(this.connection);
@ -572,6 +620,11 @@ export class GameScene extends ResizableScene implements CenterListener {
this.gameMap.setPosition(this.CurrentPlayer.x, this.CurrentPlayer.y);
return connection;
}).catch(error => {
if (error === connexionErrorTypes.tooManyUsers) {
this.offlineMode = true;
MessageUI.warningMessage('Too many users. You switched to offline mode. Please try to connect again later.');
}
});
}
@ -621,7 +674,7 @@ export class GameScene extends ResizableScene implements CenterListener {
this.chatModeSprite.setFrame(3);
}
}
private initStartXAndStartY() {
// If there is an init position passed
if (this.initPosition !== null) {

View File

@ -0,0 +1,71 @@
import {TextField} from "../Components/TextField";
import Image = Phaser.GameObjects.Image;
import {ResizableScene} from "../Login/ResizableScene";
enum ReconnectingTextures {
icon = "icon",
mainFont = "main_font"
}
export class ErrorScene extends ResizableScene {
private reconnectingField!: TextField;
private catImage!: Phaser.Physics.Arcade.Sprite;
private logo!: Image;
private text: string = '';
private status: number = 404;
constructor(key: string) {
super({
key: key
});
}
init(data: {status: number, text: string}){
this.text = data.text;
this .status = data.status;
}
preload() {
this.load.image(ReconnectingTextures.icon, "resources/logos/tcm_full.png");
// Note: arcade.png from the Phaser 3 examples at: https://github.com/photonstorm/phaser3-examples/tree/master/public/assets/fonts/bitmap
this.load.bitmapFont(ReconnectingTextures.mainFont, 'resources/fonts/arcade.png', 'resources/fonts/arcade.xml');
this.load.spritesheet(
'cat',
'resources/characters/pipoya/Cat 01-1.png',
{frameWidth: 32, frameHeight: 32}
);
}
create() {
this.logo = new Image(this, this.game.renderer.width - 30, this.game.renderer.height - 20, ReconnectingTextures.icon);
this.add.existing(this.logo);
this.reconnectingField = new TextField(
this,
this.game.renderer.width / 2,
this.game.renderer.height / 2,
this.text);
this.catImage = this.physics.add.sprite(
this.game.renderer.width / 2,
this.game.renderer.height / 2 - 70,
'cat');
this.anims.create({
key: 'right',
frames: this.anims.generateFrameNumbers('cat', {start: 6, end: 8}),
frameRate: 10,
repeat: -1
});
this.catImage.play('right');
}
onResize(){
this.reconnectingField.x = this.game.renderer.width / 2;
this.reconnectingField.y = this.game.renderer.height / 2;
this.catImage.x = this.game.renderer.width / 2;
this.catImage.y = this.game.renderer.height / 2 - 70;
this.logo.x = this.game.renderer.width - 30;
this.logo.y = this.game.renderer.height - 30;
}
}

View File

@ -193,6 +193,10 @@ message SendUserMessage{
string message = 2;
}
message CloseMessage{
int32 status = 1;
}
message ServerToClientMessage {
oneof message {
BatchMessage batchMessage = 1;
@ -207,5 +211,6 @@ message ServerToClientMessage {
TeleportMessageMessage teleportMessageMessage = 10;
SendJitsiJwtMessage sendJitsiJwtMessage = 11;
SendUserMessage sendUserMessage = 12;
CloseMessage closeMessage = 13;
}
}