When a game's consumated and guest discovers it isn't using the same

dict, give chance to switch, and to download if required.  Because of
the way the JNI thread works, and JNI's requirement in general that
env instances match up (e.g. dicts must be destroyed in the same
thread that creates them), substituting into a live game is too hard.
So the game's saved with its new dict and then reloaded.
This commit is contained in:
Eric House 2012-10-24 07:17:21 -07:00
parent 13c74cfbd5
commit aec03fc572
12 changed files with 140 additions and 22 deletions

View file

@ -263,13 +263,14 @@ and_util_informUndo( XW_UtilCtxt* uc )
}
static void
and_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName,
and_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode lang,
const XP_UCHAR* oldName,
const XP_UCHAR* newName, const XP_UCHAR* newSum,
XWPhoniesChoice phoniesAction )
{
LOG_FUNC();
UTIL_CBK_HEADER( "informNetDict",
"(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;L"
"(ILjava/lang/String;Ljava/lang/String;Ljava/lang/String;L"
PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") ";)V" );
jstring jnew = (*env)->NewStringUTF( env, newName );
jstring jsum = (*env)->NewStringUTF( env, newSum );
@ -277,7 +278,8 @@ and_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName,
jobject jphon = intToJEnum( env, phoniesAction,
PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") );
(*env)->CallVoidMethod( env, util->jutil, mid, jold, jnew, jsum, jphon );
(*env)->CallVoidMethod( env, util->jutil, mid, lang, jold, jnew, jsum,
jphon );
deleteLocalRefs( env, jnew, jold, jsum, jphon, DELETE_NO_REF );
UTIL_CBK_TAIL();

View file

@ -1287,6 +1287,22 @@ Java_org_eehouse_android_xw4_jni_XwJNI_game_1hasComms
return result;
}
#ifdef XWFEATURE_CHANGEDICT
JNIEXPORT jboolean JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_game_1changeDict
( JNIEnv* env, jclass C, jint gamePtr, jobject jgi, jstring jname,
jbyteArray jDictBytes, jstring jpath )
{
XWJNI_START_GLOBALS();
DictionaryCtxt* dict = makeDict( MPPARM(mpool) env, globals->jniutil,
jname, jDictBytes, jpath, NULL, false );
game_changeDict( MPPARM(mpool) &state->game, globals->gi, dict );
setJGI( env, jgi, globals->gi );
XWJNI_END();
return XP_FALSE; /* no need to redraw */
}
#endif
JNIEXPORT void JNICALL
Java_org_eehouse_android_xw4_jni_XwJNI_comms_1resendAll
( JNIEnv* env, jclass C, jint gamePtr, jboolean thenAck )

View file

@ -2110,9 +2110,16 @@
<!-- -->
<string name="inform_dict_diffversionf">You and the host of this
game are using different versions of the wordlist %1$s.</string>
<string name="inform_dict_title">Wordlist mismatch</string>
<!-- -->
<string name="inform_dict_diffdictf">You are using the wordlist
%1$s but the game host is using %2$s.</string>
%1$s but the game host is using %2$s. Would you like to use %3$s
too?</string>
<string name="inform_dict_download">\u0020(You will have to download it
first.)</string>
<!-- Shown in toast when relaunching after switching dicts -->
<string name="reload_new_dict">Reloading game with %1$s</string>
<!-- -->
<string name="inform_dict_phonies">\u0020(Remember that phonies
will be discarded based on the host\'s wordlist.)</string>

View file

@ -56,7 +56,8 @@ import org.eehouse.android.xw4.jni.CurGameInfo.DeviceRole;
public class BoardActivity extends XWActivity
implements TransportProcs.TPMsgHandler, View.OnClickListener {
implements TransportProcs.TPMsgHandler, View.OnClickListener,
NetUtils.DownloadFinishedListener {
public static final String INTENT_KEY_CHAT = "chat";
@ -72,6 +73,9 @@ public class BoardActivity extends XWActivity
private static final int DLG_INVITE = DLG_OKONLY + 9;
private static final int DLG_SCORES_BLK = DLG_OKONLY + 10;
private static final int PICK_TILE_REQUESTTRAY_BLK = DLG_OKONLY + 11;
private static final int DLG_USEDICT = DLG_OKONLY + 12;
private static final int DLG_GETDICT = DLG_OKONLY + 13;
private static final int CHAT_REQUEST = 1;
private static final int BT_INVITE_RESULT = 2;
@ -105,6 +109,7 @@ public class BoardActivity extends XWActivity
private static final String PWDNAME = "PWDNAME";
private static final String TOASTSTR = "TOASTSTR";
private static final String WORDS = "WORDS";
private static final String GETDICT = "GETDICT";
private BoardView m_view;
private int m_jniGamePtr;
@ -155,6 +160,7 @@ public class BoardActivity extends XWActivity
private String m_toastStr;
private String[] m_words;
private String m_pwdName;
private String m_getDict;
private int m_missing;
private boolean m_haveInvited = false;
@ -244,6 +250,30 @@ public class BoardActivity extends XWActivity
Utils.setRemoveOnDismiss( this, dialog, id );
break;
case DLG_USEDICT:
case DLG_GETDICT:
lstnr = new DialogInterface.OnClickListener() {
public void onClick( DialogInterface dlg,
int whichButton ) {
if ( DLG_USEDICT == id ) {
setGotGameDict();
} else {
NetUtils.launchAndDownload( BoardActivity.this,
m_gi.dictLang,
m_getDict,
BoardActivity.this );
}
}
};
dialog = new AlertDialog.Builder( this )
.setTitle( m_dlgTitle )
.setMessage( m_dlgBytes )
.setPositiveButton( R.string.button_yes, lstnr )
.setNegativeButton( R.string.button_no, null )
.create();
Utils.setRemoveOnDismiss( this, dialog, id );
break;
case DLG_DELETED:
ab = new AlertDialog.Builder( BoardActivity.this )
.setTitle( R.string.query_title )
@ -512,6 +542,7 @@ public class BoardActivity extends XWActivity
outState.putString( TOASTSTR, m_toastStr );
outState.putStringArray( WORDS, m_words );
outState.putString( PWDNAME, m_pwdName );
outState.putString( GETDICT, m_getDict );
}
private void getBundledData( Bundle bundle )
@ -524,6 +555,7 @@ public class BoardActivity extends XWActivity
m_toastStr = bundle.getString( TOASTSTR );
m_words = bundle.getStringArray( WORDS );
m_pwdName = bundle.getString( PWDNAME );
m_getDict = bundle.getString( GETDICT );
}
}
@ -1014,6 +1046,31 @@ public class BoardActivity extends XWActivity
}
}
//////////////////////////////////////////////////
// NetUtils.DownloadFinishedListener interface
//////////////////////////////////////////////////
public void downloadFinished( final boolean success )
{
if ( success ) {
post( new Runnable() {
public void run() {
setGotGameDict();
}
} );
}
}
private void setGotGameDict()
{
Assert.assertNotNull( m_getDict );
m_jniThread.setSaveDict( m_getDict );
String msg = getString( R.string.reload_new_dict, m_getDict );
Utils.showToast( this, msg );
finish();
GameUtils.launchGame( this, m_rowid, false );
}
private XwJNI.XP_Key keyCodeToXPKey( int keyCode )
{
XwJNI.XP_Key xpKey = XwJNI.XP_Key.XP_KEY_NONE;
@ -1473,7 +1530,8 @@ public class BoardActivity extends XWActivity
}
@Override
public void informNetDict( String oldName, String newName, String newSum,
public void informNetDict( int code, String oldName,
String newName, String newSum,
CurGameInfo.XWPhoniesChoice phonies )
{
// If it's same dict and same sum, we're good. That
@ -1492,14 +1550,18 @@ public class BoardActivity extends XWActivity
} else {
// Different dict! If we have the other one, switch
// to it. Otherwise offer to download
int dlgID;
msg = getString( R.string.inform_dict_diffdictf,
oldName, newName );
}
if ( null != msg ) {
if ( CurGameInfo.XWPhoniesChoice.PHONIES_DISALLOW == phonies ) {
msg += getString( R.string.inform_dict_phonies );
oldName, newName, newName );
if ( DictLangCache.haveDict( BoardActivity.this, code,
newName ) ) {
dlgID = DLG_USEDICT;
} else {
dlgID = DLG_GETDICT;
msg += getString( R.string.inform_dict_download );
}
nonBlockingDialog( DLG_OKONLY, msg );
m_getDict = newName;
nonBlockingDialog( dlgID, msg );
}
}
@ -1779,6 +1841,11 @@ public class BoardActivity extends XWActivity
case DLG_BADWORDS:
m_dlgTitle = R.string.badwords_title;
break;
case DLG_USEDICT:
case DLG_GETDICT:
m_dlgTitle = R.string.inform_dict_title;
break;
default:
Assert.fail();
}

