add class that maintains read and write locks per-game record to

prevent writes to a game while it's open.  Seems to work, but needs
more testing.
This commit is contained in:
Andy2 2011-03-01 21:44:59 -08:00
parent 4eb77809fb
commit ce6eca3fe5
8 changed files with 289 additions and 132 deletions

View file

@ -68,6 +68,7 @@ public class BoardActivity extends XWActivity
private BoardView m_view; private BoardView m_view;
private int m_jniGamePtr; private int m_jniGamePtr;
private DBUtils.GameLock m_gameLock;
private CurGameInfo m_gi; private CurGameInfo m_gi;
CommsTransport m_xport; CommsTransport m_xport;
private Handler m_handler; private Handler m_handler;
@ -290,38 +291,19 @@ public class BoardActivity extends XWActivity
} }
} // onCreate } // onCreate
@Override
protected void onStart()
{
loadGame();
super.onStart();
}
@Override @Override
protected void onPause() protected void onPause()
{ {
if ( null != m_jniThread ) { waitCloseGame( true );
m_jniThread.setInBackground( true );
}
m_isVisible = false;
super.onPause(); super.onPause();
} }
@Override @Override
protected void onResume() protected void onResume()
{ {
if ( null != m_jniThread ) {
m_jniThread.setInBackground( false );
}
m_isVisible = true;
super.onResume(); super.onResume();
} loadGame();
super.onResume();
@Override
protected void onDestroy()
{
waitCloseGame( true );
super.onDestroy();
} }
@Override @Override
@ -1070,7 +1052,10 @@ public class BoardActivity extends XWActivity
private void loadGame() private void loadGame()
{ {
if ( 0 == m_jniGamePtr ) { if ( 0 == m_jniGamePtr ) {
byte[] stream = GameUtils.savedGame( this, m_path ); Assert.assertNull( m_gameLock );
m_gameLock = new DBUtils.GameLock( m_path, true ).lock();
byte[] stream = GameUtils.savedGame( this, m_gameLock );
XwJNI.gi_from_stream( m_gi, stream ); XwJNI.gi_from_stream( m_gi, stream );
Utils.logf( "loadGame: dict name: %s", m_gi.dictName ); Utils.logf( "loadGame: dict name: %s", m_gi.dictName );
@ -1095,7 +1080,7 @@ public class BoardActivity extends XWActivity
} }
m_jniThread = new m_jniThread = new
JNIThread( m_jniGamePtr, m_gi, m_view, m_path, this, JNIThread( m_jniGamePtr, m_gi, m_view, m_gameLock, this,
new Handler() { new Handler() {
public void handleMessage( Message msg ) { public void handleMessage( Message msg ) {
switch( msg.what ) { switch( msg.what ) {
@ -1313,6 +1298,9 @@ public class BoardActivity extends XWActivity
XwJNI.game_dispose( m_jniGamePtr ); XwJNI.game_dispose( m_jniGamePtr );
m_jniGamePtr = 0; m_jniGamePtr = 0;
m_gameLock.unlock();
m_gameLock = null;
} }
} }

View file

@ -28,8 +28,12 @@ import java.util.StringTokenizer;
import android.content.ContentValues; import android.content.ContentValues;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;
import junit.framework.Assert; import junit.framework.Assert;
import org.eehouse.android.xw4.jni.*; import org.eehouse.android.xw4.jni.*;
@ -63,7 +67,88 @@ public class DBUtils {
boolean sourceLocal; boolean sourceLocal;
} }
// Implements read-locks and write-locks per game. A read lock is
// obtainable when other read locks are granted but not when a
// write lock is. Write-locks are exclusive.
public static class GameLock {
private String m_path;
private boolean m_isForWrite;
private ReentrantReadWriteLock m_rwlock;
// This will leak empty ReentrantReadWriteLock instances for
// now.
private static HashMap<String, ReentrantReadWriteLock>
s_locks = new HashMap<String,ReentrantReadWriteLock>();
public GameLock( String path, boolean isForWrite )
{
m_path = path;
m_isForWrite = isForWrite;
synchronized( s_locks ) {
if ( s_locks.containsKey( m_path ) ) {
m_rwlock = s_locks.get( m_path );
} else {
m_rwlock = new ReentrantReadWriteLock();
s_locks.put( path, m_rwlock );
}
}
}
public GameLock lock()
{
getLock().lock();
return this;
}
public boolean tryLock()
{
boolean gotIt = false;
synchronized( s_locks ) {
boolean hasWaiters = m_isForWrite && m_rwlock.isWriteLocked();
if ( !hasWaiters ) {
getLock().lock();
gotIt = true;
}
}
return gotIt;
}
public void unlock()
{
synchronized( s_locks ) {
getLock().unlock();
}
}
public String getPath()
{
return m_path;
}
// used only for asserts
public boolean canWrite()
{
return m_isForWrite && m_rwlock.isWriteLocked();
}
private Lock getLock()
{
Lock lock = m_isForWrite? m_rwlock.writeLock()
: m_rwlock.readLock();
return lock;
}
}
public static GameSummary getSummary( Context context, String file ) public static GameSummary getSummary( Context context, String file )
{
DBUtils.GameLock lock = new DBUtils.GameLock( file, false ).lock();
GameSummary result = getSummary( context, lock );
lock.unlock();
return result;
}
public static GameSummary getSummary( Context context, DBUtils.GameLock lock )
{ {
initDB( context ); initDB( context );
GameSummary summary = null; GameSummary summary = null;
@ -80,7 +165,7 @@ public class DBUtils {
DBHelper.SCORES, DBHelper.HASMSGS, DBHelper.SCORES, DBHelper.HASMSGS,
DBHelper.LASTPLAY_TIME DBHelper.LASTPLAY_TIME
}; };
String selection = DBHelper.FILE_NAME + "=\"" + file + "\""; String selection = DBHelper.FILE_NAME + "=\"" + lock.getPath() + "\"";
Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns, Cursor cursor = db.query( DBHelper.TABLE_NAME_SUM, columns,
selection, null, null, null, null ); selection, null, null, null, null );
@ -168,15 +253,17 @@ public class DBUtils {
} }
if ( null == summary ) { if ( null == summary ) {
summary = GameUtils.summarize( context, file ); summary = GameUtils.summarize( context, lock );
saveSummary( context, file, summary ); saveSummary( context, lock, summary );
} }
return summary; return summary;
} // getSummary } // getSummary
public static void saveSummary( Context context, String path, public static void saveSummary( Context context, DBUtils.GameLock lock,
GameSummary summary ) GameSummary summary )
{ {
Assert.assertTrue( lock.canWrite() );
String path = lock.getPath();
initDB( context ); initDB( context );
synchronized( s_dbHelper ) { synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase(); SQLiteDatabase db = s_dbHelper.getWritableDatabase();
@ -416,9 +503,11 @@ public class DBUtils {
} }
} }
public static void saveGame( Context context, String path, byte[] bytes, public static void saveGame( Context context, GameLock lock, byte[] bytes,
boolean setCreate ) boolean setCreate )
{ {
Assert.assertTrue( lock.canWrite() );
String path = lock.getPath();
initDB( context ); initDB( context );
synchronized( s_dbHelper ) { synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase(); SQLiteDatabase db = s_dbHelper.getWritableDatabase();
@ -445,8 +534,9 @@ public class DBUtils {
} }
} }
public static byte[] loadGame( Context context, String path ) public static byte[] loadGame( Context context, GameLock lock )
{ {
String path = lock.getPath();
Assert.assertNotNull( path ); Assert.assertNotNull( path );
byte[] result = null; byte[] result = null;
initDB( context ); initDB( context );
@ -467,12 +557,12 @@ public class DBUtils {
return result; return result;
} }
public static void deleteGame( Context context, String path ) public static void deleteGame( Context context, GameLock lock )
{ {
initDB( context ); initDB( context );
synchronized( s_dbHelper ) { synchronized( s_dbHelper ) {
SQLiteDatabase db = s_dbHelper.getWritableDatabase(); SQLiteDatabase db = s_dbHelper.getWritableDatabase();
String selection = DBHelper.FILE_NAME + "=\"" + path + "\""; String selection = DBHelper.FILE_NAME + "=\"" + lock.getPath() + "\"";
db.delete( DBHelper.TABLE_NAME_SUM, selection, null ); db.delete( DBHelper.TABLE_NAME_SUM, selection, null );
db.close(); db.close();
} }

View file

@ -85,6 +85,7 @@ public class GameConfig extends XWActivity
private String m_path; private String m_path;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private CurGameInfo m_giOrig; private CurGameInfo m_giOrig;
private DBUtils.GameLock m_gameLock;
private int m_whichPlayer; private int m_whichPlayer;
// private Spinner m_roleSpinner; // private Spinner m_roleSpinner;
// private Spinner m_connectSpinner; // private Spinner m_connectSpinner;
@ -365,9 +366,37 @@ public class GameConfig extends XWActivity
setContentView(R.layout.game_config); setContentView(R.layout.game_config);
m_connectSet = findViewById(R.id.connect_set);
m_addPlayerButton = (Button)findViewById(R.id.add_player);
m_addPlayerButton.setOnClickListener( this );
m_jugglePlayersButton = (Button)findViewById(R.id.juggle_players);
m_jugglePlayersButton.setOnClickListener( this );
m_playButton = (Button)findViewById( R.id.play_button );
m_playButton.setOnClickListener( this );
m_playerLayout = (LinearLayout)findViewById( R.id.player_list );
m_dictSpinner = (Spinner)findViewById( R.id.dict_spinner );
m_phoniesSpinner = (Spinner)findViewById( R.id.phonies_spinner );
m_smartnessSpinner = (Spinner)findViewById( R.id.smart_robot );
String fmt = getString( m_notNetworkedGame ?
R.string.title_game_configf
: R.string.title_gamenet_configf );
setTitle( String.format( fmt, GameUtils.gameName( this, m_path ) ) );
} // onCreate
@Override
protected void onStart()
{
super.onStart();
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
m_giOrig = new CurGameInfo( this ); m_giOrig = new CurGameInfo( this );
GameUtils.loadMakeGame( this, gamePtr, m_giOrig, m_path ); // Lock in case we're going to config. We *could* re-get the
// lock once the user decides to make changes. PENDING.
m_gameLock = new DBUtils.GameLock( m_path, true ).lock();
GameUtils.loadMakeGame( this, gamePtr, m_giOrig, m_gameLock );
m_gameStarted = XwJNI.model_getNMoves( gamePtr ) > 0 m_gameStarted = XwJNI.model_getNMoves( gamePtr ) > 0
|| XwJNI.comms_isConnected( gamePtr ); || XwJNI.comms_isConnected( gamePtr );
m_giOrig.setInProgress( m_gameStarted ); m_giOrig.setInProgress( m_gameStarted );
@ -423,25 +452,12 @@ public class GameConfig extends XWActivity
adjustConnectStuff(); adjustConnectStuff();
} }
m_connectSet = findViewById(R.id.connect_set);
m_addPlayerButton = (Button)findViewById(R.id.add_player);
m_addPlayerButton.setOnClickListener( this );
m_jugglePlayersButton = (Button)findViewById(R.id.juggle_players);
m_jugglePlayersButton.setOnClickListener( this );
m_playButton = (Button)findViewById( R.id.play_button );
m_playButton.setOnClickListener( this );
m_playerLayout = (LinearLayout)findViewById( R.id.player_list );
loadPlayers(); loadPlayers();
m_dictSpinner = (Spinner)findViewById( R.id.dict_spinner );
configDictSpinner(); configDictSpinner();
m_phoniesSpinner = (Spinner)findViewById( R.id.phonies_spinner );
m_phoniesSpinner.setSelection( m_gi.phoniesAction.ordinal() ); m_phoniesSpinner.setSelection( m_gi.phoniesAction.ordinal() );
m_smartnessSpinner = (Spinner)findViewById( R.id.smart_robot );
setSmartnessSpinner(); setSmartnessSpinner();
Utils.setChecked( this, R.id.hints_allowed, !m_gi.hintsNotAllowed ); Utils.setChecked( this, R.id.hints_allowed, !m_gi.hintsNotAllowed );
@ -459,12 +475,17 @@ public class GameConfig extends XWActivity
}; };
check.setOnCheckedChangeListener( lstnr ); check.setOnCheckedChangeListener( lstnr );
Utils.setChecked( this, R.id.use_timer, m_gi.timerEnabled ); Utils.setChecked( this, R.id.use_timer, m_gi.timerEnabled );
} // onStart
String fmt = getString( m_notNetworkedGame ? @Override
R.string.title_game_configf protected void onStop()
: R.string.title_gamenet_configf ); {
setTitle( String.format( fmt, GameUtils.gameName( this, m_path ) ) ); if ( null != m_gameLock ) {
} // onCreate m_gameLock.unlock();
m_gameLock = null;
}
super.onStop();
}
// DeleteCallback interface // DeleteCallback interface
public void deleteCalled( int myPosition ) public void deleteCalled( int myPosition )
@ -889,7 +910,7 @@ public class GameConfig extends XWActivity
private void applyChanges( boolean forceNew ) private void applyChanges( boolean forceNew )
{ {
GameUtils.applyChanges( this, m_gi, m_car, m_path, forceNew ); GameUtils.applyChanges( this, m_gi, m_car, m_gameLock, forceNew );
} }
private void launchGame() private void launchGame()

