package org.anddev.andengine.engine;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11;
import org.anddev.andengine.audio.music.MusicFactory;
import org.anddev.andengine.audio.music.MusicManager;
import org.anddev.andengine.audio.sound.SoundFactory;
import org.anddev.andengine.audio.sound.SoundManager;
import org.anddev.andengine.engine.camera.Camera;
import org.anddev.andengine.engine.handler.IUpdateHandler;
import org.anddev.andengine.engine.handler.UpdateHandlerList;
import org.anddev.andengine.engine.handler.runnable.RunnableHandler;
import org.anddev.andengine.engine.options.EngineOptions;
import org.anddev.andengine.entity.scene.Scene;
import org.anddev.andengine.input.touch.TouchEvent;
import org.anddev.andengine.input.touch.controller.ITouchController;
import org.anddev.andengine.input.touch.controller.ITouchController.ITouchEventCallback;
import org.anddev.andengine.input.touch.controller.SingleTouchControler;
import org.anddev.andengine.opengl.buffer.BufferObjectManager;
import org.anddev.andengine.opengl.font.FontFactory;
import org.anddev.andengine.opengl.font.FontManager;
import org.anddev.andengine.opengl.texture.TextureManager;
import org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;
import org.anddev.andengine.opengl.util.GLHelper;
import org.anddev.andengine.sensor.SensorDelay;
import org.anddev.andengine.sensor.accelerometer.AccelerometerData;
import org.anddev.andengine.sensor.accelerometer.AccelerometerSensorOptions;
import org.anddev.andengine.sensor.accelerometer.IAccelerometerListener;
import org.anddev.andengine.sensor.location.ILocationListener;
import org.anddev.andengine.sensor.location.LocationProviderStatus;
import org.anddev.andengine.sensor.location.LocationSensorOptions;
import org.anddev.andengine.sensor.orientation.IOrientationListener;
import org.anddev.andengine.sensor.orientation.OrientationData;
import org.anddev.andengine.sensor.orientation.OrientationSensorOptions;
import org.anddev.andengine.util.Debug;
import org.anddev.andengine.util.constants.TimeConstants;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.location.LocationProvider;
import android.os.Bundle;
import android.os.Vibrator;
import android.view.Display;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.WindowManager;
/**
* (c) 2010 Nicolas Gramlich
* (c) 2011 Zynga Inc.
*
* @author Nicolas Gramlich
* @since 12:21:31 - 08.03.2010
*/
public class Engine implements SensorEventListener, OnTouchListener, ITouchEventCallback, TimeConstants, LocationListener {
// ===========================================================
// Constants
// ===========================================================
private static final SensorDelay SENSORDELAY_DEFAULT = SensorDelay.GAME;
private static final int UPDATEHANDLERS_CAPACITY_DEFAULT = 32;
// ===========================================================
// Fields
// ===========================================================
private boolean mRunning = false;
private long mLastTick = -1;
private float mSecondsElapsedTotal = 0;
private final State mThreadLocker = new State();
private final UpdateThread mUpdateThread = new UpdateThread();
private final RunnableHandler mUpdateThreadRunnableHandler = new RunnableHandler();
private final EngineOptions mEngineOptions;
protected final Camera mCamera;
private ITouchController mTouchController;
private SoundManager mSoundManager;
private MusicManager mMusicManager;
private final TextureManager mTextureManager = new TextureManager();
private final BufferObjectManager mBufferObjectManager = new BufferObjectManager();
private final FontManager mFontManager = new FontManager();
protected Scene mScene;
private Vibrator mVibrator;
private ILocationListener mLocationListener;
private Location mLocation;
private IAccelerometerListener mAccelerometerListener;
private AccelerometerData mAccelerometerData;
private IOrientationListener mOrientationListener;
private OrientationData mOrientationData;
private final UpdateHandlerList mUpdateHandlers = new UpdateHandlerList(UPDATEHANDLERS_CAPACITY_DEFAULT);
protected int mSurfaceWidth = 1; // 1 to prevent accidental DIV/0
protected int mSurfaceHeight = 1; // 1 to prevent accidental DIV/0
private boolean mIsMethodTracing;
// ===========================================================
// Constructors
// ===========================================================
public Engine(final EngineOptions pEngineOptions) {
BitmapTextureAtlasTextureRegionFactory.reset();
SoundFactory.reset();
MusicFactory.reset();
FontFactory.reset();
BufferObjectManager.setActiveInstance(this.mBufferObjectManager);
this.mEngineOptions = pEngineOptions;
this.setTouchController(new SingleTouchControler());
this.mCamera = pEngineOptions.getCamera();
if(this.mEngineOptions.needsSound()) {
this.mSoundManager = new SoundManager();
}
if(this.mEngineOptions.needsMusic()) {
this.mMusicManager = new MusicManager();
}
this.mUpdateThread.start();
}
// ===========================================================
// Getter & Setter
// ===========================================================
public boolean isRunning() {
return this.mRunning;
}
public synchronized void start() {
if(!this.mRunning) {
this.mLastTick = System.nanoTime();
this.mRunning = true;
}
}
public synchronized void stop() {
if(this.mRunning) {
this.mRunning = false;
}
}
public Scene getScene() {
return this.mScene;
}
public void setScene(final Scene pScene) {
this.mScene = pScene;
}
public EngineOptions getEngineOptions() {
return this.mEngineOptions;
}
public Camera getCamera() {
return this.mCamera;
}
public float getSecondsElapsedTotal() {
return this.mSecondsElapsedTotal;
}
public void setSurfaceSize(final int pSurfaceWidth, final int pSurfaceHeight) {
// Debug.w("SurfaceView size changed to (width x height): " + pSurfaceWidth + " x " + pSurfaceHeight, new Exception());
this.mSurfaceWidth = pSurfaceWidth;
this.mSurfaceHeight = pSurfaceHeight;
this.onUpdateCameraSurface();
}
protected void onUpdateCameraSurface() {
this.mCamera.setSurfaceSize(0, 0, this.mSurfaceWidth, this.mSurfaceHeight);
}
public int getSurfaceWidth() {
return this.mSurfaceWidth;
}
public int getSurfaceHeight() {
return this.mSurfaceHeight;
}
public ITouchController getTouchController() {
return this.mTouchController;
}
public void setTouchController(final ITouchController pTouchController) {
this.mTouchController = pTouchController;
this.mTouchController.applyTouchOptions(this.mEngineOptions.getTouchOptions());
this.mTouchController.setTouchEventCallback(this);
}
public AccelerometerData getAccelerometerData() {
return this.mAccelerometerData;
}
public OrientationData getOrientationData() {
return this.mOrientationData;
}
public SoundManager getSoundManager() throws IllegalStateException {
if(this.mSoundManager != null) {
return this.mSoundManager;
} else {
throw new IllegalStateException("To enable the SoundManager, check the EngineOptions!");
}
}
public MusicManager getMusicManager() throws IllegalStateException {
if(this.mMusicManager != null) {
return this.mMusicManager;
} else {
throw new IllegalStateException("To enable the MusicManager, check the EngineOptions!");
}
}
public TextureManager getTextureManager() {
return this.mTextureManager;
}
public FontManager getFontManager() {
return this.mFontManager;
}
public void clearUpdateHandlers() {
this.mUpdateHandlers.clear();
}
public void registerUpdateHandler(final IUpdateHandler pUpdateHandler) {
this.mUpdateHandlers.add(pUpdateHandler);
}
public void unregisterUpdateHandler(final IUpdateHandler pUpdateHandler) {
this.mUpdateHandlers.remove(pUpdateHandler);
}
public boolean isMethodTracing() {
return this.mIsMethodTracing;
}
public void startMethodTracing(final String pTraceFileName) {
if(!this.mIsMethodTracing) {
this.mIsMethodTracing = true;
android.os.Debug.startMethodTracing(pTraceFileName);
}
}
public void stopMethodTracing() {
if(this.mIsMethodTracing) {
android.os.Debug.stopMethodTracing();
this.mIsMethodTracing = false;
}
}
// ===========================================================
// Methods for/from SuperClass/Interfaces
// ===========================================================
@Override
public void onAccuracyChanged(final Sensor pSensor, final int pAccuracy) {
if(this.mRunning) {
switch(pSensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
if(this.mAccelerometerData != null) {
this.mAccelerometerData.setAccuracy(pAccuracy);
this.mAccelerometerListener.onAccelerometerChanged(this.mAccelerometerData);
} else if(this.mOrientationData != null) {
this.mOrientationData.setAccelerometerAccuracy(pAccuracy);
this.mOrientationListener.onOrientationChanged(this.mOrientationData);
}
break;
case Sensor.TYPE_MAGNETIC_FIELD:
this.mOrientationData.setMagneticFieldAccuracy(pAccuracy);
this.mOrientationListener.onOrientationChanged(this.mOrientationData);
break;
}
}
}
@Override
public void onSensorChanged(final SensorEvent pEvent) {
if(this.mRunning) {
switch(pEvent.sensor.getType()) {
case Sensor.TYPE_ACCELEROMETER:
if(this.mAccelerometerData != null) {
this.mAccelerometerData.setValues(pEvent.values);
this.mAccelerometerListener.onAccelerometerChanged(this.mAccelerometerData);
} else if(this.mOrientationData != null) {
this.mOrientationData.setAccelerometerValues(pEvent.values);
this.mOrientationListener.onOrientationChanged(this.mOrientationData);
}
break;
case Sensor.TYPE_MAGNETIC_FIELD:
this.mOrientationData.setMagneticFieldValues(pEvent.values);
this.mOrientationListener.onOrientationChanged(this.mOrientationData);
break;
}
}
}
@Override
public void onLocationChanged(final Location pLocation) {
if(this.mLocation == null) {
this.mLocation = pLocation;
} else {
if(pLocation == null) {
this.mLocationListener.onLocationLost();
} else {
this.mLocation = pLocation;
this.mLocationListener.onLocationChanged(pLocation);
}
}
}
@Override
public void onProviderDisabled(final String pProvider) {
this.mLocationListener.onLocationProviderDisabled();
}
@Override
public void onProviderEnabled(final String pProvider) {
this.mLocationListener.onLocationProviderEnabled();
}
@Override
public void onStatusChanged(final String pProvider, final int pStatus, final Bundle pExtras) {
switch(pStatus) {
case LocationProvider.AVAILABLE:
this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.AVAILABLE, pExtras);
break;
case LocationProvider.OUT_OF_SERVICE:
this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.OUT_OF_SERVICE, pExtras);
break;
case LocationProvider.TEMPORARILY_UNAVAILABLE:
this.mLocationListener.onLocationProviderStatusChanged(LocationProviderStatus.TEMPORARILY_UNAVAILABLE, pExtras);
break;
}
}
@Override
public boolean onTouch(final View pView, final MotionEvent pSurfaceMotionEvent) {
if(this.mRunning) {
final boolean handled = this.mTouchController.onHandleMotionEvent(pSurfaceMotionEvent);
try {
/*
* As a human cannot interact 1000x per second, we pause the
* UI-Thread for a little.
*/
Thread.sleep(20); // TODO Maybe this can be removed, when TouchEvents are handled on the UpdateThread!
} catch (final InterruptedException e) {
Debug.e(e);
}
return handled;
} else {
return false;
}
}
@Override
public boolean onTouchEvent(final TouchEvent pSurfaceTouchEvent) {
/*
* Let the engine determine which scene and camera this event should be
* handled by.
*/
final Scene scene = this.getSceneFromSurfaceTouchEvent(pSurfaceTouchEvent);
final Camera camera = this.getCameraFromSurfaceTouchEvent(pSurfaceTouchEvent);
this.convertSurfaceToSceneTouchEvent(camera, pSurfaceTouchEvent);
if(this.onTouchHUD(camera, pSurfaceTouchEvent)) {
return true;
} else {
/* If HUD didn't handle it, Scene may handle it. */
return this.onTouchScene(scene, pSurfaceTouchEvent);
}
}
protected boolean onTouchHUD(final Camera pCamera, final TouchEvent pSceneTouchEvent) {
if(pCamera.hasHUD()) {
return pCamera.getHUD().onSceneTouchEvent(pSceneTouchEvent);
} else {
return false;
}
}
protected boolean onTouchScene(final Scene pScene, final TouchEvent pSceneTouchEvent) {
if(pScene != null) {
return pScene.onSceneTouchEvent(pSceneTouchEvent);
} else {
return false;
}
}
// ===========================================================
// Methods
// ===========================================================
public void runOnUpdateThread(final Runnable pRunnable) {
this.mUpdateThreadRunnableHandler.postRunnable(pRunnable);
}
public void interruptUpdateThread(){
this.mUpdateThread.interrupt();
}
public void onResume() {
// TODO GLHelper.reset(pGL); ?
this.mTextureManager.reloadTextures();
this.mFontManager.reloadFonts();
BufferObjectManager.setActiveInstance(this.mBufferObjectManager);
this.mBufferObjectManager.reloadBufferObjects();
}
public void onPause() {
}
protected Camera getCameraFromSurfaceTouchEvent(@SuppressWarnings("unused") final TouchEvent pTouchEvent) {
return this.getCamera();
}
protected Scene getSceneFromSurfaceTouchEvent(@SuppressWarnings("unused") final TouchEvent pTouchEvent) {
return this.mScene;
}
protected void convertSurfaceToSceneTouchEvent(final Camera pCamera, final TouchEvent pSurfaceTouchEvent) {
pCamera.convertSurfaceToSceneTouchEvent(pSurfaceTouchEvent, this.mSurfaceWidth, this.mSurfaceHeight);
}
public void onLoadComplete(final Scene pScene) {
this.setScene(pScene);
}
void onTickUpdate() throws InterruptedException {
if(this.mRunning) {
final long secondsElapsed = this.getNanosecondsElapsed();
this.onUpdate(secondsElapsed);
this.yieldDraw();
} else {
this.yieldDraw();
Thread.sleep(16);
}
}
private void yieldDraw() throws InterruptedException {
final State threadLocker = this.mThreadLocker;
threadLocker.notifyCanDraw();
threadLocker.waitUntilCanUpdate();
}
protected void onUpdate(final long pNanosecondsElapsed) throws InterruptedException {
final float pSecondsElapsed = (float)pNanosecondsElapsed / TimeConstants.NANOSECONDSPERSECOND;
this.mSecondsElapsedTotal += pSecondsElapsed;
this.mLastTick += pNanosecondsElapsed;
this.mTouchController.onUpdate(pSecondsElapsed);
this.updateUpdateHandlers(pSecondsElapsed);
this.onUpdateScene(pSecondsElapsed);
}
protected void onUpdateScene(final float pSecondsElapsed) {
if(this.mScene != null) {
this.mScene.onUpdate(pSecondsElapsed);
}
}
protected void updateUpdateHandlers(final float pSecondsElapsed) {
this.mUpdateThreadRunnableHandler.onUpdate(pSecondsElapsed);
this.mUpdateHandlers.onUpdate(pSecondsElapsed);
this.getCamera().onUpdate(pSecondsElapsed);
}
public void onDrawFrame(final GL10 pGL) throws InterruptedException {
final State threadLocker = this.mThreadLocker;
threadLocker.waitUntilCanDraw();
this.mTextureManager.updateTextures(pGL);
this.mFontManager.updateFonts(pGL);
if(GLHelper.EXTENSIONS_VERTEXBUFFEROBJECTS) {
this.mBufferObjectManager.updateBufferObjects((GL11) pGL);
}
this.onDrawScene(pGL);
threadLocker.notifyCanUpdate();
}
protected void onDrawScene(final GL10 pGL) {
final Camera camera = this.getCamera();
this.mScene.onDraw(pGL, camera);
camera.onDrawHUD(pGL);
}
private long getNanosecondsElapsed() {
final long now = System.nanoTime();
return this.calculateNanosecondsElapsed(now, this.mLastTick);
}
protected long calculateNanosecondsElapsed(final long pNow, final long pLastTick) {
return pNow - pLastTick;
}
public boolean enableVibrator(final Context pContext) {
this.mVibrator = (Vibrator) pContext.getSystemService(Context.VIBRATOR_SERVICE);
return this.mVibrator != null;
}
public void vibrate(final long pMilliseconds) throws IllegalStateException {
if(this.mVibrator != null) {
this.mVibrator.vibrate(pMilliseconds);
} else {
throw new IllegalStateException("You need to enable the Vibrator before you can use it!");
}
}
public void vibrate(final long[] pPattern, final int pRepeat) throws IllegalStateException {
if(this.mVibrator != null) {
this.mVibrator.vibrate(pPattern, pRepeat);
} else {
throw new IllegalStateException("You need to enable the Vibrator before you can use it!");
}
}
public void enableLocationSensor(final Context pContext, final ILocationListener pLocationListener, final LocationSensorOptions pLocationSensorOptions) {
this.mLocationListener = pLocationListener;
final LocationManager locationManager = (LocationManager) pContext.getSystemService(Context.LOCATION_SERVICE);
final String locationProvider = locationManager.getBestProvider(pLocationSensorOptions, pLocationSensorOptions.isEnabledOnly());
// TODO locationProvider can be null, in that case return false. Successful case should return true.
locationManager.requestLocationUpdates(locationProvider, pLocationSensorOptions.getMinimumTriggerTime(), pLocationSensorOptions.getMinimumTriggerDistance(), this);
this.onLocationChanged(locationManager.getLastKnownLocation(locationProvider));
}
public void disableLocationSensor(final Context pContext) {
final LocationManager locationManager = (LocationManager) pContext.getSystemService(Context.LOCATION_SERVICE);
locationManager.removeUpdates(this);
}
/**
* @see {@link Engine#enableAccelerometerSensor(Context, IAccelerometerListener, AccelerometerSensorOptions)}
*/
public boolean enableAccelerometerSensor(final Context pContext, final IAccelerometerListener pAccelerometerListener) {
return this.enableAccelerometerSensor(pContext, pAccelerometerListener, new AccelerometerSensorOptions(SENSORDELAY_DEFAULT));
}
/**
* @return <code>true</code> when the sensor was successfully enabled, <code>false</code> otherwise.
*/
public boolean enableAccelerometerSensor(final Context pContext, final IAccelerometerListener pAccelerometerListener, final AccelerometerSensorOptions pAccelerometerSensorOptions) {
final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE);
if(this.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER)) {
this.mAccelerometerListener = pAccelerometerListener;
if(this.mAccelerometerData == null) {
final Display display = ((WindowManager) pContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final int displayRotation = display.getOrientation();
this.mAccelerometerData = new AccelerometerData(displayRotation);
}
this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER, pAccelerometerSensorOptions.getSensorDelay());
return true;
} else {
return false;
}
}
/**
* @return <code>true</code> when the sensor was successfully disabled, <code>false</code> otherwise.
*/
public boolean disableAccelerometerSensor(final Context pContext) {
final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE);
if(this.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER)) {
this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER);
return true;
} else {
return false;
}
}
/**
* @see {@link Engine#enableOrientationSensor(Context, IOrientationListener, OrientationSensorOptions)}
*/
public boolean enableOrientationSensor(final Context pContext, final IOrientationListener pOrientationListener) {
return this.enableOrientationSensor(pContext, pOrientationListener, new OrientationSensorOptions(SENSORDELAY_DEFAULT));
}
/**
* @return <code>true</code> when the sensor was successfully enabled, <code>false</code> otherwise.
*/
public boolean enableOrientationSensor(final Context pContext, final IOrientationListener pOrientationListener, final OrientationSensorOptions pOrientationSensorOptions) {
final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE);
if(this.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER) && this.isSensorSupported(sensorManager, Sensor.TYPE_MAGNETIC_FIELD)) {
this.mOrientationListener = pOrientationListener;
if(this.mOrientationData == null) {
final Display display = ((WindowManager) pContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final int displayRotation = display.getOrientation();
this.mOrientationData = new OrientationData(displayRotation);
}
this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER, pOrientationSensorOptions.getSensorDelay());
this.registerSelfAsSensorListener(sensorManager, Sensor.TYPE_MAGNETIC_FIELD, pOrientationSensorOptions.getSensorDelay());
return true;
} else {
return false;
}
}
/**
* @return <code>true</code> when the sensor was successfully disabled, <code>false</code> otherwise.
*/
public boolean disableOrientationSensor(final Context pContext) {
final SensorManager sensorManager = (SensorManager) pContext.getSystemService(Context.SENSOR_SERVICE);
if(this.isSensorSupported(sensorManager, Sensor.TYPE_ACCELEROMETER) && this.isSensorSupported(sensorManager, Sensor.TYPE_MAGNETIC_FIELD)) {
this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_ACCELEROMETER);
this.unregisterSelfAsSensorListener(sensorManager, Sensor.TYPE_MAGNETIC_FIELD);
return true;
} else {
return false;
}
}
private boolean isSensorSupported(final SensorManager pSensorManager, final int pType) {
return pSensorManager.getSensorList(pType).size() > 0;
}
private void registerSelfAsSensorListener(final SensorManager pSensorManager, final int pType, final SensorDelay pSensorDelay) {
final Sensor sensor = pSensorManager.getSensorList(pType).get(0);
pSensorManager.registerListener(this, sensor, pSensorDelay.getDelay());
}
private void unregisterSelfAsSensorListener(final SensorManager pSensorManager, final int pType) {
final Sensor sensor = pSensorManager.getSensorList(pType).get(0);
pSensorManager.unregisterListener(this, sensor);
}
// ===========================================================
// Inner and Anonymous Classes
// ===========================================================
private class UpdateThread extends Thread {
public UpdateThread() {
super("UpdateThread");
}
@Override
public void run() {
android.os.Process.setThreadPriority(Engine.this.mEngineOptions.getUpdateThreadPriority());
try {
while(true) {
Engine.this.onTickUpdate();
}
} catch (final InterruptedException e) {
Debug.d("UpdateThread interrupted. Don't worry - this Exception is most likely expected!", e);
this.interrupt();
}
}
}
private static class State {
boolean mDrawing = false;
public synchronized void notifyCanDraw() {
// Debug.d(">>> notifyCanDraw");
this.mDrawing = true;
this.notifyAll();
// Debug.d("<<< notifyCanDraw");
}
public synchronized void notifyCanUpdate() {
// Debug.d(">>> notifyCanUpdate");
this.mDrawing = false;
this.notifyAll();
// Debug.d("<<< notifyCanUpdate");
}
public synchronized void waitUntilCanDraw() throws InterruptedException {
// Debug.d(">>> waitUntilCanDraw");
while(!this.mDrawing) {
this.wait();
}
// Debug.d("<<< waitUntilCanDraw");
}
public synchronized void waitUntilCanUpdate() throws InterruptedException {
// Debug.d(">>> waitUntilCanUpdate");
while(this.mDrawing) {
this.wait();
}
// Debug.d("<<< waitUntilCanUpdate");
}
}
}