Run JNIThread inside a Looper; use BlockingActivity to implement

dialogs requiring an immediate response.  The Looper change was made
in the hopes that a new Activity wouldn't be required and may not be
necessary.
This commit is contained in:
ehouse 2010-01-16 18:39:27 +00:00
parent 35943e8731
commit 5c183acb5e
4 changed files with 248 additions and 132 deletions

View file

@ -23,10 +23,14 @@ import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.*;
import org.eehouse.android.xw4.jni.JNIThread.*;
public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable { public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private static final int PICK_TILE_REQUEST = 1; private static final int PICK_TILE_REQUEST = 1;
private static final int QUERY_REQUEST = 2;
private static final int INFORM_REQUEST = 3;
private BoardView m_view; private BoardView m_view;
private int m_jniGamePtr; private int m_jniGamePtr;
@ -37,7 +41,6 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private String m_path; private String m_path;
private final int DLG_OKONLY = 1; private final int DLG_OKONLY = 1;
private final int DLG_QUERY = 2;
private String m_dlgBytes = null; private String m_dlgBytes = null;
private int m_dlgTitle; private int m_dlgTitle;
private boolean m_dlgResult; private boolean m_dlgResult;
@ -48,6 +51,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
private Intent m_resultIntent = null; private Intent m_resultIntent = null;
private JNIThread m_jniThread; private JNIThread m_jniThread;
private JNIThread m_jniThread_pending;
public class TimerRunnable implements Runnable { public class TimerRunnable implements Runnable {
private int m_gamePtr; private int m_gamePtr;
@ -61,8 +65,10 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
} }
public void run() { public void run() {
m_timers[m_why] = null; m_timers[m_why] = null;
m_jniThread.handle( JNIThread.JNICmd.CMD_TIMER_FIRED, if ( null != m_jniThread ) {
new Object[] { m_why, m_when, m_handle } ); m_jniThread.handle( JNICmd.CMD_TIMER_FIRED,
m_why, m_when, m_handle );
}
} }
} }
@ -152,17 +158,26 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
m_prefs, null, dictBytes ); m_prefs, null, dictBytes );
} }
m_jniThread = new JNIThread( m_jniGamePtr, m_jniThread_pending = new
new Handler() { JNIThread( m_jniGamePtr,
public void handleMessage( Message msg ) { new Handler() {
Utils.logf( "handleMessage called" ); public void handleMessage( Message msg ) {
m_view.invalidate(); Utils.logf( "handleMessage() called" );
} switch( msg.what ) {
} ); case JNIThread.RUNNING:
m_jniThread.start(); m_jniThread = m_jniThread_pending;
m_view.startHandling( m_jniThread, m_jniGamePtr, m_gi ); m_view.startHandling( m_jniThread,
m_jniGamePtr,
m_jniThread.handle( JNIThread.JNICmd.CMD_DO ); m_gi );
m_jniThread.handle( JNICmd.CMD_DO );
break;
case JNIThread.DRAW:
m_view.invalidate();
break;
}
}
} );
m_jniThread_pending.start();
Utils.logf( "BoardActivity::onCreate() done" ); Utils.logf( "BoardActivity::onCreate() done" );
} // onCreate } // onCreate
@ -175,6 +190,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
protected void onDestroy() protected void onDestroy()
{ {
// what if m_jniThread is null?
m_jniThread.waitToStop(); m_jniThread.waitToStop();
saveGame(); saveGame();
XwJNI.game_dispose( m_jniGamePtr ); XwJNI.game_dispose( m_jniGamePtr );
@ -187,9 +203,9 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
Intent result ) Intent result )
{ {
Utils.logf( "onActivityResult called" ); Utils.logf( "onActivityResult called" );
this.m_resultCode = resultCode; m_resultCode = resultCode;
this.m_resultIntent = result; m_resultIntent = result;
this.m_forResultWait.release(); m_forResultWait.release();
} }
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
@ -270,7 +286,9 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
} }
if ( handled && cmd != JNIThread.JNICmd.CMD_NONE ) { if ( handled && cmd != JNIThread.JNICmd.CMD_NONE ) {
m_jniThread.handle( cmd ); if ( null != m_jniThread ) {
m_jniThread.handle( cmd );
}
} }
return handled; return handled;
@ -415,7 +433,7 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
public boolean engineProgressCallback() public boolean engineProgressCallback()
{ {
return !m_jniThread.busy(); return null != m_jniThread && !m_jniThread.busy();
} }
public String getUserString( int stringCode ) public String getUserString( int stringCode )
@ -509,18 +527,107 @@ public class BoardActivity extends Activity implements XW_UtilCtxt, Runnable {
public boolean userQuery( int id, String query ) public boolean userQuery( int id, String query )
{ {
String actString = XWConstants.ACTION_QUERY;
switch( id ) { switch( id ) {
case XW_UtilCtxt.QUERY_ROBOT_MOVE:
case XW_UtilCtxt.QUERY_ROBOT_TRADE:
actString = XWConstants.ACTION_INFORM;
break;
case XW_UtilCtxt.QUERY_COMMIT_TRADE: case XW_UtilCtxt.QUERY_COMMIT_TRADE:
query = getString( R.string.query_trade ); query = getString( R.string.query_trade );
break; break;
case XW_UtilCtxt.QUERY_COMMIT_TURN: case XW_UtilCtxt.QUERY_COMMIT_TURN:
case XW_UtilCtxt.QUERY_ROBOT_MOVE:
case XW_UtilCtxt.QUERY_ROBOT_TRADE:
break; break;
} }
// Need to figure out how this thing can block. // Need to figure out how this thing can block.
return true;
Intent intent = new Intent( BoardActivity.this, BlockingActivity.class );
intent.setAction( actString );
Bundle bundle = new Bundle();
bundle.putString( XWConstants.QUERY_QUERY, query );
intent.putExtra( XWConstants.QUERY_QUERY, bundle );
boolean userConfirmed = false;
try {
startActivityForResult( intent, QUERY_REQUEST );
m_forResultWait.acquire();
Utils.logf( "userQuery back from acquire" );
userConfirmed = m_resultCode != 0;
} catch ( Exception ee ) {
Utils.logf( "userPickTile got: " + ee.toString() );
}
return userConfirmed;
} }
public void userError( int code )
{
int resid = 0;
switch( code ) {
case ERR_TILES_NOT_IN_LINE:
resid = R.string.str_tiles_not_in_line;
break;
case ERR_NO_EMPTIES_IN_TURN:
resid = R.string.str_no_empties_in_turn;
break;
case ERR_TWO_TILES_FIRST_MOVE:
resid = R.string.str_two_tiles_first_move;
break;
case ERR_TILES_MUST_CONTACT:
resid = R.string.str_tiles_must_contact;
break;
case ERR_NOT_YOUR_TURN:
resid = R.string.str_not_your_turn;
break;
case ERR_NO_PEEK_ROBOT_TILES:
resid = R.string.str_no_peek_robot_tiles;
break;
case ERR_CANT_TRADE_MID_MOVE:
resid = R.string.str_cant_trade_mid_move;
break;
case ERR_TOO_FEW_TILES_LEFT_TO_TRADE:
resid = R.string.str_too_few_tiles_left_to_trade;
break;
case ERR_CANT_UNDO_TILEASSIGN:
resid = R.string.str_cant_undo_tileassign;
break;
case ERR_CANT_HINT_WHILE_DISABLED:
resid = R.string.str_cant_hint_while_disabled;
break;
case ERR_NO_PEEK_REMOTE_TILES:
resid = R.string.str_no_peek_remote_tiles;
break;
case ERR_REG_UNEXPECTED_USER:
resid = R.string.str_reg_unexpected_user;
break;
case ERR_SERVER_DICT_WINS:
resid = R.string.str_server_dict_wins;
break;
case ERR_REG_SERVER_SANS_REMOTE:
resid = R.string.str_reg_server_sans_remote;
break;
}
if ( resid != 0 ) {
String txt = getString( resid );
Intent intent = new Intent( BoardActivity.this, BlockingActivity.class );
intent.setAction( XWConstants.ACTION_INFORM );
Bundle bundle = new Bundle();
bundle.putString( XWConstants.QUERY_QUERY, txt );
intent.putExtra( XWConstants.QUERY_QUERY, bundle );
try {
startActivityForResult( intent, INFORM_REQUEST );
m_forResultWait.acquire();
} catch ( Exception ee ) {
Utils.logf( "userPickTile got: " + ee.toString() );
}
}
} // userError
} // class BoardActivity } // class BoardActivity