View file

@ -37,7 +37,10 @@ public class GameConverter {
Utils.logf( "GameConverter::convert() converting %s", Utils.logf( "GameConverter::convert() converting %s",
game ); game );
byte[] bytes = savedGame( context, game ); byte[] bytes = savedGame( context, game );
DBUtils.saveGame( context, game, bytes, true ); DBUtils.GameLock lock =
new DBUtils.GameLock( game, true ).lock();
DBUtils.saveGame( context, lock, bytes, true );
lock.unlock();
context.deleteFile( game ); context.deleteFile( game );
} }
} }

View file

@ -41,15 +41,23 @@ public class GameUtils {
public static byte[] savedGame( Context context, String path ) public static byte[] savedGame( Context context, String path )
{ {
return DBUtils.loadGame( context, path ); DBUtils.GameLock lock = new DBUtils.GameLock( path, false ).lock();
byte[] result = savedGame( context, lock );
lock.unlock();
return result;
}
public static byte[] savedGame( Context context, DBUtils.GameLock lock )
{
return DBUtils.loadGame( context, lock );
} // savedGame } // savedGame
/** /**
* Open an existing game, and use its gi and comms addr as the * Open an existing game, and use its gi and comms addr as the
* basis for a new one. * basis for a new one.
*/ */
public static void resetGame( Context context, String pathIn, public static void resetGame( Context context, DBUtils.GameLock lockSrc,
String pathOut ) DBUtils.GameLock lockDest )
{ {
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = new CurGameInfo( context );
@ -57,7 +65,7 @@ public class GameUtils {
// loadMakeGame, if makinga new game, will add comms as long // loadMakeGame, if makinga new game, will add comms as long
// as DeviceRole.SERVER_STANDALONE != gi.serverRole // as DeviceRole.SERVER_STANDALONE != gi.serverRole
loadMakeGame( context, gamePtr, gi, pathIn ); loadMakeGame( context, gamePtr, gi, lockSrc );
byte[] dictBytes = GameUtils.openDict( context, gi.dictName ); byte[] dictBytes = GameUtils.openDict( context, gi.dictName );
if ( XwJNI.game_hasComms( gamePtr ) ) { if ( XwJNI.game_hasComms( gamePtr ) ) {
@ -81,25 +89,28 @@ public class GameUtils {
XwJNI.comms_setAddr( gamePtr, addr ); XwJNI.comms_setAddr( gamePtr, addr );
} }
saveGame( context, gamePtr, gi, pathOut, true ); saveGame( context, gamePtr, gi, lockDest, true );
summarizeAndClose( context, pathOut, gamePtr, gi ); summarizeAndClose( context, lockDest, gamePtr, gi );
} // resetGame } // resetGame
public static void resetGame( Context context, String pathIn ) public static void resetGame( Context context, String pathIn )
{ {
tellRelayDied( context, pathIn, true ); DBUtils.GameLock lock = new DBUtils.GameLock( pathIn, true )
resetGame( context, pathIn, pathIn ); .lock();
tellRelayDied( context, lock, true );
resetGame( context, lock, lock );
lock.unlock();
} }
private static GameSummary summarizeAndClose( Context context, private static GameSummary summarizeAndClose( Context context,
String path, DBUtils.GameLock lock,
int gamePtr, CurGameInfo gi ) int gamePtr, CurGameInfo gi )
{ {
return summarizeAndClose( context, path, gamePtr, gi, null ); return summarizeAndClose( context, lock, gamePtr, gi, null );
} }
private static GameSummary summarizeAndClose( Context context, private static GameSummary summarizeAndClose( Context context,
String path, DBUtils.GameLock lock,
int gamePtr, CurGameInfo gi, int gamePtr, CurGameInfo gi,
FeedUtilsImpl feedImpl ) FeedUtilsImpl feedImpl )
{ {
@ -118,47 +129,55 @@ public class GameUtils {
} }
} }
DBUtils.saveSummary( context, path, summary ); DBUtils.saveSummary( context, lock, summary );
XwJNI.game_dispose( gamePtr ); XwJNI.game_dispose( gamePtr );
return summary; return summary;
} }
public static GameSummary summarize( Context context, String path ) public static GameSummary summarize( Context context, DBUtils.GameLock lock )
{ {
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = new CurGameInfo( context );
loadMakeGame( context, gamePtr, gi, path ); loadMakeGame( context, gamePtr, gi, lock );
return summarizeAndClose( context, path, gamePtr, gi ); return summarizeAndClose( context, lock, gamePtr, gi );
} }
public static String dupeGame( Context context, String pathIn ) public static String dupeGame( Context context, String pathIn )
{ {
DBUtils.GameLock lockSrc = new DBUtils.GameLock( pathIn, false ).lock();
String newName = newName( context ); String newName = newName( context );
resetGame( context, pathIn, newName ); DBUtils.GameLock lockDest =
new DBUtils.GameLock( newName, true ).lock();
resetGame( context, lockSrc, lockDest );
lockDest.unlock();
lockSrc.unlock();
return newName; return newName;
} }
public static void deleteGame( Context context, String path, public static void deleteGame( Context context, String path, boolean informNow )
boolean informNow )
{ {
// does this need to be synchronized? // does this need to be synchronized?
tellRelayDied( context, path, informNow ); DBUtils.GameLock lock = new DBUtils.GameLock( path, true );
DBUtils.deleteGame( context, path ); if ( lock.tryLock() ) {
tellRelayDied( context, lock, informNow );
DBUtils.deleteGame( context, lock );
lock.unlock();
}
} }
public static void loadMakeGame( Context context, int gamePtr, public static void loadMakeGame( Context context, int gamePtr,
CurGameInfo gi, String path ) CurGameInfo gi, DBUtils.GameLock lock )
{ {
loadMakeGame( context, gamePtr, gi, null, path ); loadMakeGame( context, gamePtr, gi, null, lock );
} }
public static void loadMakeGame( Context context, int gamePtr, public static void loadMakeGame( Context context, int gamePtr,
CurGameInfo gi, UtilCtxt util, CurGameInfo gi, UtilCtxt util,
String path ) DBUtils.GameLock lock )
{ {
byte[] stream = savedGame( context, path ); byte[] stream = savedGame( context, lock );
XwJNI.gi_from_stream( gi, stream ); XwJNI.gi_from_stream( gi, stream );
byte[] dictBytes = GameUtils.openDict( context, gi.dictName ); byte[] dictBytes = GameUtils.openDict( context, gi.dictName );
@ -175,30 +194,36 @@ public class GameUtils {
} }
public static void saveGame( Context context, int gamePtr, public static void saveGame( Context context, int gamePtr,
CurGameInfo gi, String path, CurGameInfo gi, DBUtils.GameLock lock,
boolean setCreate ) boolean setCreate )
{ {
byte[] stream = XwJNI.game_saveToStream( gamePtr, gi ); byte[] stream = XwJNI.game_saveToStream( gamePtr, gi );
saveGame( context, stream, path, setCreate ); saveGame( context, stream, lock, setCreate );
} }
public static void saveGame( Context context, int gamePtr, public static void saveGame( Context context, int gamePtr,
CurGameInfo gi ) CurGameInfo gi )
{ {
saveGame( context, gamePtr, gi, newName( context ), false ); String path = newName( context );
DBUtils.GameLock lock =
new DBUtils.GameLock( path, true ).lock();
saveGame( context, gamePtr, gi, lock, false );
lock.unlock();
} }
public static void saveGame( Context context, byte[] bytes, public static void saveGame( Context context, byte[] bytes,
String path, boolean setCreate ) DBUtils.GameLock lock, boolean setCreate )
{ {
DBUtils.saveGame( context, path, bytes, setCreate ); DBUtils.saveGame( context, lock, bytes, setCreate );
} }
public static String saveGame( Context context, byte[] bytes ) public static DBUtils.GameLock saveGame( Context context, byte[] bytes )
{ {
String name = newName( context ); String name = newName( context );
saveGame( context, bytes, name, false ); DBUtils.GameLock lock =
return name; new DBUtils.GameLock( name, true ).lock();
saveGame( context, bytes, lock, false );
return lock;
} }
public static boolean gameDictHere( Context context, String path ) public static boolean gameDictHere( Context context, String path )
@ -443,30 +468,34 @@ public class GameUtils {
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = new CurGameInfo( context );
FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, path ); FeedUtilsImpl feedImpl = new FeedUtilsImpl( context, path );
loadMakeGame( context, gamePtr, gi, feedImpl, path ); DBUtils.GameLock lock = new DBUtils.GameLock( path, true );
if ( lock.tryLock() ) {
loadMakeGame( context, gamePtr, gi, feedImpl, lock );
for ( byte[] msg : msgs ) { for ( byte[] msg : msgs ) {
draw = XwJNI.game_receiveMessage( gamePtr, msg ) || draw; draw = XwJNI.game_receiveMessage( gamePtr, msg ) || draw;
} }
// update gi to reflect changes due to messages // update gi to reflect changes due to messages
XwJNI.game_getGi( gamePtr, gi ); XwJNI.game_getGi( gamePtr, gi );
saveGame( context, gamePtr, gi, path, false ); saveGame( context, gamePtr, gi, lock, false );
summarizeAndClose( context, path, gamePtr, gi, feedImpl ); summarizeAndClose( context, lock, gamePtr, gi, feedImpl );
int flags = GameSummary.MSG_FLAGS_NONE; int flags = GameSummary.MSG_FLAGS_NONE;
if ( feedImpl.m_gotChat ) { if ( feedImpl.m_gotChat ) {
flags |= GameSummary.MSG_FLAGS_CHAT; flags |= GameSummary.MSG_FLAGS_CHAT;
} }
if ( feedImpl.m_gotMsg ) { if ( feedImpl.m_gotMsg ) {
flags |= GameSummary.MSG_FLAGS_TURN; flags |= GameSummary.MSG_FLAGS_TURN;
} }
if ( feedImpl.m_gameOver ) { if ( feedImpl.m_gameOver ) {
flags |= GameSummary.MSG_FLAGS_GAMEOVER; flags |= GameSummary.MSG_FLAGS_GAMEOVER;
} }
if ( GameSummary.MSG_FLAGS_NONE != flags ) { if ( GameSummary.MSG_FLAGS_NONE != flags ) {
draw = true; draw = true;
DBUtils.setMsgFlags( path, flags ); DBUtils.setMsgFlags( path, flags );
}
lock.unlock();
} }
} }
Utils.logf( "feedMessages=>%s", draw?"true":"false" ); Utils.logf( "feedMessages=>%s", draw?"true":"false" );
@ -479,7 +508,8 @@ public class GameUtils {
public static void replaceDict( Context context, String path, public static void replaceDict( Context context, String path,
String dict ) String dict )
{ {
byte[] stream = savedGame( context, path ); DBUtils.GameLock lock = new DBUtils.GameLock( path, true ).lock();
byte[] stream = savedGame( context, lock );
CurGameInfo gi = new CurGameInfo( context ); CurGameInfo gi = new CurGameInfo( context );
byte[] dictBytes = GameUtils.openDict( context, dict ); byte[] dictBytes = GameUtils.openDict( context, dict );
@ -490,13 +520,15 @@ public class GameUtils {
CommonPrefs.get( context ) ); CommonPrefs.get( context ) );
gi.dictName = dict; gi.dictName = dict;
saveGame( context, gamePtr, gi, path, false ); saveGame( context, gamePtr, gi, lock, false );
summarizeAndClose( context, path, gamePtr, gi ); summarizeAndClose( context, lock, gamePtr, gi );
lock.unlock();
} }
public static void applyChanges( Context context, CurGameInfo gi, public static void applyChanges( Context context, CurGameInfo gi,
CommsAddrRec car, String path, CommsAddrRec car, DBUtils.GameLock lock,
boolean forceNew ) boolean forceNew )
{ {
// This should be a separate function, commitChanges() or // This should be a separate function, commitChanges() or
@ -509,9 +541,9 @@ public class GameUtils {
CommonPrefs cp = CommonPrefs.get( context ); CommonPrefs cp = CommonPrefs.get( context );
if ( forceNew ) { if ( forceNew ) {
tellRelayDied( context, path, true ); tellRelayDied( context, lock, true );
} else { } else {
byte[] stream = GameUtils.savedGame( context, path ); byte[] stream = GameUtils.savedGame( context, lock );
// Will fail if there's nothing in the stream but a gi. // Will fail if there's nothing in the stream but a gi.
madeGame = XwJNI.game_makeFromStream( gamePtr, stream, madeGame = XwJNI.game_makeFromStream( gamePtr, stream,
JNIUtilsImpl.get(), JNIUtilsImpl.get(),
@ -529,11 +561,11 @@ public class GameUtils {
XwJNI.comms_setAddr( gamePtr, car ); XwJNI.comms_setAddr( gamePtr, car );
} }
saveGame( context, gamePtr, gi, path, false ); saveGame( context, gamePtr, gi, lock, false );
GameSummary summary = new GameSummary( gi ); GameSummary summary = new GameSummary( gi );
XwJNI.game_summarize( gamePtr, summary ); XwJNI.game_summarize( gamePtr, summary );
DBUtils.saveSummary( context, path, summary ); DBUtils.saveSummary( context, lock, summary );
XwJNI.game_dispose( gamePtr ); XwJNI.game_dispose( gamePtr );
} // applyChanges } // applyChanges
@ -573,10 +605,10 @@ public class GameUtils {
} }
} }
private static void tellRelayDied( Context context, String path, private static void tellRelayDied( Context context, DBUtils.GameLock lock,
boolean informNow ) boolean informNow )
{ {
GameSummary summary = DBUtils.getSummary( context, path ); GameSummary summary = DBUtils.getSummary( context, lock );
if ( null != summary.relayID ) { if ( null != summary.relayID ) {
DBUtils.addDeceased( context, summary.relayID, summary.seed ); DBUtils.addDeceased( context, summary.relayID, summary.seed );
if ( informNow ) { if ( informNow ) {

View file

@ -431,8 +431,9 @@ public class GamesList extends XWListActivity
showOKOnlyDialog( R.string.no_copy_network ); showOKOnlyDialog( R.string.no_copy_network );
} else { } else {
byte[] stream = GameUtils.savedGame( this, path ); byte[] stream = GameUtils.savedGame( this, path );
newName = GameUtils.saveGame( this, stream ); DBUtils.GameLock lock = GameUtils.saveGame( this, stream );
DBUtils.saveSummary( this, newName, summary ); DBUtils.saveSummary( this, lock, summary );
lock.unlock();
} }
break; break;
@ -486,7 +487,9 @@ public class GamesList extends XWListActivity
String path = null; String path = null;
byte[] bytes = XwJNI.gi_to_stream( gi ); byte[] bytes = XwJNI.gi_to_stream( gi );
if ( null != bytes ) { if ( null != bytes ) {
path = GameUtils.saveGame( this, bytes ); DBUtils.GameLock lock = GameUtils.saveGame( this, bytes );
path = lock.getPath();
lock.unlock();
} }
return path; return path;
} }

View file

@ -42,6 +42,7 @@ public class RelayGameActivity extends XWActivity
private String m_path; private String m_path;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private DBUtils.GameLock m_gameLock;
private CommsAddrRec m_car; private CommsAddrRec m_car;
private Button m_playButton; private Button m_playButton;
private Button m_configButton; private Button m_configButton;
@ -59,9 +60,22 @@ public class RelayGameActivity extends XWActivity
m_path = m_path.substring( 1 ); m_path = m_path.substring( 1 );
} }
m_playButton = (Button)findViewById( R.id.play_button );
m_playButton.setOnClickListener( this );
m_configButton = (Button)findViewById( R.id.config_button );
m_configButton.setOnClickListener( this );
} // onCreate
@Override
protected void onStart()
{
super.onStart();
int gamePtr = XwJNI.initJNI(); int gamePtr = XwJNI.initJNI();
m_gi = new CurGameInfo( this ); m_gi = new CurGameInfo( this );
GameUtils.loadMakeGame( this, gamePtr, m_gi, m_path ); m_gameLock = new DBUtils.GameLock( m_path, true ).lock();
GameUtils.loadMakeGame( this, gamePtr, m_gi, m_gameLock );
m_car = new CommsAddrRec( this ); m_car = new CommsAddrRec( this );
if ( XwJNI.game_hasComms( gamePtr ) ) { if ( XwJNI.game_hasComms( gamePtr ) ) {
XwJNI.comms_getAddr( gamePtr, m_car ); XwJNI.comms_getAddr( gamePtr, m_car );
@ -77,13 +91,17 @@ public class RelayGameActivity extends XWActivity
String fmt = getString( R.string.relay_game_explainf ); String fmt = getString( R.string.relay_game_explainf );
TextView text = (TextView)findViewById( R.id.explain ); TextView text = (TextView)findViewById( R.id.explain );
text.setText( String.format( fmt, lang ) ); text.setText( String.format( fmt, lang ) );
}
m_playButton = (Button)findViewById( R.id.play_button ); @Override
m_playButton.setOnClickListener( this ); protected void onPause()
{
m_configButton = (Button)findViewById( R.id.config_button ); if ( null != m_gameLock ) {
m_configButton.setOnClickListener( this ); m_gameLock.unlock();
} // onCreate m_gameLock = null;
}
super.onPause();
}
@Override @Override
public void onClick( View view ) public void onClick( View view )
@ -115,7 +133,9 @@ public class RelayGameActivity extends XWActivity
m_gi.setFirstLocalName( name ); m_gi.setFirstLocalName( name );
} }
m_car.ip_relay_invite = room; m_car.ip_relay_invite = room;
GameUtils.applyChanges( this, m_gi, m_car, m_path, false ); GameUtils.applyChanges( this, m_gi, m_car, m_gameLock, false );
m_gameLock.unlock();
m_gameLock = null;
} }
} // class RelayGameActivity } // class RelayGameActivity

