new script to run many games per device

I need it to be much closer to Android....
This commit is contained in:
Eric House 2023-11-17 19:39:45 -08:00
parent 47004a0e08
commit 55e36e10a7
23 changed files with 985 additions and 303 deletions

View file

@ -1378,13 +1378,32 @@ comms_getChannelAddr( const CommsCtxt* comms, XP_PlayerAddr channelNo,
XP_ASSERT( found );
}
typedef struct _NonAcks {
int count;
} NonAcks;
static ForEachAct
countNonAcks( MsgQueueElem* elem, void* closure )
{
if ( IS_INVITE(elem) || 0 != elem->msgID ) {
NonAcks* nap = (NonAcks*)closure;
++nap->count;
}
return FEA_OK;
}
XP_U16
comms_countPendingPackets( const CommsCtxt* comms, XP_Bool* quashed )
{
if ( !!quashed ) {
*quashed = QUASHED(comms);
}
return comms->queueLen;
NonAcks na = {0};
forEachElem( (CommsCtxt*)comms, countNonAcks, &na );
// XP_LOGFF( "=> %d (queueLen = %d)", na.count, comms->queueLen );
return na.count;
}
static XP_Bool
@ -2933,8 +2952,7 @@ validateChannelMessage( CommsCtxt* comms, XWEnv xwe, const CommsAddrRec* addr,
} else if ( msgID != rec->lastMsgRcd + 1 ) {
XP_LOGFF( TAGFMT() "expected %d, got %d", TAGPRMS,
rec->lastMsgRcd + 1, msgID );
// Add this if adding ACK to queue isn't enough
// ackAnyImpl( comms, xwe, XP_TRUE );
ackAnyImpl( comms, xwe, XP_TRUE );
rec = NULL;
}
} else {
@ -3572,7 +3590,7 @@ rememberChannelAddress( CommsCtxt* comms, XP_PlayerAddr channelNo,
// addr_setTypes( &recs->addr, addr_getTypes( &comms->selfAddr ) );
}
}
listRecs( comms, "leaving rememberChannelAddress" );
listRecs( comms, "leaving rememberChannelAddress()" );
THREAD_CHECK_END();
return rec;
} /* rememberChannelAddress */

View file

