Moving KegsThread out of KegsView.

This commit is contained in:
James Sanford 2012-10-16 01:30:02 -07:00
parent 589a28af6a
commit 1d82d39eb2
5 changed files with 214 additions and 203 deletions

View File

@ -283,8 +283,9 @@ x_update_color(int col_num, int red, int green, int blue, word32 rgb)
// Instead of 'KegsView$KegsThread', the $ is encoded as _00024.
// (not any more, but it was KegsView_00024KegsThread_mainLoop)
JNIEXPORT void JNICALL
Java_com_froop_app_kegs_KegsView_00024KegsThread_mainLoop( JNIEnv* env, jobject thiz, jobject bitmap, jobject eventqueue )
Java_com_froop_app_kegs_KegsThread_mainLoop( JNIEnv* env, jobject thiz, jobject bitmap, jobject eventqueue )
{
int ret;

View File

@ -30,6 +30,9 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
private static final String FRAGMENT_DOWNLOAD = "download";
private static final String FRAGMENT_ERROR = "error";
private static final String FRAGMENT_SPEED = "speed";
private static final String FRAGMENT_DISKIMAGE = "diskimage";
private KegsThread mKegsThread;
protected KegsView mKegsView;
private KegsTouch mKegsTouch;
@ -106,11 +109,11 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
new SpeedFragment().show(getFragmentManager(), FRAGMENT_SPEED);
return true;
} else if (item_id == R.id.warm_reset) {
mKegsView.doWarmReset();
getThread().doWarmReset();
return true;
} else if (item_id == R.id.power_cycle) {
mKegsView.doPowerOff();
mKegsView.allowPowerOn();
getThread().doPowerOff();
getThread().allowPowerOn();
return true;
}
return false;
@ -168,7 +171,7 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
}
} else {
Config.checkConfig(mRomfile);
mKegsView.setReady(true);
getThread().setReady(true);
}
}
}
@ -202,6 +205,10 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
}
}
public KegsThread getThread() {
return mKegsThread;
}
private void updateActionBar(boolean showActionBar) {
final ActionBar actionBar = getActionBar();
if (showActionBar) {
@ -246,7 +253,7 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
}
// Force another redraw of the bitmap into the canvas. Bug workaround.
mKegsView.getThread().updateScreen();
getThread().updateScreen();
}
private void workaroundScreenSize() {
@ -322,7 +329,8 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
// drop down menu for special keys...?
return true;
} else if (item_id == R.id.action_diskimage) {
// start fragment for disk images
// FIXME
// new DiskImageFragment().show(getFragmentManager(), FRAGMENT_DISKIMAGE);
return true;
}
return false;
@ -335,12 +343,15 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
mKegsView = (KegsView)findViewById(R.id.kegsview);
mKegsThread = new KegsThread(mKegsView.getBitmap());
mKegsThread.registerUpdateScreenInterface(mKegsView);
workaroundScreenSize();
mKegsTouch = new KegsTouch(mKegsView.getEventQueue());
mKegsTouch = new KegsTouch(getThread().getEventQueue());
final GestureDetector inputDetect = new GestureDetector(this, mKegsTouch);
mJoystick = new TouchJoystick(mKegsView.getEventQueue());
mJoystick = new TouchJoystick(getThread().getEventQueue());
final View mainView = findViewById(R.id.mainview);
mainView.setClickable(true);
@ -356,7 +367,7 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
}
});
mKegsKeyboard = new KegsKeyboard(mKegsView.getEventQueue());
mKegsKeyboard = new KegsKeyboard(getThread().getEventQueue());
mKegsKeyboard.setOnStickyReset(this);
mSettingsMenu = new PopupMenu(this, findViewById(R.id.key_settings));
@ -380,20 +391,20 @@ public class KegsMain extends Activity implements KegsKeyboard.StickyReset {
chooseRom.show(getFragmentManager(), FRAGMENT_ROM);
} else {
Config.checkConfig(romfile);
mKegsView.setReady(true);
getThread().setReady(true);
}
}
@Override
protected void onPause() {
super.onPause();
mKegsView.getThread().onPause();
getThread().onPause();
}
@Override
protected void onResume() {
super.onResume();
mKegsView.getThread().onResume();
getThread().onResume();
}
@Override

