diff --git a/xwords4/common/nli.c b/xwords4/common/nli.c index c2809060d..eefbf6aa6 100644 --- a/xwords4/common/nli.c +++ b/xwords4/common/nli.c @@ -208,6 +208,7 @@ nli_makeFromStream( NetLaunchInfo* nli, XWStreamCtxt* stream ) void nli_makeAddrRec( const NetLaunchInfo* nli, CommsAddrRec* addr ) { + XP_MEMSET( addr, 0, sizeof(*addr) ); CommsConnType type; for ( XP_U32 state = 0; types_iter( nli->_conTypes, &type, &state ); ) { addr_addType( addr, type ); diff --git a/xwords4/wasm/Makefile b/xwords4/wasm/Makefile index 12ca2380e..8b6892701 100644 --- a/xwords4/wasm/Makefile +++ b/xwords4/wasm/Makefile @@ -72,7 +72,7 @@ main.html: ${INPUTS} $(MAKEFILE) shell_minimal.html --preload-file assets_dir --shell-file shell_minimal.html \ -s USE_SDL_TTF=2 -s USE_SDL_IMAGE=2 -s SDL2_IMAGE_FORMATS='["png"]' \ -s "EXTRA_EXPORTED_RUNTIME_METHODS=['ccall']" \ - -s EXPORTED_FUNCTIONS='["_main", "_button", "_newgame", "_gotMQTTMsg", "_jscallback", "_onDlgButton"]' \ + -s EXPORTED_FUNCTIONS='["_main", "_button", "_newgame", "_gotMQTTMsg", "_jscallback", "_onDlgButton", "_MQTTConnectedChanged"]' \ -s WASM=1 \ ${INPUTS} -o $@ diff --git a/xwords4/wasm/main.c b/xwords4/wasm/main.c index dd0f7ad8f..7f6c6f357 100644 --- a/xwords4/wasm/main.c +++ b/xwords4/wasm/main.c @@ -1,4 +1,4 @@ -/* -*- compile-command: "cd ../wasm && make main.html -j3"; -*- */ +/* -*- compile-command: "cd ../wasm && make MEMDEBUG=TRUE main.html -j3"; -*- */ /* * Copyright 2021 by Eric House (xwords@eehouse.org). All rights reserved. * @@ -105,6 +105,35 @@ EM_JS(void, setButtonText, (const char* id, const char* text), { static void updateScreen( Globals* globals, bool doSave ); +typedef void (*ConfirmProc)( void* closure, bool confirmed ); + +typedef struct _ConfirmState { + Globals* globals; + ConfirmProc proc; + void* closure; +} ConfirmState; + +static void +onConfirmed( void* closure, const char* button ) +{ + bool confirmed = 0 == strcmp( button, BUTTON_OK ); + ConfirmState* cs = (ConfirmState*)closure; + (*cs->proc)( cs->closure, confirmed ); + XP_FREE( cs->globals->mpool, cs ); +} + +static void +call_confirm( Globals* globals, const char* msg, + ConfirmProc proc, void* closure ) +{ + const char* buttons[] = { BUTTON_CANCEL, BUTTON_OK, NULL }; + ConfirmState* cs = XP_MALLOC( globals->mpool, sizeof(*cs) ); + cs->globals = globals; + cs->proc = proc; + cs->closure = closure; + call_dialog( msg, buttons, onConfirmed, cs ); +} + static void call_alert( const char* msg ) { @@ -134,11 +163,9 @@ send_msg( XWEnv xwe, const XP_U8* buf, XP_U16 len, XP_U16 streamLen = stream_getSize( stream ); - XP_LOGFF( "calling call_mqttSend" ); if ( call_mqttSend( topic, stream_getPtr( stream ), streamLen ) ) { nSent = len; } - XP_LOGFF( "back from call_mqttSend" ); stream_destroy( stream, NULL ); } @@ -215,29 +242,18 @@ startGame( Globals* globals ) LOG_RETURN_VOID(); } -static bool -gameFromInvite( Globals* globals, const NetLaunchInfo* invite ) -{ - bool loaded = false; - bool needsLoad = true; - XP_LOGFF( "model: %p", globals->game.model ); - if ( NULL != globals->game.model ) { - XP_LOGFF( "have game: TRUE" ); - /* there's a current game. Ignore the invitation if it has the same - gameID. Otherwise ask to replace */ - if ( globals->gi.gameID == invite->gameID ) { - XP_LOGFF( "duplicate invite; ignoring" ); - needsLoad = false; - } else { - call_alert( "Invitation received; replace current game?" ); - // needsLoad = false; - } - } else if ( invite->lang != 1 ) { - call_alert( "Invitations are only supported for play in English right now." ); - needsLoad = false; - } +typedef struct _AskReplaceState { + Globals* globals; + NetLaunchInfo invite; +} AskReplaceState; - if ( needsLoad ) { +static void +onReplaceConfirmed( void* closure, bool confirmed ) +{ + AskReplaceState* ars = (AskReplaceState*)closure; + Globals* globals = ars->globals; + + if ( confirmed ) { if ( !!globals->util ) { game_dispose( &globals->game, NULL ); wasm_util_destroy( globals->util ); @@ -250,15 +266,44 @@ gameFromInvite( Globals* globals, const NetLaunchInfo* invite ) globals->util = wasm_util_make( MPPARM(globals->mpool) &globals->gi, globals->dutil, globals ); - loaded = game_makeFromInvite( MPPARM(globals->mpool) NULL, invite, - &globals->game, &globals->gi, - globals->dict, NULL, - globals->util, globals->draw, - &globals->cp, &globals->procs ); + game_makeFromInvite( MPPARM(globals->mpool) NULL, &ars->invite, + &globals->game, &globals->gi, + globals->dict, NULL, + globals->util, globals->draw, + &globals->cp, &globals->procs ); - } else { - loaded = true; + startGame( globals ); } + + XP_FREE( globals->mpool, ars ); +} + +static bool +gameFromInvite( Globals* globals, const NetLaunchInfo* invite ) +{ + bool needsLoad = true; + XP_LOGFF( "model: %p", globals->game.model ); + if ( NULL != globals->game.model ) { + XP_LOGFF( "have game: TRUE" ); + /* there's a current game. Ignore the invitation if it has the same + gameID. Otherwise ask to replace */ + if ( globals->gi.gameID == invite->gameID ) { + XP_LOGFF( "duplicate invite; ignoring" ); + needsLoad = false; + } else { + AskReplaceState* ars = XP_MALLOC( globals->mpool, sizeof(*ars) ); + ars->globals = globals; + XP_MEMCPY( &ars->invite, invite, sizeof(ars->invite) ); + call_confirm( globals, "Invitation received; replace current game?", + onReplaceConfirmed, ars ); + needsLoad = false; + } + } else if ( invite->lang != 1 ) { + call_alert( "Invitations are only supported for play in English right now." ); + needsLoad = false; + } + + bool loaded = !needsLoad; LOG_RETURNF( "%d", loaded ); return loaded; } @@ -353,9 +398,24 @@ void main_onGameMessage( Globals* globals, XP_U32 gameID, const CommsAddrRec* from, XWStreamCtxt* stream ) { - XP_Bool draw = game_receiveMessage( &globals->game, NULL, stream, from ); - if ( draw ) { - updateScreen( globals, true ); + if ( gameID == globals->gi.gameID ) { + XP_Bool draw = game_receiveMessage( &globals->game, NULL, stream, from ); + if ( draw ) { + updateScreen( globals, true ); + } + } else { + XP_LOGFF( "dropping packet for wrong game" ); + + XWStreamCtxt* stream = mem_stream_make_raw( MPPARM(globals->mpool) + globals->vtMgr ); + dvc_makeMQTTNoSuchGame( globals->dutil, NULL, stream, gameID ); + + XP_UCHAR topic[64]; + formatMQTTTopic( &from->u.mqtt.devID, topic, sizeof(topic) ); + + XP_U16 streamLen = stream_getSize( stream ); + call_mqttSend( topic, stream_getPtr( stream ), streamLen ); + stream_destroy( stream, NULL ); } } @@ -767,6 +827,16 @@ newgame( void* closure, bool p0, bool p1 ) call_dialog( query, buttons, onNewgameResponse, ngs ); } +void +MQTTConnectedChanged( void* closure, bool connected ) +{ + XP_LOGFF( "connected=%d", connected); + Globals* globals = (Globals*)closure; + if ( connected && !!globals->game.comms ) { + comms_resendAll( globals->game.comms, NULL, COMMS_CONN_MQTT, XP_TRUE ); + } +} + void gotMQTTMsg( void* closure, int len, const uint8_t* msg ) { diff --git a/xwords4/wasm/xwutils.js b/xwords4/wasm/xwutils.js index 6f2b6ff5f..93914e32d 100644 --- a/xwords4/wasm/xwutils.js +++ b/xwords4/wasm/xwutils.js @@ -30,12 +30,17 @@ function onHaveDevID(closure, devid) { state.closure = closure; document.getElementById("mqtt_span").textContent=devid; + function tellConnected(isConn) { + Module.ccall('MQTTConnectedChanged', null, ['number', 'boolean'], [state.closure, isConn]); + } + state.client = new Paho.MQTT.Client("eehouse.org", 8883, '/wss', devid); // set callback handlers state.client.onConnectionLost = function onConnectionLost(responseObject) { state.connected = false; document.getElementById("mqtt_status").textContent="Disconnected"; + tellConnected(false); if (responseObject.errorCode !== 0) { console.log("onConnectionLost:"+responseObject.errorMessage); } @@ -50,6 +55,7 @@ function onHaveDevID(closure, devid) { function onConnect() { state.connected = true document.getElementById("mqtt_status").textContent="Connected"; + tellConnected(true); var subscribeOptions = { qos: 2, // QoS