WIP: how to respond to too many users
This commit is contained in:
parent
664e699fd3
commit
326c2e4183
5 changed files with 61 additions and 12 deletions
|
@ -163,8 +163,8 @@ export class IoSocketController {
|
||||||
let memberTags: string[] = [];
|
let memberTags: string[] = [];
|
||||||
let memberTextures: CharacterTexture[] = [];
|
let memberTextures: CharacterTexture[] = [];
|
||||||
const room = await socketManager.getOrCreateRoom(roomId);
|
const room = await socketManager.getOrCreateRoom(roomId);
|
||||||
if (room.getUsers().size > MAX_USERS_PER_ROOM) {
|
if (room.isFull()) {
|
||||||
res.writeStatus("302").end('Too many users');
|
res.writeStatus("503").end('Too many users');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import {HttpRequest, HttpResponse, TemplatedApp} from "uWebSockets.js";
|
||||||
import {BaseController} from "./BaseController";
|
import {BaseController} from "./BaseController";
|
||||||
import {parse} from "query-string";
|
import {parse} from "query-string";
|
||||||
import {adminApi} from "../Services/AdminApi";
|
import {adminApi} from "../Services/AdminApi";
|
||||||
|
import {socketManager} from "../Services/SocketManager";
|
||||||
|
|
||||||
//todo: delete this
|
//todo: delete this
|
||||||
export class MapController extends BaseController{
|
export class MapController extends BaseController{
|
||||||
|
@ -12,6 +13,7 @@ export class MapController extends BaseController{
|
||||||
super();
|
super();
|
||||||
this.App = App;
|
this.App = App;
|
||||||
this.getMapUrl();
|
this.getMapUrl();
|
||||||
|
this.canConnectToMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -67,4 +69,23 @@ export class MapController extends BaseController{
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
canConnectToMap() {
|
||||||
|
this.App.options("/canjoin", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
this.addCorsHeaders(res);
|
||||||
|
|
||||||
|
res.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
this.App.get("/canjoin", (res: HttpResponse, req: HttpRequest) => {
|
||||||
|
const query = parse(req.getQuery());
|
||||||
|
const roomId = query.roomId as string;
|
||||||
|
const world = socketManager.getWorlds().get(roomId);
|
||||||
|
if (!world) {
|
||||||
|
res.writeStatus('404').end('No room found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
res.writeStatus("200").end(world.isFull() ? '1':'0');
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
|
||||||
import {Movable} from "_Model/Movable";
|
import {Movable} from "_Model/Movable";
|
||||||
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
|
||||||
import {arrayIntersect} from "../Services/ArrayHelper";
|
import {arrayIntersect} from "../Services/ArrayHelper";
|
||||||
|
import {MAX_USERS_PER_ROOM} from "_Enum/EnvironmentVariable";
|
||||||
|
|
||||||
export type ConnectCallback = (user: User, group: Group) => void;
|
export type ConnectCallback = (user: User, group: Group) => void;
|
||||||
export type DisconnectCallback = (user: User, group: Group) => void;
|
export type DisconnectCallback = (user: User, group: Group) => void;
|
||||||
|
@ -179,6 +180,10 @@ export class GameRoom {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
isFull() : boolean {
|
||||||
|
return this.getUsers().size > MAX_USERS_PER_ROOM;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes a user leave a group and closes and destroy the group if the group contains only one remaining person.
|
* Makes a user leave a group and closes and destroy the group if the group contains only one remaining person.
|
||||||
*
|
*
|
||||||
|
|
|
@ -9,6 +9,11 @@ import {Room} from "./Room";
|
||||||
|
|
||||||
const URL_ROOM_STARTED = '/Floor0/floor0.json';
|
const URL_ROOM_STARTED = '/Floor0/floor0.json';
|
||||||
|
|
||||||
|
export enum connexionErrorTypes {
|
||||||
|
serverError = 1,
|
||||||
|
tooManyUsers,
|
||||||
|
}
|
||||||
|
|
||||||
class ConnectionManager {
|
class ConnectionManager {
|
||||||
private localUser!:LocalUser;
|
private localUser!:LocalUser;
|
||||||
|
|
||||||
|
@ -90,8 +95,12 @@ class ConnectionManager {
|
||||||
return new Promise<RoomConnection>((resolve, reject) => {
|
return new Promise<RoomConnection>((resolve, reject) => {
|
||||||
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
|
const connection = new RoomConnection(this.localUser.jwtToken, roomId, name, characterLayers, position, viewport);
|
||||||
connection.onConnectError((error: object) => {
|
connection.onConnectError((error: object) => {
|
||||||
console.log('An error occurred while connecting to socket server. Retrying');
|
console.log(error);
|
||||||
reject(error);
|
if (false) { //todo: how to check error type?
|
||||||
|
reject(connexionErrorTypes.tooManyUsers);
|
||||||
|
} else {
|
||||||
|
reject(connexionErrorTypes.serverError);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
connection.onConnect(() => {
|
connection.onConnect(() => {
|
||||||
resolve(connection);
|
resolve(connection);
|
||||||
|
@ -99,6 +108,8 @@ class ConnectionManager {
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
// Let's retry in 4-6 seconds
|
// Let's retry in 4-6 seconds
|
||||||
return new Promise<RoomConnection>((resolve, reject) => {
|
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(() => {
|
setTimeout(() => {
|
||||||
//todo: allow a way to break recurrsion?
|
//todo: allow a way to break recurrsion?
|
||||||
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
this.connectToRoomSocket(roomId, name, characterLayers, position, viewport).then((connection) => resolve(connection));
|
||||||
|
|
|
@ -48,13 +48,14 @@ import {ActionableItem} from "../Items/ActionableItem";
|
||||||
import {UserInputManager} from "../UserInput/UserInputManager";
|
import {UserInputManager} from "../UserInput/UserInputManager";
|
||||||
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
import {UserMovedMessage} from "../../Messages/generated/messages_pb";
|
||||||
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
import {ProtobufClientUtils} from "../../Network/ProtobufClientUtils";
|
||||||
import {connectionManager} from "../../Connexion/ConnectionManager";
|
import {connectionManager, connexionErrorTypes} from "../../Connexion/ConnectionManager";
|
||||||
import {RoomConnection} from "../../Connexion/RoomConnection";
|
import {RoomConnection} from "../../Connexion/RoomConnection";
|
||||||
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
import {GlobalMessageManager} from "../../Administration/GlobalMessageManager";
|
||||||
import {UserMessageManager} from "../../Administration/UserMessageManager";
|
import {UserMessageManager} from "../../Administration/UserMessageManager";
|
||||||
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
import {ConsoleGlobalMessageManager} from "../../Administration/ConsoleGlobalMessageManager";
|
||||||
import {ResizableScene} from "../Login/ResizableScene";
|
import {ResizableScene} from "../Login/ResizableScene";
|
||||||
import {Room} from "../../Connexion/Room";
|
import {Room} from "../../Connexion/Room";
|
||||||
|
import {MessageUI} from "../../Logger/MessageUI";
|
||||||
|
|
||||||
|
|
||||||
export enum Textures {
|
export enum Textures {
|
||||||
|
@ -122,6 +123,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
// A promise that will resolve when the "create" method is called (signaling loading is ended)
|
||||||
private createPromise: Promise<void>;
|
private createPromise: Promise<void>;
|
||||||
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
private createPromiseResolve!: (value?: void | PromiseLike<void>) => void;
|
||||||
|
private offlineMode: boolean = false;
|
||||||
|
|
||||||
MapKey: string;
|
MapKey: string;
|
||||||
MapUrlFile: string;
|
MapUrlFile: string;
|
||||||
|
@ -426,7 +428,7 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
if (this.connection === undefined) {
|
if (this.connection === undefined) {
|
||||||
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
|
// Let's wait 0.5 seconds before printing the "connecting" screen to avoid blinking
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (this.connection === undefined) {
|
if (this.connection === undefined && !this.offlineMode) {
|
||||||
this.scene.sleep();
|
this.scene.sleep();
|
||||||
this.scene.launch(ReconnectingSceneName);
|
this.scene.launch(ReconnectingSceneName);
|
||||||
}
|
}
|
||||||
|
@ -629,6 +631,11 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
this.scene.sleep(ReconnectingSceneName);
|
this.scene.sleep(ReconnectingSceneName);
|
||||||
|
|
||||||
return connection;
|
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.');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -956,13 +963,18 @@ export class GameScene extends ResizableScene implements CenterListener {
|
||||||
|
|
||||||
const nextSceneKey = this.checkToExit();
|
const nextSceneKey = this.checkToExit();
|
||||||
if (nextSceneKey) {
|
if (nextSceneKey) {
|
||||||
|
this.goToNextScene(nextSceneKey.key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private goToNextScene(nextSceneKey: string): void {
|
||||||
|
|
||||||
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
// We are completely destroying the current scene to avoid using a half-backed instance when coming back to the same map.
|
||||||
this.connection.closeConnection();
|
this.connection.closeConnection();
|
||||||
this.simplePeer.unregister();
|
this.simplePeer.unregister();
|
||||||
this.scene.stop();
|
this.scene.stop();
|
||||||
this.scene.remove(this.scene.key);
|
this.scene.remove(this.scene.key);
|
||||||
this.scene.start(nextSceneKey.key);
|
this.scene.start(nextSceneKey);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private checkToExit(): {key: string, hash: string} | null {
|
private checkToExit(): {key: string, hash: string} | null {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue