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
This commit is contained in:
parent
2a65ca4cb7
commit
cf18f48182
@ -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…
x
Reference in New Issue
Block a user