diff --git a/engine/src/core/com/jme3/app/state/AppStateManager.java b/engine/src/core/com/jme3/app/state/AppStateManager.java index 76c4047d0..81228af15 100644 --- a/engine/src/core/com/jme3/app/state/AppStateManager.java +++ b/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.renderer.RenderManager; -import java.util.ArrayList; +import com.jme3.util.SafeArrayList; +import java.util.Arrays; +import java.util.List; /** * The AppStateManager holds a list of {@link AppState}s which @@ -42,13 +44,50 @@ import java.util.ArrayList; * When an {@link AppState} is attached or detached, the * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods - * will be called respectively. + * will be called respectively. * - * @author Kirill Vainer + *

The lifecycle for an attached AppState is as follows:

+ * + * + * @author Kirill Vainer, Paul Speed */ public class AppStateManager { - private final ArrayList states = new ArrayList(); + /** + * List holding the attached app states that are pending + * initialization. Once initialized they will be added to + * the running app states. + */ + private final SafeArrayList initializing = new SafeArrayList(AppState.class); + + /** + * Holds the active states once they are initialized. + */ + private final SafeArrayList states = new SafeArrayList(AppState.class); + + /** + * List holding the detached app states that are pending + * cleanup. + */ + private final SafeArrayList terminating = new SafeArrayList(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 AppState[] stateArray; @@ -56,12 +95,21 @@ public class AppStateManager { this.app = app; } - protected AppState[] getArray(){ + protected AppState[] getInitializing() { synchronized (states){ - if (stateArray == null){ - stateArray = states.toArray(new AppState[states.size()]); - } - return stateArray; + return initializing.getArray(); + } + } + + 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){ synchronized (states){ - if (!states.contains(state)){ + if (!states.contains(state) && !initializing.contains(state)){ state.stateAttached(this); - states.add(state); - stateArray = null; + initializing.add(state); return true; }else{ return false; @@ -98,7 +145,11 @@ public class AppStateManager { if (states.contains(state)){ state.stateDetached(this); states.remove(state); - stateArray = null; + terminating.add(state); + return true; + } else if(initializing.contains(state)){ + state.stateDetached(this); + initializing.remove(state); return true; }else{ return false; @@ -116,7 +167,7 @@ public class AppStateManager { */ public boolean hasState(AppState state){ synchronized (states){ - return states.contains(state); + return states.contains(state) || initializing.contains(state); } } @@ -128,9 +179,19 @@ public class AppStateManager { */ public T getState(Class stateClass){ synchronized (states){ - int num = states.size(); - for (int i = 0; i < num; i++){ - AppState state = states.get(i); + AppState[] array = getStates(); + for (AppState state : array) { + 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())){ return (T) state; } @@ -139,16 +200,51 @@ public class AppStateManager { 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 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. * @param tpf Time per frame. */ 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()) { 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 */ public void render(RenderManager rm){ - AppState[] array = getArray(); + AppState[] array = getStates(); for (AppState state : array){ - if (!state.isInitialized()) - state.initialize(this, app); - if (state.isEnabled()) { 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(){ - AppState[] array = getArray(); + AppState[] array = getStates(); for (AppState state : array){ - if (!state.isInitialized()) - state.initialize(this, app); - if (state.isEnabled()) { state.postRender(); } @@ -190,7 +280,7 @@ public class AppStateManager { * Calls cleanup on attached states, do not call directly. */ public void cleanup(){ - AppState[] array = getArray(); + AppState[] array = getStates(); for (AppState state : array){ state.cleanup(); }