View File

@ -0,0 +1,157 @@
package com.froop.app.kegs;
import android.graphics.Bitmap;
import android.util.AttributeSet;
import android.util.Log;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentLinkedQueue;
// This is the primary interface into the native KEGS thread, and also
// where the native KEGS thread calls back into Java.
class KegsThread extends Thread {
private ConcurrentLinkedQueue<Event.KegsEvent> mEventQueue = new ConcurrentLinkedQueue<Event.KegsEvent>();
private Bitmap mBitmap;
private final ReentrantLock mPauseLock = new ReentrantLock();
private final ReentrantLock mPowerWait = new ReentrantLock();
// Look also at mPauseLock.
private boolean mPaused = false;
private boolean mReady = false; // 'true' will begin the native thread.
interface UpdateScreen {
void updateScreen();
}
private UpdateScreen mUpdateScreen;
public KegsThread(Bitmap bitmap) {
mBitmap = bitmap;
}
public ConcurrentLinkedQueue getEventQueue() {
return mEventQueue;
}
public void registerUpdateScreenInterface(UpdateScreen update) {
mUpdateScreen = update;
}
protected void updateScreen() {
if (mUpdateScreen != null) {
mUpdateScreen.updateScreen();
}
}
private void checkForPause() {
if (mPaused) {
mPauseLock.lock();
// deadlock here until onResume. Maybe not efficient.
mPauseLock.unlock();
}
}
// See jni/android_driver.c:mainLoop()
private native void mainLoop(Bitmap b, ConcurrentLinkedQueue q);
@Override
public void run() {
while(true) {
mPowerWait.lock();
mPowerWait.unlock();
mainLoop(mBitmap, mEventQueue);
}
// For TESTING:
// while (true) {
// try {
// Thread.sleep(500);
// checkForPause();
// } catch (InterruptedException e) {}
// }
}
public void onPause() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
if (!mPaused) {
mPaused = true;
mPauseLock.lock();
}
}
public void onResume() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
updateScreen();
if (mPaused) {
mPaused = false;
mPauseLock.unlock();
} else if (!isAlive()) {
start();
}
}
// Has the thread itself actually paused waiting to acquire the lock?
public boolean nowPaused() {
return mPauseLock.hasQueuedThreads();
}
public void doPowerOff() {
// Tell the native thread loop to wait before powering on again.
mPowerWait.lock();
// Special event, see android_driver.c:x_key_special()
mEventQueue.add(new Event.KeyKegsEvent(120 + 0x80, true));
}
public void allowPowerOn() {
// Intended to be called from the UI thread.
// As the native thread is allowed to loop by default,
// this is only useful after using doPowerOff().
if (mPowerWait.isHeldByCurrentThread()) {
mPowerWait.unlock();
}
}
// Is native thread loop sitting around waiting for us to allow power on?
public boolean nowWaitingForPowerOn() {
return mPowerWait.hasQueuedThreads();
}
public void setEmulationSpeed(int speed) {
// Speed matches g_limit_speed inside KEGS.
// Instead of a separate "control" event, key ids with bit 8 high are
// special events. See android_driver.c:x_key_special()
getEventQueue().add(new Event.KeyKegsEvent(speed + 0x80, true));
}
public void setStartupSlot(int slot) {
if (slot >= 0 && slot <= 9) {
// Special event, see android_driver.c:x_key_special()
getEventQueue().add(new Event.KeyKegsEvent(slot + 10 + 0x80, true));
}
}
public void doWarmReset() {
// Press keys down.
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_OPEN_APPLE, false));
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_CONTROL, false));
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_RESET, false));
// Release reset key first, then the others.
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_RESET, true));
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_CONTROL, true));
getEventQueue().add(new Event.KeyKegsEvent(KegsKeyboard.KEY_OPEN_APPLE, true));
}
public void setReady(boolean ready) {
final boolean wasReady = mReady;
mReady = ready;
if (ready && !wasReady) {
// Will start the thread if not already started.
onResume();
}
}
}

