Internal surgery to AppStateManager to provide more

consistent app state lifecycle, fix some state transition
related bugs, and stop confusing users... well, at least
confusing them less hopefully.


git-svn-id: https://jmonkeyengine.googlecode.com/svn/trunk@8524 75d07b2b-3a1a-0410-a2c5-0572b91ccdca
3.0
PSp..om 13 years ago
parent 2a65ca4cb7
commit cf18f48182
  1. 150
      engine/src/core/com/jme3/app/state/AppStateManager.java

@ -34,7 +34,9 @@ package com.jme3.app.state;
import com.jme3.app.Application; import com.jme3.app.Application;
import com.jme3.renderer.RenderManager; import com.jme3.renderer.RenderManager;
import java.util.ArrayList; import com.jme3.util.SafeArrayList;
import java.util.Arrays;
import java.util.List;
/** /**
* The <code>AppStateManager</code> holds a list of {@link AppState}s which * The <code>AppStateManager</code> holds a list of {@link AppState}s which
@ -44,11 +46,48 @@ import java.util.ArrayList;
* {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods
* will be called respectively. * will be called respectively.
* *
* @author Kirill Vainer * <p>The lifecycle for an attached AppState is as follows:</p>
* <ul>
* <li>stateAttached() : called when the state is attached on the thread on which
* the state was attached.
* <li>initialize() : called ONCE on the render thread at the beginning of the next
* AppStateManager.update().
* <li>stateDetached() : called when the state is attached on the thread on which
* the state was detached. This is not necessarily on the
* render thread and it is not necessarily safe to modify
* the scene graph, etc..
* <li>cleanup() : called ONCE on the render thread at the beginning of the next update
* after the state has been detached or when the application is
* terminating.
* </ul>
*
* @author Kirill Vainer, Paul Speed
*/ */
public class AppStateManager { public class AppStateManager {
private final ArrayList<AppState> states = new ArrayList<AppState>(); /**
* List holding the attached app states that are pending
* initialization. Once initialized they will be added to
* the running app states.
*/
private final SafeArrayList<AppState> initializing = new SafeArrayList<AppState>(AppState.class);
/**
* Holds the active states once they are initialized.
*/
private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);
/**
* List holding the detached app states that are pending
* cleanup.
*/
private final SafeArrayList<AppState> terminating = new SafeArrayList<AppState>(AppState.class);
// All of the above lists need to be thread safe but access will be
// synchronized separately.... but always on the states list. This
// is to avoid deadlocking that may occur and the most common use case
// is that they are all modified from the same thread anyway.
private final Application app; private final Application app;
private AppState[] stateArray; private AppState[] stateArray;
@ -56,12 +95,21 @@ public class AppStateManager {
this.app = app; this.app = app;
} }
protected AppState[] getArray(){ protected AppState[] getInitializing() {
synchronized (states){ synchronized (states){
if (stateArray == null){ return initializing.getArray();
stateArray = states.toArray(new AppState[states.size()]);
} }
return stateArray; }
protected AppState[] getTerminating() {
synchronized (states){
return terminating.getArray();
}
}
protected AppState[] getStates(){
synchronized (states){
return states.getArray();
} }
} }
@ -75,10 +123,9 @@ public class AppStateManager {
*/ */
public boolean attach(AppState state){ public boolean attach(AppState state){
synchronized (states){ synchronized (states){
if (!states.contains(state)){ if (!states.contains(state) && !initializing.contains(state)){
state.stateAttached(this); state.stateAttached(this);
states.add(state); initializing.add(state);
stateArray = null;
return true; return true;
}else{ }else{
return false; return false;
@ -98,7 +145,11 @@ public class AppStateManager {
if (states.contains(state)){ if (states.contains(state)){
state.stateDetached(this); state.stateDetached(this);
states.remove(state); states.remove(state);
stateArray = null; terminating.add(state);
return true;
} else if(initializing.contains(state)){
state.stateDetached(this);
initializing.remove(state);
return true; return true;
}else{ }else{
return false; return false;
@ -116,7 +167,7 @@ public class AppStateManager {
*/ */
public boolean hasState(AppState state){ public boolean hasState(AppState state){
synchronized (states){ synchronized (states){
return states.contains(state); return states.contains(state) || initializing.contains(state);
} }
} }
@ -128,9 +179,19 @@ public class AppStateManager {
*/ */
public <T extends AppState> T getState(Class<T> stateClass){ public <T extends AppState> T getState(Class<T> stateClass){
synchronized (states){ synchronized (states){
int num = states.size(); AppState[] array = getStates();
for (int i = 0; i < num; i++){ for (AppState state : array) {
AppState state = states.get(i); if (stateClass.isAssignableFrom(state.getClass())){
return (T) state;
}
}
// This may be more trouble than its worth but I think
// it's necessary for proper decoupling of states and provides
// similar behavior to before where a state could be looked
// up even if it wasn't initialized. -pspeed
array = getInitializing();
for (AppState state : array) {
if (stateClass.isAssignableFrom(state.getClass())){ if (stateClass.isAssignableFrom(state.getClass())){
return (T) state; return (T) state;
} }
@ -139,16 +200,51 @@ public class AppStateManager {
return null; return null;
} }
protected void initializePending(){
AppState[] array = getInitializing();
synchronized( states ) {
// Move the states that will be initialized
// into the active array. In all but one case the
// order doesn't matter but if we do this here then
// a state can detach itself in initialize(). If we
// did it after then it couldn't.
List<AppState> transfer = Arrays.asList(array);
states.addAll(transfer);
initializing.removeAll(transfer);
}
for (AppState state : array) {
state.initialize(this, app);
}
}
protected void terminatePending(){
AppState[] array = getTerminating();
for (AppState state : array) {
state.cleanup();
}
synchronized( states ) {
// Remove just the states that were terminated...
// which might now be a subset of the total terminating
// list.
terminating.removeAll(Arrays.asList(array));
}
}
/** /**
* Calls update for attached states, do not call directly. * Calls update for attached states, do not call directly.
* @param tpf Time per frame. * @param tpf Time per frame.
*/ */
public void update(float tpf){ public void update(float tpf){
AppState[] array = getArray();
for (AppState state : array){
if (!state.isInitialized())
state.initialize(this, app);
// Cleanup any states pending
terminatePending();
// Initialize any states pending
initializePending();
// Update enabled states
AppState[] array = getStates();
for (AppState state : array){
if (state.isEnabled()) { if (state.isEnabled()) {
state.update(tpf); state.update(tpf);
} }
@ -156,15 +252,12 @@ public class AppStateManager {
} }
/** /**
* Calls render for all attached states, do not call directly. * Calls render for all attached and initialized states, do not call directly.
* @param rm The RenderManager * @param rm The RenderManager
*/ */
public void render(RenderManager rm){ public void render(RenderManager rm){
AppState[] array = getArray(); AppState[] array = getStates();
for (AppState state : array){ for (AppState state : array){
if (!state.isInitialized())
state.initialize(this, app);
if (state.isEnabled()) { if (state.isEnabled()) {
state.render(rm); state.render(rm);
} }
@ -172,14 +265,11 @@ public class AppStateManager {
} }
/** /**
* Calls render for all attached states, do not call directly. * Calls render for all attached and initialized states, do not call directly.
*/ */
public void postRender(){ public void postRender(){
AppState[] array = getArray(); AppState[] array = getStates();
for (AppState state : array){ for (AppState state : array){
if (!state.isInitialized())
state.initialize(this, app);
if (state.isEnabled()) { if (state.isEnabled()) {
state.postRender(); state.postRender();
} }
@ -190,7 +280,7 @@ public class AppStateManager {
* Calls cleanup on attached states, do not call directly. * Calls cleanup on attached states, do not call directly.
*/ */
public void cleanup(){ public void cleanup(){
AppState[] array = getArray(); AppState[] array = getStates();
for (AppState state : array){ for (AppState state : array){
state.cleanup(); state.cleanup();
} }

Loading…
Cancel
Save