Adding a Pusher container as a middleware/dispatcher between front and back

This commit is contained in:
David Négrier 2020-11-13 18:00:22 +01:00
parent 6c6d046891
commit 4c1e566a6c
86 changed files with 12172 additions and 983 deletions

View file

@ -1,6 +1,6 @@
import {PointInterface} from "./Websocket/PointInterface";
import {Group} from "./Group";
import {User} from "./User";
import {User, UserSocket} from "./User";
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {PositionInterface} from "_Model/PositionInterface";
import {Identificable} from "_Model/Websocket/Identificable";
@ -11,6 +11,9 @@ import {Movable} from "_Model/Movable";
import {extractDataFromPrivateRoomId, extractRoomSlugPublicRoomId, isRoomAnonymous} from "./RoomIdentifier";
import {arrayIntersect} from "../Services/ArrayHelper";
import {MAX_USERS_PER_ROOM} from "../Enum/EnvironmentVariable";
import {JoinRoomMessage} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "../Model/Websocket/ProtobufUtils";
import { ZoneSocket } from "src/RoomManager";
export type ConnectCallback = (user: User, group: Group) => void;
export type DisconnectCallback = (user: User, group: Group) => void;
@ -42,6 +45,7 @@ export class GameRoom {
public readonly roomSlug: string;
public readonly worldSlug: string = '';
public readonly organizationSlug: string = '';
private nextUserId: number = 1;
constructor(roomId: string,
connectCallback: ConnectCallback,
@ -85,26 +89,34 @@ export class GameRoom {
return this.users;
}
public join(socket : ExSocketInterface, userPosition: PointInterface): void {
const user = new User(socket.userId, socket.userUuid, userPosition, false, this.positionNotifier, socket);
this.users.set(socket.userId, user);
public join(socket : UserSocket, joinRoomMessage: JoinRoomMessage): User {
const positionMessage = joinRoomMessage.getPositionmessage();
if (positionMessage === undefined) {
throw new Error('Missing position message');
}
const position = ProtobufUtils.toPointInterface(positionMessage);
const user = new User(this.nextUserId, joinRoomMessage.getUseruuid(), position, false, this.positionNotifier, socket, joinRoomMessage.getTagList(), joinRoomMessage.getName(), ProtobufUtils.toCharacterLayerObjects(joinRoomMessage.getCharacterlayerList()));
this.nextUserId++;
this.users.set(user.id, user);
// Let's call update position to trigger the join / leave room
//this.updatePosition(socket, userPosition);
this.updateUserGroup(user);
return user;
}
public leave(user : Identificable){
const userObj = this.users.get(user.userId);
public leave(user : User){
const userObj = this.users.get(user.id);
if (userObj === undefined) {
console.warn('User ', user.userId, 'does not belong to world! It should!');
console.warn('User ', user.id, 'does not belong to this game room! It should!');
}
if (userObj !== undefined && typeof userObj.group !== 'undefined') {
this.leaveGroup(userObj);
}
this.users.delete(user.userId);
this.users.delete(user.id);
if (userObj !== undefined) {
this.positionNotifier.removeViewport(userObj);
this.positionNotifier.leave(userObj);
}
}
@ -117,12 +129,7 @@ export class GameRoom {
return this.users.size === 0;
}
public updatePosition(socket : Identificable, userPosition: PointInterface): void {
const user = this.users.get(socket.userId);
if(typeof user === 'undefined') {
return;
}
public updatePosition(user : User, userPosition: PointInterface): void {
user.setPosition(userPosition);
this.updateUserGroup(user);
@ -170,12 +177,7 @@ export class GameRoom {
}
}
setSilent(socket: Identificable, silent: boolean) {
const user = this.users.get(socket.userId);
if(typeof user === 'undefined') {
console.warn('In setSilent, could not find user with ID "'+socket.userId+'" in world.');
return;
}
setSilent(user: User, silent: boolean) {
if (user.silent === silent) {
return;
}
@ -186,7 +188,7 @@ export class GameRoom {
}
if (!silent) {
// If we are back to life, let's trigger a position update to see if we can join some group.
this.updatePosition(socket, user.getPosition());
this.updatePosition(user, user.getPosition());
}
}
@ -281,17 +283,15 @@ export class GameRoom {
return this.itemsState;
}
setViewport(socket : Identificable, viewport: ViewportInterface): Movable[] {
const user = this.users.get(socket.userId);
if(typeof user === 'undefined') {
console.warn('In setViewport, could not find user with ID "'+socket.userId+'" in world.');
return [];
}
return this.positionNotifier.setViewport(user, viewport);
}
canAccess(userTags: string[]): boolean {
public canAccess(userTags: string[]): boolean {
return arrayIntersect(userTags, this.tags);
}
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
return this.positionNotifier.addZoneListener(call, x, y);
}
public removeZoneListener(call: ZoneSocket, x: number, y: number): void {
return this.positionNotifier.removeZoneListener(call, x, y);
}
}

View file

@ -9,11 +9,9 @@
* number of players around the current player.
*/
import {EntersCallback, LeavesCallback, MovesCallback, Zone} from "./Zone";
import {PointInterface} from "_Model/Websocket/PointInterface";
import {User} from "_Model/User";
import {ViewportInterface} from "_Model/Websocket/ViewportMessage";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
import {ZoneSocket} from "../RoomManager";
interface ZoneDescriptor {
i: number;
@ -36,44 +34,6 @@ export class PositionNotifier {
}
}
/**
* Sets the viewport coordinates.
* Returns the list of new users to add
*/
public setViewport(user: User, viewport: ViewportInterface): Movable[] {
if (viewport.left > viewport.right || viewport.top > viewport.bottom) {
console.warn('Invalid viewport received: ', viewport);
return [];
}
const oldZones = user.listenedZones;
const newZones = new Set<Zone>();
const topLeftDesc = this.getZoneDescriptorFromCoordinates(viewport.left, viewport.top);
const bottomRightDesc = this.getZoneDescriptorFromCoordinates(viewport.right, viewport.bottom);
for (let j = topLeftDesc.j; j <= bottomRightDesc.j; j++) {
for (let i = topLeftDesc.i; i <= bottomRightDesc.i; i++) {
newZones.add(this.getZone(i, j));
}
}
const addedZones = [...newZones].filter(x => !oldZones.has(x));
const removedZones = [...oldZones].filter(x => !newZones.has(x));
let things: Movable[] = [];
for (const zone of addedZones) {
zone.startListening(user);
things = things.concat(Array.from(zone.getThings()))
}
for (const zone of removedZones) {
zone.stopListening(user);
}
return things;
}
public enter(thing: Movable): void {
const position = thing.getPosition();
const zoneDesc = this.getZoneDescriptorFromCoordinates(position.x, position.y);
@ -108,13 +68,6 @@ export class PositionNotifier {
oldZone.leave(thing, null);
}
public removeViewport(user: User): void {
// Also, let's stop listening on viewports
for (const zone of user.listenedZones) {
zone.stopListening(user);
}
}
private getZone(i: number, j: number): Zone {
let zoneRow = this.zones[j];
if (zoneRow === undefined) {
@ -129,4 +82,15 @@ export class PositionNotifier {
}
return zone;
}
public addZoneListener(call: ZoneSocket, x: number, y: number): Set<Movable> {
const zone = this.getZone(x, y);
zone.addListener(call);
return zone.getThings();
}
public removeZoneListener(call: ZoneSocket, x: number, y: number): void {
const zone = this.getZone(x, y);
zone.removeListener(call);
}
}

View file

@ -2,9 +2,13 @@ import { Group } from "./Group";
import { PointInterface } from "./Websocket/PointInterface";
import {Zone} from "_Model/Zone";
import {Movable} from "_Model/Movable";
import {PositionInterface} from "_Model/PositionInterface";
import {PositionNotifier} from "_Model/PositionNotifier";
import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
import {ServerDuplexStream, ServerWritableStream} from "grpc";
import {BatchMessage, PusherToBackMessage, ServerToClientMessage, SubMessage} from "../Messages/generated/messages_pb";
import {ProtobufUtils} from "_Model/Websocket/ProtobufUtils";
import {CharacterLayer, ExSocketInterface} from "_Model/Websocket/ExSocketInterface";
export type UserSocket = ServerDuplexStream<PusherToBackMessage, ServerToClientMessage>;
export class User implements Movable {
public listenedZones: Set<Zone>;
@ -12,11 +16,14 @@ export class User implements Movable {
public constructor(
public id: number,
public uuid: string,
public readonly uuid: string,
private position: PointInterface,
public silent: boolean,
private positionNotifier: PositionNotifier,
public readonly socket: ExSocketInterface
public readonly socket: UserSocket,
public readonly tags: string[],
public readonly name: string,
public readonly characterLayers: CharacterLayer[]
) {
this.listenedZones = new Set<Zone>();
@ -32,4 +39,27 @@ export class User implements Movable {
this.position = position;
this.positionNotifier.updatePosition(this, position, oldPosition);
}
private batchedMessages: BatchMessage = new BatchMessage();
private batchTimeout: NodeJS.Timeout|null = null;
public emitInBatch(payload: SubMessage): void {
this.batchedMessages.addPayload(payload);
if (this.batchTimeout === null) {
this.batchTimeout = setTimeout(() => {
/*if (socket.disconnecting) {
return;
}*/
const serverToClientMessage = new ServerToClientMessage();
serverToClientMessage.setBatchmessage(this.batchedMessages);
this.socket.write(serverToClientMessage);
this.batchedMessages = new BatchMessage();
this.batchTimeout = null;
}, 100);
}
}
}

View file

@ -13,7 +13,7 @@ import {PositionInterface} from "_Model/PositionInterface";
export class ProtobufUtils {
public static toPositionMessage(point: PointInterface): PositionMessage {
let direction: PositionMessage.DirectionMap[keyof PositionMessage.DirectionMap];
let direction: Direction;
switch (point.direction) {
case 'up':
direction = Direction.UP;
@ -105,4 +105,14 @@ export class ProtobufUtils {
return message;
});
}
public static toCharacterLayerObjects(characterLayers: CharacterLayerMessage[]): CharacterLayer[] {
return characterLayers.map(function(characterLayer): CharacterLayer {
const url = characterLayer.getUrl();
return {
name: characterLayer.getName(),
url: url ? url : undefined,
};
});
}
}

View file

@ -2,20 +2,21 @@ import {User} from "./User";
import {PositionInterface} from "_Model/PositionInterface";
import {Movable} from "./Movable";
import {Group} from "./Group";
import {ZoneSocket} from "../RoomManager";
export type EntersCallback = (thing: Movable, listener: User) => void;
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: User) => void;
export type LeavesCallback = (thing: Movable, listener: User) => void;
export type EntersCallback = (thing: Movable, fromZone: Zone|null, listener: ZoneSocket) => void;
export type MovesCallback = (thing: Movable, position: PositionInterface, listener: ZoneSocket) => void;
export type LeavesCallback = (thing: Movable, newZone: Zone|null, listener: ZoneSocket) => void;
export class Zone {
private things: Set<Movable> = new Set<Movable>();
private listeners: Set<User> = new Set<User>();
private listeners: Set<ZoneSocket> = new Set<ZoneSocket>();
/**
* @param x For debugging purpose only
* @param y For debugging purpose only
*/
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, private x: number, private y: number) {
constructor(private onEnters: EntersCallback, private onMoves: MovesCallback, private onLeaves: LeavesCallback, public readonly x: number, public readonly y: number) {
}
/**
@ -40,9 +41,9 @@ export class Zone {
*/
private notifyLeft(thing: Movable, newZone: Zone|null) {
for (const listener of this.listeners) {
if (listener !== thing && (newZone === null || !listener.listenedZones.has(newZone))) {
this.onLeaves(thing, listener);
}
//if (listener !== thing && (newZone === null || !listener.listenedZones.has(newZone))) {
this.onLeaves(thing, newZone, listener);
//}
}
}
@ -56,17 +57,20 @@ export class Zone {
*/
private notifyEnter(thing: Movable, oldZone: Zone|null, position: PositionInterface) {
for (const listener of this.listeners) {
if (listener === thing) {
/*if (listener === thing) {
continue;
}
if (oldZone === null || !listener.listenedZones.has(oldZone)) {
this.onEnters(thing, listener);
} else {
this.onMoves(thing, position, listener);
}
}*/
this.onEnters(thing, oldZone, listener);
}
}
public move(thing: Movable, position: PositionInterface) {
if (!this.things.has(thing)) {
this.things.add(thing);
@ -75,13 +79,13 @@ export class Zone {
}
for (const listener of this.listeners) {
if (listener !== thing) {
//if (listener !== thing) {
this.onMoves(thing,position, listener);
}
//}
}
}
public startListening(listener: User): void {
/*public startListening(listener: User): void {
for (const thing of this.things) {
if (thing !== listener) {
this.onEnters(thing, listener);
@ -101,9 +105,18 @@ export class Zone {
this.listeners.delete(listener);
listener.listenedZones.delete(this);
}
}*/
public getThings(): Set<Movable> {
return this.things;
}
public addListener(socket: ZoneSocket): void {
this.listeners.add(socket);
// TODO: here, we should trigger in some way the sending of current items
}
public removeListener(socket: ZoneSocket): void {
this.listeners.delete(socket);
}
}