View File

@ -1,158 +1,22 @@
package com.froop.app.kegs;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.ConcurrentLinkedQueue;
// This is the primary interface into the native KEGS thread, and also
// where the native KEGS thread calls back into Java.
class KegsView extends SurfaceView implements SurfaceHolder.Callback {
class KegsView extends SurfaceView implements KegsThread.UpdateScreen, SurfaceHolder.Callback {
// Reported area of this view, see updateScreenSize()
private int mWidth = 0;
private int mHeight = 0;
// Look also at mPauseLock.
private boolean mPaused = false;
private boolean mReady = false; // 'true' will begin the native thread.
protected ConcurrentLinkedQueue<Event.KegsEvent> mEventQueue = new ConcurrentLinkedQueue<Event.KegsEvent>();
private Bitmap mBitmap;
private final BitmapThread mBitmapThread = new BitmapThread();
class KegsThread extends Thread {
private Handler mHandler;
private Bitmap mBitmap;
private Canvas mCanvas;
private SurfaceHolder mSurfaceHolder;
private Context mContext;
private final ReentrantLock mPauseLock = new ReentrantLock();
private final ReentrantLock mPowerWait = new ReentrantLock();
public KegsThread(SurfaceHolder surfaceHolder, Context context) {
mSurfaceHolder = surfaceHolder;
mContext = context;
mBitmap = Bitmap.createBitmap(BitmapSize.Const.A2Width,
BitmapSize.Const.A2Height,
Bitmap.Config.RGB_565);
mBitmap.setHasAlpha(false);
mBitmapThread.setBitmap(surfaceHolder, mBitmap);
mHandler = mBitmapThread.getHandler();
}
private FpsCounter fpsCount = new FpsCounter("kegs", "native");
// Typically updateScreen is called by the native thread,
// but it may also be run on the UI thread.
//
// We use a Handler to tell the bitmap thread to actually draw
// on the canvas. No locking is involved, so it is possible for
// the canvas to get a bitmap that is in the process of being updated
// by the native thread. This should be relatively uncommon.
//
// If you wish to draw to the canvas in the native thread, it should
// be safe to bypass the Handler and call mBitmapThread.updateScreen()
// here instead.
protected void updateScreen() {
// Empty the queue first in case bitmap thread is lagging behind.
mHandler.removeMessages(0);
mHandler.sendEmptyMessage(0);
fpsCount.fps();
}
private void checkForPause() {
if (mPaused) {
mPauseLock.lock();
// deadlock here until onResume. Maybe not efficient.
mPauseLock.unlock();
}
}
// See jni/android_driver.c:mainLoop()
private native void mainLoop(Bitmap b, ConcurrentLinkedQueue q);
@Override
public void run() {
while(true) {
mPowerWait.lock();
mPowerWait.unlock();
mainLoop(mBitmap, mEventQueue);
}
// For TESTING:
// while (true) {
// try {
// Thread.sleep(500);
// checkForPause();
// } catch (InterruptedException e) {}
// }
}
public void onPause() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
if (!mPaused) {
mPaused = true;
mPauseLock.lock();
}
}
public void onResume() {
if (!mReady) {
return; // bail out, we haven't started doing anything yet
}
updateScreen();
if (mPaused) {
mPaused = false;
mPauseLock.unlock();
} else if (!thread.isAlive()) {
thread.start();
mBitmapThread.start();
}
}
// Has the thread itself actually paused waiting to acquire the lock?
public boolean nowPaused() {
return mPauseLock.hasQueuedThreads();
}
public void doPowerOff() {
// Tell the native thread loop to wait before powering on again.
mPowerWait.lock();
// Special event, see android_driver.c:x_key_special()
mEventQueue.add(new Event.KeyKegsEvent(120 + 0x80, true));
}
public void allowPowerOn() {
// Intended to be called from the UI thread.
// As the native thread is allowed to loop by default,
// this is only useful after using doPowerOff().
if (mPowerWait.isHeldByCurrentThread()) {
mPowerWait.unlock();
}
}
// Is native thread loop sitting around waiting for us to allow power on?
public boolean nowWaitingForPowerOn() {
return mPowerWait.hasQueuedThreads();
}
}
private KegsThread thread;
private Handler mHandler;
public KegsView(Context context, AttributeSet attrs) {
super(context, attrs);
@ -160,69 +24,47 @@ class KegsView extends SurfaceView implements SurfaceHolder.Callback {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
thread = new KegsThread(holder, context);
Bitmap bitmap = Bitmap.createBitmap(BitmapSize.Const.A2Width,
BitmapSize.Const.A2Height,
Bitmap.Config.RGB_565);
mBitmap = bitmap;
mBitmapThread.setBitmap(holder, bitmap);
mHandler = mBitmapThread.getHandler();
mBitmapThread.start();
setFocusable(true);
setFocusableInTouchMode(true);
requestFocus();
}
public void setEmulationSpeed(int speed) {
// Speed matches g_limit_speed inside KEGS.
// Instead of a separate "control" event, key ids with bit 8 high are
// special events. See android_driver.c:x_key_special()
mEventQueue.add(new Event.KeyKegsEvent(speed + 0x80, true));
}
public void setStartupSlot(int slot) {
if (slot >= 0 && slot <= 9) {
// Special event, see android_driver.c:x_key_special()
mEventQueue.add(new Event.KeyKegsEvent(slot + 10 + 0x80, true));
}
}
public void doWarmReset() {
// Press keys down.
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_OPEN_APPLE, false));
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_CONTROL, false));
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_RESET, false));
// Release reset key first, then the others.
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_RESET, true));
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_CONTROL, true));
mEventQueue.add(new Event.KeyKegsEvent(KegsKeyboard.KEY_OPEN_APPLE, true));
}
public void doPowerOff() {
thread.doPowerOff();
}
public void allowPowerOn() {
thread.allowPowerOn();
}
public KegsThread getThread() {
return thread;
}
public ConcurrentLinkedQueue getEventQueue() {
return mEventQueue;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mWidth, mHeight);
}
public native String stringFromJNI();
private FpsCounter fpsCount = new FpsCounter("kegs", "native");
public void setReady(boolean ready) {
final boolean wasReady = mReady;
mReady = ready;
public Bitmap getBitmap() {
return mBitmap;
}
if (ready && !wasReady) {
// Will start the thread if not already started.
thread.onResume();
}
// Typically updateScreen is called by the native thread,
// but it may also be run on the UI thread.
//
// We use a Handler to tell the bitmap thread to actually draw
// on the canvas. No locking is involved, so it is possible for
// the canvas to get a bitmap that is in the process of being updated
// by the native thread. This should be relatively uncommon.
//
// If you wish to draw to the canvas in the native thread, it should
// be safe to bypass the Handler and call mBitmapThread.updateScreen()
// here instead.
public void updateScreen() {
// Empty the queue first in case bitmap thread is lagging behind.
mHandler.removeMessages(0);
mHandler.sendEmptyMessage(0);
fpsCount.fps();
}
public void updateScreenSize(BitmapSize bitmapSize) {

View File

@ -22,7 +22,7 @@ public class SpeedFragment extends DialogFragment {
if (item > 3) {
item = 0;
}
((KegsMain)getActivity()).mKegsView.setEmulationSpeed(item);
((KegsMain)getActivity()).getThread().setEmulationSpeed(item);
}
});
return builder.create();