@ -510,8 +510,9 @@ dvc_parseMQTTPacket( XW_DUtilCtxt* dutil, XWEnv xwe, const XP_UCHAR* topic,
stream_putBytes( stream, buf, len );
XP_U8 proto = 0;
if ( stream_gotU8( stream, &proto )
&& (proto == PROTO_1 || proto == PROTO_3 ) ) {
if ( !stream_gotU8( stream, &proto ) ) {
XP_LOGFF( "bad message: too short" );
} else if ( proto == PROTO_1 || proto == PROTO_3 ) {
MQTTDevID senderID;
stream_getBytes( stream, &senderID, sizeof(senderID) );
senderID = be64toh( senderID );

View file

@ -80,6 +80,24 @@ nliToGI( const NetLaunchInfo* nli, XWEnv xwe, XW_UtilCtxt* util, CurGameInfo* gi
{
gi_setNPlayers( gi, xwe, util, nli->nPlayersT, nli->nPlayersH );
gi->gameID = nli->gameID;
XP_U16 nLocals = 0;
XP_Bool remotesAreRobots = nli->remotesAreRobots;
XW_DUtilCtxt* duc = util_getDevUtilCtxt( util, xwe );
for ( int ii = 0; ii < gi->nPlayers; ++ii ) {
LocalPlayer* lp = &gi->players[ii];
if ( lp->isLocal ) {
if ( nli->remotesAreRobots ) {
lp->robotIQ = 1;
}
XP_UCHAR buf[64];
XP_U16 len = VSIZE(buf);
dutil_getUsername( duc, xwe, nLocals++, XP_TRUE, remotesAreRobots,
buf, &len );
replaceStringIfDifferent( util->mpool, &lp->name, buf );
}
}
/* These defaults can be overwritten when host starts game after all
register */
gi->boardSize = 15;

View file

@ -243,10 +243,9 @@ OBJ = \
$(BUILD_PLAT_DIR)/relaycon.o \
$(BUILD_PLAT_DIR)/mqttcon.o \
$(BUILD_PLAT_DIR)/lindutil.o \
$(BUILD_PLAT_DIR)/cmdspipe.o \
$(CURSES_OBJS) $(GTK_OBJS) $(MAIN_OBJS)
LIBS = -lm -lpthread -luuid -lcurl -ljson-c $(GPROFFLAG)
LIBS = -lm -lpthread -luuid -lcurl -lcjson $(GPROFFLAG)
ifdef USE_SQLITE
LIBS += -lsqlite3
LIBS += -lmosquitto

View file

@ -1,42 +0,0 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2023 by Eric House (xwords@eehouse.org). All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "cmdspipe.h"
XP_Bool
cmds_readCmd( CmdBuf* cb, const char* buf )
{
XP_Bool success = XP_TRUE;
XP_MEMSET( cb, 0, sizeof(*cb) );
XP_LOGFF( "got buf: %s", buf );
gchar** strs = g_strsplit ( buf, " ", 100 );
if ( 0 == strcmp( strs[0], "null" ) ) {
cb->cmd = CMD_NONE;
} else if ( 0 == strcmp( strs[0], "quit" ) ) {
cb->cmd = CMD_QUIT;
} else {
XP_ASSERT(0);
success = XP_FALSE;
}
g_strfreev( strs );
return success;
}

View file

@ -1,35 +0,0 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j5"; -*- */
/*
* Copyright 2023 by Eric House (xwords@eehouse.org). All rights reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _CMDSPIPE_H_
# define _CMDSPIPE_H_
#include "xptypes.h"
typedef enum { CMD_NONE,
CMD_QUIT,
} Cmd;
typedef struct _CmdBuf {
Cmd cmd;
} CmdBuf;
XP_Bool cmds_readCmd( CmdBuf* cb, const char* buf );
#endif

View file

@ -78,7 +78,7 @@ static void
addOne( CursGameList* cgl, sqlite3_int64 rowid )
{
GameInfo gib;
if ( gdb_getGameInfo( cgl->params->pDb, rowid, &gib ) ) {
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
GameInfo* gibp = g_malloc( sizeof(*gibp) );
*gibp = gib;
cgl->games = g_slist_append( cgl->games, gibp );
@ -121,7 +121,7 @@ cgl_refreshOne( CursGameList* cgl, sqlite3_int64 rowid, bool select )
// elem
GameInfo gib;
if ( gdb_getGameInfo( cgl->params->pDb, rowid, &gib ) ) {
if ( gdb_getGameInfoForRow( cgl->params->pDb, rowid, &gib ) ) {
GameInfo* found;
GSList* elem = findFor( cgl, rowid );
if ( !!elem ) {
@ -289,6 +289,16 @@ cgl_getSel( CursGameList* cgl )
return g_slist_nth_data( cgl->games, cgl->curSel );
}
void
cgl_setSel( CursGameList* cgl, int sel )
{
if ( sel < 0 ) {
sel = XP_RANDOM() % g_slist_length( cgl->games );
}
cgl->curSel = sel;
adjustCurSel( cgl );
}
int
cgl_getNGames( CursGameList* cgl )
{

View file

@ -40,6 +40,7 @@ void cgl_moveSel( CursGameList* cgl, bool down );
void cgl_draw( CursGameList* cgl );
const GameInfo* cgl_getSel( CursGameList* cgl );
void cgl_setSel( CursGameList* cgl, int sel );
int cgl_getNGames( CursGameList* cgl );
#endif

View file

@ -192,14 +192,14 @@ cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, const cb_dims* dims )
comms_resendAll( cGlobals->game.comms, NULL_XWE, COMMS_CONN_NONE, XP_FALSE );
}
if ( bGlobals->cGlobals.params->forceInvite ) {
(void)ADD_ONETIME_IDLE( inviteIdle, bGlobals);
(void)ADD_ONETIME_IDLE( inviteIdle, bGlobals );
}
}
bool
cb_new( CursesBoardState* cbState, const cb_dims* dims )
cb_new( CursesBoardState* cbState, const cb_dims* dims, const CurGameInfo* gi )
{
CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1, NULL, NULL );
CursesBoardGlobals* bGlobals = findOrOpen( cbState, -1, gi, NULL );
if ( !!bGlobals ) {
initMenus( bGlobals );
enableDraw( bGlobals, dims );
@ -217,8 +217,7 @@ cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
CommsAddrRec selfAddr;
makeSelfAddress( &selfAddr, params );
CursesBoardGlobals* bGlobals =
commonInit( cbState, -1, NULL );
CursesBoardGlobals* bGlobals = commonInit( cbState, -1, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
initCP( cGlobals );
if ( game_makeFromInvite( &cGlobals->game, NULL_XWE, nli, &selfAddr,
@ -630,6 +629,7 @@ cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid, XP_U16 expectSeed,
bool success = 0 == expectSeed || seed == expectSeed;
if ( success ) {
gameGotBuf( cGlobals, XP_TRUE, buf, len, from );
linuxSaveGame( &bGlobals->cGlobals );
} else {
XP_LOGFF( "msg for seed %d but I opened %d", expectSeed, seed );
}
@ -654,10 +654,35 @@ cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
}
}
void
cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
const CommsAddrRec* destAddr )
{
sqlite3_int64 rowids[1];
int nRowIDs = VSIZE(rowids);
gdb_getRowsForGameID( cbState->params->pDb, gameID, rowids, &nRowIDs );
XP_ASSERT( 1 == nRowIDs );
CursesBoardGlobals* bGlobals = findOrOpen( cbState, rowids[0], NULL, NULL );
CommonGlobals* cGlobals = &bGlobals->cGlobals;
CommsCtxt* comms = cGlobals->game.comms;
CommsAddrRec selfAddr;
comms_getSelfAddr( comms, &selfAddr );
NetLaunchInfo nli;
nli_init( &nli, cGlobals->gi, &selfAddr, 1, forceChannel );
nli.remotesAreRobots = XP_TRUE;
comms_invite( comms, NULL_XWE, &nli, destAddr, XP_TRUE );
linuxSaveGame( &bGlobals->cGlobals );
}
static void
kill_board( gpointer data )
{
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)data;
linuxSaveGame( &bGlobals->cGlobals );
disposeBoard( bGlobals );
}
@ -786,7 +811,7 @@ static void
curses_util_turnChanged( XW_UtilCtxt* uc, XWEnv XP_UNUSED(xwe),
XP_S16 XP_UNUSED_DBG(newTurn) )
{
XP_LOGF( "%s(newTurn=%d)", __func__, newTurn );
XP_LOGFF( "(newTurn=%d)", newTurn );
CursesBoardGlobals* bGlobals = (CursesBoardGlobals*)uc->closure;
linuxSaveGame( &bGlobals->cGlobals );
}

View file

@ -40,7 +40,8 @@ CursesBoardState* cb_init( CursesAppGlobals* aGlobals, LaunchParams* params,
void cb_resized( CursesBoardState* cbState, const cb_dims* dims );
void cb_open( CursesBoardState* cbState, sqlite3_int64 rowid, const cb_dims* dims );
bool cb_new( CursesBoardState* cbState, const cb_dims* dims );
bool cb_new( CursesBoardState* cbState, const cb_dims* dims,
const CurGameInfo* gi /* optional: use from globals if unset */ );
void cb_newFor( CursesBoardState* cbState, const NetLaunchInfo* nli,
const cb_dims* dims );
@ -49,6 +50,8 @@ bool cb_feedRow( CursesBoardState* cbState, sqlite3_int64 rowid,
const CommsAddrRec* from );
void cb_feedGame( CursesBoardState* cbState, XP_U32 gameID,
const XP_U8* buf, XP_U16 len, const CommsAddrRec* from );
void cb_addInvite( CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel,
const CommsAddrRec* destAddr );
void cb_closeAll( CursesBoardState* cbState );
#endif

View file

@ -25,6 +25,8 @@
#include <ctype.h>
#include <sys/time.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <netdb.h> /* gethostbyname */
#include <errno.h>
@ -40,6 +42,8 @@
#include <arpa/inet.h>
#include <netinet/in.h>
#include <cjson/cJSON.h>
#include "linuxmain.h"
#include "linuxutl.h"
#include "linuxdict.h"
@ -86,7 +90,6 @@ struct CursesAppGlobals {
CursesMenuState* menuState;
CursGameList* gameList;
CursesBoardState* cbState;
GSList* openGames;
WINDOW* mainWin;
int winWidth, winHeight;
@ -199,8 +202,8 @@ initCurses( CursesAppGlobals* aGlobals )
keypad(stdscr, TRUE); /* effects wgetch only? */
getmaxyx( aGlobals->mainWin, aGlobals->winHeight, aGlobals->winWidth );
XP_LOGF( "%s: getmaxyx()->w:%d; h:%d", __func__, aGlobals->winWidth,
aGlobals->winHeight );
XP_LOGFF( "getmaxyx()->w:%d; h:%d", aGlobals->winWidth,
aGlobals->winHeight );
}
/* globals->statusLine = height - MENU_WINDOW_HEIGHT - 1; */
@ -312,7 +315,7 @@ handleNewGame( void* closure, int XP_UNUSED(key) )
const CurGameInfo* gi = &aGlobals->cag.params->pgi;
if ( !canMakeFromGI(gi) ) {
ca_inform( aGlobals->mainWin, "Unable to create game (check params?)" );
} else if ( !cb_new( aGlobals->cbState, &dims ) ) {
} else if ( !cb_new( aGlobals->cbState, &dims, NULL ) ) {
XP_ASSERT(0);
}
return XP_TRUE;
@ -624,7 +627,7 @@ handle_quitwrite( GIOChannel* source, GIOCondition XP_UNUSED(condition), gpointe
static gboolean
handle_winchwrite( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gpointer data )
{
XP_LOGF( "%s(condition=%x)", __func__, condition );
XP_LOGFF( "(condition=%x)", condition );
CursesAppGlobals* aGlobals = (CursesAppGlobals*)data;
/* Read from the pipe so it won't call again */
@ -632,7 +635,7 @@ handle_winchwrite( GIOChannel* source, GIOCondition XP_UNUSED_DBG(condition), gp
struct winsize ws;
ioctl( STDIN_FILENO, TIOCGWINSZ, &ws );
XP_LOGF( "%s(): lines %d, columns %d", __func__, ws.ws_row, ws.ws_col );
XP_LOGFF( "lines %d, columns %d", ws.ws_row, ws.ws_col );
aGlobals->winHeight = ws.ws_row;
aGlobals->winWidth = ws.ws_col;
@ -712,8 +715,8 @@ fireCursesTimer( CursesAppGlobals* globals )
board_draw( globals->cGlobals.game.board );
}
} else {
XP_LOGF( "skipping timer: now (%ld) < when (%ld)",
now, smallestTip->when );
XP_LOGFF( "skipping timer: now (%ld) < when (%ld)",
now, smallestTip->when );
}
}
} /* fireCursesTimer */
@ -922,10 +925,9 @@ initClientSocket( CursesAppGlobals* globals, char* serverName )
userError( globals, "unable to get host info for %s\n", serverName );
} else {
char* hostName = inet_ntoa( *(struct in_addr*)hostinfo->h_addr );
XP_LOGF( "gethostbyname returned %s", hostName );
XP_LOGFF( "gethostbyname returned %s", hostName );
globals->csInfo.client.serverAddr = inet_addr(hostName);
XP_LOGF( "inet_addr returned %lu",
globals->csInfo.client.serverAddr );
XP_LOGFF( "inet_addr returned %lu", globals->csInfo.client.serverAddr );
}
} /* initClientSocket */
#endif
@ -1298,7 +1300,7 @@ curses_requestMsgs( gpointer data )
if ( '\0' != devIDBuf[0] ) {
relaycon_requestMsgs( aGlobals->cag.params, devIDBuf );
} else {
XP_LOGF( "%s: not requesting messages as don't have relay id", __func__ );
XP_LOGFF( "not requesting messages as don't have relay id" );
}
return 0; /* don't run again */
}
@ -1330,7 +1332,7 @@ cursesDevIDReceived( void* closure, const XP_UCHAR* devID,
CursesAppGlobals* aGlobals = (CursesAppGlobals*)closure;
sqlite3* pDb = aGlobals->cag.params->pDb;
if ( !!devID ) {
XP_LOGF( "%s(devID='%s')", __func__, devID );
XP_LOGFF( "(devID='%s')", devID );
/* If we already have one, make sure it's the same! Else store. */
gchar buf[64];
@ -1357,7 +1359,7 @@ cursesErrorMsgRcvd( void* closure, const XP_UCHAR* msg )
{
CursesAppGlobals* globals = (CursesAppGlobals*)closure;
if ( !!globals->lastErr && 0 == strcmp( globals->lastErr, msg ) ) {
XP_LOGF( "skipping error message from relay" );
XP_LOGFF( "skipping error message from relay" );
} else {
g_free( globals->lastErr );
globals->lastErr = g_strdup( msg );
@ -1445,6 +1447,254 @@ onGameSaved( CursesAppGlobals* aGlobals, sqlite3_int64 rowid, bool isNew )
cgl_refreshOne( aGlobals->gameList, rowid, isNew );
}
static XP_Bool
makeGameFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
LaunchParams* params = aGlobals->cag.params;
CurGameInfo gi = {0};
gi_copy( MPPARM(params->mpool) &gi, &params->pgi );
gi.serverRole = SERVER_ISSERVER;
gi.boardSize = 15;
gi.traySize = 7;
cJSON* tmp = cJSON_GetObjectItem( args, "gid" );
XP_ASSERT( !!tmp );
sscanf( tmp->valuestring, "%X", &gi.gameID );
tmp = cJSON_GetObjectItem( args, "nPlayers" );
XP_ASSERT( !!tmp );
gi.nPlayers = tmp->valueint;
tmp = cJSON_GetObjectItem( args, "hostPosn" );
XP_ASSERT( !!tmp );
int hostPosn = tmp->valueint;
replaceStringIfDifferent( params->mpool, &gi.players[hostPosn].name,
params->localName );
for ( int ii = 0; ii < gi.nPlayers; ++ii ) {
gi.players[ii].isLocal = ii == hostPosn;
gi.players[ii].robotIQ = 1;
}
tmp = cJSON_GetObjectItem( args, "dict" );
XP_ASSERT( tmp );
replaceStringIfDifferent( params->mpool, &gi.dictName, tmp->valuestring );
cb_dims dims;
figureDims( aGlobals, &dims );
LOGGI( &gi, "prior to cb_new call" );
bool success = cb_new( aGlobals->cbState, &dims, &gi );
XP_ASSERT( success );
gi_disposePlayerInfo( MPPARM(params->mpool) &gi );
return success;
}
static XP_Bool
inviteFromArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
/* char buf[1000]; */
/* if ( cJSON_PrintPreallocated( args, buf, sizeof(buf), 0 ) ) { */
/* XP_LOGFF( "(%s)", buf ); */
/* } */
XP_U32 gameID;
cJSON* tmp = cJSON_GetObjectItem( args, "gid" );
XP_ASSERT( !!tmp );
sscanf( tmp->valuestring, "%X", &gameID );
tmp = cJSON_GetObjectItem( args, "channel" );
XP_ASSERT( !!tmp );
XP_U16 channel = tmp->valueint;
XP_LOGFF( "read channel: %X", channel );
/* CursesBoardState* cbState, XP_U32 gameID, XP_U16 forceChannel, */
/* const CommsAddrRec* destAddr */
CommsAddrRec destAddr = {0};
cJSON* addr = cJSON_GetObjectItem( args, "addr" );
XP_ASSERT( !!addr );
tmp = cJSON_GetObjectItem( addr, "mqtt" );
if ( !!tmp ) {
XP_LOGFF( "parsing mqtt: %s", tmp->valuestring );
addr_addType( &destAddr, COMMS_CONN_MQTT );
XP_Bool success = strToMQTTCDevID( tmp->valuestring, &destAddr.u.mqtt.devID );
XP_ASSERT( success );
}
tmp = cJSON_GetObjectItem( addr, "sms" );
if ( !!tmp ) {
XP_LOGFF( "parsing sms: %s", tmp->valuestring );
addr_addType( &destAddr, COMMS_CONN_SMS );
XP_STRCAT( destAddr.u.sms.phone, tmp->valuestring );
destAddr.u.sms.port = 1;
}
cb_addInvite( aGlobals->cbState, gameID, channel, &destAddr );
LOG_RETURN_VOID();
return XP_TRUE;
}
static cJSON*
getGamesStateForArgs( CursesAppGlobals* aGlobals, cJSON* args )
{
cJSON* result = cJSON_CreateArray();
LaunchParams* params = aGlobals->cag.params;
cJSON* gids = cJSON_GetObjectItem(args, "gids" );
for ( int ii = 0 ; ii < cJSON_GetArraySize(gids) ; ++ii ) {
XP_U32 gameID;
cJSON* gid = cJSON_GetArrayItem( gids, ii );
sscanf( gid->valuestring, "%X", &gameID );
GameInfo gib;
if ( gdb_getGameInfoForGID( params->pDb, gameID, &gib ) ) {
cJSON* item = cJSON_CreateObject();
cJSON_AddStringToObject( item, "gid", gid->valuestring );
cJSON_AddBoolToObject( item, "gameOver", gib.gameOver );
cJSON_AddNumberToObject( item, "nPending", gib.nPending );
cJSON_AddNumberToObject( item, "nMoves", gib.nMoves );
cJSON_AddNumberToObject( item, "nTiles", gib.nTiles );
cJSON_AddItemToArray( result, item );
}
}
return result;
}
static cJSON*
makeBoolObj( const char* key, XP_Bool value )
{
cJSON* result = cJSON_CreateObject();
cJSON_AddBoolToObject( result, key, value );
/* char buf[1000]; */
/* if ( cJSON_PrintPreallocated( result, buf, sizeof(buf), 0 ) ) { */
/* XP_LOGFF( "(%s=>%s)=>%s", key, boolToStr(value), buf ); */
/* } */
return result;
}
static gboolean
on_incoming_signal( GSocketService* XP_UNUSED(service),
GSocketConnection* connection,
GObject* XP_UNUSED(source_object), gpointer user_data )
{
XP_LOGFF( "called" );
CursesAppGlobals* aGlobals = (CursesAppGlobals*)user_data;
LaunchParams* params = aGlobals->cag.params;
GInputStream* istream = g_io_stream_get_input_stream( G_IO_STREAM(connection) );
short len;
gssize nread = g_input_stream_read( istream, &len, sizeof(len), NULL, NULL );
XP_ASSERT( nread == sizeof(len) );
len = ntohs(len);
gchar buf[len+1];
nread = g_input_stream_read( istream, buf, len, NULL, NULL );
if ( 0 <= nread ) {
XP_ASSERT( nread == len );
buf[nread] = '\0';
XP_LOGFF( "Message was: \"%s\"\n", buf );
cJSON* reply = cJSON_CreateArray();
cJSON* cmds = cJSON_Parse( buf );
XP_LOGFF( "got msg with array of len %d", cJSON_GetArraySize(cmds) );
for ( int ii = 0 ; ii < cJSON_GetArraySize(cmds) ; ++ii ) {
cJSON* item = cJSON_GetArrayItem( cmds, ii );
cJSON* cmd = cJSON_GetObjectItem( item, "cmd" );
cJSON* key = cJSON_GetObjectItem( item, "key" );
cJSON* args = cJSON_GetObjectItem( item, "args" );
const char* cmdStr = cmd->valuestring;
cJSON* response = NULL;
if ( 0 == strcmp( cmdStr, "quit" ) ) {
response = getGamesStateForArgs( aGlobals, args );
handleQuit( aGlobals, 0 );
} else if ( 0 == strcmp( cmdStr, "getMQTTDevID" ) ) {
MQTTDevID devID;
dvc_getMQTTDevID( params->dutil, NULL_XWE, &devID );
char buf[64];
formatMQTTDevID( &devID, buf, sizeof(buf) );
response = cJSON_CreateString( buf );
} else if ( 0 == strcmp( cmdStr, "makeGame" ) ) {
XP_Bool success = makeGameFromArgs( aGlobals, args );
response = makeBoolObj( "success", success );
} else if ( 0 == strcmp( cmdStr, "invite" ) ) {
XP_Bool success = inviteFromArgs( aGlobals, args );
response = makeBoolObj( "success", success );
} else if ( 0 == strcmp( cmdStr, "gamesState" ) ) {
response = getGamesStateForArgs( aGlobals, args );
} else {
XP_ASSERT(0);
}
XP_ASSERT( !!response );
if ( !!response ) {
cJSON* tmp = cJSON_CreateObject();
cJSON_AddStringToObject( tmp, "cmd", cmdStr );
cJSON_AddNumberToObject( tmp, "key", key->valueint );
cJSON_AddItemToObject( tmp, "response", response );
/*(void)*/cJSON_AddItemToArray( reply, tmp );
}
}
cJSON_Delete( cmds ); /* this apparently takes care of all children */
char* replyStr = cJSON_PrintUnformatted( reply );
short replyStrLen = strlen(replyStr);
XP_LOGFF( "len(%s): %d", replyStr, replyStrLen );
short replyStrNBOLen = htons(replyStrLen);
GOutputStream* ostream = g_io_stream_get_output_stream( G_IO_STREAM(connection) );
gsize nwritten;
gboolean wroteall = g_output_stream_write_all( ostream, &replyStrNBOLen, sizeof(replyStrNBOLen),
&nwritten, NULL, NULL );
XP_ASSERT( wroteall && nwritten == sizeof(replyStrNBOLen) );
wroteall = g_output_stream_write_all( ostream, replyStr, replyStrLen, &nwritten, NULL, NULL );
XP_ASSERT( wroteall && nwritten == replyStrLen );
GError* error = NULL;
g_output_stream_close( ostream, NULL, &error );
if ( !!error ) {
XP_LOGFF( "g_output_stream_close()=>%s", error->message );
g_error_free( error );
}
cJSON_Delete( reply );
free( replyStr );
}
LOG_RETURN_VOID();
return FALSE;
}
static GSocketService*
addCmdListener( CursesAppGlobals* aGlobals )
{
LOG_FUNC();
LaunchParams* params = aGlobals->cag.params;
const XP_UCHAR* cmdsSocket = params->cmdsSocket;
GSocketService* service = NULL;
if ( !!cmdsSocket ) {
service = g_socket_service_new();
struct sockaddr_un addr = {0};
addr.sun_family = AF_UNIX;
strncpy( addr.sun_path, cmdsSocket, sizeof(addr.sun_path) - 1);
GSocketAddress* gsaddr
= g_socket_address_new_from_native (&addr, sizeof(addr) );
GError* error = NULL;
if ( g_socket_listener_add_address( (GSocketListener*)service, gsaddr, G_SOCKET_TYPE_STREAM,
G_SOCKET_PROTOCOL_DEFAULT, NULL, NULL, &error ) ) {
} else {
XP_LOGFF( "g_socket_listener_add_address() failed: %s", error->message );
}
g_object_unref( gsaddr );
g_signal_connect( service, "incoming", G_CALLBACK(on_incoming_signal), aGlobals );
}
LOG_RETURNF( "%p", service );
return service;
}
void
cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
{
@ -1468,6 +1718,8 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
g_globals.gameList = cgl_init( params, g_globals.winWidth, params->cursesListWinHt );
cgl_refresh( g_globals.gameList );
GSocketService* cmdService = addCmdListener( &g_globals );
// g_globals.amServer = isServer;
/* g_globals.cGlobals.params = params; */
/* #ifdef XWFEATURE_RELAY */
@ -1566,14 +1818,17 @@ cursesmain( XP_Bool XP_UNUSED(isServer), LaunchParams* params )
handleNewGame( &g_globals, 0 );
}
} else {
/* Always open a game. Without that it won't attempt to connect and
stalls are likely in the test script case at least. If that's
annoying when running manually add a launch flag */
/* Always open a game (at random). Without that it won't attempt to
connect and stalls are likely in the test script case at least. If
that's annoying when running manually add a launch flag */
cgl_setSel( g_globals.gameList, -1 );
handleOpenGame( &g_globals, 0 );
}
g_main_loop_run( g_globals.loop );
g_object_unref( cmdService );
cb_closeAll( g_globals.cbState );
#ifdef XWFEATURE_BLUETOOTH