View file

@ -89,7 +89,7 @@ public class JNIThread extends Thread {
private boolean m_stopped = false; private boolean m_stopped = false;
private int m_jniGamePtr; private int m_jniGamePtr;
private String m_path; private DBUtils.GameLock m_lock;
private Context m_context; private Context m_context;
private CurGameInfo m_gi; private CurGameInfo m_gi;
private Handler m_handler; private Handler m_handler;
@ -111,12 +111,12 @@ public class JNIThread extends Thread {
} }
public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer, public JNIThread( int gamePtr, CurGameInfo gi, SyncedDraw drawer,
String path, Context context, Handler handler ) DBUtils.GameLock lock, Context context, Handler handler )
{ {
m_jniGamePtr = gamePtr; m_jniGamePtr = gamePtr;
m_gi = gi; m_gi = gi;
m_drawer = drawer; m_drawer = drawer;
m_path = path; m_lock = lock;
m_context = context; m_context = context;
m_handler = handler; m_handler = handler;
@ -260,8 +260,8 @@ public class JNIThread extends Thread {
GameSummary summary = new GameSummary( m_gi ); GameSummary summary = new GameSummary( m_gi );
XwJNI.game_summarize( m_jniGamePtr, summary ); XwJNI.game_summarize( m_jniGamePtr, summary );
byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, null ); byte[] state = XwJNI.game_saveToStream( m_jniGamePtr, null );
GameUtils.saveGame( m_context, state, m_path, false ); GameUtils.saveGame( m_context, state, m_lock, false );
DBUtils.saveSummary( m_context, m_path, summary ); DBUtils.saveSummary( m_context, m_lock, summary );
break; break;
case CMD_DRAW: case CMD_DRAW: