From 4a5ef4ab8631d314f26f252bd589fb9533876e87 Mon Sep 17 00:00:00 2001 From: Alexis Faizeau Date: Wed, 12 Jan 2022 10:48:41 +0100 Subject: [PATCH 1/2] Change group radius management --- back/src/Model/GameRoom.ts | 112 ++++++++++++++++++++++++++++--------- back/src/Model/Group.ts | 57 +++++++++++++++---- 2 files changed, 133 insertions(+), 36 deletions(-) diff --git a/back/src/Model/GameRoom.ts b/back/src/Model/GameRoom.ts index aefade43..7d7b24a5 100644 --- a/back/src/Model/GameRoom.ts +++ b/back/src/Model/GameRoom.ts @@ -213,13 +213,13 @@ export class GameRoom { } private updateUserGroup(user: User): void { - user.group?.updatePosition(); - user.group?.searchForNearbyUsers(); - if (user.silent) { return; } + const group = user.group; + const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); + if (group === undefined) { // If the user is not part of a group: // should he join a group? @@ -229,12 +229,11 @@ export class GameRoom { return; } - const closestItem: User | Group | null = this.searchClosestAvailableUserOrGroup(user); - if (closestItem !== null) { if (closestItem instanceof Group) { // Let's join the group! closestItem.join(user); + closestItem.setOutOfBounds(false); } else { const closestUser: User = closestItem; const group: Group = new Group( @@ -249,32 +248,95 @@ export class GameRoom { } } } else { - // If the user is part of a group: - // should he leave the group? - let noOneOutOfBounds = true; - group.getUsers().forEach((foreignUser: User) => { - if (foreignUser.group === undefined) { - return; + let hasKickOutSomeone = false; + let followingMembers: User[] = []; + + const previewNewGroupPosition = group.previewGroupPosition(); + + if (!previewNewGroupPosition) { + this.leaveGroup(user); + return; + } + + if (user.hasFollowers() || user.following) { + followingMembers = user.hasFollowers() + ? group.getUsers().filter((currentUser) => currentUser.following === user) + : group.getUsers().filter((currentUser) => currentUser.following === user.following); + + // If all group members are part of the same follow group + if (group.getUsers().length - 1 === followingMembers.length) { + let isOutOfBounds = false; + + // If a follower is far away from the leader, "outOfBounds" is set to true + for (const member of followingMembers) { + const distance = GameRoom.computeDistanceBetweenPositions( + member.getPosition(), + previewNewGroupPosition + ); + + if (distance > this.groupRadius) { + isOutOfBounds = true; + break; + } + } + group.setOutOfBounds(isOutOfBounds); } - const usrPos = foreignUser.getPosition(); - const grpPos = foreignUser.group.getPosition(); - const distance = GameRoom.computeDistanceBetweenPositions(usrPos, grpPos); + } + + // Check if the moving user has kicked out another user + for (const headMember of group.getGroupHeads()) { + if (!headMember.group) { + this.leaveGroup(headMember); + continue; + } + + const headPosition = headMember.getPosition(); + const distance = GameRoom.computeDistanceBetweenPositions(headPosition, previewNewGroupPosition); if (distance > this.groupRadius) { - if (foreignUser.hasFollowers() || foreignUser.following) { - // If one user is out of the group bounds BUT following, the group still exists... but should be hidden. - // We put it in 'outOfBounds' mode - group.setOutOfBounds(true); - noOneOutOfBounds = false; - } else { - this.leaveGroup(foreignUser); - } + hasKickOutSomeone = true; + break; + } + } + + /** + * If the current moving user has kicked another user from the radius, + * the moving user leaves the group because he is too far away. + */ + const userDistance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), previewNewGroupPosition); + + if (hasKickOutSomeone && userDistance > this.groupRadius) { + if (user.hasFollowers() && group.getUsers().length === 3 && followingMembers.length === 1) { + const other = group + .getUsers() + .find((currentUser) => !currentUser.hasFollowers() && !currentUser.following); + if (other) { + this.leaveGroup(other); + } + } else if (user.hasFollowers()) { + this.leaveGroup(user); + for (const member of followingMembers) { + this.leaveGroup(member); + } + + // Re-create a group with the followers + const newGroup: Group = new Group( + this.roomUrl, + [user, ...followingMembers], + this.groupRadius, + this.connectCallback, + this.disconnectCallback, + this.positionNotifier + ); + this.groups.add(newGroup); + } else { + this.leaveGroup(user); } - }); - if (noOneOutOfBounds && !user.group?.isEmpty()) { - group.setOutOfBounds(false); } } + + user.group?.updatePosition(); + user.group?.searchForNearbyUsers(); } public sendToOthersInGroupIncludingUser(user: User, message: ServerToClientMessage): void { diff --git a/back/src/Model/Group.ts b/back/src/Model/Group.ts index 0782bd1b..c14d509f 100644 --- a/back/src/Model/Group.ts +++ b/back/src/Model/Group.ts @@ -59,6 +59,39 @@ export class Group implements Movable { }; } + /** + * Returns the list of users of the group, ignoring any "followers". + * Useful to compute the position of the group if a follower is "trapped" far away from the the leader. + */ + getGroupHeads(): User[] { + return Array.from(this.users).filter((user) => user.group?.leader === user || !user.following); + } + + /** + * Preview the position of the group but don't update it + */ + previewGroupPosition(): { x: number; y: number } | undefined { + const users = this.getGroupHeads(); + + let x = 0; + let y = 0; + + if (users.length === 0) { + return undefined; + } + + users.forEach((user: User) => { + const position = user.getPosition(); + x += position.x; + y += position.y; + }); + + x /= users.length; + y /= users.length; + + return { x, y }; + } + /** * Computes the barycenter of all users (i.e. the center of the group) */ @@ -66,19 +99,15 @@ export class Group implements Movable { const oldX = this.x; const oldY = this.y; - let x = 0; - let y = 0; // Let's compute the barycenter of all users. - this.users.forEach((user: User) => { - const position = user.getPosition(); - x += position.x; - y += position.y; - }); - x /= this.users.size; - y /= this.users.size; - if (this.users.size === 0) { - throw new Error("EMPTY GROUP FOUND!!!"); + const newPosition = this.previewGroupPosition(); + + if (!newPosition) { + return; } + + const { x, y } = newPosition; + this.x = x; this.y = y; @@ -97,10 +126,12 @@ export class Group implements Movable { if (!this.currentZone) return; for (const user of this.positionNotifier.getAllUsersInSquareAroundZone(this.currentZone)) { + // Todo: Merge two groups with a leader if (user.group || this.isFull()) return; //we ignore users that are already in a group. const distance = GameRoom.computeDistanceBetweenPositions(user.getPosition(), this.getPosition()); if (distance < this.groupRadius) { this.join(user); + this.setOutOfBounds(false); this.updatePosition(); } } @@ -176,4 +207,8 @@ export class Group implements Movable { this.outOfBounds = true; } } + + get getOutOfBounds() { + return this.outOfBounds; + } } From e43c4cd5aec0d1c9f2c7eeaab35f2adf296fd3e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20N=C3=A9grier?= Date: Fri, 28 Jan 2022 09:58:24 +0100 Subject: [PATCH 2/2] Fixing a freeze in MapStore on several unsubscribes For some reason (I could not reproduce this in unit tests alas), the unsubscribe function could be called several times in a row, leading to a complete map freeze. Closes #1736 --- front/src/Stores/Utils/MapStore.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/front/src/Stores/Utils/MapStore.ts b/front/src/Stores/Utils/MapStore.ts index 63c6c819..54c7b793 100644 --- a/front/src/Stores/Utils/MapStore.ts +++ b/front/src/Stores/Utils/MapStore.ts @@ -96,6 +96,7 @@ export class MapStore extends Map implements Readable> { const unsubscribe = storeByKey.subscribe((newMapValue) => { if (unsubscribeDeepStore) { unsubscribeDeepStore(); + unsubscribeDeepStore = undefined; } if (newMapValue === undefined) { set(undefined); @@ -115,6 +116,7 @@ export class MapStore extends Map implements Readable> { unsubscribe(); if (unsubscribeDeepStore) { unsubscribeDeepStore(); + unsubscribeDeepStore = undefined; } }; });