View file

@ -72,7 +72,6 @@ void cursesDrawCtxtFree( DrawCtx* dctx );
* message.... Clearly there will need to be such a thing.
*/
void cursesmain( XP_Bool isServer, LaunchParams* params );
bool handleQuit( void* closure, int unused_key );
void inviteReceivedCurses( void* aGlobals, const NetLaunchInfo* invite );

View file

@ -550,12 +550,12 @@ gdb_getRelayIDsToRowsMap( sqlite3* pDb )
}
XP_Bool
gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
gdb_getGameInfoForRow( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
{
XP_Bool success = XP_FALSE;
const char* fmt = "SELECT ended, turn, local, nmoves, ntotal, nmissing, "
"isoCode, seed, connvia, gameid, lastMoveTime, dupTimerExpires, "
"relayid, scores, nPending, role, channel, created, snap "
"relayid, scores, nPending, nTiles, role, channel, created, snap "
"FROM games WHERE rowid = %lld";
XP_UCHAR query[256];
snprintf( query, sizeof(query), fmt, rowid );
@ -587,6 +587,7 @@ gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
len = sizeof(gib->scores);
getColumnText( ppStmt, col++, gib->scores, &len );
gib->nPending = sqlite3_column_int( ppStmt, col++ );
gib->nTiles = sqlite3_column_int( ppStmt, col++ );
gib->role = sqlite3_column_int( ppStmt, col++ );
gib->channelNo = sqlite3_column_int( ppStmt, col++ );
gib->created = sqlite3_column_int( ppStmt, col++ );
@ -613,6 +614,16 @@ gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib )
return success;
}
XP_Bool
gdb_getGameInfoForGID( sqlite3* pDb, XP_U32 gameID, GameInfo* gib )
{
sqlite3_int64 rowids[4];
int nRowIDs = VSIZE(rowids);
gdb_getRowsForGameID( pDb, gameID, rowids, &nRowIDs );
return 1 == nRowIDs
&& gdb_getGameInfoForRow( pDb, rowids[0], gib );
}
void
gdb_getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
int* nRowIDs )

