Compare commits
15 Commits
develop
...
usersLimit
Author | SHA1 | Date |
---|---|---|
Gregoire Parant | 3c0bd9da6c | |
David Négrier | 6f10a48d39 | |
Gregoire Parant | 1f7f94ea9c | |
Gregoire Parant | 00ce899a74 | |
Gregoire Parant | 1cf2f19e47 | |
Gregoire Parant | bd4b6e468e | |
Gregoire Parant | 7df605e771 | |
Gregoire Parant | 4f8b315727 | |
Gregoire Parant | fd1904c246 | |
grégoire parant | f002170ed5 | |
Gregoire Parant | ff9f5cfc32 | |
Gregoire Parant | f9bb749c56 | |
Gregoire Parant | 7ac4a2b849 | |
arp | 326c2e4183 | |
arp | 664e699fd3 |
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
tests/*
|
|
@ -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));
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue