handle invitations for excessive new addresses

Deal with somebody creating a new invitation, e.g. because it's been
realized that an existing one won't be accepted. Unused channels are
used first (though there will be none in a 4-device game), then
channels with old invites (only) are recycled.
This commit is contained in:
Eric House 2024-07-27 23:18:30 -07:00
parent 6d0686f737
commit f3fb43e93b
2 changed files with 71 additions and 35 deletions

View file

@ -1709,16 +1709,19 @@ haveRealChannel( const CommsCtxt* comms, XP_PlayerAddr channelNo )
}
typedef struct _GetInviteChannelsData {
XP_U16 mask;
XP_U16 hasInvitesMask;
XP_U16 hasNonInvitesMask;
} GetInviteChannelsData;
static ForEachAct
getInviteChannels( MsgQueueElem* elem, void* closure )
{
GetInviteChannelsData* gicdp = (GetInviteChannelsData*)closure;
if ( IS_INVITE(elem) ) {
GetInviteChannelsData* gicdp = (GetInviteChannelsData*)closure;
XP_ASSERT( 0 == (gicdp->mask & (1 << elem->channelNo)) );
gicdp->mask |= 1 << elem->channelNo;
XP_ASSERT( 0 == (gicdp->hasInvitesMask & (1 << elem->channelNo)) );
gicdp->hasInvitesMask |= 1 << elem->channelNo;
} else {
gicdp->hasNonInvitesMask |= 1 << elem->channelNo;
}
return FEA_OK;
}
@ -1742,17 +1745,33 @@ pickChannel( const CommsCtxt* comms, const NetLaunchInfo* nli,
}
if ( 0 == result ) {
/* Now find the first channelNo that doesn't have an invitation on it
already */
/* Data useful for next two steps: unused channel, then invites-only
channel */
GetInviteChannelsData gicd = {0};
forEachElem( (CommsCtxt*)comms, getInviteChannels, &gicd );
const XP_U16 nPlayers = comms->util->gameInfo->nPlayers;
for ( XP_PlayerAddr chan = 1; chan <= nPlayers; ++chan ) {
if ( 0 == (gicd.mask & (1 << chan)) ) {
/* Now find the first channelNo that doesn't have an invitation on it
already */
// const XP_U16 nPlayers = comms->util->gameInfo->nPlayers;
for ( XP_PlayerAddr chan = 1; chan <= CHANNEL_MASK; ++chan ) {
if ( 0 == (gicd.hasInvitesMask & (1 << chan)) ) {
result = chan;
XP_LOGFF( "using unused channel" );
break;
}
}
if ( 0 == result ) {
/* We need to find a channel to recycle. It should be one that has
only an invite on it.*/
for ( XP_PlayerAddr chan = 1; chan <= CHANNEL_MASK; ++chan ) {
if ( 0 == (gicd.hasNonInvitesMask & (1 << chan)) ) {
result = chan;
XP_LOGFF( "recycling channel" );
break;
}
}
}
}
LOG_RETURNF( "%d", result );
@ -1769,33 +1788,38 @@ comms_invite( CommsCtxt* comms, XWEnv xwe, const NetLaunchInfo* nli,
XP_PlayerAddr forceChannel = pickChannel(comms, nli, destAddr);
XP_LOGFF( "forceChannel: %d", forceChannel );
XP_ASSERT( 0 < forceChannel && (forceChannel & CHANNEL_MASK) == forceChannel );
if ( !haveRealChannel( comms, forceChannel ) ) {
/* See if we have a channel for this address. Then see if we have an
invite matching this one, and if not add one. Then trigger a send of
it. */
if ( 0 < forceChannel ) {
XP_ASSERT( (forceChannel & CHANNEL_MASK) == forceChannel );
if ( !haveRealChannel( comms, forceChannel ) ) {
/* See if we have a channel for this address. Then see if we have an
invite matching this one, and if not add one. Then trigger a send of
it. */
/* remove the old rec, if found */
nukeInvites( comms, xwe, forceChannel );
/* remove the old rec, if found */
nukeInvites( comms, xwe, forceChannel );
XP_U16 flags = COMMS_VERSION;
/*AddressRecord* rec = */rememberChannelAddress( comms, forceChannel,
0, destAddr, flags );
MsgQueueElem* elem = makeInviteElem( comms, xwe, forceChannel, nli );
XP_U16 flags = COMMS_VERSION;
/*AddressRecord* rec = */rememberChannelAddress( comms, forceChannel,
0, destAddr, flags );
MsgQueueElem* elem = makeInviteElem( comms, xwe, forceChannel, nli );
elem = addToQueue( comms, xwe, elem, XP_TRUE );
if ( !!elem ) {
XP_ASSERT( !elem->smp.next );
COMMS_LOGFF( "added invite with sum %s on channel %d", elem->sb.buf,
elem->channelNo & CHANNEL_MASK );
/* Let's let platform code decide whether to call sendMsg() . On
Android creating a game with an invitation in its queue is always
followed by opening the game, which results in comms_resendAll()
getting called leading to a second send immediately after this. So
let Android drop it. Linux, though, needs it for now. */
if ( sendNow && !!comms->procs.sendInvt ) {
sendMsg( comms, xwe, elem, COMMS_CONN_NONE );
elem = addToQueue( comms, xwe, elem, XP_TRUE );
if ( !!elem ) {
XP_ASSERT( !elem->smp.next );
COMMS_LOGFF( "added invite with sum %s on channel %d", elem->sb.buf,
elem->channelNo & CHANNEL_MASK );
/* Let's let platform code decide whether to call sendMsg() . On
Android creating a game with an invitation in its queue is always
followed by opening the game, which results in comms_resendAll()
getting called leading to a second send immediately after this. So
let Android drop it. Linux, though, needs it for now. */
if ( sendNow && !!comms->procs.sendInvt ) {
sendMsg( comms, xwe, elem, COMMS_CONN_NONE );
}
}
}
} else {
XP_LOGFF( "dropping invite; no open channel found" );
}
COMMS_MUTEX_UNLOCK();
LOG_RETURN_VOID();
@ -2050,7 +2074,8 @@ removeProc( MsgQueueElem* elem, void* closure )
XP_PlayerAddr maskedElemChannelNo = ~CHANNEL_MASK & elem->channelNo;
if ( (maskedElemChannelNo == 0) && (rdp->channelNo != 0) ) {
XP_ASSERT( !rdp->comms->isServer || IS_INVITE(elem) );
// not sure what this was doing....
// XP_ASSERT( !rdp->comms->isServer || IS_INVITE(elem) );
XP_ASSERT( elem->msgID == 0 );
} else if ( maskedElemChannelNo != maskedChannelNo ) {
knownGood = XP_TRUE;

View file

@ -108,7 +108,9 @@ class GameStatus():
self.isSolo = False
def harvest(self, dev, isSolo):
self.players.append(dev.host)
# Sending lots of bogus invitations gets duplicates here
if not dev.host in self.players:
self.players.append(dev.host)
self.allOver = self.allOver and dev.gameOver(self.gid)
self.isSolo = isSolo
@ -424,12 +426,18 @@ class Device():
def invite(self, game):
remotes = []
guestDevs = []
useRandomDevID = random.randint(0, 100) < self.args.BAD_INVITE_PCT
for ii in reversed(range(len(game.guestNames))):
guestDev = Device.getForPlayer(game.guestNames[ii])
guestDevs.append(guestDev)
addr = {}
if self.args.WITH_MQTT: addr['mqtt'] = guestDev.mqttDevID
if self.args.WITH_MQTT:
if useRandomDevID:
mqttAddr = '{:016X}'.format(random.randint(1, 0x7FFFFFF))
else:
mqttAddr = guestDev.mqttDevID
addr['mqtt'] = mqttAddr
if self.args.WITH_SMS: addr['sms'] = guestDev.smsNumber
remotes.append({'addr': addr})
@ -439,7 +447,7 @@ class Device():
if response['success']:
for guestDev in guestDevs:
guestDev.expectInvite(game.gid, game.rematchLevel)
game.needsInvite = False
game.needsInvite = useRandomDevID
def expectInvite(self, gid, rematchLevel):
self.guestGames.append(GuestGameInfo(self, gid, rematchLevel))
@ -824,6 +832,9 @@ def mkParser():
help='what percent of moves will trade tiles')
parser.add_argument('--sub7-trades-pct', dest = 'SUB7_TRADES_PCT', default = 10, type=int)
parser.add_argument('--bad-invite-pct', dest = 'BAD_INVITE_PCT', default = 0, type=int,
help='What pct (0..99) of MQTT invitations will be to non-existant devices')
parser.add_argument('--timer-seconds', dest='TIMER_SECS', default=10, type=int,
help='Enable game timer with game this many seconds long')