View file

@ -127,6 +127,7 @@ public class JNIThread extends Thread {
private SyncedDraw m_drawer;
private static final int kMinDivWidth = 10;
private int m_connsIconID = 0;
private String m_newDict = null;
LinkedBlockingQueue<QueueElem> m_queue;
@ -199,6 +200,14 @@ public class JNIThread extends Thread {
}
}
// Gross hack. This is the easiest way to set the dict without
// rewriting game loading code or running into cross-threading
// issues.
public void setSaveDict( String newDict )
{
m_newDict = newDict;
}
private boolean toggleTray() {
boolean draw;
int state = XwJNI.board_getTrayVisState( m_jniGamePtr );
@ -272,9 +281,12 @@ public class JNIThread extends Thread {
XwJNI.server_do( m_jniGamePtr );
XwJNI.game_getGi( m_jniGamePtr, m_gi );
if ( null != m_newDict ) {
m_gi.dictName = m_newDict;
}
GameSummary summary = new GameSummary( m_context, m_gi );
XwJNI.game_summarize( m_jniGamePtr, summary );
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, null );
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, m_gi );
GameUtils.saveGame( m_context, state, m_lock, false );
DBUtils.saveSummary( m_context, m_lock, summary );
// There'd better be no way for saveGame above to fail!

