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:
+ *
+ * - stateAttached() : called when the state is attached on the thread on which
+ * the state was attached.
+ *
- initialize() : called ONCE on the render thread at the beginning of the next
+ * AppStateManager.update().
+ *
- 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..
+ *
- 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.
+ *
+ *
+ * @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();
}