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.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
@ -44,11 +46,48 @@ import java.util.ArrayList;
* {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods
* 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 {
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 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 initializing.getArray();
}
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){
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 extends AppState> T getState(Class<T> 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<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.
* @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();
}

Loading…
Cancel
Save