View file

@ -10,8 +10,8 @@ public interface XWConstants {
public static final String PICK_TILE_TILES public static final String PICK_TILE_TILES
= "org.eehouse.android.xw4.PICK_TILE_TILES"; = "org.eehouse.android.xw4.PICK_TILE_TILES";
public static final String PICK_TILE_TILE // public static final String PICK_TILE_TILE
= "org.eehouse.android.xw4.PICK_TILE_TILE"; // = "org.eehouse.android.xw4.PICK_TILE_TILE";
// These are duplicated in AndroidManifest.xml. If change here // These are duplicated in AndroidManifest.xml. If change here
// must change there too to keep in sync. // must change there too to keep in sync.
@ -19,4 +19,10 @@ public interface XWConstants {
= "org.eehouse.android.xw4.action.PICK_TILE"; = "org.eehouse.android.xw4.action.PICK_TILE";
public final String CATEGORY_PICK_TILE public final String CATEGORY_PICK_TILE
= "org.eehouse.android.xw4.category.PICK_TILE"; = "org.eehouse.android.xw4.category.PICK_TILE";
public final String ACTION_QUERY = "org.eehouse.android.xw4.action.QUERY";
public final String ACTION_INFORM= "org.eehouse.android.xw4.action.INFORM";
public static final String QUERY_QUERY
= "org.eehouse.android.xw4.QUERY_QUERY";
} }

