Skip to content

Commit

Permalink
added life cycle methods observing, code improvements
Browse files Browse the repository at this point in the history
  • Loading branch information
pavly-gerges committed Nov 2, 2021
1 parent 838682e commit 6ee6637
Showing 1 changed file with 127 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@
import android.os.Handler;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleEventObserver;
import androidx.lifecycle.LifecycleOwner;
import com.jme3.app.LegacyApplication;
import com.jme3.audio.AudioRenderer;
import com.jme3.input.JoyInput;
Expand All @@ -17,14 +21,10 @@
import com.jme3.system.SystemListener;
import com.jme3.system.android.JmeAndroidSystem;
import com.jme3.system.android.OGLESContext;

import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

/**
* <b>A RelativeLayout Class Holder that holds a #{{@link GLSurfaceView}} using #{{@link OGLESContext}} as a renderer to render
* a JME game on an android view for custom xmL designs.</b>
Expand All @@ -34,7 +34,7 @@
* an image or even play a preface game music of choice.
* @author pavl_g.
*/
public class JmeSurfaceView extends RelativeLayout implements SystemListener , DialogInterface.OnClickListener {
public class JmeSurfaceView extends RelativeLayout implements SystemListener, DialogInterface.OnClickListener, LifecycleEventObserver {

/*using #{@link LegacyApplication} instead of #{@link SimpleApplication} to include all classes extends LegacyApplication*/
private LegacyApplication legacyApplication;
Expand All @@ -59,6 +59,7 @@ public class JmeSurfaceView extends RelativeLayout implements SystemListener , D
public static final int NO_DELAY = 1;
private int delayMillis = NO_DELAY;
private static final int TOLERANCE_TIMER = 100;
private boolean showErrorDialog = true;

public JmeSurfaceView(@NonNull Context context) {
super(context);
Expand All @@ -80,45 +81,66 @@ public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, in
*/
public void startRenderer(int delayMillis) {
this.delayMillis = Math.max(NO_DELAY , delayMillis);
if (legacyApplication != null) {
try {
/*initialize App Settings & start the Game*/
appSettings = new AppSettings(true);
appSettings.setAudioRenderer(audioRendererType);
appSettings.setResolution(JmeSurfaceView.this.getLayoutParams().width , JmeSurfaceView.this.getLayoutParams().height);
appSettings.setAlphaBits(eglAlphaBits);
appSettings.setDepthBits(eglDepthBits);
appSettings.setSamples(eglSamples);
appSettings.setStencilBits(eglStencilBits);
appSettings.setBitsPerPixel(eglBitsPerPixel);
appSettings.setEmulateKeyboard(emulateKeyBoard);
appSettings.setEmulateMouse(emulateMouse);
appSettings.setUseJoysticks(useJoyStickEvents);
legacyApplication.setSettings(appSettings);
/*start jme game context*/
legacyApplication.start();
/*attach the game to JmE OpenGL.Renderer context */
OGLESContext oglesContext = (OGLESContext) legacyApplication.getContext();
/*create a glSurfaceView that will hold the renderer thread*/
glSurfaceView = oglesContext.createView(JmeSurfaceView.this.getContext());
/*set the current view as the system engine thread view for future uses*/
JmeAndroidSystem.setView(JmeSurfaceView.this);
/*set JME system Listener to initialize game , update , requestClose & destroy on closure*/
oglesContext.setSystemListener(JmeSurfaceView.this);
/* set the glSurfaceView to fit the widget */
glSurfaceView.setLayoutParams(new LayoutParams(JmeSurfaceView.this.getLayoutParams().width , JmeSurfaceView.this.getLayoutParams().height));
/*post delay the renderer join into the UI thread*/
handler.postDelayed(new RendererThread() , delayMillis);
} catch (Exception e) {
jmeSurfaceViewLogger.log(Level.WARNING , e.getMessage());
showErrorDialog(e , e.getMessage());
if (onExceptionThrown != null) {
onExceptionThrown.onExceptionThrown(e);
}
if (legacyApplication == null) {
throw new IllegalStateException("Cannot build a SurfaceView for a null app, make sure to use setLegacyApplication() to pass in your app !");
}
try {
/*initialize App Settings & start the Game*/
appSettings = new AppSettings(true);
appSettings.setAudioRenderer(audioRendererType);
appSettings.setResolution(JmeSurfaceView.this.getLayoutParams().width , JmeSurfaceView.this.getLayoutParams().height);
appSettings.setAlphaBits(eglAlphaBits);
appSettings.setDepthBits(eglDepthBits);
appSettings.setSamples(eglSamples);
appSettings.setStencilBits(eglStencilBits);
appSettings.setBitsPerPixel(eglBitsPerPixel);
appSettings.setEmulateKeyboard(emulateKeyBoard);
appSettings.setEmulateMouse(emulateMouse);
appSettings.setUseJoysticks(useJoyStickEvents);
legacyApplication.setSettings(appSettings);
/*start jme game context*/
legacyApplication.start();
/*attach the game to JmE OpenGL.Renderer context */
OGLESContext oglesContext = (OGLESContext) legacyApplication.getContext();
/*create a glSurfaceView that will hold the renderer thread*/
glSurfaceView = oglesContext.createView(JmeSurfaceView.this.getContext());
/*set the current view as the system engine thread view for future uses*/
JmeAndroidSystem.setView(JmeSurfaceView.this);
/*set JME system Listener to initialize game , update , requestClose & destroy on closure*/
oglesContext.setSystemListener(JmeSurfaceView.this);
/* set the glSurfaceView to fit the widget */
glSurfaceView.setLayoutParams(new LayoutParams(JmeSurfaceView.this.getLayoutParams().width , JmeSurfaceView.this.getLayoutParams().height));
/*post delay the renderer join into the UI thread*/
handler.postDelayed(new RendererThread() , delayMillis);
} catch (Exception e) {
jmeSurfaceViewLogger.log(Level.WARNING , e.getMessage());
showErrorDialog(e , e.getMessage());
if (onExceptionThrown != null) {
onExceptionThrown.onExceptionThrown(e);
}
}
}

/**
* A state change observer to the current Activity life cycle.
* @param source the life cycle source, aka the observable object.
* @param event the fired event by the observable object, which is dispatched and sent to the observers.
*/
@Override
public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) {
switch (event){
case ON_DESTROY:
destroy();
break;
case ON_PAUSE:
loseFocus();
break;
case ON_RESUME:
gainFocus();
break;
}
}

/**
* Custom thread that delays the appearance of the display of jme game on the screen for the sake of initial frame pacing & splash screens.
*/
Expand All @@ -132,36 +154,40 @@ private class RendererThread implements Runnable {
public void run() {
/*jme Renderer joins the UIThread at that point*/
JmeSurfaceView.this.addView(glSurfaceView);
//register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner
if(getContext() instanceof LifecycleOwner) {
((LifecycleOwner) getContext()).getLifecycle().addObserver(JmeSurfaceView.this);
}
jmeSurfaceViewLogger.log(Level.CONFIG , "JmeSurfaceView's joined the UI thread.......");
}
}

@Override
public void initialize() {
if (legacyApplication != null) {
legacyApplication.initialize();
/*log for display*/
jmeSurfaceViewLogger.log(Level.INFO , "JmeGame started in GLThread Asynchronously.......");
if (legacyApplication == null) {
return;
}
legacyApplication.initialize();
/*log for display*/
jmeSurfaceViewLogger.log(Level.INFO , "JmeGame started in GLThread Asynchronously.......");
}

@Override
public void reshape(int width , int height) {
if (legacyApplication != null) {
legacyApplication.reshape(width , height);
if (legacyApplication == null) {
return;
}
legacyApplication.reshape(width , height);
}

@Override
public void update() {
if (legacyApplication == null) {
if (legacyApplication == null || glSurfaceView == null) {
return;
}
if (glSurfaceView != null) {
legacyApplication.update();
}
legacyApplication.update();
int timeToPlay = synthesizedTime.addAndGet(1);
if (timeToPlay == (delayMillis>100 ? (delayMillis-TOLERANCE_TIMER) : delayMillis)) {
if (timeToPlay == (delayMillis > 100 ? (delayMillis - TOLERANCE_TIMER) : delayMillis)) {
((Activity)getContext()).runOnUiThread(() -> {
jmeSurfaceViewLogger.log(Level.INFO,"SplashScreen Dismissed , User Delay completed with 0 errors.......");
if (onRendererCompleted != null) {
Expand All @@ -173,51 +199,56 @@ public void update() {

@Override
public void requestClose(boolean esc) {
if (legacyApplication != null) {
legacyApplication.requestClose(esc);
if (legacyApplication == null) {
return;
}
legacyApplication.requestClose(esc);
}

@Override
public void gainFocus() {
if (legacyApplication != null) {
/*resume the audio*/
AudioRenderer audioRenderer = legacyApplication.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.resumeAll();
}
/*resume the sensors (aka joysticks)*/
if (legacyApplication.getContext() != null) {
JoyInput joyInput = legacyApplication.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.resumeSensors();
}
if (legacyApplication == null || glSurfaceView == null) {
return;
}
glSurfaceView.onResume();
/*resume the audio*/
AudioRenderer audioRenderer = legacyApplication.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.resumeAll();
}
/*resume the sensors (aka joysticks)*/
if (legacyApplication.getContext() != null) {
JoyInput joyInput = legacyApplication.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.resumeSensors();
}
legacyApplication.gainFocus();
}
legacyApplication.gainFocus();
}
setGLThreadPaused(false);
}

@Override
public void loseFocus() {
if (legacyApplication != null) {
/*pause the audio*/
legacyApplication.loseFocus();
AudioRenderer audioRenderer = legacyApplication.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.pauseAll();
}
/*pause the sensors (aka joysticks)*/
if (legacyApplication.getContext() != null) {
JoyInput joyInput = legacyApplication.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.pauseSensors();
}
if (legacyApplication == null || glSurfaceView == null) {
return;
}
glSurfaceView.onPause();
/*pause the audio*/
legacyApplication.loseFocus();
AudioRenderer audioRenderer = legacyApplication.getAudioRenderer();
if (audioRenderer != null) {
audioRenderer.pauseAll();
}
/*pause the sensors (aka joysticks)*/
if (legacyApplication.getContext() != null) {
JoyInput joyInput = legacyApplication.getContext().getJoyInput();
if (joyInput != null) {
if (joyInput instanceof AndroidSensorJoyInput) {
AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput;
androidJoyInput.pauseSensors();
}
}
}
Expand All @@ -236,8 +267,7 @@ public void handleError(String errorMsg , Throwable throwable) {
@Override
public void destroy() {
if (legacyApplication != null) {
legacyApplication.stop(isGLThreadPaused());
legacyApplication.destroy();
legacyApplication.stop(!isGLThreadPaused());
}
}

Expand All @@ -247,6 +277,9 @@ public void destroy() {
* @param message the string message.
*/
protected void showErrorDialog(Throwable throwable , String message) {
if(!isShowErrorDialog()){
return;
}
((Activity)getContext()).runOnUiThread(() -> {
AlertDialog alertDialog = new AlertDialog.Builder(getContext()).create();
alertDialog.setTitle(new StringBuffer(String.valueOf(throwable)));
Expand All @@ -258,6 +291,14 @@ protected void showErrorDialog(Throwable throwable , String message) {
});
}

public void setShowErrorDialog(boolean showErrorDialog) {
this.showErrorDialog = showErrorDialog;
}

public boolean isShowErrorDialog() {
return showErrorDialog;
}

@Override
public void onClick(DialogInterface dialog , int which) {
switch (which) {
Expand Down

0 comments on commit 6ee6637

Please sign in to comment.