diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java index b208c83d9..93e47f0cf 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BTService.java @@ -53,6 +53,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.XwJNI; +import org.eehouse.android.xw4.jni.JNIThread; import org.eehouse.android.xw4.loc.LocUtils; import junit.framework.Assert; @@ -591,9 +592,12 @@ public class BTService extends XWService { boolean[] isLocalP = new boolean[1]; for ( long rowid : rowids ) { - boolean consumed = - BoardDelegate.feedMessage( rowid, buffer, addr ); - if ( !consumed && haveGame ) { + JNIThread jniThread = JNIThread.getRetained( rowid, false ); + boolean consumed = false; + if ( null != jniThread ) { + consumed = true; + jniThread.receive( buffer, addr ).release(); + } else if ( haveGame ) { GameUtils.BackMoveResult bmr = new GameUtils.BackMoveResult(); if ( GameUtils.feedMessage( BTService.this, rowid, diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java index 1f78b1b42..36f5fda53 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardDelegate.java @@ -128,6 +128,7 @@ public class BoardDelegate extends DelegateBase private Thread m_blockingThread; private JNIThread m_jniThread; + private JNIThread m_jniThreadRef; private JNIThread.GameStateInfo m_gsi; private DlgID m_blockingDlgID = DlgID.NONE; @@ -142,55 +143,55 @@ public class BoardDelegate extends DelegateBase private boolean m_haveInvited = false; private boolean m_overNotShown; - private static Set s_this = new HashSet(); + // private static Set s_this = new HashSet(); - public static boolean feedMessage( long rowid, byte[] msg, - CommsAddrRec ret ) - { - return feedMessages( rowid, new byte[][]{msg}, ret ); - } + // public static boolean feedMessage( long rowid, byte[] msg, + // CommsAddrRec ret ) + // { + // return feedMessages( rowid, new byte[][]{msg}, ret ); + // } - public static boolean feedMessages( long rowid, byte[][] msgs, - CommsAddrRec ret ) - { - boolean delivered = false; - Assert.assertNotNull( msgs ); - int size; - synchronized( s_this ) { - size = s_this.size(); - if ( 1 == size ) { - BoardDelegate self = s_this.iterator().next(); - Assert.assertNotNull( self.m_gi ); - Assert.assertNotNull( self.m_gameLock ); - Assert.assertNotNull( self.m_jniThread ); - if ( rowid == self.m_rowid ) { - delivered = true; // even if no messages! - for ( byte[] msg : msgs ) { - self.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg, ret ); - } - } - } - } - if ( 1 < size ) { - noteSkip(); - } - return delivered; - } + // public static boolean feedMessages( long rowid, byte[][] msgs, + // CommsAddrRec ret ) + // { + // boolean delivered = false; + // // Assert.assertNotNull( msgs ); + // // int size; + // // synchronized( s_this ) { + // // size = s_this.size(); + // // if ( 1 == size ) { + // // BoardDelegate self = s_this.iterator().next(); + // // Assert.assertNotNull( self.m_gi ); + // // Assert.assertNotNull( self.m_gameLock ); + // // Assert.assertNotNull( self.m_jniThread ); + // // if ( rowid == self.m_rowid ) { + // // delivered = true; // even if no messages! + // // for ( byte[] msg : msgs ) { + // // self.m_jniThread.handle( JNICmd.CMD_RECEIVE, msg, ret ); + // // } + // // } + // // } + // // } + // // if ( 1 < size ) { + // // noteSkip(); + // // } + // return delivered; + // } private static void setThis( BoardDelegate self ) { - synchronized( s_this ) { - Assert.assertTrue( !s_this.contains(self) ); // here - s_this.add( self ); - } + // synchronized( s_this ) { + // Assert.assertTrue( !s_this.contains(self) ); // here + // s_this.add( self ); + // } } private static void clearThis( BoardDelegate self ) { - synchronized( s_this ) { - Assert.assertTrue( s_this.contains( self ) ); - s_this.remove( self ); - } + // synchronized( s_this ) { + // Assert.assertTrue( s_this.contains( self ) ); + // s_this.remove( self ); + // } } public class TimerRunnable implements Runnable { @@ -614,18 +615,14 @@ public class BoardDelegate extends DelegateBase m_haveInvited = args.getBoolean( GameUtils.INVITED, false ); m_overNotShown = true; + m_jniThreadRef = JNIThread.getRetained( m_rowid, true ); + NFCUtils.register( m_activity, this ); // Don't seem to need to unregister... setBackgroundColor(); setKeepScreenOn(); } // init - protected void onPause() - { - closeIfFinishing( false ); - super.onPause(); - } - @Override protected void onStart() { @@ -640,21 +637,39 @@ public class BoardDelegate extends DelegateBase doResume( false ); } + protected void onPause() + { + closeIfFinishing( false ); + m_handler = null; + ConnStatusHandler.setHandler( null ); + waitCloseGame( true ); + pauseGame(); + super.onPause(); + } + + @Override + protected void onStop() + { + if ( isFinishing() ) { + m_jniThreadRef.release(); + m_jniThreadRef = null; + } + super.onStop(); + } + @Override protected void onDestroy() { closeIfFinishing( true ); - GamesListDelegate.boardDestroyed( m_rowid ); + if ( null != m_jniThreadRef ) { + m_jniThreadRef.release(); + m_jniThreadRef = null; + // Assert.assertNull( m_jniThreadRef ); // firing + GamesListDelegate.boardDestroyed( m_rowid ); + } super.onDestroy(); } - // @Override - // protected void onDestroy() - // { - // GamesListDelegate.boardDestroyed( m_rowid ); - // super.onDestroy(); - // } - protected void onSaveInstanceState( Bundle outState ) { outState.putInt( DLG_TITLE, m_dlgTitle ); @@ -2028,14 +2043,23 @@ public class BoardDelegate extends DelegateBase if ( null == m_jniGamePtr ) { try { String gameName = DBUtils.getName( m_activity, m_rowid ); - String[] dictNames = GameUtils.dictNames( m_activity, m_rowid ); + String[] dictNames; + + Assert.assertNull( m_gameLock ); + m_gameLock = m_jniThreadRef.getLock(); + if ( null == m_gameLock ) { + dictNames = GameUtils.dictNames( m_activity, m_rowid ); + } else { + dictNames = GameUtils.dictNames( m_activity, m_gameLock ); + } DictUtils.DictPairs pairs = DictUtils.openDicts( m_activity, dictNames ); if ( pairs.anyMissing( dictNames ) ) { showDictGoneFinish(); } else { - Assert.assertNull( m_gameLock ); - m_gameLock = new GameLock( m_rowid, true ).lock(); + if ( null == m_gameLock ) { + m_gameLock = new GameLock( m_rowid, true ).lock(); + } byte[] stream = GameUtils.savedGame( m_activity, m_gameLock ); m_gi = new CurGameInfo( m_activity ); @@ -2110,13 +2134,13 @@ public class BoardDelegate extends DelegateBase } } }; - m_jniThread = - new JNIThread( m_jniGamePtr, stream, m_gi, - m_view, m_gameLock, m_activity, handler ); + m_jniThread = m_jniThreadRef + .configure( m_jniGamePtr, stream, m_gi, m_view, + m_gameLock, m_activity, handler ); // see http://stackoverflow.com/questions/680180/where-to-stop-\ // destroy-threads-in-android-service-class - m_jniThread.setDaemon( true ); - m_jniThread.start(); + m_jniThread.setDaemonOnce( true ); // firing + m_jniThread.startOnce(); m_view.startHandling( m_activity, m_jniThread, m_jniGamePtr, m_gi, m_connTypes ); @@ -2392,20 +2416,12 @@ public class BoardDelegate extends DelegateBase } } - private void waitCloseGame( boolean save ) + private void pauseGame() { if ( null != m_jniGamePtr ) { - if ( null != m_xport ) { - m_xport.waitToStop(); - m_xport = null; - } - interruptBlockingThread(); - if ( null != m_jniThread ) { - m_jniThread.waitToStop( save ); - m_jniThread = null; - } + m_jniThread = null; m_view.stopHandling(); clearThis( this ); @@ -2417,12 +2433,25 @@ public class BoardDelegate extends DelegateBase GameUtils.takeSnapshot( m_activity, m_jniGamePtr, m_gi ); DBUtils.saveThumbnail( m_activity, m_gameLock, thumb ); } + } + } + + private void waitCloseGame( boolean save ) + { + pauseGame(); + if ( null != m_jniGamePtr ) { + if ( null != m_xport ) { + m_xport.waitToStop(); + m_xport = null; + } + + clearThis( this ); m_jniGamePtr.release(); m_jniGamePtr = null; m_gi = null; - m_gameLock.unlock(); + // m_gameLock.unlock(); // likely the problem m_gameLock = null; } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java index 3b3676c8f..b94196123 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/BoardView.java @@ -307,15 +307,20 @@ public class BoardView extends View implements BoardHandler, SyncedDraw { // SyncedDraw interface implementation public void doJNIDraw() { - boolean drew; + boolean drew = false; synchronized( this ) { - if ( !XwJNI.board_draw( m_jniGamePtr ) ) { - DbgUtils.logf( "doJNIDraw: draw not complete" ); + if ( null != m_jniGamePtr ) { + drew = XwJNI.board_draw( m_jniGamePtr ); + if ( !drew ) { + DbgUtils.logf( "doJNIDraw: draw not complete" ); + } } } // Force update now that we have bits to copy - m_parent.runOnUiThread( m_invalidator ); + if ( drew ) { + m_parent.runOnUiThread( m_invalidator ); + } } public void dimsChanged( BoardDims dims ) diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java index b288c1394..a616ec7d9 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/ChatDelegate.java @@ -54,6 +54,7 @@ public class ChatDelegate extends DelegateBase { private EditText m_edit; private TableLayout m_layout; private ScrollView m_scroll; + private JNIThread m_jniThreadRef; public ChatDelegate( Delegator delegator, Bundle savedInstanceState ) { @@ -112,6 +113,21 @@ public class ChatDelegate extends DelegateBase { } } + @Override + protected void onResume() + { + m_jniThreadRef = JNIThread.getRetained( m_rowid ); + Assert.assertNotNull( m_jniThreadRef ); + super.onResume(); + } + + @Override + protected void onPause() + { + m_jniThreadRef.release(); + super.onPause(); + } + private void addRow( String msg, int playerIndx ) { TableRow row = (TableRow)inflate( R.layout.chat_row ); @@ -167,15 +183,15 @@ public class ChatDelegate extends DelegateBase { addRow( text, m_curPlayer ); m_edit.setText( null ); - JNIThread jniThread = JNIThread.getCurrent(); - if ( null != jniThread ) { - jniThread.handle( JNIThread.JNICmd.CMD_SENDCHAT, text ); - } else { - Intent result = new Intent(); - result.putExtra( BoardDelegate.INTENT_KEY_CHAT, text ); - setResult( Activity.RESULT_OK, result ); - finish(); - } + m_jniThreadRef.sendChat( text ); + // if ( null != jniThread ) { + // jniThread.handle( JNIThread.JNICmd.CMD_SENDCHAT, text ); + // } else { + // Intent result = new Intent(); + // result.putExtra( BoardDelegate.INTENT_KEY_CHAT, text ); + // setResult( Activity.RESULT_OK, result ); + // finish(); + // } } // finish(); break; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java index 3c90e1b42..2146dbad0 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameListItem.java @@ -351,7 +351,7 @@ public class GameListItem extends LinearLayout @Override protected GameSummary doInBackground( Void... unused ) { - return DBUtils.getSummary( m_context, m_rowid, 150 ); + return DBUtils.getSummary( m_context, m_rowid, 500 ); } // doInBackground @Override diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java index 98b342d1f..afa7ad718 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameLock.java @@ -28,7 +28,9 @@ import junit.framework.Assert; // obtainable when other read locks are granted but not when a // write lock is. Write-locks are exclusive. public class GameLock { - private static final int SLEEP_TIME = 25; + private static final boolean DEBUG_LOCKS = true; + private static final int SLEEP_TIME = 100; + private static final long ASSERT_TIME = 2000; private long m_rowid; private boolean m_isForWrite; private int m_lockCount; @@ -43,7 +45,7 @@ public class GameLock { m_rowid = rowid; m_isForWrite = isForWrite; m_lockCount = 0; - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "GameLock.GameLock(rowid:%d,isForWrite:%b)=>" + "this: %H", rowid, isForWrite, this ); DbgUtils.printStack(); @@ -69,26 +71,26 @@ public class GameLock { ++m_lockCount; gotIt = true; - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { StackTraceElement[] trace = Thread.currentThread().getStackTrace(); m_lockTrace = new StackTraceElement[trace.length]; System.arraycopy( trace, 0, m_lockTrace, 0, trace.length ); } } else if ( this == owner && ! m_isForWrite ) { - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "tryLock(): incrementing lock count" ); } Assert.assertTrue( 0 == m_lockCount ); ++m_lockCount; gotIt = true; owner = null; - } else if ( XWApp.DEBUG_LOCKS ) { + } else if ( DEBUG_LOCKS ) { DbgUtils.logf( "tryLock(): rowid %d already held by lock %H", m_rowid, owner ); } } - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "GameLock.tryLock %H (rowid=%d) => %b", this, m_rowid, gotIt ); } @@ -105,11 +107,10 @@ public class GameLock { public GameLock lock( long maxMillis ) { GameLock result = null; - final long assertTime = 2000; - Assert.assertTrue( maxMillis < assertTime ); + Assert.assertTrue( maxMillis < ASSERT_TIME ); long sleptTime = 0; - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "lock %H (rowid:%d, maxMillis=%d)", this, m_rowid, maxMillis ); } @@ -120,9 +121,9 @@ public class GameLock { result = this; break; } - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "GameLock.lock() %H failed; sleeping", this ); - if ( 0 == sleptTime || sleptTime + SLEEP_TIME >= assertTime ) { + if ( 0 == sleptTime || sleptTime + SLEEP_TIME >= ASSERT_TIME ) { DbgUtils.logf( "lock %H seeking stack:", curOwner ); DbgUtils.printStack( curOwner.m_lockTrace ); DbgUtils.logf( "lock %H seeking stack:", this ); @@ -137,18 +138,18 @@ public class GameLock { break; } - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "GameLock.lock() %H awake; " + "sleptTime now %d millis", this, sleptTime ); } if ( 0 < maxMillis && sleptTime >= maxMillis ) { break; - } else if ( sleptTime >= assertTime ) { - if ( XWApp.DEBUG_LOCKS ) { + } else if ( sleptTime >= ASSERT_TIME ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "lock %H overlocked", this ); } - Assert.fail(); + Assert.fail(); // firing } } // DbgUtils.logf( "GameLock.lock(%s) done", m_path ); @@ -167,7 +168,7 @@ public class GameLock { } --m_lockCount; - if ( XWApp.DEBUG_LOCKS ) { + if ( DEBUG_LOCKS ) { DbgUtils.logf( "GameLock.unlock: this: %H (rowid:%d) unlocked", this, m_rowid ); } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java index 3d61efb02..0dc951f76 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/GameUtils.java @@ -369,6 +369,7 @@ public class GameUtils { ThumbCanvas canvas = new ThumbCanvas( context, thumb ); XwJNI.board_setDraw( gamePtr, canvas ); XwJNI.board_invalAll( gamePtr ); + Assert.assertNotNull( gamePtr ); XwJNI.board_draw( gamePtr ); } } @@ -746,6 +747,15 @@ public class GameUtils { // } // } + + public static String[] dictNames( Context context, GameLock lock ) + { + byte[] stream = savedGame( context, lock ); + CurGameInfo gi = new CurGameInfo( context ); + XwJNI.gi_from_stream( gi, stream ); + return gi.dictNames(); + } + public static String[] dictNames( Context context, long rowid, int[] missingLang ) { diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java index caea9a11a..722f8b9c4 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/RelayService.java @@ -54,6 +54,7 @@ import org.eehouse.android.xw4.jni.GameSummary; import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.UtilCtxt.DevIDType; import org.eehouse.android.xw4.jni.XwJNI; +import org.eehouse.android.xw4.jni.JNIThread; import org.eehouse.android.xw4.loc.LocUtils; public class RelayService extends XWService @@ -999,9 +1000,10 @@ public class RelayService extends XWService { DbgUtils.logdf( "RelayService::feedMessage: %d bytes for rowid %d", msg.length, rowid ); - if ( BoardDelegate.feedMessage( rowid, msg, s_addr ) ) { + JNIThread jniThread = JNIThread.getRetained( rowid, false ); + if ( null != jniThread ) { + jniThread.receive( msg, s_addr ).release(); DbgUtils.logdf( "feedMessage: board ate it" ); - // do nothing } else { RelayMsgSink sink = new RelayMsgSink(); sink.setRowID( rowid ); @@ -1043,19 +1045,31 @@ public class RelayService extends XWService // if game has messages, open it and feed 'em to it. if ( null != forOne ) { BackMoveResult bmr = new BackMoveResult(); - sink.setRowID( rowIDs[ii] ); + long rowid = rowIDs[ii]; + sink.setRowID( rowid ); // since BoardDelegate.feedMessages can't know: isLocalP[0] = false; - if ( BoardDelegate.feedMessages( rowIDs[ii], forOne, s_addr ) - || GameUtils.feedMessages( this, rowIDs[ii], - forOne, s_addr, - sink, bmr, isLocalP ) ) { + boolean delivered = true; + + JNIThread jniThread = JNIThread.getRetained( rowid, false ); + if ( null != jniThread ) { + for ( byte[] msg : forOne ) { + jniThread.receive( msg, s_addr ); + } + jniThread.release(); + } else if ( GameUtils.feedMessages( this, rowid, forOne, s_addr, + sink, bmr, isLocalP ) ) { + } else { + delivered = false; + } + + if ( delivered ) { idsWMsgs.add( relayIDs[ii] ); bmrs.add( bmr ); isLocals.add( isLocalP[0] ); } else { DbgUtils.logf( "RelayService.process(): message for %s (rowid %d)" - + " not consumed", relayIDs[ii], rowIDs[ii] ); + + " not consumed", relayIDs[ii], rowid ); } } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java index c13e02c70..d390705f3 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/SMSService.java @@ -62,6 +62,7 @@ import org.eehouse.android.xw4.jni.CommsAddrRec.CommsConnType; import org.eehouse.android.xw4.jni.CommsAddrRec; import org.eehouse.android.xw4.jni.LastMoveInfo; import org.eehouse.android.xw4.jni.XwJNI; +import org.eehouse.android.xw4.jni.JNIThread; import org.eehouse.android.xw4.loc.LocUtils; public class SMSService extends XWService { @@ -709,8 +710,9 @@ public class SMSService extends XWService { } else { boolean[] isLocalP = new boolean[1]; for ( long rowid : rowids ) { - if ( BoardDelegate.feedMessage( rowid, msg, addr ) ) { - // do nothing + JNIThread jniThread = JNIThread.getRetained( rowid, false ); + if ( null != jniThread ) { + jniThread.receive( msg, addr ).release(); } else { SMSMsgSink sink = new SMSMsgSink( this ); BackMoveResult bmr = new BackMoveResult(); diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java index b45520354..220261fcd 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/XWApp.java @@ -38,8 +38,7 @@ public class XWApp extends Application { public static final boolean REMATCH_SUPPORTED = true; public static final boolean RELAYINVITE_SUPPORTED = false; public static final boolean ATTACH_SUPPORTED = false; - public static final boolean DEBUG_LOCKS = false; - public static final boolean LOG_LIFECYLE = false; + public static final boolean LOG_LIFECYLE = true; public static final boolean DEBUG_EXP_TIMERS = false; public static final boolean GCM_IGNORED = false; public static final boolean UDP_ENABLED = true; diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java index eaa569f5c..4d1c8ccf1 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/JNIThread.java @@ -30,6 +30,8 @@ import android.os.Message; import java.lang.InterruptedException; import java.util.Arrays; import java.util.Iterator; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import java.util.HashSet; import java.util.Set; @@ -160,6 +162,8 @@ public class JNIThread extends Thread { private static final int kMinDivWidth = 10; private int m_connsIconID = 0; private String m_newDict = null; + private long m_rowid; + private int m_refCount; private LinkedBlockingQueue m_queue; @@ -173,27 +177,35 @@ public class JNIThread extends Thread { Object[] m_args; } - public JNIThread( GamePtr gamePtr, byte[] gameAtStart, CurGameInfo gi, - SyncedDraw drawer, GameLock lock, Context context, - Handler handler ) + private JNIThread( long rowid ) { - synchronized( s_curThreads ) { - s_curThreads.add( this ); - // DbgUtils.logf( "JNIThread(): added %H; now %d threads", this, s_curThreads.size() ); + m_rowid = rowid; + m_queue = new LinkedBlockingQueue(); + } + + public JNIThread configure( GamePtr gamePtr, byte[] gameAtStart, CurGameInfo gi, + SyncedDraw drawer, GameLock lock, Context context, + Handler handler ) + { + if ( null != m_jniGamePtr ) { + m_jniGamePtr.release(); } - - m_jniGamePtr = gamePtr; + m_jniGamePtr = gamePtr.retain(); m_gameAtStart = gameAtStart; m_gi = gi; m_drawer = drawer; - m_lock = lock; + Assert.assertTrue( lock.canWrite() ); + m_lock = lock; // I own this now! + Assert.assertTrue( lock.canWrite() ); m_context = context; m_handler = handler; - m_queue = new LinkedBlockingQueue(); + return this; } - public void waitToStop( boolean save ) + public GameLock getLock() { return m_lock; } + + private void waitToStop( boolean save ) { synchronized ( this ) { m_stopped = true; @@ -210,12 +222,9 @@ public class JNIThread extends Thread { } catch ( java.lang.InterruptedException ie ) { DbgUtils.loge( ie ); } - - synchronized( s_curThreads ) { - Assert.assertTrue( s_curThreads.contains( this ) ); - s_curThreads.remove( this ); - // DbgUtils.logf( "waitToStop: removed %H; now %d threads", this, s_curThreads.size() ); - } + // m_jniGamePtr.release(); + // m_jniGamePtr = null; + m_lock.unlock(); } public boolean busy() @@ -340,6 +349,22 @@ public class JNIThread extends Thread { } } + boolean m_running = false; + public void startOnce() + { + if ( !m_running ) { + m_running = true; + start(); + } + } + + public void setDaemonOnce( boolean val ) + { + if ( !m_running ) { + setDaemon( val ); + } + } + @SuppressWarnings("fallthrough") public void run() { @@ -637,7 +662,9 @@ public class JNIThread extends Thread { } else { DbgUtils.logf( "JNIThread.run(): exiting without saving" ); } - XwJNI.threadDone(); + m_jniGamePtr.release(); + m_jniGamePtr = null; + // XwJNI.threadDone(); } // run public void handleBkgrnd( JNICmd cmd, Object... args ) @@ -647,6 +674,17 @@ public class JNIThread extends Thread { m_queue.add( elem ); } + public JNIThread receive( byte[] msg, CommsAddrRec addr ) + { + handle( JNICmd.CMD_RECEIVE, msg, addr ); + return this; + } + + public void sendChat( String chat ) + { + handle( JNICmd.CMD_SENDCHAT, chat ); + } + public void handle( JNICmd cmd, Object... args ) { QueueElem elem = new QueueElem( cmd, true, args ); @@ -667,10 +705,54 @@ public class JNIThread extends Thread { return XwJNI.server_do( gamePtr ); } - // public void run( boolean isUI, Runnable runnable ) - // { - // Object[] args = { runnable }; - // QueueElem elem = new QueueElem( JNICmd.CMD_RUN, isUI, args ); - // m_queue.add( elem ); - // } + private static Map s_instances = new HashMap(); + private void retain_sync() + { + ++m_refCount; + DbgUtils.logf( "JNIThread.retain_sync: m_refCount: %d", m_refCount ); + } + + public void retain() + { + synchronized( s_instances ) { + retain_sync(); + } + } + + public void release() + { + boolean stop = false; + synchronized( s_instances ) { + if ( 0 == --m_refCount ) { + s_instances.remove( m_rowid ); + stop = true; + } + } + DbgUtils.logf( "JNIThread.release: m_refCount: %d", m_refCount ); + + if ( stop ) { + waitToStop( true ); + } + } + + public static JNIThread getRetained( long rowid ) + { + return getRetained( rowid, false ); + } + + public static JNIThread getRetained( long rowid, boolean makeNew ) + { + JNIThread result = null; + synchronized( s_instances ) { + result = s_instances.get( rowid ); + if ( null == result && makeNew ) { + result = new JNIThread( rowid ); + s_instances.put( rowid, result ); + } + if ( null != result ) { + result.retain_sync(); + } + } + return result; + } } diff --git a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java index 98cf3a274..a991ffb72 100644 --- a/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java +++ b/xwords4/android/XWords4/src/org/eehouse/android/xw4/jni/XwJNI.java @@ -34,18 +34,30 @@ public class XwJNI { public static class GamePtr { private int m_ptr = 0; + private int m_refCount = 0; - private GamePtr( int ptr ) { m_ptr = ptr; } + private GamePtr( int ptr ) { + m_ptr = ptr; + retain(); + } public int ptr() { Assert.assertTrue( 0 != m_ptr ); return m_ptr; } + public synchronized GamePtr retain() + { + ++m_refCount; + return this; + } + // Force (via an assert in finalize() below) that this is called. It's // better if jni stuff isn't being done on the finalizer thread - public void release() + public synchronized void release() { - if ( 0 != m_ptr ) { - game_dispose( this ); - m_ptr = 0; + if ( 0 == --m_refCount ) { + if ( 0 != m_ptr ) { + game_dispose( this ); + m_ptr = 0; + } } }