Feat(meeting): minimise sequential user pick (#13831)

Updating random user picker to reduce the probability that the exact same viewers would be selected first in successive random-pick runs
This commit is contained in:
hiroshisuga 2021-12-08 02:36:34 +09:00 committed by GitHub
parent 1a89f66c0e
commit 0bc62f66f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 51 additions and 13 deletions

View File

@ -50,6 +50,8 @@ trait SystemConfiguration {
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false) lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2) lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
lazy val reduceDuplicatedPick = Try(config.getBoolean("apps.reduceDuplicatedPick")).getOrElse(false)
// Redis server configuration // Redis server configuration
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1") lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379) lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)

View File

@ -5,6 +5,7 @@ import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ UserState, Users2x } import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.SystemConfiguration
import scala.util.Random import scala.util.Random
trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait { trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
@ -15,7 +16,7 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = { def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg) log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: Integer): Unit = { def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId) val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing) val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId) val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
@ -31,11 +32,27 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
val reason = "No permission to select random user." val reason = "No permission to select random user."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else { } else {
val users = Users2x.findNotPresentersNorModerators(liveMeeting.users2x) val users = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, false)
val usersPicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
val randNum = new scala.util.Random val randNum = new scala.util.Random
val pickedUser = if (usersPicked.size == 0) "" else usersPicked(randNum.nextInt(usersPicked.size)).intId
if (reduceDuplicatedPick) {
if (usersPicked.size == 1) {
// Initialise the exemption
val usersToUnexempt = Users2x.findAll(liveMeeting.users2x)
usersToUnexempt foreach { u =>
Users2x.setUserExempted(liveMeeting.users2x, u.intId, false)
}
} else if (usersPicked.size > 1) {
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
}
val userIds = users.map { case (v) => v.intId } val userIds = users.map { case (v) => v.intId }
broadcastEvent(msg, userIds, if (users.size == 0) -1 else randNum.nextInt(users.size)) broadcastEvent(msg, userIds, pickedUser)
} }
} }
} }

View File

@ -71,8 +71,13 @@ object Users2x {
users.toVector.filter(u => !u.presenter) users.toVector.filter(u => !u.presenter)
} }
def findNotPresentersNorModerators(users: Users2x): Vector[UserState] = { def getRandomlyPickableUsers(users: Users2x, reduceDup: Boolean): Vector[UserState] = {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE)
if (reduceDup) {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left && !u.pickExempted)
} else {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left)
}
} }
def findViewers(users: Users2x): Vector[UserState] = { def findViewers(users: Users2x): Vector[UserState] = {
@ -150,6 +155,16 @@ object Users2x {
} }
} }
def setUserExempted(users: Users2x, intId: String, exempted: Boolean): Option[UserState] = {
for {
u <- findWithIntId(users, intId)
} yield {
val newUser = u.modify(_.pickExempted).setTo(exempted)
users.save(newUser)
newUser
}
}
def hasPresenter(users: Users2x): Boolean = { def hasPresenter(users: Users2x): Boolean = {
findPresenter(users) match { findPresenter(users) match {
case Some(p) => true case Some(p) => true
@ -294,6 +309,7 @@ case class UserState(
lastActivityTime: Long = System.currentTimeMillis(), lastActivityTime: Long = System.currentTimeMillis(),
lastInactivityInspect: Long = 0, lastInactivityInspect: Long = 0,
clientType: String, clientType: String,
pickExempted: Boolean,
userLeftFlag: UserLeftFlag userLeftFlag: UserLeftFlag
) )

View File

@ -64,6 +64,7 @@ trait HandlerHelpers extends SystemConfiguration {
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin, locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = regUser.avatarURL, avatar = regUser.avatarURL,
clientType = clientType, clientType = clientType,
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0)
) )
} }

View File

@ -69,7 +69,7 @@ trait FakeTestData {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role,
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown", emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, clientType = "unknown",
userLeftFlag = UserLeftFlag(false, 0)) pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
} }
} }

View File

@ -76,6 +76,7 @@ apps {
ejectOnViolation = false ejectOnViolation = false
endMeetingWhenNoMoreAuthedUsers = false endMeetingWhenNoMoreAuthedUsers = false
endMeetingWhenNoMoreAuthedUsersAfterMinutes = 2 endMeetingWhenNoMoreAuthedUsersAfterMinutes = 2
reduceDuplicatedPick = false
} }
analytics { analytics {

View File

@ -409,4 +409,4 @@ case class SelectRandomViewerReqMsgBody(requestedBy: String)
*/ */
object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" } object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: Integer) case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: String)

View File

@ -8,7 +8,7 @@ export default function randomlySelectedUser({ header, body }) {
check(meetingId, String); check(meetingId, String);
check(requestedBy, String); check(requestedBy, String);
check(userIds, Array); check(userIds, Array);
check(choice, Number); check(choice, String);
updateRandomViewer(meetingId, userIds, choice, requestedBy); updateRandomViewer(meetingId, userIds, choice, requestedBy);
} }

View File

@ -5,7 +5,7 @@ import { check } from 'meteor/check';
export default function updateRandomUser(meetingId, userIds, choice, requesterId) { export default function updateRandomUser(meetingId, userIds, choice, requesterId) {
check(meetingId, String); check(meetingId, String);
check(userIds, Array); check(userIds, Array);
check(choice, Number); check(choice, String);
check(requesterId, String); check(requesterId, String);
const selector = { const selector = {
@ -13,7 +13,7 @@ export default function updateRandomUser(meetingId, userIds, choice, requesterId
}; };
const userList = []; const userList = [];
if (choice < 0) { // no viewer if (choice == "") { // no viewer
userList.push([requesterId,0]); userList.push([requesterId,0]);
} else if (userIds.length == 1) { } else if (userIds.length == 1) {
userList.push([userIds[0],0]); userList.push([userIds[0],0]);
@ -26,7 +26,7 @@ export default function updateRandomUser(meetingId, userIds, choice, requesterId
} }
userList.push([userId, intervals.shift()]); userList.push([userId, intervals.shift()]);
} }
userList[userList.length-1][0] = userIds[choice]; // last one should be chosen in akka-app userList[userList.length-1][0] = choice; // last one should be chosen in akka-app
} }
if (userIds.length == 2) { if (userIds.length == 2) {

View File

@ -126,7 +126,8 @@ class RandomUserSelect extends Component {
const counter = SELECT_RANDOM_USER_COUNTDOWN ? this.state.count : 0; const counter = SELECT_RANDOM_USER_COUNTDOWN ? this.state.count : 0;
if (mappedRandomlySelectedUsers.length < counter + 1) return null; if (mappedRandomlySelectedUsers.length < counter + 1) return null;
const selectedUser = mappedRandomlySelectedUsers[counter][0]; const selectedUser = SELECT_RANDOM_USER_COUNTDOWN ? mappedRandomlySelectedUsers[counter][0] :
mappedRandomlySelectedUsers[mappedRandomlySelectedUsers.length - 1][0];
const countDown = SELECT_RANDOM_USER_COUNTDOWN ? const countDown = SELECT_RANDOM_USER_COUNTDOWN ?
mappedRandomlySelectedUsers.length - this.state.count - 1 : 0; mappedRandomlySelectedUsers.length - this.state.count - 1 : 0;