View file

@ -8,8 +8,8 @@ public class CommonPrefs {
public CommonPrefs() { public CommonPrefs() {
showBoardArrow = true; showBoardArrow = true;
showRobotScores = false; showRobotScores = true;
hideTileValues = false; hideTileValues = false;
skipCommitConfirm = true; skipCommitConfirm = false;
} }
} }

View file

@ -9,6 +9,7 @@ import java.lang.InterruptedException;
import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.LinkedBlockingQueue;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.os.Looper;
public class JNIThread extends Thread { public class JNIThread extends Thread {
@ -29,33 +30,26 @@ public class JNIThread extends Thread {
CMD_HINT, CMD_HINT,
CMD_NEXT_HINT, CMD_NEXT_HINT,
CMD_VALUES, CMD_VALUES,
CMD_STOP,
}; };
private boolean m_stopped = false; public static final int RUNNING = 1;
public static final int DRAW = 2;
private int m_jniGamePtr; private int m_jniGamePtr;
private Handler m_handler; private Handler m_parentHandler;
LinkedBlockingQueue<QueueElem> m_queue; private Handler m_loopHandler;
private boolean[] m_barr = new boolean[1]; // scratch boolean
private class QueueElem {
protected QueueElem( JNICmd cmd, Object[] args )
{
m_cmd = cmd; m_args = args;
}
JNICmd m_cmd;
Object[] m_args;
}
public JNIThread( int gamePtr, Handler handler ) { public JNIThread( int gamePtr, Handler handler ) {
Utils.logf( "in JNIThread()" ); Utils.logf( "in JNIThread()" );
m_jniGamePtr = gamePtr; m_jniGamePtr = gamePtr;
m_handler = handler; m_parentHandler = handler;
m_queue = new LinkedBlockingQueue<QueueElem>();
} }
public void waitToStop() { public void waitToStop() {
m_stopped = true; handle( JNICmd.CMD_STOP );
handle( JNICmd.CMD_NONE ); // tickle it
try { try {
join(); join();
} catch ( java.lang.InterruptedException ie ) { } catch ( java.lang.InterruptedException ie ) {
@ -64,9 +58,10 @@ public class JNIThread extends Thread {
} }
public boolean busy() public boolean busy()
{ // synchronize this!!! {
int siz = m_queue.size(); // HTF to I tell if my queue has anything in it. Do I have to
return siz > 0; // keep a counter? Which means synchronizing...
return false;
} }
private boolean toggleTray() { private boolean toggleTray() {
@ -82,103 +77,111 @@ public class JNIThread extends Thread {
public void run() public void run()
{ {
boolean[] barr = new boolean[1]; // scratch boolean Looper.prepare();
while ( !m_stopped ) {
QueueElem elem; m_loopHandler = new Handler() {
Object[] args; public void handleMessage( Message msg ) {
try { boolean draw = false;
elem = m_queue.take(); Object[] args = (Object[])msg.obj;
} catch ( InterruptedException ie ) { switch( JNICmd.values()[msg.what] ) {
Utils.logf( "interrupted; killing thread" );
break;
}
boolean draw = false;
args = elem.m_args;
switch( elem.m_cmd ) {
case CMD_DRAW: case CMD_DRAW:
draw = true; draw = true;
break; break;
case CMD_DO: case CMD_DO:
draw = XwJNI.server_do( m_jniGamePtr ); draw = XwJNI.server_do( m_jniGamePtr );
break; break;
case CMD_PEN_DOWN: case CMD_PEN_DOWN:
draw = XwJNI.board_handlePenDown( m_jniGamePtr, draw = XwJNI.board_handlePenDown( m_jniGamePtr,
((Integer)args[0]).intValue(), ((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(), ((Integer)args[1]).intValue(),
barr ); m_barr );
break; break;
case CMD_PEN_MOVE: case CMD_PEN_MOVE:
draw = XwJNI.board_handlePenMove( m_jniGamePtr, draw = XwJNI.board_handlePenMove( m_jniGamePtr,
((Integer)args[0]).intValue(), ((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() ); ((Integer)args[1]).intValue() );
break; break;
case CMD_PEN_UP: case CMD_PEN_UP:
draw = XwJNI.board_handlePenUp( m_jniGamePtr, draw = XwJNI.board_handlePenUp( m_jniGamePtr,
((Integer)args[0]).intValue(), ((Integer)args[0]).intValue(),
((Integer)args[1]).intValue() ); ((Integer)args[1]).intValue() );
break; break;
case CMD_COMMIT: case CMD_COMMIT:
draw = XwJNI.board_commitTurn( m_jniGamePtr ); draw = XwJNI.board_commitTurn( m_jniGamePtr );
break; break;
case CMD_JUGGLE: case CMD_JUGGLE:
draw = XwJNI.board_juggleTray( m_jniGamePtr ); draw = XwJNI.board_juggleTray( m_jniGamePtr );
break; break;
case CMD_FLIP: case CMD_FLIP:
draw = XwJNI.board_flip( m_jniGamePtr ); draw = XwJNI.board_flip( m_jniGamePtr );
break; break;
case CMD_TOGGLE_TRAY: case CMD_TOGGLE_TRAY:
draw = toggleTray(); draw = toggleTray();
break; break;
case CMD_TOGGLE_TRADE: case CMD_TOGGLE_TRADE:
draw = XwJNI.board_beginTrade( m_jniGamePtr ); draw = XwJNI.board_beginTrade( m_jniGamePtr );
break; break;
case CMD_UNDO_CUR: case CMD_UNDO_CUR:
draw = XwJNI.board_replaceTiles( m_jniGamePtr ); draw = XwJNI.board_replaceTiles( m_jniGamePtr );
break; break;
case CMD_UNDO_LAST: case CMD_UNDO_LAST:
XwJNI.server_handleUndo( m_jniGamePtr ); XwJNI.server_handleUndo( m_jniGamePtr );
draw = true; draw = true;
break; break;
case CMD_HINT: case CMD_HINT:
XwJNI.board_resetEngine( m_jniGamePtr ); XwJNI.board_resetEngine( m_jniGamePtr );
// fallthru // fallthru
case CMD_NEXT_HINT: case CMD_NEXT_HINT:
draw = XwJNI.board_requestHint( m_jniGamePtr, false, barr ); draw = XwJNI.board_requestHint( m_jniGamePtr, false, m_barr );
if ( barr[0] ) { if ( m_barr[0] ) {
handle( JNICmd.CMD_NEXT_HINT ); handle( JNICmd.CMD_NEXT_HINT );
}
break;
case CMD_VALUES:
draw = XwJNI.board_toggle_showValues( m_jniGamePtr );
break;
case CMD_TIMER_FIRED:
draw = XwJNI.timerFired( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
((Integer)args[2]).intValue() );
break;
case CMD_STOP:
Looper.myLooper().quit();
break;
}
if ( draw ) {
if ( !XwJNI.board_draw( m_jniGamePtr ) ) {
Utils.logf( "draw not complete" );
}
Message.obtain( m_parentHandler, DRAW ).sendToTarget();
}
} }
break; };
case CMD_VALUES: // Safe to use us now
draw = XwJNI.board_toggle_showValues( m_jniGamePtr ); Message.obtain( m_parentHandler, RUNNING ).sendToTarget();
break;
Looper.loop();
case CMD_TIMER_FIRED:
draw = XwJNI.timerFired( m_jniGamePtr,
((Integer)args[0]).intValue(),
((Integer)args[1]).intValue(),
((Integer)args[2]).intValue() );
break;
}
if ( draw ) {
if ( !XwJNI.board_draw( m_jniGamePtr ) ) {
Utils.logf( "draw not complete" );
}
m_handler.post(null);
}
}
Utils.logf( "run exiting" ); Utils.logf( "run exiting" );
} // run } // run
/** Post a cmd to be handled by the JNI thread
*/
public void handle( JNICmd cmd, Object... args ) public void handle( JNICmd cmd, Object... args )
{ {
QueueElem elem = new QueueElem( cmd, args ); Message message = Message.obtain( m_loopHandler, cmd.ordinal(), args );
m_queue.add( elem ); message.sendToTarget();
} }
} }