/* -*- compile-command: "find-and-gradle.sh inXw4dDeb"; -*- */ /* * Copyright © 2009 - 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 #include #include #include #include #include "comtypes.h" #include "game.h" #include "board.h" #include "mempool.h" #include "strutils.h" #include "dbgutil.h" #include "dictnry.h" #include "dictiter.h" #include "dictmgr.h" #include "nli.h" #include "smsproto.h" #include "device.h" #include "knownplyr.h" #include "utilwrapper.h" #include "drawwrapper.h" #include "xportwrapper.h" #include "anddict.h" #include "andutils.h" #include "andglobals.h" #include "jniutlswrapper.h" #include "paths.h" #ifdef MAP_THREAD_TO_ENV # define LOG_MAPPING #endif // #define LOG_MAPPING_ALL #ifdef MAP_THREAD_TO_ENV typedef struct _EnvThreadEntry { JNIEnv* env; pthread_t owner; XP_U16 refcount; #ifdef LOG_MAPPING const char* ownerFunc; #endif } EnvThreadEntry; struct _EnvThreadInfo { pthread_mutex_t mtxThreads; int nEntries; EnvThreadEntry* entries; MPSLOT }; #endif /* Globals for the whole game */ typedef struct _JNIGlobalState { #ifdef MAP_THREAD_TO_ENV EnvThreadInfo ti; #endif DictMgrCtxt* dictMgr; SMSProto* smsProto; VTableMgr* vtMgr; XW_DUtilCtxt* dutil; JNIUtilCtxt* jniutil; XP_Bool mpoolInUse; const char* mpoolUser; MPSLOT } JNIGlobalState; #ifdef MEM_DEBUG static MemPoolCtx* getMPoolImpl( JNIGlobalState* globalState, const char* user ) { if ( globalState->mpoolInUse ) { XP_LOGFF( "mpoolInUse ALREADY SET!!!! (by %s)", globalState->mpoolUser ); } globalState->mpoolInUse = XP_TRUE; globalState->mpoolUser = user; return globalState->mpool; } #define GETMPOOL(gs) getMPoolImpl( (gs), __func__ ) static void releaseMPool( JNIGlobalState* globalState ) { // XP_ASSERT( globalState->mpoolInUse ); /* fired again!!! */ if ( !globalState->mpoolInUse ) { XP_LOGFF( "line %d; ERROR ERROR ERROR mpoolInUse not set", __LINE__ ); } globalState->mpoolInUse = XP_FALSE; } #else # define releaseMPool(s) #endif #define GAMEPTR_IS_OBJECT #ifdef GAMEPTR_IS_OBJECT typedef jobject GamePtrType; #else typedef long GamePtrType; #endif #ifdef LOG_MAPPING # ifdef DEBUG static int countUsed( const EnvThreadInfo* ti ) { int count = 0; for ( int ii = 0; ii < ti->nEntries; ++ii ) { EnvThreadEntry* entry = &ti->entries[ii]; if ( 0 != entry->owner ) { # ifdef LOG_MAPPING_ALL XP_LOGFF( "ii=%d; owner: %x", ii, (unsigned int)entry->owner ); # endif ++count; } } return count; } # endif #endif #ifdef MAP_THREAD_TO_ENV # define MAP_THREAD( ti, env ) map_thread_prv( (ti), (env), __func__ ) static void map_thread_prv( EnvThreadInfo* ti, JNIEnv* env, const char* caller ) { pthread_t self = pthread_self(); pthread_mutex_lock( &ti->mtxThreads ); XP_Bool found = false; int nEntries = ti->nEntries; EnvThreadEntry* firstEmpty = NULL; for ( int ii = 0; !found && ii < nEntries; ++ii ) { EnvThreadEntry* entry = &ti->entries[ii]; if ( 0 == entry->owner ) { if ( NULL == firstEmpty ) { firstEmpty = entry; } } else if ( self == entry->owner ) { found = true; if ( env != entry->env ) { /* this DOES happen!!! */ RAW_LOG( "(ti=%p): replacing env %p with env %p for thread %x", ti, entry->env, env, (int)self ); entry->env = env; } } } if ( !found ) { if ( !firstEmpty ) { /* out of slots */ if ( 0 == nEntries ) { /* first time */ nEntries = 2; XP_ASSERT( !ti->entries ); } else { nEntries *= 2; } EnvThreadEntry* entries = XP_CALLOC( ti->mpool, nEntries * sizeof(*entries) ); if ( !!ti->entries ) { XP_MEMCPY( entries, ti->entries, ti->nEntries * sizeof(*ti->entries) ); } firstEmpty = &entries[ti->nEntries]; /* first new entry */ ti->entries = entries; ti->nEntries = nEntries; #ifdef LOG_MAPPING RAW_LOG( "num env entries now %d", nEntries ); #endif } XP_ASSERT( !!firstEmpty ); firstEmpty->owner = self; firstEmpty->env = env; XP_ASSERT( 0 == firstEmpty->refcount ); ++firstEmpty->refcount; #ifdef LOG_MAPPING firstEmpty->ownerFunc = caller; RAW_LOG( "entry %zu: mapped env %p to thread %x", firstEmpty - ti->entries, env, (int)self ); RAW_LOG( "num entries USED now %d", countUsed(ti) ); #endif } pthread_mutex_unlock( &ti->mtxThreads ); } /* map_thread_prv */ static void map_init( MPFORMAL EnvThreadInfo* ti, JNIEnv* env ) { pthread_mutex_init( &ti->mtxThreads, NULL ); MPASSIGN( ti->mpool, mpool ); MAP_THREAD( ti, env ); } #define MAP_REMOVE( ti, env ) map_remove_prv((ti), (env), __func__) static void map_remove_prv( EnvThreadInfo* ti, JNIEnv* env, const char* func ) { XP_Bool found = false; pthread_mutex_lock( &ti->mtxThreads ); for ( int ii = 0; !found && ii < ti->nEntries; ++ii ) { EnvThreadEntry* entry = &ti->entries[ii]; found = env == entry->env; if ( found ) { XP_ASSERT( pthread_self() == entry->owner ); #ifdef LOG_MAPPING RAW_LOG( "UNMAPPED env %p to thread %x (from %s; mapped by %s)", entry->env, (int)entry->owner, func, entry->ownerFunc ); RAW_LOG( "%d entries left", countUsed( ti ) ); entry->ownerFunc = NULL; #endif XP_ASSERT( 1 == entry->refcount ); --entry->refcount; entry->env = NULL; entry->owner = 0; } } pthread_mutex_unlock( &ti->mtxThreads ); XP_ASSERT( found ); } static void map_destroy( EnvThreadInfo* ti ) { pthread_mutex_destroy( &ti->mtxThreads ); } static JNIEnv* prvEnvForMe( EnvThreadInfo* ti ) { JNIEnv* result = NULL; pthread_t self = pthread_self(); pthread_mutex_lock( &ti->mtxThreads ); for ( int ii = 0; !result && ii < ti->nEntries; ++ii ) { if ( self == ti->entries[ii].owner ) { result = ti->entries[ii].env; } } pthread_mutex_unlock( &ti->mtxThreads ); return result; } #else # define MAP_THREAD( ti, env ) # define MAP_REMOVE( ti, env ) # define map_init( ... ) # define map_destroy( ti ) #endif // MAP_THREAD_TO_ENV #ifdef MAP_THREAD_TO_ENV static pthread_mutex_t g_globalStateLock = PTHREAD_MUTEX_INITIALIZER; static JNIGlobalState* g_globalState = NULL; void setGlobalState( JNIGlobalState* state ) { pthread_mutex_lock( &g_globalStateLock ); g_globalState = state; pthread_mutex_unlock( &g_globalStateLock ); } JNIEnv* envForMe( EnvThreadInfo* ti, const char* caller ) { JNIEnv* result = prvEnvForMe( ti ); #ifdef DEBUG if( !result ) { pthread_t self = pthread_self(); XP_LOGFF( "no env for %s (thread %x)", caller, (int)self ); XP_ASSERT(0); } #endif return result; } #else # define setGlobalState(s) #endif JNIEnv* waitEnvFromGlobals() /* hanging */ { JNIEnv* result = NULL; #ifdef MAP_THREAD_TO_ENV pthread_mutex_lock( &g_globalStateLock ); JNIGlobalState* state = g_globalState; if ( !!state ) { result = prvEnvForMe( &state->ti ); } if ( !result ) { pthread_mutex_unlock( &g_globalStateLock ); } #endif return result; } void releaseEnvFromGlobals( JNIEnv* env ) { #ifdef MAP_THREAD_TO_ENV XP_ASSERT( !!env ); JNIGlobalState* state = g_globalState; XP_ASSERT( !!state ); XP_ASSERT( env == prvEnvForMe( &state->ti ) ); pthread_mutex_unlock( &g_globalStateLock ); #endif } static void tilesArrayToTileSet( JNIEnv* env, jintArray jtiles, TrayTileSet* tset ) { if ( jtiles != NULL ) { XP_ASSERT( !!jtiles ); jsize nTiles = (*env)->GetArrayLength( env, jtiles ); int tmp[MAX_TRAY_TILES]; getIntsFromArray( env, tmp, jtiles, nTiles, XP_FALSE ); tset->nTiles = nTiles; for ( int ii = 0; ii < nTiles; ++ii ) { tset->tiles[ii] = tmp[ii]; } } } #ifdef GAMEPTR_IS_OBJECT static JNIState* getState( JNIEnv* env, GamePtrType gamePtr, const char* func ) { #ifdef DEBUG if ( NULL == gamePtr ) { XP_LOGFF( "ERROR: getState() called from %s() with null gamePtr", func ); } #endif XP_ASSERT( NULL != gamePtr ); /* fired */ jmethodID mid = getMethodID( env, gamePtr, "ptr", "()J" ); XP_ASSERT( !!mid ); return (JNIState*)(*env)->CallLongMethod( env, gamePtr, mid ); } #else # define getState( env, gamePtr, func ) ((JNIState*)(gamePtr)) #endif JNIEXPORT jlong JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_globalsInit ( JNIEnv* env, jclass C, jobject jdutil, jobject jniu, jlong jseed ) { #ifdef MEM_DEBUG MemPoolCtx* mpool = mpool_make( NULL ); XP_LOGFF( "ptr size: %zu", sizeof(mpool) ); #endif int seed = (int)jseed; XP_LOGFF( "calling srandom(seed %d)", seed ); srandom( seed ); JNIGlobalState* globalState = (JNIGlobalState*)XP_CALLOC( mpool, sizeof(*globalState) ); map_init( MPPARM(mpool) &globalState->ti, env ); globalState->jniutil = makeJNIUtil( MPPARM(mpool) env, TI_IF(&globalState->ti) jniu ); globalState->vtMgr = make_vtablemgr( MPPARM_NOCOMMA(mpool) ); globalState->dictMgr = dmgr_make( MPPARM_NOCOMMA( mpool ) ); globalState->dutil = makeDUtil( MPPARM(mpool) env, TI_IF(&globalState->ti) jdutil, globalState->vtMgr, globalState->dictMgr, globalState->jniutil, NULL ); globalState->smsProto = smsproto_init( MPPARM( mpool ) env, globalState->dutil ); MPASSIGN( globalState->mpool, mpool ); setGlobalState( globalState ); // LOG_RETURNF( "%p", globalState ); return (jlong)globalState; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_cleanGlobals ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { // LOG_FUNC(); if ( 0 != jniGlobalPtr ) { setGlobalState( NULL ); JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif ASSERT_ENV( &globalState->ti, env ); smsproto_free( globalState->smsProto ); vtmgr_destroy( MPPARM(mpool) globalState->vtMgr ); dmgr_destroy( globalState->dictMgr, env ); destroyDUtil( &globalState->dutil, env ); destroyJNIUtil( env, &globalState->jniutil ); map_destroy( &globalState->ti ); XP_FREE( mpool, globalState ); mpool_destroy( mpool ); } } static const SetInfo gi_ints[] = { ARR_MEMBER( CurGameInfo, nPlayers ) ,ARR_MEMBER( CurGameInfo, gameSeconds ) ,ARR_MEMBER( CurGameInfo, boardSize ) ,ARR_MEMBER( CurGameInfo, traySize ) ,ARR_MEMBER( CurGameInfo, bingoMin ) ,ARR_MEMBER( CurGameInfo, gameID ) ,ARR_MEMBER( CurGameInfo, forceChannel ) }; static const SetInfo gi_bools[] = { ARR_MEMBER( CurGameInfo, hintsNotAllowed ) ,ARR_MEMBER( CurGameInfo, timerEnabled ) ,ARR_MEMBER( CurGameInfo, allowPickTiles ) ,ARR_MEMBER( CurGameInfo, allowHintRect ) ,ARR_MEMBER( CurGameInfo, inDuplicateMode ) }; static const SetInfo pl_ints[] = { ARR_MEMBER( LocalPlayer, robotIQ ) ,ARR_MEMBER( LocalPlayer, secondsUsed ) }; static CurGameInfo* makeGI( MPFORMAL JNIEnv* env, jobject jgi ) { CurGameInfo* gi = (CurGameInfo*)XP_CALLOC( mpool, sizeof(*gi) ); XP_UCHAR buf[256]; /* in case needs whole path */ getInts( env, (void*)gi, jgi, AANDS(gi_ints) ); getBools( env, (void*)gi, jgi, AANDS(gi_bools) ); /* Unlike on other platforms, gi is created without a call to game_makeNewGame, which sets gameID. So check here if it's still unset and if necessary set it -- including back in the java world. */ if ( 0 == gi->gameID ) { while ( 0 == gi->gameID ) { gi->gameID = getCurSeconds( env ); } setInt( env, jgi, "gameID", gi->gameID ); } gi->phoniesAction = jenumFieldToInt( env, jgi, "phoniesAction", PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") ); gi->serverRole = jenumFieldToInt( env, jgi, "serverRole", PKG_PATH("jni/CurGameInfo$DeviceRole")); getString( env, jgi, "dictName", AANDS(buf) ); gi->dictName = copyString( mpool, buf ); getString( env, jgi, "isoCodeStr", AANDS(buf) ); XP_STRNCPY( gi->isoCodeStr, buf, VSIZE(gi->isoCodeStr) ); XP_ASSERT( gi->nPlayers <= MAX_NUM_PLAYERS ); jobject jplayers = getObjectField( env, jgi, "players", "[L" PKG_PATH("jni/LocalPlayer") ";" ); XP_ASSERT( !!jplayers ); for ( int ii = 0; !!jplayers && ii < gi->nPlayers; ++ii ) { LocalPlayer* lp = &gi->players[ii]; jobject jlp = (*env)->GetObjectArrayElement( env, jplayers, ii ); XP_ASSERT( !!jlp ); getInts( env, (void*)lp, jlp, AANDS(pl_ints) ); lp->isLocal = getBool( env, jlp, "isLocal" ); getString( env, jlp, "name", AANDS(buf) ); lp->name = copyString( mpool, buf ); getString( env, jlp, "password", AANDS(buf) ); lp->password = copyString( mpool, buf ); getString( env, jlp, "dictName", AANDS(buf) ); lp->dictName = copyString( mpool, buf ); deleteLocalRef( env, jlp ); } deleteLocalRef( env, jplayers ); return gi; } /* makeGI */ static void setJGI( JNIEnv* env, jobject jgi, const CurGameInfo* gi ) { // set fields setInts( env, jgi, (void*)gi, AANDS(gi_ints) ); setBools( env, jgi, (void*)gi, AANDS(gi_bools) ); setString( env, jgi, "dictName", gi->dictName ); setString( env, jgi, "isoCodeStr", gi->isoCodeStr ); intToJenumField( env, jgi, gi->phoniesAction, "phoniesAction", PKG_PATH("jni/CurGameInfo$XWPhoniesChoice") ); intToJenumField( env, jgi, gi->serverRole, "serverRole", PKG_PATH("jni/CurGameInfo$DeviceRole") ); jobject jplayers = getObjectField( env, jgi, "players", "[L" PKG_PATH("jni/LocalPlayer") ";" ); if ( !!jplayers ) { for ( int ii = 0; ii < gi->nPlayers; ++ii ) { const LocalPlayer* lp = &gi->players[ii]; jobject jlp = (*env)->GetObjectArrayElement( env, jplayers, ii ); XP_ASSERT( !!jlp ); setInts( env, jlp, (void*)lp, AANDS(pl_ints) ); setBool( env, jlp, "isLocal", lp->isLocal ); setString( env, jlp, "name", lp->name ); setString( env, jlp, "password", lp->password ); setString( env, jlp, "dictName", lp->dictName ); deleteLocalRef( env, jlp ); } deleteLocalRef( env, jplayers ); } else { XP_ASSERT(0); } } /* setJGI */ #ifdef COMMON_LAYOUT static const SetInfo bd_ints[] = { ARR_MEMBER( BoardDims, left ), ARR_MEMBER( BoardDims, top ), ARR_MEMBER( BoardDims, width ), ARR_MEMBER( BoardDims, height ), ARR_MEMBER( BoardDims, scoreLeft ), ARR_MEMBER( BoardDims, scoreHt ), ARR_MEMBER( BoardDims, scoreWidth ), ARR_MEMBER( BoardDims, boardWidth ), ARR_MEMBER( BoardDims, boardHt ), ARR_MEMBER( BoardDims, trayLeft ), ARR_MEMBER( BoardDims, trayTop ), ARR_MEMBER( BoardDims, trayWidth ), ARR_MEMBER( BoardDims, trayHt ), ARR_MEMBER( BoardDims, traySize ), ARR_MEMBER( BoardDims, cellSize ), ARR_MEMBER( BoardDims, maxCellSize ), ARR_MEMBER( BoardDims, timerWidth ), }; static void dimsJToC( JNIEnv* env, BoardDims* out, jobject jdims ) { getInts( env, (void*)out, jdims, AANDS(bd_ints) ); } static void dimsCtoJ( JNIEnv* env, jobject jdims, const BoardDims* in ) { setInts( env, jdims, (void*)in, AANDS(bd_ints) ); } #endif static void destroyGI( MPFORMAL CurGameInfo** gip ) { CurGameInfo* gi = *gip; if ( !!gi ) { gi_disposePlayerInfo( MPPARM(mpool) gi ); XP_FREE( mpool, gi ); *gip = NULL; } } static void loadCommonPrefs( JNIEnv* env, CommonPrefs* cp, jobject j_cp ) { XP_ASSERT( !!j_cp ); cp->showBoardArrow = getBool( env, j_cp, "showBoardArrow" ); cp->showRobotScores = getBool( env, j_cp, "showRobotScores" ); cp->hideTileValues = getBool( env, j_cp, "hideTileValues" ); cp->skipCommitConfirm = getBool( env, j_cp, "skipCommitConfirm" ); cp->showColors = getBool( env, j_cp, "showColors" ); cp->sortNewTiles = getBool( env, j_cp, "sortNewTiles" ); cp->allowPeek = getBool( env, j_cp, "allowPeek" ); cp->skipMQTTAdd = getBool( env, j_cp, "skipMQTTAdd" ); #ifdef XWFEATURE_CROSSHAIRS cp->hideCrosshairs = getBool( env, j_cp, "hideCrosshairs" ); #endif cp->tvType = jenumFieldToInt( env, j_cp, "tvType", PKG_PATH("jni/CommonPrefs$TileValueType")); } static XWStreamCtxt* streamFromJStream( MPFORMAL JNIEnv* env, VTableMgr* vtMgr, jbyteArray jstream ) { XP_ASSERT( !!jstream ); int len = (*env)->GetArrayLength( env, jstream ); XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) vtMgr, len, NULL, 0, NULL, NULL ); jbyte* jelems = (*env)->GetByteArrayElements( env, jstream, NULL ); stream_putBytes( stream, jelems, len ); (*env)->ReleaseByteArrayElements( env, jstream, jelems, 0 ); return stream; } /* streamFromJStream */ /**************************************************** * These methods are stateless: no gamePtr ****************************************************/ #define DVC_HEADER(PTR) { \ JNIGlobalState* globalState = (JNIGlobalState*)(PTR); \ #define DVC_HEADER_END() } \ JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1getMQTTDevID ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { jstring result; DVC_HEADER(jniGlobalPtr); MQTTDevID devID; dvc_getMQTTDevID( globalState->dutil, env, &devID ); XP_UCHAR buf[64]; formatMQTTDevID( &devID, buf, VSIZE(buf) ); result = (*env)->NewStringUTF( env, buf ); DVC_HEADER_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1resetMQTTDevID ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { DVC_HEADER(jniGlobalPtr); dvc_resetMQTTDevID( globalState->dutil, env ); DVC_HEADER_END(); } static void jstrToDevID( JNIEnv* env, jstring jstr, MQTTDevID* outDevID ) { const char* str = (*env)->GetStringUTFChars( env, jstr, NULL ); #ifdef DEBUG XP_Bool success = #endif strToMQTTCDevID( str, outDevID ); XP_ASSERT( success ); (*env)->ReleaseStringUTFChars( env, jstr, str ); } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1getMQTTSubTopics ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { jobjectArray result = NULL; DVC_HEADER(jniGlobalPtr); XP_UCHAR storage[256]; XP_UCHAR* topics[4]; XP_U16 nTopics = VSIZE(topics); dvc_getMQTTSubTopics( globalState->dutil, env, storage, VSIZE(storage), &nTopics, topics ); result = makeStringArray( env, nTopics, (const XP_UCHAR* const*)topics ); DVC_HEADER_END(); return result; } typedef struct _MTPData { JNIEnv* env; int count; const XP_UCHAR* topics[4]; jbyteArray jPackets[4]; XP_UCHAR storage[256]; int offset; } MTPData; static void msgAndTopicProc( void* closure, const XP_UCHAR* topic, const XP_U8* msgBuf, XP_U16 msgLen ) { MTPData* mtp = (MTPData*)closure; JNIEnv* env = mtp->env; const XP_UCHAR* ptr = mtp->topics[mtp->count] = &mtp->storage[mtp->offset]; size_t siz = XP_SNPRINTF( (char*)ptr, VSIZE(mtp->storage) - mtp->offset, "%s", topic ); XP_ASSERT( siz < VSIZE(mtp->storage) - mtp->offset ); XP_USE( siz ); mtp->offset += 1 + XP_STRLEN(ptr); mtp->jPackets[mtp->count] = makeByteArray( env, msgLen, (const jbyte*)msgBuf ); ++mtp->count; XP_ASSERT( mtp->count < VSIZE(mtp->topics) ); } static jobject wrapResults( MTPData* mtp ) { JNIEnv* env = mtp->env; jobject result = makeObjectEmptyConstr( env, PKG_PATH("jni/XwJNI$TopicsAndPackets")); jobjectArray jTopics = makeStringArray( env, mtp->count, mtp->topics ); setObjectField( env, result, "topics", "[Ljava/lang/String;", jTopics ); jobjectArray jPackets = makeByteArrayArray( env, mtp->count ); for ( int ii = 0; ii < mtp->count; ++ii ) { (*env)->SetObjectArrayElement( env, jPackets, ii, mtp->jPackets[ii] ); deleteLocalRef( env, mtp->jPackets[ii] ); } setObjectField( env, result, "packets", "[[B", jPackets ); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTInvites ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jAddressee, jobject jnli ) { jobject result; DVC_HEADER(jniGlobalPtr); NetLaunchInfo nli; loadNLI( env, &nli, jnli ); LOGNLI( &nli ); MTPData mtp = { .env = env, }; MQTTDevID addressee; jstrToDevID( env, jAddressee, &addressee ); dvc_makeMQTTInvites( globalState->dutil, env, msgAndTopicProc, &mtp, &addressee, &nli ); result = wrapResults( &mtp ); DVC_HEADER_END(); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTNukeInvite ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jnli ) { jobject result; DVC_HEADER(jniGlobalPtr); NetLaunchInfo nli; loadNLI( env, &nli, jnli ); MTPData mtp = { .env = env, }; dvc_makeMQTTNukeInvite( globalState->dutil, env, msgAndTopicProc, &mtp, &nli ); result = wrapResults( &mtp ); DVC_HEADER_END(); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTMessages ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jAddressee, jint jGameID, jbyteArray jmsg ) { jobject result; LOG_FUNC(); DVC_HEADER(jniGlobalPtr); MTPData mtp = { .env = env, }; MQTTDevID addressee; jstrToDevID( env, jAddressee, &addressee ); XP_U16 len = (*env)->GetArrayLength( env, jmsg ); jbyte* buf = (*env)->GetByteArrayElements( env, jmsg, NULL ); dvc_makeMQTTMessages( globalState->dutil, env, msgAndTopicProc, &mtp, &addressee, jGameID, (const XP_U8*)buf, len ); (*env)->ReleaseByteArrayElements( env, jmsg, buf, 0 ); result = wrapResults( &mtp ); DVC_HEADER_END(); LOG_RETURN_VOID(); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1makeMQTTNoSuchGames ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jAddressee, jint jgameid ) { jobject result; DVC_HEADER(jniGlobalPtr); MTPData mtp = { .env = env, }; MQTTDevID addressee; jstrToDevID( env, jAddressee, &addressee ); dvc_makeMQTTNoSuchGames( globalState->dutil, env, msgAndTopicProc, &mtp, &addressee, jgameid ); result = wrapResults( &mtp ); DVC_HEADER_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dvc_1parseMQTTPacket ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jtopic, jbyteArray jmsg ) { DVC_HEADER(jniGlobalPtr); XP_U16 len = (*env)->GetArrayLength( env, jmsg ); jbyte* buf = (*env)->GetByteArrayElements( env, jmsg, NULL ); const char* topic = (*env)->GetStringUTFChars( env, jtopic, NULL ); dvc_parseMQTTPacket( globalState->dutil, env, topic, (XP_U8*)buf, len ); (*env)->ReleaseStringUTFChars( env, jtopic, topic ); (*env)->ReleaseByteArrayElements( env, jmsg, buf, 0 ); DVC_HEADER_END(); } # ifdef XWFEATURE_KNOWNPLAYERS JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_kplr_1getPlayers ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { jobjectArray jnames = NULL; DVC_HEADER(jniGlobalPtr); XP_U16 nFound = 0; kplr_getNames( globalState->dutil, env, NULL, &nFound ); if ( 0 < nFound ) { const XP_UCHAR* names[nFound]; kplr_getNames( globalState->dutil, env, names, &nFound ); jnames = makeStringArray( env, nFound, names ); } DVC_HEADER_END(); return jnames; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_kplr_1renamePlayer ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jOldName, jstring jNewName ) { jboolean result; DVC_HEADER(jniGlobalPtr); const char* oldName = (*env)->GetStringUTFChars( env, jOldName, NULL ); const char* newName = (*env)->GetStringUTFChars( env, jNewName, NULL ); result = KP_OK == kplr_renamePlayer( globalState->dutil, env, oldName, newName ); (*env)->ReleaseStringUTFChars( env, jOldName, oldName ); (*env)->ReleaseStringUTFChars( env, jNewName, newName ); DVC_HEADER_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_kplr_1deletePlayer ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jName ) { DVC_HEADER(jniGlobalPtr); const char* name = (*env)->GetStringUTFChars( env, jName, NULL ); kplr_deletePlayer( globalState->dutil, env, name ); (*env)->ReleaseStringUTFChars( env, jName, name ); DVC_HEADER_END(); } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_kplr_1getAddr ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jName, jintArray jLastMod ) { jobject jaddr = NULL; DVC_HEADER(jniGlobalPtr); CommsAddrRec addr; const char* name = (*env)->GetStringUTFChars( env, jName, NULL ); XP_U32 lastMod; kplr_getAddr( globalState->dutil, env, name, &addr, &lastMod ); (*env)->ReleaseStringUTFChars( env, jName, name ); jaddr = makeObjectEmptyConstr( env, PKG_PATH("jni/CommsAddrRec") ); setJAddrRec( env, jaddr, &addr ); if ( !!jLastMod ) { setIntInArray( env, jLastMod, 0, lastMod ); } DVC_HEADER_END(); return jaddr; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_kplr_1nameForMqttDev ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jstring jDevID ) { jstring result = NULL; DVC_HEADER(jniGlobalPtr); const char* devid = (*env)->GetStringUTFChars( env, jDevID, NULL ); const XP_UCHAR* name = kplr_nameForMqttDev( globalState->dutil, env, devid ); result = (*env)->NewStringUTF( env, name ); (*env)->ReleaseStringUTFChars( env, jDevID, devid ); DVC_HEADER_END(); return result; } #endif JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_gi_1from_1stream ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jgi, jbyteArray jstream ) { JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globalState->vtMgr, jstream ); CurGameInfo gi = {0}; // XP_MEMSET( &gi, 0, sizeof(gi) ); if ( game_makeFromStream( MPPARM(mpool) env, stream, NULL, &gi, NULL, NULL, NULL, NULL ) ) { setJGI( env, jgi, &gi ); } else { XP_LOGFF( "game_makeFromStream failed" ); } gi_disposePlayerInfo( MPPARM(mpool) &gi ); stream_destroy( stream ); releaseMPool( globalState ); } JNIEXPORT jbyteArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_nli_1to_1stream ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jnli ) { LOG_FUNC(); JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif jbyteArray result; NetLaunchInfo nli; loadNLI( env, &nli, jnli ); XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr, NULL, 0, NULL, NULL); nli_saveToStream( &nli, stream ); result = streamToBArray( env, stream ); stream_destroy( stream ); releaseMPool( globalState ); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_nli_1from_1stream ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jstream ) { jobject jnli = NULL; LOG_FUNC(); JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globalState->vtMgr, jstream ); NetLaunchInfo nli = {0}; if ( nli_makeFromStream( &nli, stream ) ) { jnli = makeObjectEmptyConstr( env, PKG_PATH("NetLaunchInfo") ); setNLI( env, jnli, &nli ); } else { XP_LOGFF( "nli_makeFromStream failed" ); } stream_destroy( stream ); releaseMPool( globalState ); return jnli; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getUUID ( JNIEnv* env, jclass C ) { jstring jstr = #ifdef XWFEATURE_BLUETOOTH (*env)->NewStringUTF( env, XW_BT_UUID ) #else NULL #endif ; return jstr; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_lcToLocale ( JNIEnv* env, jclass C, jint lc ) { jstring result = NULL; const XP_UCHAR* locale = lcToLocale( lc ); if ( !!locale ) { result = (*env)->NewStringUTF( env, locale ); } return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_haveLocaleToLc ( JNIEnv* env, jclass C, jstring jIsoCode, jintArray jOutArray ) { XP_ASSERT( !!jIsoCode ); XP_LangCode lc; const char* isoCode = (*env)->GetStringUTFChars( env, jIsoCode, NULL ); jboolean result = haveLocaleToLc( isoCode, &lc ); if ( result ) { setIntInArray( env, jOutArray, 0, lc ); } (*env)->ReleaseStringUTFChars( env, jIsoCode, isoCode ); return result; } JNIEXPORT jlong JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1make ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jDictBytes, jstring jname, jstring jpath ) { JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif /* makeDict calls dict_ref() */ DictionaryCtxt* dictPtr = makeDict( MPPARM(mpool) env, TI_IF(&globalState->ti) globalState->dictMgr,globalState->jniutil, jname, jDictBytes, jpath, NULL, false ); return (jlong)dictPtr; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1ref ( JNIEnv* env, jclass C, jlong dictPtr ) { if ( 0 != dictPtr ) { DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; dict_ref( dict, env ); } } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1unref ( JNIEnv* env, jclass C, jlong dictPtr ) { if ( 0 != dictPtr ) { DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; dict_unref( dict, env ); } } typedef struct _FTData { JNIEnv* env; jbyteArray arrays[16]; int nArrays; } FTData; static XP_Bool onFoundTiles( void* closure, const Tile* tiles, int nTiles ) { FTData* ftd = (FTData*)closure; ftd->arrays[ftd->nArrays++] = makeByteArray( ftd->env, nTiles, (const jbyte*)tiles ); return ftd->nArrays < VSIZE(ftd->arrays); /* still have room? */ } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1strToTiles ( JNIEnv* env, jclass C, jlong dictPtr, jstring jstr ) { jobjectArray result = NULL; DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; const char* str = (*env)->GetStringUTFChars( env, jstr, NULL ); FTData ftd = { .env = env, }; dict_tilesForString( dict, str, 0, onFoundTiles, &ftd ); if ( ftd.nArrays > 0 ) { result = makeByteArrayArray( env, ftd.nArrays ); for ( int ii = 0; ii < ftd.nArrays; ++ii ) { (*env)->SetObjectArrayElement( env, result, ii, ftd.arrays[ii] ); deleteLocalRef( env, ftd.arrays[ii] ); } } (*env)->ReleaseStringUTFChars( env, jstr, str ); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1hasDuplicates ( JNIEnv* env, jclass C, jlong dictPtr ) { jboolean result; DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; result = dict_hasDuplicates( dict ); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTilesInfo ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr ) { jstring result = NULL; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; #ifdef MEM_DEBUG MemPoolCtx* mpool = GETMPOOL( globalState ); #endif DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globalState->vtMgr, NULL, 0, NULL, NULL ); dict_writeTilesInfo( dict, 15, stream ); result = streamToJString( env, stream ); stream_destroy( stream ); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1tilesToStr ( JNIEnv* env, jclass C, jlong dictPtr, jbyteArray jtiles, jstring jdelim ) { jstring result = NULL; DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; if ( !!jtiles && !!dict ) { XP_UCHAR buf[64]; const XP_UCHAR* delim = NULL; if ( !!jdelim ) { delim = (*env)->GetStringUTFChars( env, jdelim, NULL ); } XP_U16 nTiles = (*env)->GetArrayLength( env, jtiles ); jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL ); XP_U16 strLen = dict_tilesToString( dict, (Tile*)tiles, nTiles, buf, VSIZE(buf), delim ); if ( 0 < strLen ) { buf[strLen] = '\0'; result = (*env)->NewStringUTF( env, buf ); } if ( !!jdelim ) { (*env)->ReleaseStringUTFChars( env, jdelim, delim ); } (*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 ); } else { XP_LOGFF( "null jtiles array" ); } return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getInfo ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr, jboolean check ) { jobject jinfo = NULL; #ifdef MAP_THREAD_TO_ENV JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; MAP_THREAD( &globalState->ti, env ); #endif DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; if ( NULL != dict ) { jinfo = makeObjectEmptyConstr( env, PKG_PATH("jni/DictInfo") ); setInt( env, jinfo, "wordCount", dict_getWordCount( dict, env ) ); setString( env, jinfo, "md5Sum", dict_getMd5Sum( dict ) ); setString( env, jinfo, "isoCodeStr", dict_getISOCode( dict ) ); setString( env, jinfo, "langName", dict_getLangName( dict ) ); } return jinfo; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getDesc ( JNIEnv* env, jclass C, jlong dictPtr ) { jstring result = NULL; DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; const XP_UCHAR* disc = dict_getDesc( dict ); if ( NULL != disc && '\0' != disc[0] ) { result = (*env)->NewStringUTF( env, disc ); } return result; } /* Dictionary methods: don't use gamePtr */ JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1tilesAreSame ( JNIEnv* env, jclass C, jlong dictPtr1, jlong dictPtr2 ) { jboolean result; const DictionaryCtxt* dict1 = (DictionaryCtxt*)dictPtr1; XP_ASSERT( !!dict1 ); const DictionaryCtxt* dict2 = (DictionaryCtxt*)dictPtr2; XP_ASSERT( !!dict2 ); result = dict_tilesAreSame( dict1, dict2 ); return result; } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getChars ( JNIEnv* env, jclass C, jlong dictPtr ) { jobject result = NULL; result = and_dictionary_getChars( env, (DictionaryCtxt*)dictPtr ); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_dict_1getTileValue ( JNIEnv* env, jclass C, jlong dictPtr, jint tile ) { return dict_getTileValue( (DictionaryCtxt*)dictPtr, tile ); } static jobjectArray msgArrayToByteArrays( JNIEnv* env, const SMSMsgArray* arr ) { XP_ASSERT( arr->format == FORMAT_NET ); jobjectArray result = makeByteArrayArray( env, arr->nMsgs ); for ( int ii = 0; ii < arr->nMsgs; ++ii ) { SMSMsgNet* msg = &arr->u.msgsNet[ii]; jbyteArray arr = makeByteArray( env, msg->len, (const jbyte*)msg->data ); (*env)->SetObjectArrayElement( env, result, ii, arr ); deleteLocalRef( env, arr ); } return result; } static jobjectArray msgArrayToJMsgArray( JNIEnv* env, const SMSMsgArray* arr ) { XP_ASSERT( arr->format == FORMAT_LOC ); jclass clas = (*env)->FindClass( env, PKG_PATH("jni/XwJNI$SMSProtoMsg") ); jobjectArray result = (*env)->NewObjectArray( env, arr->nMsgs, clas, NULL ); jmethodID initId = (*env)->GetMethodID( env, clas, "", "()V" ); for ( int ii = 0; ii < arr->nMsgs; ++ii ) { jobject jmsg = (*env)->NewObject( env, clas, initId ); const SMSMsgLoc* msgsLoc = &arr->u.msgsLoc[ii]; intToJenumField( env, jmsg, msgsLoc->cmd, "cmd", PKG_PATH("jni/XwJNI$SMS_CMD") ); setInt( env, jmsg, "gameID", msgsLoc->gameID ); jbyteArray arr = makeByteArray( env, msgsLoc->len, (const jbyte*)msgsLoc->data ); setObjectField( env, jmsg, "data", "[B", arr ); (*env)->SetObjectArrayElement( env, result, ii, jmsg ); deleteLocalRef( env, jmsg ); } deleteLocalRef( env, clas ); return result; } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepOutbound ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jobject jCmd, jint jGameID, jbyteArray jData, jstring jToPhone, jint jPort, jintArray jWaitSecsArr ) { jobjectArray result = NULL; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; MAP_THREAD( &globalState->ti, env ); SMS_CMD cmd = jEnumToInt( env, jCmd ); jbyte* data = NULL; int len = 0; if ( NULL != jData ) { len = (*env)->GetArrayLength( env, jData ); data = (*env)->GetByteArrayElements( env, jData, NULL ); } const char* toPhone = (*env)->GetStringUTFChars( env, jToPhone, NULL ); XP_U16 waitSecs; SMSMsgArray* arr = smsproto_prepOutbound( globalState->smsProto, env, cmd, jGameID, (const XP_U8*)data, len, toPhone, jPort, XP_FALSE, &waitSecs ); if ( !!arr ) { result = msgArrayToByteArrays( env, arr ); smsproto_freeMsgArray( globalState->smsProto, arr ); } setIntInArray( env, jWaitSecsArr, 0, waitSecs ); (*env)->ReleaseStringUTFChars( env, jToPhone, toPhone ); if ( NULL != jData ) { (*env)->ReleaseByteArrayElements( env, jData, data, 0 ); } return result; } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_smsproto_1prepInbound ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jbyteArray jData, jstring jFromPhone, jint jWantPort ) { jobjectArray result = NULL; if ( !!jData ) { JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; MAP_THREAD( &globalState->ti, env ); int len = (*env)->GetArrayLength( env, jData ); jbyte* data = (*env)->GetByteArrayElements( env, jData, NULL ); const char* fromPhone = (*env)->GetStringUTFChars( env, jFromPhone, NULL ); SMSMsgArray* arr = smsproto_prepInbound( globalState->smsProto, env, fromPhone, jWantPort, (XP_U8*)data, len ); if ( !!arr ) { result = msgArrayToJMsgArray( env, arr ); smsproto_freeMsgArray( globalState->smsProto, arr ); } (*env)->ReleaseStringUTFChars( env, jFromPhone, fromPhone ); (*env)->ReleaseByteArrayElements( env, jData, data, 0 ); } else { XP_LOGFF( " => null (null input)" ); } return result; } struct _JNIState { XWGame game; JNIGlobalState* globalJNI; AndGameGlobals globals; XP_U16 curSaveCount; XP_U16 lastSavedSize; #ifdef DEBUG const char* envSetterFunc; XP_U32 guard; #endif MPSLOT }; #define GAME_GUARD 0x453627 #if 0 # define LOG_FUNC_IF() LOG_FUNC() # define LOG_RETURN_VOID_IF() LOG_RETURN_VOID() #else # define LOG_FUNC_IF() # define LOG_RETURN_VOID_IF() #endif #define XWJNI_START(GP) { \ JNIState* state = getState( env, (GP), __func__ ); \ LOG_FUNC_IF(); \ XP_ASSERT( state->guard == GAME_GUARD ); \ MPSLOT; \ MPASSIGN( mpool, state->mpool ); \ XP_ASSERT( !!state->globalJNI ); \ MAP_THREAD( &state->globalJNI->ti, env ); \ #define XWJNI_START_GLOBALS(GP) \ XWJNI_START(GP); \ AndGameGlobals* globals = &state->globals; \ XP_USE(globals); /*no warnings */ \ #define XWJNI_END() \ LOG_RETURN_VOID_IF(); \ } \ JNIEXPORT jlong JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_gameJNIInit ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { #ifdef MEM_DEBUG MemPoolCtx* mpool = ((JNIGlobalState*)jniGlobalPtr)->mpool; #endif JNIState* state = (JNIState*)XP_CALLOC( mpool, sizeof(*state) ); #ifdef DEBUG state->guard = GAME_GUARD; #endif state->globalJNI = (JNIGlobalState*)jniGlobalPtr; MAP_THREAD( &state->globalJNI->ti, env ); AndGameGlobals* globals = &state->globals; globals->dutil = state->globalJNI->dutil; globals->state = (JNIState*)state; MPASSIGN( state->mpool, mpool ); globals->vtMgr = make_vtablemgr(MPPARM_NOCOMMA(mpool)); /* LOG_RETURNF( "%p", state ); */ return (jlong) state; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_envDone ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { #ifdef MAP_THREAD_TO_ENV JNIGlobalState* globalJNI = (JNIGlobalState*)jniGlobalPtr; MAP_REMOVE( &globalJNI->ti, env ); #endif } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeNewGame ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject j_gi, jobject j_selfAddr, jobject j_hostAddr, jobject j_util, jobject j_draw, jobject j_cp, jobject j_procs ) { XWJNI_START_GLOBALS(gamePtr); CurGameInfo* gi = makeGI( MPPARM(mpool) env, j_gi ); globals->gi = gi; globals->util = makeUtil( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) j_util, gi, globals ); globals->jniutil = state->globalJNI->jniutil; DrawCtx* dctx = NULL; if ( !!j_draw ) { dctx = makeDraw( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) j_draw ); } globals->dctx = dctx; if ( !!j_procs ) { globals->xportProcs = makeXportProcs( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) j_procs ); } CommonPrefs cp = {0}; loadCommonPrefs( env, &cp, j_cp ); CommsAddrRec selfAddr; CommsAddrRec* selfAddrP = NULL; if ( !!j_selfAddr ) { getJAddrRec( env, &selfAddr, j_selfAddr ); selfAddrP = &selfAddr; } CommsAddrRec hostAddr; CommsAddrRec* hostAddrP = NULL; if ( !!j_hostAddr ) { XP_ASSERT( gi->serverRole == SERVER_ISCLIENT ); getJAddrRec( env, &hostAddr, j_hostAddr ); hostAddrP = &hostAddr; } else { XP_ASSERT( gi->serverRole != SERVER_ISCLIENT ); } game_makeNewGame( MPPARM(mpool) env, &state->game, gi, selfAddrP, hostAddrP, globals->util, dctx, &cp, globals->xportProcs ); XWJNI_END(); } /* makeNewGame */ static void initGameGlobals( JNIEnv* env, JNIState* state, jobject jutil, jobject jprocs ) { AndGameGlobals* globals = &state->globals; globals->gi = (CurGameInfo*)XP_CALLOC( state->mpool, sizeof(*globals->gi) ); if ( !!jutil ) { XP_ASSERT( !globals->util ); globals->util = makeUtil( MPPARM(state->mpool) env, TI_IF(&state->globalJNI->ti) jutil, globals->gi, globals ); } if ( !!jprocs ) { globals->xportProcs = makeXportProcs( MPPARM(state->mpool) env, TI_IF(&state->globalJNI->ti) jprocs ); } globals->jniutil = state->globalJNI->jniutil; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeRematch ( JNIEnv* env, jclass C, GamePtrType gamePtr, GamePtrType gamePtrNew, jobject jutil, jobject jcp, jstring jGameName ) { XWJNI_START_GLOBALS(gamePtr); JNIState* oldState = state; /* state about to go out-of-scope */ XWJNI_START_GLOBALS(gamePtrNew); initGameGlobals( env, state, jutil, NULL ); CommonPrefs cp; loadCommonPrefs( env, &cp, jcp ); const char* gameName = (*env)->GetStringUTFChars( env, jGameName, NULL ); game_makeRematch( &oldState->game, env, globals->util, &cp, &state->game, gameName, XP_FALSE ); (*env)->ReleaseStringUTFChars( env, jGameName, gameName ); XWJNI_END(); /* matches second XWJNI_START_GLOBALS! */ XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromInvite ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jnli, jobject jutil, jobject jSelfAddr, jobject jcp, jobject jprocs ) { jboolean result; XWJNI_START_GLOBALS(gamePtr); initGameGlobals( env, state, jutil, jprocs ); NetLaunchInfo nli; loadNLI( env, &nli, jnli ); LOGNLI( &nli ); XP_ASSERT( !!jSelfAddr ); CommsAddrRec* selfAddrP = NULL; CommsAddrRec selfAddr; if ( !!jSelfAddr ) { selfAddrP = &selfAddr; getJAddrRec( env, selfAddrP, jSelfAddr ); } CommonPrefs cp; loadCommonPrefs( env, &cp, jcp ); result = game_makeFromInvite( &state->game, env, &nli, selfAddrP, globals->util, globals->dctx, &cp, globals->xportProcs ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1dispose ( JNIEnv* env, jclass claz, GamePtrType gamePtr ) { JNIState* state = getState( env, gamePtr, __func__ ); XP_ASSERT( state->guard == GAME_GUARD ); #ifdef MEM_DEBUG MemPoolCtx* mpool = state->mpool; #endif AndGameGlobals* globals = &state->globals; game_dispose( &state->game, env ); /* Must happen after game_dispose, which uses it */ destroyGI( MPPARM(mpool) &globals->gi ); destroyDraw( &globals->dctx, env ); destroyXportProcs( &globals->xportProcs, env ); destroyUtil( &globals->util, env ); vtmgr_destroy( MPPARM(mpool) globals->vtMgr ); MAP_REMOVE( &state->globalJNI->ti, env ); #ifdef DEBUG XP_MEMSET( state, -1, sizeof(*state) ); #endif XP_FREE( mpool, state ); } /* game_dispose */ JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1makeFromStream ( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject /*out*/jgi, jobject jutil, jobject jdraw, jobject jcp, jobject jprocs ) { jboolean result; XWJNI_START_GLOBALS(gamePtr); globals->gi = (CurGameInfo*)XP_CALLOC( mpool, sizeof(*globals->gi) ); globals->util = makeUtil( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) jutil, globals->gi, globals); globals->jniutil = state->globalJNI->jniutil; if ( !!jdraw ) { globals->dctx = makeDraw( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) jdraw ); } globals->xportProcs = makeXportProcs( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) jprocs ); XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globals->vtMgr, jstream ); CommonPrefs cp; loadCommonPrefs( env, &cp, jcp ); result = game_makeFromStream( MPPARM(mpool) env, stream, &state->game, globals->gi, globals->util, globals->dctx, &cp, globals->xportProcs ); stream_destroy( stream ); if ( result ) { XP_ASSERT( 0 != globals->gi->gameID ); if ( !!jgi ) { setJGI( env, jgi, globals->gi ); } } XWJNI_END(); return result; } /* makeFromStream */ JNIEXPORT jbyteArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1saveToStream ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jgi ) { jbyteArray result; XWJNI_START_GLOBALS(gamePtr); /* Use our copy of gi if none's provided. That's because only the caller knows if its gi should win -- user has changed game config -- or if ours should -- changes like remote players being added. */ CurGameInfo* gi = (NULL == jgi) ? globals->gi : makeGI( MPPARM(mpool) env, jgi ); XWStreamCtxt* stream = mem_stream_make_sized( MPPARM(mpool) globals->vtMgr, state->lastSavedSize, NULL, 0, NULL, NULL ); XP_ASSERT( gi_equal( gi, globals->util->gameInfo ) ); game_saveToStream( &state->game, gi, stream, ++state->curSaveCount ); if ( NULL != jgi ) { destroyGI( MPPARM(mpool) &gi ); } state->lastSavedSize = stream_getSize( stream ); result = streamToBArray( env, stream ); stream_destroy( stream ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1saveSucceeded ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); game_saveSucceeded( &state->game, env, state->curSaveCount ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1setDraw ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jdraw ) { XWJNI_START_GLOBALS(gamePtr); DrawCtx* newDraw = makeDraw( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) jdraw ); board_setDraw( state->game.board, env, newDraw ); destroyDraw( &globals->dctx, env ); globals->dctx = newDraw; XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1invalAll ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); board_invalAll( state->game.board ); XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1draw ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = board_draw( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1drawSnapshot ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jdraw, jint width, jint height ) { XWJNI_START(gamePtr); DrawCtx* newDraw = makeDraw( MPPARM(mpool) env, TI_IF(&state->globalJNI->ti) jdraw ); board_drawSnapshot( state->game.board, env, newDraw, width, height ); destroyDraw( &newDraw, env ); XWJNI_END(); } #ifdef COMMON_LAYOUT JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1figureLayout ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jgi, jint left, jint top, jint width, jint height, jint scorePct, jint trayPct, jint scoreWidth, jint fontWidth, jint fontHt, jboolean squareTiles, jobject jdims ) { XWJNI_START(gamePtr); CurGameInfo* gi = makeGI( MPPARM(mpool) env, jgi ); BoardDims dims; board_figureLayout( state->game.board, env, gi, left, top, width, height, 115, scorePct, trayPct, scoreWidth, fontWidth, fontHt, squareTiles, ((!!jdims) ? &dims : NULL) ); destroyGI( MPPARM(mpool) &gi ); if ( !!jdims ) { dimsCtoJ( env, jdims, &dims ); } XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1applyLayout ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jdims ) { XWJNI_START(gamePtr); BoardDims dims; dimsJToC( env, &dims, jdims ); board_applyLayout( state->game.board, env, &dims ); XWJNI_END(); } #else JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1setPos (JNIEnv* env, jclass C, GamePtrType gamePtr, jint left, jint top, jint width, jint height, jint maxCellSize, jboolean lefty ) { XWJNI_START(gamePtr); board_setPos( state->game.board, left, top, width, height, maxCellSize, lefty ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1setScoreboardLoc ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint left, jint top, jint width, jint height, jboolean divideHorizontally ) { XWJNI_START(gamePtr); board_setScoreboardLoc( state->game.board, left, top, width, height, divideHorizontally ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1setTrayLoc ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint left, jint top, jint width, jint height, jint minDividerWidth ) { XWJNI_START(gamePtr); board_setTrayLoc( state->game.board, left, top, width, height, minDividerWidth ); XWJNI_END(); } #endif JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1zoom ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint zoomBy, jbooleanArray jCanZoom ) { jboolean result; XWJNI_START(gamePtr); XP_Bool canInOut[2]; result = board_zoom( state->game.board, env, zoomBy, canInOut ); jboolean canZoom[2] = { canInOut[0], canInOut[1] }; setBoolArray( env, jCanZoom, VSIZE(canZoom), canZoom ); XWJNI_END(); return result; } #ifdef XWFEATURE_ACTIVERECT JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1getActiveRect ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jrect, jintArray dims ) { jboolean result; XWJNI_START(gamePtr); XP_Rect rect; XP_U16 nCols, nRows; result = board_getActiveRect( state->game.board, &rect, &nCols, &nRows ); if ( result ) { setInt( env, jrect, "left", rect.left ); setInt( env, jrect, "top", rect.top ); setInt( env, jrect, "right", rect.left + rect.width ); setInt( env, jrect, "bottom", rect.top + rect.height ); if ( !!dims ) { setIntInArray( env, dims, 0, nCols ); setIntInArray( env, dims, 1, nRows ); } } XWJNI_END(); return result; } #endif #ifdef POINTER_SUPPORT JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1handlePenDown ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint xx, jint yy, jbooleanArray barray ) { jboolean result; XWJNI_START(gamePtr); XP_Bool bb; /* drop this for now */ result = board_handlePenDown( state->game.board, env, xx, yy, &bb ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1handlePenMove ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint xx, jint yy ) { jboolean result; XWJNI_START(gamePtr); result = board_handlePenMove( state->game.board, env, xx, yy ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1handlePenUp ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint xx, jint yy ) { jboolean result; XWJNI_START(gamePtr); result = board_handlePenUp( state->game.board, env, xx, yy ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1containsPt ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint xx, jint yy ) { jboolean result; XWJNI_START(gamePtr); result = board_containsPt( state->game.board, xx, yy ); XWJNI_END(); return result; } #endif JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1juggleTray (JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = board_juggleTray( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1getTrayVisState (JNIEnv* env, jclass C, GamePtrType gamePtr) { jboolean result; XWJNI_START(gamePtr); result = board_getTrayVisState( state->game.board ); XWJNI_END(); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1getSelPlayer (JNIEnv* env, jclass C, GamePtrType gamePtr) { jint result; XWJNI_START(gamePtr); result = board_getSelPlayer( state->game.board ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1passwordProvided (JNIEnv* env, jclass C, GamePtrType gamePtr, jint player, jstring jpasswd ) { jboolean result; XWJNI_START(gamePtr); const char* passwd = (*env)->GetStringUTFChars( env, jpasswd, NULL ); result = board_passwordProvided( state->game.board, env, player, passwd ); (*env)->ReleaseStringUTFChars( env, jpasswd, passwd ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1hideTray (JNIEnv* env, jclass C, GamePtrType gamePtr) { jboolean result; XWJNI_START(gamePtr); result = board_hideTray( state->game.board, env); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1showTray (JNIEnv* env, jclass C, GamePtrType gamePtr) { jboolean result; XWJNI_START(gamePtr); result = board_showTray( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1beginTrade ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = board_beginTrade( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1endTrade ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = board_endTrade( state->game.board ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1setBlankValue ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint player, jint col, jint row, jint tile ) { jboolean result; XWJNI_START(gamePtr); result = board_setBlankValue( state->game.board, player, col, row, tile ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1commitTurn ( JNIEnv* env, jclass C, GamePtrType gamePtr, jboolean phoniesConfirmed, jboolean turnConfirmed, jintArray jNewTiles ) { jboolean result; XWJNI_START(gamePtr); TrayTileSet* newTilesP = NULL; TrayTileSet newTiles; if ( jNewTiles != NULL ) { tilesArrayToTileSet( env, jNewTiles, &newTiles ); newTilesP = &newTiles; } result = board_commitTurn( state->game.board, env, phoniesConfirmed, turnConfirmed, newTilesP ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1flip (JNIEnv* env, jclass C, GamePtrType gamePtr) { jboolean result; XWJNI_START(gamePtr); result = board_flip( state->game.board ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1replaceTiles (JNIEnv* env, jclass C, GamePtrType gamePtr) { jboolean result; XWJNI_START(gamePtr); result = board_replaceTiles( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1redoReplacedTiles ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = board_redoReplacedTiles( state->game.board, env ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1reset (JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); server_reset( state->game.server, env, state->game.comms ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1handleUndo (JNIEnv* env, jclass C, GamePtrType gamePtr) { XWJNI_START(gamePtr); server_handleUndo( state->game.server, env, 0 ); XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1do (JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); XP_ASSERT( !!state->game.server ); result = server_do( state->game.server, env ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1tilesPicked ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint player, jintArray jNewTiles ) { XWJNI_START(gamePtr); TrayTileSet newTiles; tilesArrayToTileSet( env, jNewTiles, &newTiles ); server_tilesPicked( state->game.server, env, player, &newTiles ); XWJNI_END(); } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1countTilesInPool ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jint result; XWJNI_START(gamePtr); result = server_countTilesInPool( state->game.server ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1resetEngine (JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); board_resetEngine( state->game.board ); XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1requestHint ( JNIEnv* env, jclass C, GamePtrType gamePtr, jboolean useLimits, jboolean goBack, jbooleanArray workRemains ) { jboolean result; XWJNI_START(gamePtr); XP_Bool tmpbool; result = board_requestHint( state->game.board, env, #ifdef XWFEATURE_SEARCHLIMIT useLimits, #endif goBack, &tmpbool ); /* If passed need to do workRemains[0] = tmpbool */ if ( workRemains ) { jboolean jbool = tmpbool; setBoolArray( env, workRemains, 1, &jbool ); } XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_timerFired ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint why, jint when, jint handle ) { jboolean result; XWJNI_START_GLOBALS(gamePtr); XW_UtilCtxt* util = globals->util; result = utilTimerFired( util, env, why, handle ); XWJNI_END(); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1formatRemainingTiles (JNIEnv* env, jclass C, GamePtrType gamePtr ) { jstring result; XWJNI_START_GLOBALS(gamePtr); XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr, NULL, 0, NULL, NULL ); board_formatRemainingTiles( state->game.board, env, stream ); result = streamToJString( env, stream ); stream_destroy( stream ); XWJNI_END(); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1formatDictCounts ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint nCols ) { jstring result; XWJNI_START_GLOBALS(gamePtr); XWStreamCtxt* stream = and_empty_stream( MPPARM(mpool) globals ); server_formatDictCounts( state->game.server, env, stream, nCols, XP_FALSE ); result = streamToJString( env, stream ); stream_destroy( stream ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1getGameIsOver ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = server_getGameIsOver( state->game.server ); XWJNI_END(); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_model_1writeGameHistory ( JNIEnv* env, jclass C, GamePtrType gamePtr, jboolean gameOver ) { jstring result; XWJNI_START_GLOBALS(gamePtr); XWStreamCtxt* stream = and_empty_stream( MPPARM(mpool) globals ); model_writeGameHistory( state->game.model, env, stream, state->game.server, gameOver ); result = streamToJString( env, stream ); stream_destroy( stream ); XWJNI_END(); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_model_1getNMoves ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jint result; XWJNI_START(gamePtr); XP_ASSERT( !!state->game.model ); result = model_getNMoves( state->game.model ); XWJNI_END(); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_model_1getNumTilesInTray ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint player ) { jint result; XWJNI_START(gamePtr); XP_ASSERT( !!state->game.model ); result = model_getNumTilesInTray( state->game.model, player ); XWJNI_END(); return result; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_model_1getPlayersLastScore ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint player ) { jobject jlmi; XWJNI_START(gamePtr); XP_ASSERT( !!state->game.model ); LastMoveInfo lmi; XP_Bool valid = model_getPlayersLastScore( state->game.model, env, player, &lmi ); jlmi = makeObjectEmptyConstr( env, PKG_PATH("jni/LastMoveInfo") ); setBool( env, jlmi, "isValid", valid ); if ( valid ) { setBool( env, jlmi, "inDuplicateMode", lmi.inDuplicateMode ); setInt( env, jlmi, "score", lmi.score ); setInt( env, jlmi, "nTiles", lmi.nTiles ); setInt( env, jlmi, "moveType", lmi.moveType ); setStringArray( env, jlmi, "names", lmi.nWinners, lmi.names ); setString( env, jlmi, "word", lmi.word ); } XWJNI_END(); return jlmi; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1writeFinalScores ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jstring result; XWJNI_START_GLOBALS(gamePtr); XWStreamCtxt* stream = and_empty_stream( MPPARM(mpool) globals ); server_writeFinalScores( state->game.server, env, stream ); result = streamToJString( env, stream ); stream_destroy( stream ); XWJNI_END(); return result; } void and_send_on_close( XWStreamCtxt* stream, XWEnv xwe, void* closure ) { AndGameGlobals* globals = (AndGameGlobals*)closure; JNIState* state = (JNIState*)globals->state; XP_ASSERT( !!state->game.comms ); comms_send( state->game.comms, xwe, stream ); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1initClientConnection ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; LOG_FUNC(); XWJNI_START_GLOBALS(gamePtr); result = server_initClientConnection( state->game.server, env ); XWJNI_END(); LOG_RETURNF( "%s", boolToStr(result) ); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1start ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); CommsCtxt* comms = state->game.comms; if ( !!comms ) { comms_start( comms, env ); } XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1stop ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); CommsCtxt* comms = state->game.comms; if ( !!comms ) { comms_stop( comms #ifdef XWFEATURE_RELAY , env #endif ); } XWJNI_END(); } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getSelfAddr (JNIEnv* env, jclass C, GamePtrType gamePtr ) { jobject jaddr; XWJNI_START(gamePtr); XP_ASSERT( state->game.comms ); CommsAddrRec addr; comms_getSelfAddr( state->game.comms, &addr ); jaddr = makeObjectEmptyConstr( env, PKG_PATH("jni/CommsAddrRec") ); setJAddrRec( env, jaddr, &addr ); XWJNI_END(); return jaddr; } JNIEXPORT jobject JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getHostAddr (JNIEnv* env, jclass C, GamePtrType gamePtr ) { jobject jaddr = NULL; XWJNI_START(gamePtr); XP_ASSERT( state->game.comms ); CommsAddrRec addr; if ( comms_getHostAddr( state->game.comms, &addr ) ) { jaddr = makeObjectEmptyConstr( env, PKG_PATH("jni/CommsAddrRec") ); setJAddrRec( env, jaddr, &addr ); } XWJNI_END(); return jaddr; } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getAddrs ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jobjectArray result = NULL; XWJNI_START(gamePtr); XP_ASSERT( state->game.comms ); if ( !!state->game.comms ) { CommsAddrRec addrs[MAX_NUM_PLAYERS]; XP_U16 count = VSIZE(addrs); comms_getAddrs( state->game.comms, addrs, &count ); result = makeAddrArray( env, count, addrs ); } XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1receiveMessage ( JNIEnv* env, jclass C, GamePtrType gamePtr, jbyteArray jstream, jobject jaddr ) { jboolean result; XWJNI_START_GLOBALS(gamePtr); XWStreamCtxt* stream = streamFromJStream( MPPARM(mpool) env, globals->vtMgr, jstream ); CommsAddrRec* addrp = NULL; CommsAddrRec addr = {0}; XP_ASSERT( !!jaddr ); if ( NULL != jaddr ) { getJAddrRec( env, &addr, jaddr ); addrp = &addr; } result = game_receiveMessage( &state->game, env, stream, addrp ); stream_destroy( stream ); XWJNI_END(); return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1summarize ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jsummary ) { XWJNI_START_GLOBALS(gamePtr); GameSummary summary = {0}; game_summarize( &state->game, globals->gi, &summary ); setInt( env, jsummary, "nMoves", summary.nMoves ); setBool( env, jsummary, "gameOver", summary.gameOver ); setInt( env, jsummary, "turn", summary.turn ); setBool( env, jsummary, "turnIsLocal", summary.turnIsLocal ); setInt( env, jsummary, "lastMoveTime", summary.lastMoveTime ); setInt( env, jsummary, "dupTimerExpires", summary.dupTimerExpires ); if ( !!state->game.comms ) { CommsCtxt* comms = state->game.comms; setInt( env, jsummary, "seed", comms_getChannelSeed( comms ) ); setInt( env, jsummary, "missingPlayers", summary.missingPlayers ); setInt( env, jsummary, "nPacketsPending", summary.nPacketsPending ); CommsAddrRec addr; comms_getSelfAddr( comms, &addr ); setTypeSetFieldIn( env, &addr, jsummary, "conTypes" ); CommsConnType typ; for ( XP_U32 st = 0; addr_iter( &addr, &typ, &st ); ) { switch( typ ) { case COMMS_CONN_RELAY: { #ifdef XWFEATURE_RELAY XP_UCHAR buf[128]; XP_U16 len = VSIZE(buf); if ( comms_getRelayID( comms, buf, &len ) ) { XP_ASSERT( '\0' == buf[len-1] ); /* failed! */ setString( env, jsummary, "relayID", buf ); } setString( env, jsummary, "roomName", addr.u.ip_relay.invite ); #endif } break; case COMMS_CONN_NFC: case COMMS_CONN_MQTT: break; #if defined XWFEATURE_BLUETOOTH || defined XWFEATURE_SMS || defined XWFEATURE_P2P case COMMS_CONN_BT: case COMMS_CONN_P2P: case COMMS_CONN_SMS: { CommsAddrRec addrs[MAX_NUM_PLAYERS]; XP_U16 count = VSIZE(addrs); comms_getAddrs( comms, addrs, &count ); const XP_UCHAR* addrps[count]; for ( int ii = 0; ii < count; ++ii ) { switch ( typ ) { case COMMS_CONN_BT: addrps[ii] = (XP_UCHAR*)&addrs[ii].u.bt.btAddr; break; case COMMS_CONN_P2P: addrps[ii] = (XP_UCHAR*)&addrs[ii].u.p2p.mac_addr; break; case COMMS_CONN_SMS: addrps[ii] = (XP_UCHAR*)&addrs[ii].u.sms.phone; break; default: XP_ASSERT(0); break; } XP_LOGFF( "adding btaddr/phone/mac %s", addrps[ii] ); } setStringArray( env, jsummary, "remoteDevs", count, addrps ); } break; #endif default: XP_ASSERT(0); } } } ModelCtxt* model = state->game.model; XP_U16 nPlayers = model_getNPlayers( model ); jint jvals[nPlayers]; if ( summary.gameOver ) { ScoresArray scores; model_figureFinalScores( model, &scores, NULL ); for ( int ii = 0; ii < nPlayers; ++ii ) { jvals[ii] = scores.arr[ii]; } } else { for ( int ii = 0; ii < nPlayers; ++ii ) { jvals[ii] = model_getPlayerScore( model, ii ); } } setIntArray( env, jsummary, "scores", nPlayers, jvals, sizeof(jvals[0]) ); XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1server_1prefsChanged ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jcp ) { jboolean result; XWJNI_START(gamePtr); CommonPrefs cp; loadCommonPrefs( env, &cp, jcp ); result = board_prefsChanged( state->game.board, &cp ); server_prefsChanged( state->game.server, &cp ); XWJNI_END(); return result; } #ifdef KEYBOARD_NAV JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1getFocusOwner ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jint result; XWJNI_START(gamePtr); result = board_getFocusOwner( state->game.board ); XWJNI_END(); return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1focusChanged ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint typ ) { jboolean result; XWJNI_START(gamePtr); result = board_focusChanged( state->game.board, typ, XP_TRUE ); XWJNI_END(); return result; } #endif #ifdef KEYBOARD_NAV JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1handleKey ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jkey, jboolean jup, jbooleanArray jhandled ) { jboolean result; XWJNI_START(gamePtr); XP_Bool tmpbool; XP_Key key = jEnumToInt( env, jkey ); if ( jup ) { result = board_handleKeyUp( state->game.board, key, &tmpbool ); } else { result = board_handleKeyDown( state->game.board, key, &tmpbool ); } jboolean jbool = tmpbool; setBoolArray( env, jhandled, 1, &jbool ); XWJNI_END(); return result; } #endif JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1getGi ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jgi ) { XWJNI_START_GLOBALS(gamePtr); setJGI( env, jgi, globals->gi ); XWJNI_END(); } static const SetInfo gsi_ints[] = { ARR_MEMBER( GameStateInfo, visTileCount ), ARR_MEMBER( GameStateInfo, nPendingMessages ), ARR_MEMBER( GameStateInfo, trayVisState ), }; static const SetInfo gsi_bools[] = { ARR_MEMBER( GameStateInfo,canHint ), ARR_MEMBER( GameStateInfo, canUndo ), ARR_MEMBER( GameStateInfo, canRedo ), ARR_MEMBER( GameStateInfo, inTrade ), ARR_MEMBER( GameStateInfo, tradeTilesSelected ), ARR_MEMBER( GameStateInfo, canChat ), ARR_MEMBER( GameStateInfo, canShuffle ), ARR_MEMBER( GameStateInfo, curTurnSelected ), ARR_MEMBER( GameStateInfo, canHideRack ), ARR_MEMBER( GameStateInfo, canTrade ), ARR_MEMBER( GameStateInfo, canPause ), ARR_MEMBER( GameStateInfo, canUnpause ), }; JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1getState ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jgsi ) { XWJNI_START(gamePtr); GameStateInfo info; game_getState( &state->game, env, &info ); setInts( env, jgsi, (void*)&info, AANDS(gsi_ints) ); setBools( env, jgsi, (void*)&info, AANDS(gsi_bools) ); XWJNI_END(); } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1hasComms ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = NULL != state->game.comms; XWJNI_END(); return result; } #ifdef XWFEATURE_CHANGEDICT JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_game_1changeDict ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jgi, jstring jname, jbyteArray jDictBytes, jstring jpath ) { XWJNI_START_GLOBALS(gamePtr); DictionaryCtxt* dict = makeDict( MPPARM(state->globalJNI->mpool) env, TI_IF(&globalState->ti) state->globalJNI->dictMgr, globals->jniutil, jname, jDictBytes, jpath, NULL, false ); game_changeDict( MPPARM(mpool) &state->game, globals->gi, dict ); dict_unref( dict ); setJGI( env, jgi, globals->gi ); XWJNI_END(); return XP_FALSE; /* no need to redraw */ } #endif JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1resendAll ( JNIEnv* env, jclass C, GamePtrType gamePtr, jboolean force, jobject jFilter, jboolean thenAck ) { jint result = 0; XWJNI_START(gamePtr); CommsCtxt* comms = state->game.comms; XP_ASSERT( !!comms ); if ( !!comms ) { CommsConnType filter = NULL == jFilter ? COMMS_CONN_NONE : jEnumToInt( env, jFilter ); result = comms_resendAll( comms, env, filter, force ); if ( thenAck ) { #ifdef XWFEATURE_COMMSACK comms_ackAny( comms, env ); #endif } } else { /* I've seen this once, but wasn't reproducible */ XP_LOGFF( "ERROR: called with null comms" ); } XWJNI_END(); return result; } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1countPendingPackets ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jint result = 0; XWJNI_START(gamePtr); const CommsCtxt* comms = state->game.comms; if ( !!comms ) { result = comms_countPendingPackets( comms ); } XWJNI_END(); return result; } #ifdef XWFEATURE_COMMSACK JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1ackAny ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); XP_ASSERT( !!state->game.comms ); (void)comms_ackAny( state->game.comms, env ); XWJNI_END(); } #endif JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1isConnected ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jboolean result; XWJNI_START(gamePtr); result = NULL != state->game.comms && comms_isConnected( state->game.comms ); XWJNI_END(); return result; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getStats ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { jstring result = NULL; #ifdef DEBUG XWJNI_START_GLOBALS(gamePtr); if ( NULL != state->game.comms ) { XWStreamCtxt* stream = mem_stream_make( MPPARM(mpool) globals->vtMgr, NULL, 0, NULL, NULL ); comms_getStats( state->game.comms, stream ); result = streamToJString( env, stream ); stream_destroy( stream ); } XWJNI_END(); #endif return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1addMQTTDevID ( JNIEnv* env, jclass C, GamePtrType gamePtr, jint channel, jstring jdevid ) { XWJNI_START_GLOBALS(gamePtr); if ( NULL != state->game.comms ) { const char* str = (*env)->GetStringUTFChars( env, jdevid, NULL ); MQTTDevID devID; if ( strToMQTTCDevID( str, &devID ) ) { comms_addMQTTDevID( state->game.comms, channel, &devID ); } (*env)->ReleaseStringUTFChars( env, jdevid, str ); } XWJNI_END(); } #ifdef XWFEATURE_COMMS_INVITE JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1invite ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jnli, jobject jaddr ) { XWJNI_START_GLOBALS(gamePtr); CommsCtxt* comms = state->game.comms; XP_ASSERT( NULL != comms ); if ( NULL != comms ) { CommsAddrRec destAddr; getJAddrRec( env, &destAddr, jaddr ); NetLaunchInfo nli; loadNLI( env, &nli, jnli ); comms_invite( comms, env, &nli, &destAddr, XP_FALSE ); } XWJNI_END(); } #endif JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1dropHostAddr ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jConnTyp ) { LOG_FUNC(); XWJNI_START(gamePtr); if ( NULL != state->game.comms ) { CommsConnType connType = jEnumToInt( env, jConnTyp ); comms_dropHostAddr( state->game.comms, connType ); } XWJNI_END(); LOG_RETURN_VOID(); } #ifdef DEBUG JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1setAddrDisabled ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jConnTyp, jboolean forSend, jboolean val ) { XWJNI_START(gamePtr); if ( NULL != state->game.comms ) { CommsConnType connType = jEnumToInt( env, jConnTyp ); comms_setAddrDisabled( state->game.comms, connType, forSend, val ); } XWJNI_END(); } #endif JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_comms_1getAddrDisabled ( JNIEnv* env, jclass C, GamePtrType gamePtr, jobject jConnTyp, jboolean forSend ) { jboolean result = XP_FALSE; #ifdef DEBUG XWJNI_START(gamePtr); if ( NULL != state->game.comms ) { CommsConnType connType = jEnumToInt( env, jConnTyp ); result = comms_getAddrDisabled( state->game.comms, connType, forSend ); } XWJNI_END(); #endif return result; } JNIEXPORT jboolean JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_haveEnv ( JNIEnv* env, jclass C, jlong jniGlobalPtr ) { jboolean result = XP_TRUE; #ifdef MAP_THREAD_TO_ENV JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; result = NULL != prvEnvForMe(&globalState->ti); #endif return result; } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_server_1endGame ( JNIEnv* env, jclass C, GamePtrType gamePtr ) { XWJNI_START(gamePtr); XP_ASSERT( !!state->game.server ); server_endGame( state->game.server, env ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1pause ( JNIEnv* env, jclass C, GamePtrType gamePtr, jstring jmsg ) { XWJNI_START(gamePtr); XP_ASSERT( !!state->game.board ); const char* msg = (*env)->GetStringUTFChars( env, jmsg, NULL ); board_pause( state->game.board, env, msg ); (*env)->ReleaseStringUTFChars( env, jmsg, msg ); XWJNI_END(); } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1unpause ( JNIEnv* env, jclass C, GamePtrType gamePtr, jstring jmsg ) { XWJNI_START(gamePtr); XP_ASSERT( !!state->game.board ); const char* msg = (*env)->GetStringUTFChars( env, jmsg, NULL ); board_unpause( state->game.board, env, msg ); (*env)->ReleaseStringUTFChars( env, jmsg, msg ); XWJNI_END(); } #ifdef XWFEATURE_CHAT JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_board_1sendChat ( JNIEnv* env, jclass C, GamePtrType gamePtr, jstring jmsg ) { XWJNI_START(gamePtr); XP_ASSERT( !!state->game.server ); const char* msg = (*env)->GetStringUTFChars( env, jmsg, NULL ); board_sendChat( state->game.board, env, msg ); (*env)->ReleaseStringUTFChars( env, jmsg, msg ); XWJNI_END(); } #endif #ifdef XWFEATURE_WALKDICT //////////////////////////////////////////////////////////// // Dict iterator //////////////////////////////////////////////////////////// typedef struct _DictIterData { JNIGlobalState* globalState; const DictionaryCtxt* dict; DictIter* iter; IndexData idata; XP_U16 depth; #ifdef DEBUG pthread_t lastUser; XP_U32 guard; #endif } DictIterData; static void makeIndex( DictIterData* data ); static void freeIndices( DictIterData* data ); #define GI_GUARD 0x89ab72 JNIEXPORT jlong JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1init ( JNIEnv* env, jclass C, jlong jniGlobalPtr, jlong dictPtr, jobjectArray jPatsArr, jint minLen, jint maxLen ) { jlong closure = 0; JNIGlobalState* globalState = (JNIGlobalState*)jniGlobalPtr; MAP_THREAD( &globalState->ti, env ); DictionaryCtxt* dict = (DictionaryCtxt*)dictPtr; if ( !!dict ) { PatDesc patDescs[3]; XP_MEMSET( patDescs, 0, VSIZE(patDescs) * sizeof(patDescs[0]) ); int len = 0; bool formatOK = true; if ( !!jPatsArr ) { len = (*env)->GetArrayLength( env, jPatsArr ); XP_ASSERT( len == 3 ); for ( int ii = 0; formatOK && ii < len ; ++ii ) { jobject jdesc = (*env)->GetObjectArrayElement( env, jPatsArr, ii ); if ( !!jdesc ) { jbyteArray jtiles = getObjectField( env, jdesc, "tilePat", "[B" ); if ( !!jtiles ) { int nTiles = (*env)->GetArrayLength( env, jtiles ); if ( 0 < nTiles ) { PatDesc* pd = &patDescs[ii]; /* If user adds too many tiles, we'll see it here */ if ( nTiles <= VSIZE(pd->tiles) ) { pd->nTiles = nTiles; jbyte* tiles = (*env)->GetByteArrayElements( env, jtiles, NULL ); XP_MEMCPY( &pd->tiles[0], tiles, nTiles * sizeof(pd->tiles[0]) ); (*env)->ReleaseByteArrayElements( env, jtiles, tiles, 0 ); pd->anyOrderOk = getBool( env, jdesc, "anyOrderOk" ); } else { formatOK = false; } } } deleteLocalRefs( env, jtiles, jdesc, DELETE_NO_REF ); } } } DictIter* iter = NULL; if ( formatOK ) { DIMinMax mm = { .min = minLen, .max = maxLen }; iter = di_makeIter( dict, env, &mm, NULL, 0, !!jPatsArr ? patDescs : NULL, VSIZE(patDescs) ); } if ( !!iter ) { DictIterData* data = XP_CALLOC( globalState->mpool, sizeof(*data) ); data->iter = iter; data->globalState = globalState; data->dict = dict_ref( dict, env ); data->depth = 2; #ifdef DEBUG data->guard = GI_GUARD; #endif makeIndex( data ); (void)di_firstWord( data->iter ); closure = (jlong)data; } } return closure; } #ifdef DEBUG # define DI_HEADER(THREAD_CHECK) { \ DictIterData* data = (DictIterData*)closure; \ XP_ASSERT( NULL == data || data->guard == GI_GUARD ); \ if ( THREAD_CHECK && !!data ) { \ if ( 0 == data->lastUser ) { \ data->lastUser = pthread_self(); \ } else { \ XP_ASSERT( data->lastUser == pthread_self() ); \ } \ } \ #else # define DI_HEADER(THREAD_CHECK) { \ DictIterData* data = (DictIterData*)closure; \ #endif #define DI_HEADER_END() \ } JNIEXPORT void JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1destroy ( JNIEnv* env, jclass C, jlong closure ) { DI_HEADER(XP_FALSE); if ( NULL != data ) { #ifdef MEM_DEBUG MemPoolCtx* mpool = data->globalState->mpool; #endif freeIndices( data ); MAP_REMOVE( &data->globalState->ti, env ); di_freeIter( data->iter, env); dict_unref( data->dict, env ); XP_FREE( mpool, data ); } DI_HEADER_END(); } JNIEXPORT jint JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1wordCount (JNIEnv* env, jclass C, jlong closure ) { jint result = 0; DI_HEADER(XP_TRUE); if ( NULL != data ) { result = di_getNWords( data->iter ); } DI_HEADER_END(); return result; } JNIEXPORT jintArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1getMinMax ( JNIEnv* env, jclass C, jlong closure ) { jintArray result = NULL; DI_HEADER(XP_TRUE); XP_U16 vals[2]; di_getMinMax( data->iter, &vals[0], &vals[1] ); result = makeIntArray( env, VSIZE(vals), vals, sizeof(vals[0]) ); DI_HEADER_END(); return result; } JNIEXPORT jobjectArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1getPrefixes ( JNIEnv* env, jclass C, jlong closure ) { jobjectArray result = NULL; DI_HEADER(XP_TRUE); if ( NULL != data && NULL != data->idata.prefixes ) { result = makeStringArray( env, data->idata.count, NULL ); XP_U16 depth = data->depth; for ( int ii = 0; ii < data->idata.count; ++ii ) { XP_UCHAR buf[16]; (void)dict_tilesToString( data->dict, &data->idata.prefixes[depth*ii], depth, buf, VSIZE(buf), NULL ); jstring jstr = (*env)->NewStringUTF( env, buf ); (*env)->SetObjectArrayElement( env, result, ii, jstr ); deleteLocalRef( env, jstr ); } } DI_HEADER_END(); return result; } JNIEXPORT jintArray JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1getIndices ( JNIEnv* env, jclass C, jlong closure ) { jintArray jindices = NULL; DI_HEADER(XP_TRUE); if ( NULL != data ) { if ( !!data->idata.indices ) { /* filters-block-all case */ XP_ASSERT( sizeof(jint) == sizeof(data->idata.indices[0]) ); jindices = makeIntArray( env, data->idata.count, (jint*)data->idata.indices, sizeof(data->idata.indices[0]) ); } } DI_HEADER_END(); return jindices; } JNIEXPORT jstring JNICALL Java_org_eehouse_android_xw4_jni_XwJNI_di_1nthWord ( JNIEnv* env, jclass C, jlong closure, jint jposn, jstring jdelim ) { jstring result = NULL; DI_HEADER(XP_TRUE); if ( NULL != data ) { if ( di_getNthWord( data->iter, env, jposn, data->depth, &data->idata ) ) { XP_UCHAR buf[64]; const XP_UCHAR* delim = NULL == jdelim ? NULL : (*env)->GetStringUTFChars( env, jdelim, NULL ); di_wordToString( data->iter, buf, VSIZE(buf), delim ); result = (*env)->NewStringUTF( env, buf ); if ( !!delim ) { (*env)->ReleaseStringUTFChars( env, jdelim, delim ); } } else { XP_LOGFF( "failed to get %dth word", jposn ); } } DI_HEADER_END(); return result; } static void freeIndices( DictIterData* data ) { if ( !!data ) { IndexData* idata = &data->idata; if ( !!idata->prefixes ) { XP_FREE( data->globalState->mpool, idata->prefixes ); idata->prefixes = NULL; } if( !!idata->indices ) { XP_FREE( data->globalState->mpool, idata->indices ); idata->indices = NULL; } } } static void makeIndex( DictIterData* data ) { XP_U16 nFaces = dict_numTileFaces( data->dict ); XP_U16 ii; XP_U16 count; for ( count = 1, ii = 0; ii < data->depth; ++ii ) { count *= nFaces; } freeIndices( data ); IndexData* idata = &data->idata; #ifdef MEM_DEBUG MemPoolCtx* mpool = data->globalState->mpool; #endif idata->prefixes = XP_MALLOC( mpool, count * data->depth * sizeof(*idata->prefixes) ); idata->indices = XP_MALLOC( mpool, count * sizeof(*idata->indices) ); idata->count = count; di_makeIndex( data->iter, data->depth, idata ); if ( 0 < idata->count ) { idata->prefixes = XP_REALLOC( mpool, idata->prefixes, idata->count * data->depth * sizeof(*idata->prefixes) ); idata->indices = XP_REALLOC( mpool, idata->indices, idata->count * sizeof(*idata->indices) ); } else { freeIndices( data ); } } /* makeIndex */ #endif /* XWFEATURE_BOARDWORDS */