View file

@ -118,8 +118,8 @@ public interface UtilCtxt {
void informMove( String expl, String words );
void informUndo();
void informNetDict( String oldName, String newName, String newSum,
CurGameInfo.XWPhoniesChoice phonies );
void informNetDict( int lang, String oldName, String newName,
String newSum, CurGameInfo.XWPhoniesChoice phonies );
void informMissing( boolean isServer, CommsAddrRec.CommsConnType connType,
int nMissingPlayers );

View file

@ -228,7 +228,8 @@ public class UtilCtxtImpl implements UtilCtxt {
subclassOverride( "informUndo" );
}
public void informNetDict( String oldName, String newName, String newSum,
public void informNetDict( int lang, String oldName,
String newName, String newSum,
CurGameInfo.XWPhoniesChoice phonies )
{
subclassOverride( "informNetDict" );

View file

@ -132,6 +132,13 @@ public class XwJNI {
public static native void game_getState( int gamePtr,
JNIThread.GameStateInfo gsi );
public static native boolean game_hasComms( int gamePtr );
// Keep for historical purposes. But threading issues make it
// impossible to implement this without a ton of work.
// public static native boolean game_changeDict( int gamePtr, CurGameInfo gi,
// String dictName,
// byte[] dictBytes,
// String dictPath );
public static native void game_dispose( int gamePtr );
// Board methods

View file

@ -1295,7 +1295,9 @@ client_readInitialMessage( ServerCtxt* server, XWStreamCtxt* stream )
#ifdef STREAM_VERS_BIGBOARD
if ( '\0' != rmtDictName[0] ) {
const XP_UCHAR* ourName = dict_getShortName( curDict );
util_informNetDict( server->vol.util, ourName, rmtDictName,
util_informNetDict( server->vol.util,
dict_getLangCode( curDict ),
ourName, rmtDictName,
rmtDictSum, localGI.phoniesAction );
}
#endif

View file

@ -130,7 +130,8 @@ typedef struct UtilVtable {
void (*m_util_informMove)( XW_UtilCtxt* uc, XWStreamCtxt* expl,
XWStreamCtxt* words );
void (*m_util_informUndo)( XW_UtilCtxt* uc );
void (*m_util_informNetDict)( XW_UtilCtxt* uc, const XP_UCHAR* oldName,
void (*m_util_informNetDict)( XW_UtilCtxt* uc, XP_LangCode lang,
const XP_UCHAR* oldName,
const XP_UCHAR* newName,
const XP_UCHAR* newSum,
XWPhoniesChoice phoniesAction );
@ -251,8 +252,9 @@ struct XW_UtilCtxt {
(uc)->vtable->m_util_informMove( (uc),(e),(w))
#define util_informUndo(uc) \
(uc)->vtable->m_util_informUndo( (uc))
#define util_informNetDict(uc, on, nn, ns, pa ) \
(uc)->vtable->m_util_informNetDict( (uc), (on), (nn), (ns), (pa) )
#define util_informNetDict(uc, cd, on, nn, ns, pa ) \
(uc)->vtable->m_util_informNetDict( (uc), (cd), (on), (nn), (ns), \
(pa) )
#define util_notifyGameOver( uc, q ) \
(uc)->vtable->m_util_notifyGameOver((uc), (q))

View file

@ -395,7 +395,8 @@ curses_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter )
} /* curses_util_notifyGameOver */
static void
curses_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* XP_UNUSED_DBG(oldName),
curses_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang),
const XP_UCHAR* XP_UNUSED_DBG(oldName),
const XP_UCHAR* XP_UNUSED_DBG(newName),
const XP_UCHAR* XP_UNUSED_DBG(newSum),
XWPhoniesChoice phoniesAction )

View file

@ -1475,7 +1475,8 @@ gtk_util_notifyGameOver( XW_UtilCtxt* uc, XP_S16 quitter )
} /* gtk_util_notifyGameOver */
static void
gtk_util_informNetDict( XW_UtilCtxt* uc, const XP_UCHAR* oldName,
gtk_util_informNetDict( XW_UtilCtxt* uc, XP_LangCode XP_UNUSED(lang),
const XP_UCHAR* oldName,
const XP_UCHAR* newName, const XP_UCHAR* newSum,
XWPhoniesChoice phoniesAction )
{