@ -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
@ -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
* < 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 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 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 ( ) ;
}