blob: 92a00f5534b78c2fd921f10de6844c1f864fc4e8 [file] [log] [blame]
/*
* Copyright (c) 2009-2010 jMonkeyEngine
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jme3.app.state;
import com.jme3.app.Application;
import com.jme3.renderer.RenderManager;
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
* it will update and render.<br/>
* 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.
*
* <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 {
/**
* 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;
public AppStateManager(Application app){
this.app = app;
}
protected AppState[] getInitializing() {
synchronized (states){
return initializing.getArray();
}
}
protected AppState[] getTerminating() {
synchronized (states){
return terminating.getArray();
}
}
protected AppState[] getStates(){
synchronized (states){
return states.getArray();
}
}
/**
* Attach a state to the AppStateManager, the same state cannot be attached
* twice.
*
* @param state The state to attach
* @return True if the state was successfully attached, false if the state
* was already attached.
*/
public boolean attach(AppState state){
synchronized (states){
if (!states.contains(state) && !initializing.contains(state)){
state.stateAttached(this);
initializing.add(state);
return true;
}else{
return false;
}
}
}
/**
* Detaches the state from the AppStateManager.
*
* @param state The state to detach
* @return True if the state was detached successfully, false
* if the state was not attached in the first place.
*/
public boolean detach(AppState state){
synchronized (states){
if (states.contains(state)){
state.stateDetached(this);
states.remove(state);
terminating.add(state);
return true;
} else if(initializing.contains(state)){
state.stateDetached(this);
initializing.remove(state);
return true;
}else{
return false;
}
}
}
/**
* Check if a state is attached or not.
*
* @param state The state to check
* @return True if the state is currently attached to this AppStateManager.
*
* @see AppStateManager#attach(com.jme3.app.state.AppState)
*/
public boolean hasState(AppState state){
synchronized (states){
return states.contains(state) || initializing.contains(state);
}
}
/**
* Returns the first state that is an instance of subclass of the specified class.
* @param <T>
* @param stateClass
* @return First attached state that is an instance of stateClass
*/
public <T extends AppState> T getState(Class<T> stateClass){
synchronized (states){
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;
}
}
}
return null;
}
protected void initializePending(){
AppState[] array = getInitializing();
if (array.length == 0)
return;
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();
if (array.length == 0)
return;
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){
// 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);
}
}
}
/**
* Calls render for all attached and initialized states, do not call directly.
* @param rm The RenderManager
*/
public void render(RenderManager rm){
AppState[] array = getStates();
for (AppState state : array){
if (state.isEnabled()) {
state.render(rm);
}
}
}
/**
* Calls render for all attached and initialized states, do not call directly.
*/
public void postRender(){
AppState[] array = getStates();
for (AppState state : array){
if (state.isEnabled()) {
state.postRender();
}
}
}
/**
* Calls cleanup on attached states, do not call directly.
*/
public void cleanup(){
AppState[] array = getStates();
for (AppState state : array){
state.cleanup();
}
}
}