View file

@ -46,6 +46,7 @@ typedef struct _GameInfo {
XP_S16 nMissing;
XP_U16 seed;
XP_U16 nPending;
XP_U16 nTiles;
XP_U32 lastMoveTime;
XP_U32 dupTimerExpires;
XP_U32 created;
@ -69,7 +70,8 @@ void gdb_freeGamesList( GSList* games );
/* Mapping of relayID -> rowid */
GHashTable* gdb_getRelayIDsToRowsMap( sqlite3* pDb );
XP_Bool gdb_getGameInfo( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
XP_Bool gdb_getGameInfoForRow( sqlite3* pDb, sqlite3_int64 rowid, GameInfo* gib );
XP_Bool gdb_getGameInfoForGID( sqlite3* pDb, XP_U32 gameID, GameInfo* gib );
void gdb_getRowsForGameID( sqlite3* pDb, XP_U32 gameID, sqlite3_int64* rowids,
int* nRowIDs );
XP_Bool gdb_loadGame( XWStreamCtxt* stream, sqlite3* pDb, sqlite3_int64 rowid );

View file

@ -39,6 +39,7 @@ typedef struct _WrapperState {
} WrapperState;
static GSList* s_idleProcs = NULL;
static pthread_mutex_t s_idleProcsMutex = PTHREAD_MUTEX_INITIALIZER;
static void
printElapsedFor( struct timespec* first, struct timespec* second,
@ -81,7 +82,9 @@ idle_wrapper( gpointer data )
XP_USE( procName );
#endif
if ( 0 == result ) { /* won't be getting called again */
pthread_mutex_lock( &s_idleProcsMutex );
s_idleProcs = g_slist_remove( s_idleProcs, state );
pthread_mutex_unlock( &s_idleProcsMutex );
g_free( state );
}
@ -98,7 +101,9 @@ _wrapIdle( GSourceFunc function, gpointer data,
/* XP_LOGF( TAG "%s(): installing proc %s from caller %s", __func__, */
/* procName, caller ); */
WrapperState* state = g_malloc0( sizeof(*state) );
pthread_mutex_lock( &s_idleProcsMutex );
s_idleProcs = g_slist_append( s_idleProcs, state );
pthread_mutex_unlock( &s_idleProcsMutex );
state->proc.srcProc = function;
state->data = data;
state->caller = caller;

View file

@ -87,8 +87,8 @@ enum { ROW_ITEM, ROW_THUMB, NAME_ITEM, CREATED_ITEM, GAMEID_ITEM,
#ifdef XWFEATURE_RELAY
RELAYID_ITEM,
#endif
OVER_ITEM, TURN_ITEM,LOCAL_ITEM, NMOVES_ITEM, NTOTAL_ITEM,
MISSING_ITEM, LASTTURN_ITEM, DUPTIMER_ITEM,
NPACKETS_ITEM, OVER_ITEM, TURN_ITEM,LOCAL_ITEM, NMOVES_ITEM,
NTOTAL_ITEM, MISSING_ITEM, LASTTURN_ITEM, DUPTIMER_ITEM,
N_ITEMS,
};
@ -187,6 +187,7 @@ init_games_list( GtkAppGlobals* apg )
#ifdef XWFEATURE_RELAY
addTextColumn( list, "RelayID", RELAYID_ITEM );
#endif
addTextColumn( list, "nPackets", NPACKETS_ITEM );
addTextColumn( list, "Ended", OVER_ITEM );
addTextColumn( list, "Turn", TURN_ITEM );
addTextColumn( list, "Local", LOCAL_ITEM );
@ -210,6 +211,7 @@ init_games_list( GtkAppGlobals* apg )
#ifdef XWFEATURE_RELAY
G_TYPE_STRING, /*RELAYID_ITEM */
#endif
G_TYPE_INT, /* NPACKETS_ITEM */
G_TYPE_BOOLEAN, /* OVER_ITEM */
G_TYPE_INT, /* TURN_ITEM */
G_TYPE_STRING, /* LOCAL_ITEM */
@ -287,6 +289,7 @@ add_to_list( GtkWidget* list, sqlite3_int64 rowid, XP_Bool isNew,
RELAYID_ITEM, gib->relayID,
#endif
TURN_ITEM, gib->turn,
NPACKETS_ITEM, gib->nPending,
OVER_ITEM, gib->gameOver,
LOCAL_ITEM, localString,
NMOVES_ITEM, gib->nMoves,
@ -402,7 +405,7 @@ delete_game( GtkAppGlobals* apg, sqlite3_int64 rowid )
#ifdef DEBUG
XP_Bool success =
#endif
gdb_getGameInfo( params->pDb, rowid, &gib );
gdb_getGameInfoForRow( params->pDb, rowid, &gib );
XP_ASSERT( success );
#ifdef XWFEATURE_RELAY
XP_U32 clientToken = makeClientToken( rowid, gib.seed );
@ -687,7 +690,7 @@ static void
onNewData( GtkAppGlobals* apg, sqlite3_int64 rowid, XP_Bool isNew )
{
GameInfo gib;
if ( gdb_getGameInfo( apg->cag.params->pDb, rowid, &gib ) ) {
if ( gdb_getGameInfoForRow( apg->cag.params->pDb, rowid, &gib ) ) {
add_to_list( apg->listWidget, rowid, isNew, &gib );
g_object_unref( gib.snap );
}

View file

@ -68,12 +68,18 @@ static XP_UCHAR* linux_dutil_md5sum( XW_DUtilCtxt* duc, XWEnv xwe, const XP_U8*
#endif
static void
linux_dutil_getUsername( XW_DUtilCtxt* XP_UNUSED(duc), XWEnv XP_UNUSED(xwe),
linux_dutil_getUsername( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
XP_U16 num, XP_Bool XP_UNUSED(isLocal), XP_Bool isRobot,
XP_UCHAR* buf, XP_U16* len )
{
const char* fmt = isRobot ? "Robot %d" : "Player %d";
*len = XP_SNPRINTF( buf, *len, fmt, num );
LaunchParams* params = (LaunchParams*)duc->closure;
if ( params->localName ) {
*len = XP_SNPRINTF( buf, *len, "%s", params->localName );
XP_LOGFF( "set using local name: %s", buf );
} else {
const char* fmt = isRobot ? "Robot %d" : "Player %d";
*len = XP_SNPRINTF( buf, *len, fmt, num );
}
}
static void
@ -101,7 +107,7 @@ linux_dutil_haveGame( XW_DUtilCtxt* duc, XWEnv XP_UNUSED(xwe),
XP_Bool result = XP_FALSE;
for ( int ii = 0; ii < nRowIDs; ++ii ) {
GameInfo gib;
if ( ! gdb_getGameInfo( pDb, rowids[ii], &gib ) ) {
if ( ! gdb_getGameInfoForRow( pDb, rowids[ii], &gib ) ) {
XP_ASSERT(0);
}
if ( gib.channelNo == channel ) {
@ -206,7 +212,7 @@ linux_dutil_ackMQTTMsg( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* topic,
res = curl_easy_perform(curl);
XP_Bool success = res == CURLE_OK;
XP_LOGFF( "curl_easy_perform() => %d", res );
/* XP_LOGFF( "curl_easy_perform() => %d", res ); */
if ( ! success ) {
XP_LOGFF( "curl_easy_perform() failed: %s", curl_easy_strerror(res));
}
@ -216,7 +222,7 @@ linux_dutil_ackMQTTMsg( XW_DUtilCtxt* duc, XWEnv xwe, const XP_UCHAR* topic,
curl_global_cleanup();
g_free( json );
LOG_RETURN_VOID();
/* LOG_RETURN_VOID(); */
}
XW_DUtilCtxt*

View file

@ -1,6 +1,6 @@
/* -*- compile-command: "make MEMDEBUG=TRUE -j3"; -*- */
/*
* Copyright 2000 - 2020 by Eric House (xwords@eehouse.org). All rights
* Copyright 2000 - 2023 by Eric House (xwords@eehouse.org). All rights
* reserved.
*
* This program is free software; you can redistribute it and/or
@ -75,7 +75,6 @@
#include "dbgutil.h"
#include "dictiter.h"
#include "gsrcwrap.h"
#include "cmdspipe.h"
/* #include "commgr.h" */
/* #include "compipe.h" */
#include "memstream.h"
@ -191,7 +190,7 @@ linuxOpenGame( CommonGlobals* cGlobals )
cGlobals->gi,
cGlobals->util, cGlobals->draw,
&cGlobals->cp, &cGlobals->procs );
XP_LOGFF( "loaded gi at %p", &cGlobals->gi );
LOGGI( cGlobals->gi, __func__ );
stream_destroy( stream );
}
@ -293,7 +292,7 @@ void
gameGotBuf( CommonGlobals* cGlobals, XP_Bool hasDraw, const XP_U8* buf,
XP_U16 len, const CommsAddrRec* from )
{
XP_LOGF( "%s(hasDraw=%d)", __func__, hasDraw );
XP_LOGFF( "(hasDraw=%d)", hasDraw );
XP_Bool redraw = XP_FALSE;
XWGame* game = &cGlobals->game;
XWStreamCtxt* stream = stream_from_msgbuf( cGlobals, buf, len );
@ -476,9 +475,9 @@ linuxSaveGame( CommonGlobals* cGlobals )
if ( !!pDb ) {
gdb_summarize( cGlobals );
}
XP_LOGF( "%s: saved", __func__ );
XP_LOGFF( "saved" );
} else {
XP_LOGF( "%s: simulating save failure", __func__ );
XP_LOGFF( "simulating save failure" );
}
}
} /* linuxSaveGame */
@ -715,6 +714,7 @@ typedef enum {
,CMD_DROPNTHPACKET
,CMD_NOHINTS
,CMD_PICKTILESFACEUP
,CMD_LOCALNAME
,CMD_PLAYERNAME
,CMD_REMOTEPLAYER
,CMD_ROBOTNAME
@ -869,6 +869,7 @@ static CmdInfoRec CmdInfoRecs[] = {
"drop this packet; default 0 (none)" }
,{ CMD_NOHINTS, false, "no-hints", "disallow hints" }
,{ CMD_PICKTILESFACEUP, false, "pick-face-up", "allow to pick tiles" }
,{ CMD_LOCALNAME, true, "localName", "name given all local players" }
,{ CMD_PLAYERNAME, true, "name", "name of local, non-robot player" }
,{ CMD_REMOTEPLAYER, false, "remote-player", "add an expected player" }
,{ CMD_ROBOTNAME, true, "robot", "name of local, robot player" }
@ -2555,57 +2556,6 @@ writeStatus( const char* statusSocket, const char* dbName )
}
}
static gboolean
handle_gotcmd( GIOChannel* source, GIOCondition condition,
gpointer data )
{
// XP_LOGFF( "got something!!" );
gboolean keep = TRUE;
if ( 0 != (G_IO_IN & condition) ) {
int sock = g_io_channel_unix_get_fd( source );
char buf[1024];
ssize_t nread = read( sock, buf, sizeof(buf) );
buf[nread] = '\0';
XP_LOGFF( "read: %s", buf );
LaunchParams* params = (LaunchParams*)data;
CmdBuf cb;
cmds_readCmd( &cb, buf );
switch( cb.cmd ) {
case CMD_NONE:
break;
case CMD_QUIT:
(*params->cmdProcs.quit)( params );
break;
default:
XP_ASSERT( 0 );
}
}
if ( 0 != ((G_IO_HUP) & condition) ) {
XP_LOGFF( "got G_IO_HUP; returning FALSE" );
keep = FALSE;
} else if ( 0 != ((G_IO_ERR) & condition) ) {
XP_LOGFF( "got G_IO_ERR; returning FALSE" );
} else if ( 0 != ((G_IO_NVAL) & condition) ) {
XP_LOGFF( "got G_IO_NVAL; returning FALSE" );
} else {
XP_LOGFF( "something else: 0x%X", condition );
}
return keep;
}
static void
addCmdListener( LaunchParams* params )
{
if ( !!params->cmdsSocket ) {
int fifo = open( params->cmdsSocket, O_RDWR | O_NONBLOCK );
ADD_SOCKET( params, fifo, handle_gotcmd );
}
}
int
main( int argc, char** argv )
{
@ -2939,6 +2889,9 @@ main( int argc, char** argv )
case CMD_PICKTILESFACEUP:
mainParams.pgi.allowPickTiles = XP_TRUE;
break;
case CMD_LOCALNAME:
mainParams.localName = optarg;
break;
case CMD_PLAYERNAME:
index = mainParams.pgi.nPlayers++;
XP_ASSERT( index < MAX_NUM_PLAYERS );
@ -3206,8 +3159,6 @@ main( int argc, char** argv )
}
}
addCmdListener( &mainParams );
/* add cur dir if dict search dir path is empty */
if ( !mainParams.dictDirs ) {
mainParams.dictDirs = g_slist_append( mainParams.dictDirs, "./" );

View file

@ -58,6 +58,7 @@ typedef struct _LaunchParams {
GSList* dictDirs;
char* fileName;
char* dbName;
char* localName;
sqlite3* pDb; /* null unless opened */
XP_U16 saveFailPct;
XP_U16 smsSendFailPct;
@ -302,7 +303,7 @@ struct CommonGlobals {
typedef struct _CommonAppGlobals {
LaunchParams* params;
GSList* globalsList;
GSList* globalsList; /* used by gtk only */
} CommonAppGlobals;
typedef struct _SourceData {

View file

@ -1,36 +0,0 @@
#!/usr/bin/python3
import os, subprocess, threading, time
gFIFO_NAME = '/tmp/fifo'
def launch_thread():
print('launch_thread() called')
args = ['./obj_linux_memdbg/xwords', '--curses', '--cmd-socket-name', gFIFO_NAME ]
prcss = subprocess.Popen(args, stdout = subprocess.DEVNULL)
print('launch_thread() calling communicate()')
prcss.communicate()
print('launch_thread() DONE')
def main():
os.unlink(gFIFO_NAME)
os.mkfifo(gFIFO_NAME)
# mkfifo
# launch app in background
thrd = threading.Thread( target=launch_thread)
thrd.start()
# Loop writing to fifo
for cmd in ['null', 'null', 'null', 'quit']:
time.sleep(2)
fifo_out = open( gFIFO_NAME, 'w' )
print('open DONE')
fifo_out.write( '{}'.format(cmd) )
fifo_out.close()
# Kill app
##############################################################################
if __name__ == '__main__':
main()

View file

@ -0,0 +1,535 @@
#!/usr/bin/env python3
import argparse, datetime, json, os, random, shutil, signal, \
socket, struct, subprocess, sys, threading, time
g_NAMES = ['Brynn', 'Ariela', 'Kati', 'Eric']
gDone = False
def log(args, msg):
if args.VERBOSE:
now = datetime.datetime.strftime(datetime.datetime.now(), '%X.%f')
print('{} {}'.format(now, msg))
def pick_ndevs(args):
RNUM = random.randint(0, 99)
if RNUM > 95 and args.MAXDEVS >= 4:
NDEVS = 4
elif RNUM > 90 and args.MAXDEVS >= 3:
NDEVS = 3
else:
NDEVS = 2
if NDEVS < args.MINDEVS:
NDEVS = args.MINDEVS
return NDEVS
def chooseNames(nPlayers):
players = g_NAMES[:]
result = []
for ii in range(nPlayers):
indx = random.randint(0, len(players)-1)
result.append(players.pop(indx))
return result
class GuestGameInfo():
def __init__(self, gid):
self.gid = gid
# Should be subclass of GuestGameInfo
class HostedInfo():
def __init__(self, guests):
self.guestNames = guests
self.gid = '{:X}'.format(random.randint(0, 0x7FFFFFFF))
self.invitesSent = False
def __str__(self):
return 'gid: {}, guests: {}'.format(self.gid, self.guestNames)
class Device():
_devs = {}
_logdir = None
@staticmethod
def setup():
logdir = os.path.splitext(os.path.basename(sys.argv[0]))[0] + '_logs'
Device._logdir = logdir
# Move an existing logdir aside
if os.path.exists(logdir):
shutil.rmtree(logdir)
# shutil.move(logdir, '/tmp/' + logdir + '_' + str(random.randint(0, 100000)))
os.mkdir(logdir)
for d in ['done', 'dead',]:
os.mkdir(logdir + '/' + d)
def __init__(self, args, host):
self.args = args
self.endTime = None
self.mqttDevID = None
self.smsNumber = args.WITH_SMS and '{}_phone'.format(host) or None
self.host = host
self.hostedGames = [] # array of HostedInfo for each game I host
self.guestGames = []
self.script = '{}/{}.sh'.format(Device._logdir, host)
self.dbName = '{}/{}.db'.format(Device._logdir, host)
self.logfile = '{}/{}_log.txt'.format(Device._logdir, host)
self.cmdSocketName = '{}/{}.sock'.format(Device._logdir, host)
self.gameStates = {}
self._keyCur = 10000 * (1 + g_NAMES.index(host))
def init(self):
self._checkScript()
# called by thread proc
def _launchProc(self):
assert not self.endTime
self.endTime = datetime.datetime.now() + datetime.timedelta(seconds = 5)
args = [ self.script, '--close-stdin' ]
if not self.args.USE_GTK: args.append('--curses')
with open( self.logfile, 'a' ) as logfile:
subprocess.run(args, stdout = subprocess.DEVNULL,
stderr = logfile, universal_newlines = True)
self._log('_launchProc() (in thread): subprocess FINISHED')
os.unlink(self.cmdSocketName)
self.endTime = None
def launchIfNot(self):
if not self.endTime:
self.watcher = threading.Thread(target = Device.runnerStub, args=(self,))
self.watcher.isDaemon = True
self.watcher.start()
while not self.endTime or not os.path.exists(self.cmdSocketName):
time.sleep(0.2)
def _sendWaitReply(self, cmd, **kwargs):
self.launchIfNot()
client = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
# print('connecting to: {}'.format(self.cmdSocketName))
client.connect(self.cmdSocketName);
key = self._nextKey()
params = [{'cmd': cmd, 'key': key, 'args': {**kwargs}}]
payload = json.dumps(params).encode()
client.send(struct.pack('!h', len(payload)))
client.sendall(payload)
# # Receive a response from the server
# self._log('_sendWaitReply({}): calling recv()'.format(cmd))
reslen = struct.unpack('!h', client.recv(2))[0]
response = client.recv(reslen).decode()
# self._log('_sendWaitReply({}): recv => str: {}'.format(cmd, response))
response = json.loads(response)
self._log('_sendWaitReply({}, {}): recv => {}'.format(cmd, kwargs, response))
assert 1 == len(response)
response = response[0]
assert response.get('key', 0) == key
assert response.get('cmd') == cmd
response = response.get('response')
client.close()
return response
def _nextKey(self):
self._keyCur += 1
return self._keyCur
def setDevID(self):
response = self._sendWaitReply('getMQTTDevID')
if response:
self.mqttDevID = response
def makeGames(self):
for remote in self.hostedGames:
guests = remote.guestNames
nPlayers = 1 + len(guests)
hostPosn = random.randint(0, nPlayers-1)
self._sendWaitReply('makeGame', nPlayers=nPlayers, hostPosn=hostPosn,
gid=remote.gid, dict=self.args.DICTS[0])
# This is the heart of things. Do something as long as we have a
# game that needs to run.
def step(self):
# self._log('step() called for {}'.format(self))
stepped = False
for game in self.hostedGames:
if not game.invitesSent:
self.invite(game)
stepped = True
break
if not stepped:
if not self.endTime:
self.launchIfNot()
else:
now = datetime.datetime.now()
if now > self.endTime:
self.quit()
else:
# self._log('sleeping with {} to go'.format(self.endTime-now))
time.sleep(0.5)
stepped = True;
def invite(self, game):
failed = False
for ii in range(len(game.guestNames)):
guestName = game.guestNames[ii]
# self._log('inviting {}'.format(guestName))
guestDev = self._devs[guestName]
addr = {}
if self.args.WITH_MQTT: addr['mqtt'] = guestDev.mqttDevID
if self.args.WITH_SMS: addr['sms'] = guestDev.smsNumber
response = self._sendWaitReply('invite', gid=game.gid,
channel=ii+1, addr=addr,
name=guestName) # just for logging
if response['success']:
guestDev.expectInvite(game.gid)
else:
failed = True
if not failed: game.invitesSent = True
def expectInvite(self, gid):
self.guestGames.append(GuestGameInfo(gid))
self.launchIfNot()
# Return true only if all games I host are finished on all games.
# But: what about games I don't host? For now, let's make it all
# games!
def finished(self):
allGames = self._allGames()
result = 0 < len(allGames)
for game in allGames:
if not result: break
peers = Device.devsWith(game.gid)
for dev in peers:
result = dev.gameOver(game.gid)
if not result: break
# if result: self._log('finished() => {}'.format(result))
return result
def gameOver(self, gid):
result = False
"""Is the game is over for *this* device"""
gameState = self.gameStates.get(gid, None)
if gameState:
result = gameState.get('gameOver', False) and 0 == gameState.get('nPending', 1)
# if result: self._log('gameOver({}) => {}'.format(gid, result))
return result
# this device is stalled if none of its unfinshed games has
# changed state in some interval
def stalled(self):
return False
def _allGames(self):
return self.hostedGames + self.guestGames
def haveGame(self, gid):
withGid = [game for game in self._allGames() if gid == game.gid]
return 0 < len(withGid)
def quit(self):
if self.endTime:
allGames = self._allGames()
gids = [game.gid for game in allGames if not self.gameOver(game.gid)]
response = self._sendWaitReply('quit', gids=gids)
for obj in response:
gid = obj.get('gid')
self.gameStates[gid] = obj
# wait for the thing to actually die
self.watcher.join()
self.watcher = None
assert not self.endTime
def _checkScript(self):
if not os.path.exists(self.script):
scriptArgs = ['exec'] # without exec means terminate() won't work
if self.args.VALGRIND:
scriptArgs += ['valgrind']
# args += ['--leak-check=full']
# args += ['--track-origins=yes']
scriptArgs.append(self.args.APP_NEW) # + [str(p) for p in self.params]
scriptArgs += '--db', self.dbName, '--skip-confirm'
if self.args.SEND_CHAT:
scriptArgs += '--send-chat', self.args.SEND_CHAT
scriptArgs += '--localName', self.host
scriptArgs += '--cmd-socket-name', self.cmdSocketName
if self.args.WITH_MQTT:
scriptArgs += [ '--mqtt-port', self.args.MQTT_PORT, '--mqtt-host', self.args.MQTT_HOST ]
if self.args.WITH_SMS:
scriptArgs += [ '--sms-number', self.smsNumber ]
scriptArgs += ['--board-size', '15', '--sort-tiles']
# useDupeMode = random.randint(0, 100) < self.args.DUP_PCT
# if not useDupeMode: scriptArgs += ['--trade-pct', self.args.TRADE_PCT]
# if self.devID: args.extend( ' '.split(self.devID))
scriptArgs += [ '$*' ]
with open( self.script, 'w' ) as fil:
fil.write( "#!/bin/sh\n" )
fil.write( ' '.join([str(arg) for arg in scriptArgs]) + '\n' )
os.chmod(self.script, 0o755)
@staticmethod
def deviceFor(args, host):
dev = Device._devs.get(host)
if not dev:
dev = Device(args, host)
Device._devs[host] = dev
return dev
@staticmethod
# return all devices (up to 4 of them) that are host or guest in a
# game with <gid>"""
def devsWith(gid):
result = []
for dev in Device._devs.values():
if dev.haveGame(gid):
result.append(dev)
return result
@staticmethod
def getAll():
return [dev for dev in Device._devs.values()]
def addGameWith(self, guests):
# self._log('addGameWith({})'.format(guests))
hosted = HostedInfo(guests)
self.hostedGames.append(hosted)
for guest in guests:
Device.deviceFor(self.args, guest) # in case this device never hosts
def _log(self, msg):
log(self.args, '{}: {}'.format(self.host, msg))
def __str__(self):
result = 'host: {}, devID: {}, with {} games: ' \
.format(self.host, self.mqttDevID,
len(self.hostedGames)+len(self.guestGames))
result += '{' + ', '.join(['{}'.format(game) for game in self._allGames()]) + '}'
result += ' running={}'.format(self.endTime is not None)
return result
@staticmethod
def runnerStub(self):
self._launchProc()
def openOnExit(args):
devs = Device.getAll()
for dev in devs:
appargs = [args.APP_NEW, '--db', dev.dbName]
if args.WITH_MQTT:
appargs += [ '--mqtt-port', args.MQTT_PORT, '--mqtt-host', args.MQTT_HOST ]
subprocess.Popen([str(arg) for arg in appargs], stdout = subprocess.DEVNULL,
stderr = subprocess.DEVNULL, universal_newlines = True)
def mainLoop(args, devs):
startCount = len(devs)
startTime = datetime.datetime.now()
nextStallCheck = startTime + datetime.timedelta(seconds = 20)
while 0 < len(devs):
if gDone:
print('gDone set; exiting loop')
break
dev = random.choice(devs)
dev.step()
if dev.finished():
dev.quit()
devs.remove(dev)
log(args, 'removed dev for {}; {} devs left'.format(dev.host, len(devs)))
# print('.', end='', flush=True)
now = datetime.datetime.now()
if devs and now > nextStallCheck:
nextStallCheck = now + datetime.timedelta(seconds = 10)
allStalled = True
for dev in devs:
if not dev.stalled():
allStalled = False
break
if allStalled:
log(args, 'exiting mainLoop with {} left (of {}) because all stalled' \
.format(len(devs), startCount))
break
if False and endTime < datetime.datetime.now():
log(args, 'exiting mainLoop with {} left (of {}) because out of time' \
.format(len(devs), startCount))
break
for dev in devs:
print('killing {}'.format(dev.host))
dev.quit()
# We will build one Device for each player in the set of games, and
# prime each with enough information that when we start running them
# they can invite each other.
def build_devs(args):
for ii in range(args.NGAMES):
nPlayers = pick_ndevs(args)
players = chooseNames(nPlayers)
host = players[0]
guests = players[1:]
Device.deviceFor(args, host).addGameWith(guests)
return Device.getAll()
def mkParser():
parser = argparse.ArgumentParser()
parser.add_argument('--send-chat', dest = 'SEND_CHAT', type = str, default = None,
help = 'the message to send')
parser.add_argument('--app-new', dest = 'APP_NEW', default = './obj_linux_memdbg/xwords',
help = 'the app we\'ll use')
# parser.add_argument('--app-old', dest = 'APP_OLD', default = './obj_linux_memdbg/xwords',
# help = 'the app we\'ll upgrade from')
# parser.add_argument('--start-pct', dest = 'START_PCT', default = 50, type = int,
# help = 'odds of starting with the new app, 0 <= n < 100')
# parser.add_argument('--upgrade-pct', dest = 'UPGRADE_PCT', default = 20, type = int,
# help = 'odds of upgrading at any launch, 0 <= n < 100')
parser.add_argument('--num-games', dest = 'NGAMES', type = int, default = 1, help = 'number of games')
parser.add_argument('--timeout-mins', dest = 'TIMEOUT_MINS', default = 10000, type = int,
help = 'minutes after which to timeout')
# parser.add_argument('--nochange-secs', dest = 'NO_CHANGE_SECS', default = 30, type = int,
# help = 'seconds without change after which to timeout')
# parser.add_argument('--log-root', dest='LOGROOT', default = '.', help = 'where logfiles go')
# parser.add_argument('--dup-packets', dest = 'DUP_PACKETS', default = False, action = 'store_true',
# help = 'send all packet twice')
# parser.add_argument('--phonies', dest = 'PHONIES', default = -1, type = int,
# help = '0 (ignore), 1 (warn)) or 2 (lose turn); default is pick at random')
# parser.add_argument('--make-phony-pct', dest = 'PHONY_PCT', default = 20, type = int,
# help = 'how often a robot should play a phony (only applies when --phonies==2')
parser.add_argument('--use-gtk', dest = 'USE_GTK', default = False, action = 'store_true',
help = 'run games using gtk instead of ncurses')
# parser.add_argument('--dup-pct', dest = 'DUP_PCT', default = 0, type = int,
# help = 'this fraction played in duplicate mode')
# #
# # echo " [--clean-start] \\" >&2
parser.add_argument('--game-dict', dest = 'DICTS', action = 'append', default = [])
# # echo " [--help] \\" >&2
# # echo " [--max-devs <int>] \\" >&2
parser.add_argument('--min-devs', dest = 'MINDEVS', type = int, default = 2,
help = 'No game will have fewer devices than this')
parser.add_argument('--max-devs', dest = 'MAXDEVS', type = int, default = 4,
help = 'No game will have more devices than this')
# parser.add_argument('--robots-all-same-iq', dest = 'IQS_SAME', default = False,
# action = 'store_true', help = 'give all robots the same IQ')
# parser.add_argument('--min-run', dest = 'MINRUN', type = int, default = 2,
# help = 'Keep each run alive at least this many seconds')
# # echo " [--new-app <path/to/app] \\" >&2
# # echo " [--new-app-args [arg*]] # passed only to new app \\" >&2
# # echo " [--num-rooms <int>] \\" >&2
# # echo " [--old-app <path/to/app]* \\" >&2
# parser.add_argument('--one-per', dest = 'ONEPER', default = False,
# action = 'store_true', help = 'force one player per device')
# parser.add_argument('--resign-pct', dest = 'RESIGN_PCT', default = 0, type = int, \
# help = 'Odds of resigning [0..100]')
parser.add_argument('--seed', type = int, dest = 'SEED', default = 0)
# # echo " [--send-chat <interval-in-seconds> \\" >&2
# # echo " [--udp-incr <pct>] \\" >&2
# # echo " [--udp-start <pct>] # default: $UDP_PCT_START \\" >&2
# # echo " [--undo-pct <int>] \\" >&2
# parser.add_argument('--undo-pct', dest = 'UNDO_PCT', default = 0, type = int)
# parser.add_argument('--trade-pct', dest = 'TRADE_PCT', default = 10, type = int)
parser.add_argument('--with-sms', dest = 'WITH_SMS', action = 'store_true')
parser.add_argument('--without-sms', dest = 'WITH_SMS', default = False, action = 'store_false')
# parser.add_argument('--sms-fail-pct', dest = 'SMS_FAIL_PCT', default = 0, type = int)
parser.add_argument('--with-mqtt', dest = 'WITH_MQTT', default = True, action = 'store_true')
parser.add_argument('--without-mqtt', dest = 'WITH_MQTT', action = 'store_false')
parser.add_argument('--mqtt-port', dest = 'MQTT_PORT', default = 1883 )
parser.add_argument('--mqtt-host', dest = 'MQTT_HOST', default = 'localhost' )
# parser.add_argument('--force-tray', dest = 'TRAYSIZE', default = 0, type = int,
# help = 'Always this many tiles per tray')
# parser.add_argument('--board-size', dest = 'BOARD_SIZE', type = int, default = 15,
# help = 'Use <n>x<n> size board')
# parser.add_argument('--rematch-limit-secs', dest = 'REMATCH_SECS', type = int, default = 0,
# help = 'rematch games that end within this many seconds of script launch')
# envpat = 'DISCON_COREPAT'
# parser.add_argument('--core-pat', dest = 'CORE_PAT', default = os.environ.get(envpat),
# help = "pattern for core files that should stop the script " \
# + "(default from env {})".format(envpat) )
parser.add_argument('--with-valgrind', dest = 'VALGRIND', default = False,
action = 'store_true')
parser.add_argument('--debug', dest='VERBOSE', default=False, action='store_true',
help='log stuff')
parser.add_argument('--open-on-exit', dest = 'OPEN_ON_EXIT', default = False,
action = 'store_true', help='Open devs in gtk app when finished')
return parser
def parseArgs():
args = mkParser().parse_args()
assignDefaults(args)
print(args)
return args
# print(options)
def assignDefaults(args):
if len(args.DICTS) == 0: args.DICTS.append('CollegeEng_2to8.xwd')
args.LOGDIR = os.path.splitext(os.path.basename(sys.argv[0]))[0] + '_logs'
# Move an existing logdir aside
if os.path.exists(args.LOGDIR):
shutil.move(args.LOGDIR, '/tmp/' + args.LOGDIR + '_' + str(random.randint(0, 100000)))
for d in ['', 'done', 'dead',]:
os.mkdir(args.LOGDIR + '/' + d)
def termHandler(signum, frame):
global gDone
print('termHandler() called')
gDone = True
def main():
startTime = datetime.datetime.now()
signal.signal(signal.SIGINT, termHandler)
args = parseArgs()
if args.SEED: random.seed(args.SEED)
# Hack: old files confuse things. Remove is simple fix good for now
if args.WITH_SMS:
try: rmtree('/tmp/xw_sms')
except: None
Device.setup()
devs = build_devs(args)
for dev in devs:
dev.init()
dev.launchIfNot()
time.sleep(1)
for dev in devs:
if args.WITH_MQTT: dev.setDevID()
for dev in devs:
dev.makeGames()
dev.quit()
mainLoop(args, devs)
if args.OPEN_ON_EXIT: openOnExit(args)
##############################################################################
if __name__ == '__main__':
main()

View file

@ -0,0 +1,34 @@
#!/bin/bash
set -u -e
FILES=""
ARGS=""
APP=./obj_linux_memdbg/xwords
usage() {
echo "usage: $0 [args...] file1.db [file2..n.db]"
echo "opens them with CrossWords, assuming they're dbs."
exit 0
}
while [ $# -gt 0 ]; do
if [ '--help' == $1 ]; then
usage
elif [ -f $1 ]; then
if file -L $1 | grep -q 'SQLite 3.x database'; then
FILES="${FILES} $1"
else
ARGS="${ARGS} $1"
fi
else
ARGS="${ARGS} $1"
fi
shift
done
for FILE in $FILES; do
LOGFILE="${FILE/.db/_log.txt}"
echo >> $LOGFILE
echo "******************** launch by $0 ********************" >> $LOGFILE
exec ${APP} ${ARGS} --db $FILE 2>>$LOGFILE &
done

View file

@ -1,82 +0,0 @@
#!/bin/sh
set -e -u
IN_SEQ=''
HTTP='--use-http'
CURSES='--curses'
SLEEP_SEC=10000
usage() {
[ $# -gt 0 ] && echo "ERROR: $1"
echo "usage: $0 --in-sequence|--at-once [--no-use-http] [--gtk]"
cat <<EOF
Starts a pair of devices meant to get into the same game. Verification
is by looking at the relay, usually with
./relay/scripts/showinplay.sh. Both should have an 'A' in the ACK
column.
EOF
exit 1
}
while [ $# -gt 0 ]; do
case $1 in
--in-sequence)
IN_SEQ=1
;;
--at-once)
IN_SEQ=0
;;
--no-use-http)
HTTP=''
;;
--gtk)
CURSES=''
;;
*)
usage "unexpected param $1"
;;
esac
shift
done
[ -n "$IN_SEQ" ] || usage "missing required param"
DB_TMPLATE=_cursesdb_
LOG_TMPLATE=_curseslog_
ROOM_TMPLATE=cursesRoom
echo "delete from msgs;" | psql xwgames
echo "delete from games where room like '$ROOM_TMPLATE%';" | psql xwgames
rm -f ${DB_TMPLATE}*.sqldb
rm -f ${LOG_TMPLATE}*
PIDS=''
for GAME in $(seq 1); do
ROOM=${ROOM_TMPLATE}${GAME}
for N in $(seq 2); do
# for N in $(seq 1); do
DB=$DB_TMPLATE${GAME}_${N}.sqldb
LOG=$LOG_TMPLATE${GAME}_${N}.log
exec ./obj_linux_memdbg/xwords --server $CURSES --remote-player --robot Player \
--room $ROOM --game-dict dict.xwd $HTTP\
--skip-confirm --db $DB --close-stdin --server \
>/dev/null 2>>$LOG &
PID=$!
echo "launched $PID"
if [ $IN_SEQ -eq 1 ]; then
sleep 9
kill $PID
sleep 1
elif [ $IN_SEQ -eq 0 ]; then
PIDS="$PIDS $PID"
fi
done
done
[ -n "${PIDS}" ] && sleep $SLEEP_SEC
for PID in $PIDS; do
kill $PID
done