From 9b702c75e3f17b7a4f200eecc173150b7501cbed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Tue, 15 Sep 2020 10:06:11 +0200 Subject: [PATCH] Adding batched messages + the notion of notifier / zones (not plugged in the system yet) --- back/src/Controller/IoSocketController.ts | 52 ++++- back/src/Model/PositionNotifier.ts | 106 ++++++++++ back/src/Model/UserInterface.ts | 4 +- back/src/Model/Websocket/ExSocketInterface.ts | 8 + back/src/Model/Websocket/UserMovesMessage.ts | 11 + back/src/Model/Websocket/ViewportMessage.ts | 10 + back/src/Model/World.ts | 4 +- back/src/Model/Zone.ts | 85 ++++++++ back/tests/PositionNotifierTest.ts | 193 ++++++++++++++++++ benchmark/socketio-load-test.yaml | 14 +- benchmark/socketioLoadTest.js | 8 +- front/src/Connection.ts | 37 +++- front/src/Phaser/Game/GameScene.ts | 8 +- 13 files changed, 518 insertions(+), 22 deletions(-) create mode 100644 back/src/Model/PositionNotifier.ts create mode 100644 back/src/Model/Websocket/UserMovesMessage.ts create mode 100644 back/src/Model/Websocket/ViewportMessage.ts create mode 100644 back/src/Model/Zone.ts create mode 100644 back/tests/PositionNotifierTest.ts diff --git a/back/src/Controller/IoSocketController.ts b/back/src/Controller/IoSocketController.ts index 34e0dbc8..228a0da8 100644 --- a/back/src/Controller/IoSocketController.ts +++ b/back/src/Controller/IoSocketController.ts @@ -6,8 +6,8 @@ import {ExSocketInterface} from "../Model/Websocket/ExSocketInterface"; //TODO f import Jwt, {JsonWebTokenError} from "jsonwebtoken"; import {SECRET_KEY, MINIMUM_DISTANCE, GROUP_RADIUS, ALLOW_ARTILLERY} from "../Enum/EnvironmentVariable"; //TODO fix import by "_Enum/..." import {World} from "../Model/World"; -import {Group} from "_Model/Group"; -import {UserInterface} from "_Model/UserInterface"; +import {Group} from "../Model/Group"; +import {UserInterface} from "../Model/UserInterface"; import {isSetPlayerDetailsMessage,} from "../Model/Websocket/SetPlayerDetailsMessage"; import {MessageUserJoined} from "../Model/Websocket/MessageUserJoined"; import {MessageUserMoved} from "../Model/Websocket/MessageUserMoved"; @@ -19,12 +19,13 @@ import {isPointInterface, PointInterface} from "../Model/Websocket/PointInterfac import {isWebRtcSignalMessageInterface} from "../Model/Websocket/WebRtcSignalMessage"; import {UserInGroupInterface} from "../Model/Websocket/UserInGroupInterface"; import {uuid} from 'uuidv4'; +import {isUserMovesInterface} from "../Model/Websocket/UserMovesMessage"; enum SockerIoEvent { CONNECTION = "connection", DISCONNECT = "disconnect", JOIN_ROOM = "join-room", // bi-directional - USER_POSITION = "user-position", // bi-directional + USER_POSITION = "user-position", // From client to server USER_MOVED = "user-moved", // From server to client USER_LEFT = "user-left", // From server to client WEBRTC_SIGNAL = "webrtc-signal", @@ -36,6 +37,20 @@ enum SockerIoEvent { GROUP_DELETE = "group-delete", SET_PLAYER_DETAILS = "set-player-details", SET_SILENT = "set_silent", // Set or unset the silent mode for this user. + SET_VIEWPORT = "set-viewport", + BATCH = "batch", +} + +function emitInBatch(socket: ExSocketInterface, event: string | symbol, payload: any): void { + socket.batchedMessages.push({ event, payload}); + + if (socket.batchTimeout === null) { + socket.batchTimeout = setTimeout(() => { + socket.emit(SockerIoEvent.BATCH, socket.batchedMessages); + socket.batchedMessages = []; + socket.batchTimeout = null; + }, 100); + } } export class IoSocketController { @@ -152,6 +167,11 @@ export class IoSocketController { ioConnection() { this.Io.on(SockerIoEvent.CONNECTION, (socket: Socket) => { const client : ExSocketInterface = socket as ExSocketInterface; + client.batchedMessages = []; + client.batchTimeout = null; + client.emitInBatch = (event: string | symbol, payload: any): void => { + emitInBatch(client, event, payload); + } this.sockets.set(client.userId, client); // Let's log server load when a user joins @@ -215,19 +235,20 @@ export class IoSocketController { } }); - socket.on(SockerIoEvent.USER_POSITION, (position: unknown): void => { - console.log(SockerIoEvent.USER_POSITION, position); + socket.on(SockerIoEvent.USER_POSITION, (userMovesMessage: unknown): void => { + console.log(SockerIoEvent.USER_POSITION, userMovesMessage); try { - if (!isPointInterface(position)) { + if (!isUserMovesInterface(userMovesMessage)) { socket.emit(SockerIoEvent.MESSAGE_ERROR, {message: 'Invalid USER_POSITION message.'}); - console.warn('Invalid USER_POSITION message received: ', position); + console.warn('Invalid USER_POSITION message received: ', userMovesMessage); return; } const Client = (socket as ExSocketInterface); // sending to all clients in room except sender - Client.position = position; + Client.position = userMovesMessage.position; + Client.viewport = userMovesMessage.viewport; // update position in the world const world = this.Worlds.get(Client.roomId); @@ -235,9 +256,20 @@ export class IoSocketController { console.error("Could not find world with id '", Client.roomId, "'"); return; } - world.updatePosition(Client, position); + world.updatePosition(Client, Client.position); - socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position)); + const clientsInRoom = this.Io.sockets.adapter.rooms[Client.roomId]; + console.log('clientsInRoom', clientsInRoom); + for (const clientId in clientsInRoom.sockets) { + console.log('client: %s', clientId); + const targetSocket = this.Io.sockets.connected[clientId] as ExSocketInterface; + if (socket === targetSocket) { + continue; + } + //targetSocket.emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position)); + targetSocket.emitInBatch(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position)); + } + //socket.to(Client.roomId).emit(SockerIoEvent.USER_MOVED, new MessageUserMoved(Client.userId, Client.position)); } catch (e) { console.error('An error occurred on "user_position" event'); console.error(e); diff --git a/back/src/Model/PositionNotifier.ts b/back/src/Model/PositionNotifier.ts new file mode 100644 index 00000000..f5edf8d3 --- /dev/null +++ b/back/src/Model/PositionNotifier.ts @@ -0,0 +1,106 @@ +/** + * Tracks the position of every player on the map, and sends notifications to the players interested in knowing about the move + * (i.e. players that are looking at the zone the player is currently in) + * + * Internally, the PositionNotifier works with Zones. A zone is a square area of a map. + * Each player is in a given zone, and each player tracks one or many zones (depending on the player viewport) + * + * The PositionNotifier is important for performance. It allows us to send the position of players only to a restricted + * number of players around the current player. + */ +import {UserEntersCallback, UserLeavesCallback, UserMovesCallback, Zone} from "./Zone"; +import {PointInterface} from "_Model/Websocket/PointInterface"; +import {UserInterface} from "_Model/UserInterface"; +import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; + +interface ZoneDescriptor { + i: number; + j: number; +} + +export class PositionNotifier { + + // TODO: we need a way to clean the zones if noone is in the zone and noone listening (to free memory!) + + private zones: Zone[][] = []; + + constructor(private zoneWidth: number, private zoneHeight: number, private onUserEnters: UserEntersCallback, private onUserMoves: UserMovesCallback, private onUserLeaves: UserLeavesCallback) { + } + + private getZoneDescriptorFromCoordinates(x: number, y: number): ZoneDescriptor { + return { + i: Math.floor(x / this.zoneWidth), + j: Math.floor(y / this.zoneHeight), + } + } + + public setViewport(user: UserInterface, viewport: ViewportInterface): void { + if (viewport.left > viewport.right || viewport.top > viewport.bottom) { + console.warn('Invalid viewport received: ', viewport); + return; + } + + const oldZones = user.listenedZones; + const newZones = new Set(); + + 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)); + + for (const zone of addedZones) { + zone.startListening(user); + } + for (const zone of removedZones) { + zone.stopListening(user); + } + } + + public updatePosition(user: UserInterface, userPosition: PointInterface): void { + // Did we change zone? + const oldZoneDesc = this.getZoneDescriptorFromCoordinates(user.position.x, user.position.y); + const newZoneDesc = this.getZoneDescriptorFromCoordinates(userPosition.x, userPosition.y); + + if (oldZoneDesc.i != newZoneDesc.i || oldZoneDesc.j != newZoneDesc.j) { + const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); + const newZone = this.getZone(newZoneDesc.i, newZoneDesc.j); + + // Leave old zone + oldZone.leave(user, newZone); + + // Enter new zone + newZone.enter(user, oldZone, userPosition); + } else { + const zone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); + zone.move(user, userPosition); + } + } + + public leave(user: UserInterface): void { + const oldZoneDesc = this.getZoneDescriptorFromCoordinates(user.position.x, user.position.y); + const oldZone = this.getZone(oldZoneDesc.i, oldZoneDesc.j); + oldZone.leave(user, null); + } + + private getZone(i: number, j: number): Zone { + let zoneRow = this.zones[j]; + if (zoneRow === undefined) { + zoneRow = new Array(); + this.zones[j] = zoneRow; + } + + let zone = this.zones[j][i]; + if (zone === undefined) { + zone = new Zone(this.onUserEnters, this.onUserMoves, this.onUserLeaves); + this.zones[j][i] = zone; + } + return zone; + } +} diff --git a/back/src/Model/UserInterface.ts b/back/src/Model/UserInterface.ts index 89994a31..d19ecd6f 100644 --- a/back/src/Model/UserInterface.ts +++ b/back/src/Model/UserInterface.ts @@ -1,9 +1,11 @@ import { Group } from "./Group"; import { PointInterface } from "./Websocket/PointInterface"; +import {Zone} from "_Model/Zone"; export interface UserInterface { id: string, group?: Group, position: PointInterface, - silent: boolean + silent: boolean, + listenedZones: Set } diff --git a/back/src/Model/Websocket/ExSocketInterface.ts b/back/src/Model/Websocket/ExSocketInterface.ts index 974fe63d..c5132eb7 100644 --- a/back/src/Model/Websocket/ExSocketInterface.ts +++ b/back/src/Model/Websocket/ExSocketInterface.ts @@ -2,6 +2,7 @@ import {Socket} from "socket.io"; import {PointInterface} from "./PointInterface"; import {Identificable} from "./Identificable"; import {TokenInterface} from "../../Controller/AuthenticateController"; +import {ViewportInterface} from "_Model/Websocket/ViewportMessage"; export interface ExSocketInterface extends Socket, Identificable { token: string; @@ -11,5 +12,12 @@ export interface ExSocketInterface extends Socket, Identificable { name: string; characterLayers: string[]; position: PointInterface; + viewport: ViewportInterface; isArtillery: boolean; // Whether this socket is opened by Artillery for load testing (hack) + /** + * Pushes an event that will be sent in the next batch of events + */ + emitInBatch: (event: string | symbol, payload: any) => void; + batchedMessages: Array<{ event: string | symbol, payload: any }>; + batchTimeout: NodeJS.Timeout|null; } diff --git a/back/src/Model/Websocket/UserMovesMessage.ts b/back/src/Model/Websocket/UserMovesMessage.ts new file mode 100644 index 00000000..2277d4c4 --- /dev/null +++ b/back/src/Model/Websocket/UserMovesMessage.ts @@ -0,0 +1,11 @@ +import * as tg from "generic-type-guard"; +import {isPointInterface} from "./PointInterface"; +import {isViewport} from "./ViewportMessage"; + + +export const isUserMovesInterface = + new tg.IsInterface().withProperties({ + position: isPointInterface, + viewport: isViewport, + }).get(); +export type UserMovesInterface = tg.GuardedType; diff --git a/back/src/Model/Websocket/ViewportMessage.ts b/back/src/Model/Websocket/ViewportMessage.ts new file mode 100644 index 00000000..62e2fc81 --- /dev/null +++ b/back/src/Model/Websocket/ViewportMessage.ts @@ -0,0 +1,10 @@ +import * as tg from "generic-type-guard"; + +export const isViewport = + new tg.IsInterface().withProperties({ + left: tg.isNumber, + top: tg.isNumber, + right: tg.isNumber, + bottom: tg.isNumber, + }).get(); +export type ViewportInterface = tg.GuardedType; diff --git a/back/src/Model/World.ts b/back/src/Model/World.ts index 8855702e..0f2cb050 100644 --- a/back/src/Model/World.ts +++ b/back/src/Model/World.ts @@ -6,6 +6,7 @@ import {UserInterface} from "./UserInterface"; import {ExSocketInterface} from "_Model/Websocket/ExSocketInterface"; import {PositionInterface} from "_Model/PositionInterface"; import {Identificable} from "_Model/Websocket/Identificable"; +import {Zone} from "_Model/Zone"; export type ConnectCallback = (user: string, group: Group) => void; export type DisconnectCallback = (user: string, group: Group) => void; @@ -56,7 +57,8 @@ export class World { this.users.set(socket.userId, { id: socket.userId, position: userPosition, - silent: false // FIXME: silent should be set at the correct value when joining a room. + silent: false, // FIXME: silent should be set at the correct value when joining a room. + listenedZones: new Set() }); // Let's call update position to trigger the join / leave room this.updatePosition(socket, userPosition); diff --git a/back/src/Model/Zone.ts b/back/src/Model/Zone.ts new file mode 100644 index 00000000..45d6cdd2 --- /dev/null +++ b/back/src/Model/Zone.ts @@ -0,0 +1,85 @@ +import {UserInterface} from "./UserInterface"; +import {PointInterface} from "_Model/Websocket/PointInterface"; +import {PositionInterface} from "_Model/PositionInterface"; + +export type UserEntersCallback = (user: UserInterface) => void; +export type UserMovesCallback = (user: UserInterface, position: PointInterface) => void; +export type UserLeavesCallback = (user: UserInterface) => void; + +export class Zone { + private players: Set = new Set(); + private listeners: Set = new Set(); + + constructor(private onUserEnters: UserEntersCallback, private onUserMoves: UserMovesCallback, private onUserLeaves: UserLeavesCallback) { + } + + /** + * A user leaves the zone + */ + public leave(user: UserInterface, newZone: Zone|null) { + this.players.delete(user); + this.notifyUserLeft(user, newZone); + } + + /** + * Notify listeners of this zone that this user left + */ + private notifyUserLeft(user: UserInterface, newZone: Zone|null) { + for (const listener of this.listeners) { + if (listener !== user && (newZone === null || !listener.listenedZones.has(newZone))) { + this.onUserLeaves(user); + } + } + } + + public enter(user: UserInterface, oldZone: Zone|null, position: PointInterface) { + this.players.add(user); + this.notifyUserEnter(user, oldZone, position); + } + + /** + * Notify listeners of this zone that this user entered + */ + private notifyUserEnter(user: UserInterface, oldZone: Zone|null, position: PointInterface) { + for (const listener of this.listeners) { + if (listener === user) { + continue; + } + if (oldZone === null || !listener.listenedZones.has(oldZone)) { + this.onUserEnters(user); + } else { + this.onUserMoves(user, position); + } + } + } + + public move(user: UserInterface, position: PointInterface) { + for (const listener of this.listeners) { + if (listener !== user) { + this.onUserMoves(user,position); + } + } + } + + public startListening(user: UserInterface): void { + for (const player of this.players) { + if (player !== user) { + this.onUserEnters(user); + } + } + + this.listeners.add(user); + user.listenedZones.add(this); + } + + public stopListening(user: UserInterface): void { + for (const player of this.players) { + if (player !== user) { + this.onUserLeaves(user); + } + } + + this.listeners.delete(user); + user.listenedZones.delete(this); + } +} diff --git a/back/tests/PositionNotifierTest.ts b/back/tests/PositionNotifierTest.ts new file mode 100644 index 00000000..833852b0 --- /dev/null +++ b/back/tests/PositionNotifierTest.ts @@ -0,0 +1,193 @@ +import "jasmine"; +import {World, ConnectCallback, DisconnectCallback } from "../src/Model/World"; +import {Point} from "../src/Model/Websocket/MessageUserPosition"; +import { Group } from "../src/Model/Group"; +import {PositionNotifier} from "../src/Model/PositionNotifier"; +import {UserInterface} from "../src/Model/UserInterface"; +import {PointInterface} from "../src/Model/Websocket/PointInterface"; +import {Zone} from "_Model/Zone"; + +function move(user: UserInterface, x: number, y: number, positionNotifier: PositionNotifier): void { + positionNotifier.updatePosition(user, { + x, + y, + moving: false, + direction: 'down' + }); + user.position.x = x; + user.position.y = y; +} + +describe("PositionNotifier", () => { + it("should receive notifications when player moves", () => { + let enterTriggered = false; + let moveTriggered = false; + let leaveTriggered = false; + + const positionNotifier = new PositionNotifier(300, 300, (user: UserInterface) => { + enterTriggered = true; + }, (user: UserInterface, position: PointInterface) => { + moveTriggered = true; + }, (user: UserInterface) => { + leaveTriggered = true; + }); + + let user1 = { + id: "1", + position: { + x: 500, + y: 500, + moving: false, + direction: 'down' + }, + listenedZones: new Set(), + } as UserInterface; + + let user2 = { + id: "2", + position: { + x: -9999, + y: -9999, + moving: false, + direction: 'down' + }, + listenedZones: new Set(), + } as UserInterface; + + positionNotifier.setViewport(user1, { + left: 200, + right: 600, + top: 100, + bottom: 500 + }); + + move(user2, 500, 500, positionNotifier); + + expect(enterTriggered).toBe(true); + expect(moveTriggered).toBe(false); + enterTriggered = false; + + // Move inside the zone + move(user2, 501, 500, positionNotifier); + + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(true); + moveTriggered = false; + + // Move out of the zone in a zone that we don't track + move(user2, 901, 500, positionNotifier); + + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(true); + leaveTriggered = false; + + // Move back in + move(user2, 500, 500, positionNotifier); + expect(enterTriggered).toBe(true); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(false); + enterTriggered = false; + + // Move out of the zone in a zone that we do track + move(user2, 200, 500, positionNotifier); + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(true); + expect(leaveTriggered).toBe(false); + moveTriggered = false; + + // Leave the room + positionNotifier.leave(user2); + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(true); + leaveTriggered = false; + }); + + it("should receive notifications when camera moves", () => { + let enterTriggered = false; + let moveTriggered = false; + let leaveTriggered = false; + + const positionNotifier = new PositionNotifier(300, 300, (user: UserInterface) => { + enterTriggered = true; + }, (user: UserInterface, position: PointInterface) => { + moveTriggered = true; + }, (user: UserInterface) => { + leaveTriggered = true; + }); + + let user1 = { + id: "1", + position: { + x: 500, + y: 500, + moving: false, + direction: 'down' + }, + listenedZones: new Set(), + } as UserInterface; + + let user2 = { + id: "2", + position: { + x: -9999, + y: -9999, + moving: false, + direction: 'down' + }, + listenedZones: new Set(), + } as UserInterface; + + positionNotifier.setViewport(user1, { + left: 200, + right: 600, + top: 100, + bottom: 500 + }); + + move(user2, 500, 500, positionNotifier); + + expect(enterTriggered).toBe(true); + expect(moveTriggered).toBe(false); + enterTriggered = false; + + // Move the viewport but the user stays inside. + positionNotifier.setViewport(user1, { + left: 201, + right: 601, + top: 100, + bottom: 500 + }); + + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(false); + + // Move the viewport out of the user. + positionNotifier.setViewport(user1, { + left: 901, + right: 1001, + top: 100, + bottom: 500 + }); + + expect(enterTriggered).toBe(false); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(true); + leaveTriggered = false; + + // Move the viewport back on the user. + positionNotifier.setViewport(user1, { + left: 200, + right: 600, + top: 100, + bottom: 500 + }); + + expect(enterTriggered).toBe(true); + expect(moveTriggered).toBe(false); + expect(leaveTriggered).toBe(false); + enterTriggered = false; + }); +}) diff --git a/benchmark/socketio-load-test.yaml b/benchmark/socketio-load-test.yaml index 2f9f689d..e84b0402 100644 --- a/benchmark/socketio-load-test.yaml +++ b/benchmark/socketio-load-test.yaml @@ -34,10 +34,16 @@ scenarios: - emit: channel: "user-position" data: - x: "{{ x }}" - y: "{{ y }}" - direction: 'down' - moving: false + position: + x: "{{ x }}" + y: "{{ y }}" + direction: 'down' + moving: false + viewport: + left: "{{ left }}" + top: "{{ top }}" + right: "{{ right }}" + bottom: "{{ bottom }}" - think: 0.2 count: 100 - think: 10 diff --git a/benchmark/socketioLoadTest.js b/benchmark/socketioLoadTest.js index 907982b2..540cd8bd 100644 --- a/benchmark/socketioLoadTest.js +++ b/benchmark/socketioLoadTest.js @@ -5,7 +5,11 @@ module.exports = { }; function setYRandom(context, events, done) { - context.vars.x = (883 + Math.round(Math.random() * 300)); - context.vars.y = (270 + Math.round(Math.random() * 300)); + context.vars.x = (0 + Math.round(Math.random() * 1472)); + context.vars.y = (0 + Math.round(Math.random() * 1090)); + context.vars.left = context.vars.x - 320; + context.vars.top = context.vars.y - 200; + context.vars.right = context.vars.x + 320; + context.vars.bottom = context.vars.y + 200; return done(); } diff --git a/front/src/Connection.ts b/front/src/Connection.ts index 4a184c52..fd06329d 100644 --- a/front/src/Connection.ts +++ b/front/src/Connection.ts @@ -14,7 +14,7 @@ enum EventMessage{ WEBRTC_SCREEN_SHARING_SIGNAL = "webrtc-screen-sharing-signal", WEBRTC_START = "webrtc-start", JOIN_ROOM = "join-room", // bi-directional - USER_POSITION = "user-position", // bi-directional + USER_POSITION = "user-position", // From client to server USER_MOVED = "user-moved", // From server to client USER_LEFT = "user-left", // From server to client MESSAGE_ERROR = "message-error", @@ -25,6 +25,8 @@ enum EventMessage{ CONNECT_ERROR = "connect_error", SET_SILENT = "set_silent", // Set or unset the silent mode for this user. + SET_VIEWPORT = "set-viewport", + BATCH = "batch", } export interface PointInterface { @@ -95,6 +97,23 @@ export interface StartMapInterface { startInstance: string } +export interface ViewportInterface { + left: number, + top: number, + right: number, + bottom: number, +} + +export interface UserMovesInterface { + position: PositionInterface, + viewport: ViewportInterface, +} + +export interface BatchedMessageInterface { + event: string, + payload: any +} + export class Connection implements Connection { private readonly socket: Socket; private userId: string|null = null; @@ -111,6 +130,18 @@ export class Connection implements Connection { this.socket.on(EventMessage.MESSAGE_ERROR, (message: string) => { console.error(EventMessage.MESSAGE_ERROR, message); }) + + /** + * Messages inside batched messages are extracted and sent to listeners directly. + */ + this.socket.on(EventMessage.BATCH, (batchedMessages: BatchedMessageInterface[]) => { + for (const message of batchedMessages) { + const listeners = this.socket.listeners(message.event); + for (const listener of listeners) { + listener(message.payload); + } + } + }) } public static createConnection(name: string, characterLayersSelected: string[]): Promise { @@ -160,12 +191,12 @@ export class Connection implements Connection { return promise; } - public sharePosition(x : number, y : number, direction : string, moving: boolean) : void{ + public sharePosition(x : number, y : number, direction : string, moving: boolean, viewport: ViewportInterface) : void{ if(!this.socket){ return; } const point = new Point(x, y, direction, moving); - this.socket.emit(EventMessage.USER_POSITION, point); + this.socket.emit(EventMessage.USER_POSITION, { position: point, viewport } as UserMovesInterface); } public setSilent(silent: boolean): void { diff --git a/front/src/Phaser/Game/GameScene.ts b/front/src/Phaser/Game/GameScene.ts index 8a5630dc..595fc9d3 100644 --- a/front/src/Phaser/Game/GameScene.ts +++ b/front/src/Phaser/Game/GameScene.ts @@ -677,7 +677,13 @@ export class GameScene extends Phaser.Scene implements CenterListener { private doPushPlayerPosition(event: HasMovedEvent): void { this.lastMoveEventSent = event; this.lastSentTick = this.currentTick; - this.connection.sharePosition(event.x, event.y, event.direction, event.moving); + const camera = this.cameras.main; + this.connection.sharePosition(event.x, event.y, event.direction, event.moving, { + left: camera.scrollX, + top: camera.scrollY, + right: camera.scrollX + camera.width, + bottom: camera.scrollY + camera.height, + }); } EventToClickOnTile(){