Home | History | Annotate | Download | only in state
      1 /*
      2  * Copyright (c) 2009-2010 jMonkeyEngine
      3  * All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions are
      7  * met:
      8  *
      9  * * Redistributions of source code must retain the above copyright
     10  *   notice, this list of conditions and the following disclaimer.
     11  *
     12  * * Redistributions in binary form must reproduce the above copyright
     13  *   notice, this list of conditions and the following disclaimer in the
     14  *   documentation and/or other materials provided with the distribution.
     15  *
     16  * * Neither the name of 'jMonkeyEngine' nor the names of its contributors
     17  *   may be used to endorse or promote products derived from this software
     18  *   without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
     21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
     22  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     23  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
     24  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     25  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     26  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     27  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
     28  * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
     29  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
     30  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     31  */
     32 
     33 package com.jme3.app.state;
     34 
     35 import com.jme3.app.Application;
     36 import com.jme3.renderer.RenderManager;
     37 import com.jme3.util.SafeArrayList;
     38 import java.util.Arrays;
     39 import java.util.List;
     40 
     41 /**
     42  * The <code>AppStateManager</code> holds a list of {@link AppState}s which
     43  * it will update and render.<br/>
     44  * When an {@link AppState} is attached or detached, the
     45  * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and
     46  * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods
     47  * will be called respectively.
     48  *
     49  * <p>The lifecycle for an attached AppState is as follows:</p>
     50  * <ul>
     51  * <li>stateAttached() : called when the state is attached on the thread on which
     52  *                       the state was attached.
     53  * <li>initialize() : called ONCE on the render thread at the beginning of the next
     54  *                    AppStateManager.update().
     55  * <li>stateDetached() : called when the state is attached on the thread on which
     56  *                       the state was detached.  This is not necessarily on the
     57  *                       render thread and it is not necessarily safe to modify
     58  *                       the scene graph, etc..
     59  * <li>cleanup() : called ONCE on the render thread at the beginning of the next update
     60  *                 after the state has been detached or when the application is
     61  *                 terminating.
     62  * </ul>
     63  *
     64  * @author Kirill Vainer, Paul Speed
     65  */
     66 public class AppStateManager {
     67 
     68     /**
     69      *  List holding the attached app states that are pending
     70      *  initialization.  Once initialized they will be added to
     71      *  the running app states.
     72      */
     73     private final SafeArrayList<AppState> initializing = new SafeArrayList<AppState>(AppState.class);
     74 
     75     /**
     76      *  Holds the active states once they are initialized.
     77      */
     78     private final SafeArrayList<AppState> states = new SafeArrayList<AppState>(AppState.class);
     79 
     80     /**
     81      *  List holding the detached app states that are pending
     82      *  cleanup.
     83      */
     84     private final SafeArrayList<AppState> terminating = new SafeArrayList<AppState>(AppState.class);
     85 
     86     // All of the above lists need to be thread safe but access will be
     87     // synchronized separately.... but always on the states list.  This
     88     // is to avoid deadlocking that may occur and the most common use case
     89     // is that they are all modified from the same thread anyway.
     90 
     91     private final Application app;
     92     private AppState[] stateArray;
     93 
     94     public AppStateManager(Application app){
     95         this.app = app;
     96     }
     97 
     98     protected AppState[] getInitializing() {
     99         synchronized (states){
    100             return initializing.getArray();
    101         }
    102     }
    103 
    104     protected AppState[] getTerminating() {
    105         synchronized (states){
    106             return terminating.getArray();
    107         }
    108     }
    109 
    110     protected AppState[] getStates(){
    111         synchronized (states){
    112             return states.getArray();
    113         }
    114     }
    115 
    116     /**
    117      * Attach a state to the AppStateManager, the same state cannot be attached
    118      * twice.
    119      *
    120      * @param state The state to attach
    121      * @return True if the state was successfully attached, false if the state
    122      * was already attached.
    123      */
    124     public boolean attach(AppState state){
    125         synchronized (states){
    126             if (!states.contains(state) && !initializing.contains(state)){
    127                 state.stateAttached(this);
    128                 initializing.add(state);
    129                 return true;
    130             }else{
    131                 return false;
    132             }
    133         }
    134     }
    135 
    136     /**
    137      * Detaches the state from the AppStateManager.
    138      *
    139      * @param state The state to detach
    140      * @return True if the state was detached successfully, false
    141      * if the state was not attached in the first place.
    142      */
    143     public boolean detach(AppState state){
    144         synchronized (states){
    145             if (states.contains(state)){
    146                 state.stateDetached(this);
    147                 states.remove(state);
    148                 terminating.add(state);
    149                 return true;
    150             } else if(initializing.contains(state)){
    151                 state.stateDetached(this);
    152                 initializing.remove(state);
    153                 return true;
    154             }else{
    155                 return false;
    156             }
    157         }
    158     }
    159 
    160     /**
    161      * Check if a state is attached or not.
    162      *
    163      * @param state The state to check
    164      * @return True if the state is currently attached to this AppStateManager.
    165      *
    166      * @see AppStateManager#attach(com.jme3.app.state.AppState)
    167      */
    168     public boolean hasState(AppState state){
    169         synchronized (states){
    170             return states.contains(state) || initializing.contains(state);
    171         }
    172     }
    173 
    174     /**
    175      * Returns the first state that is an instance of subclass of the specified class.
    176      * @param <T>
    177      * @param stateClass
    178      * @return First attached state that is an instance of stateClass
    179      */
    180     public <T extends AppState> T getState(Class<T> stateClass){
    181         synchronized (states){
    182             AppState[] array = getStates();
    183             for (AppState state : array) {
    184                 if (stateClass.isAssignableFrom(state.getClass())){
    185                     return (T) state;
    186                 }
    187             }
    188 
    189             // This may be more trouble than its worth but I think
    190             // it's necessary for proper decoupling of states and provides
    191             // similar behavior to before where a state could be looked
    192             // up even if it wasn't initialized. -pspeed
    193             array = getInitializing();
    194             for (AppState state : array) {
    195                 if (stateClass.isAssignableFrom(state.getClass())){
    196                     return (T) state;
    197                 }
    198             }
    199         }
    200         return null;
    201     }
    202 
    203     protected void initializePending(){
    204         AppState[] array = getInitializing();
    205         if (array.length == 0)
    206             return;
    207 
    208         synchronized( states ) {
    209             // Move the states that will be initialized
    210             // into the active array.  In all but one case the
    211             // order doesn't matter but if we do this here then
    212             // a state can detach itself in initialize().  If we
    213             // did it after then it couldn't.
    214             List<AppState> transfer = Arrays.asList(array);
    215             states.addAll(transfer);
    216             initializing.removeAll(transfer);
    217         }
    218         for (AppState state : array) {
    219             state.initialize(this, app);
    220         }
    221     }
    222 
    223     protected void terminatePending(){
    224         AppState[] array = getTerminating();
    225         if (array.length == 0)
    226             return;
    227 
    228         for (AppState state : array) {
    229             state.cleanup();
    230         }
    231         synchronized( states ) {
    232             // Remove just the states that were terminated...
    233             // which might now be a subset of the total terminating
    234             // list.
    235             terminating.removeAll(Arrays.asList(array));
    236         }
    237     }
    238 
    239     /**
    240      * Calls update for attached states, do not call directly.
    241      * @param tpf Time per frame.
    242      */
    243     public void update(float tpf){
    244 
    245         // Cleanup any states pending
    246         terminatePending();
    247 
    248         // Initialize any states pending
    249         initializePending();
    250 
    251         // Update enabled states
    252         AppState[] array = getStates();
    253         for (AppState state : array){
    254             if (state.isEnabled()) {
    255                 state.update(tpf);
    256             }
    257         }
    258     }
    259 
    260     /**
    261      * Calls render for all attached and initialized states, do not call directly.
    262      * @param rm The RenderManager
    263      */
    264     public void render(RenderManager rm){
    265         AppState[] array = getStates();
    266         for (AppState state : array){
    267             if (state.isEnabled()) {
    268                 state.render(rm);
    269             }
    270         }
    271     }
    272 
    273     /**
    274      * Calls render for all attached and initialized states, do not call directly.
    275      */
    276     public void postRender(){
    277         AppState[] array = getStates();
    278         for (AppState state : array){
    279             if (state.isEnabled()) {
    280                 state.postRender();
    281             }
    282         }
    283     }
    284 
    285     /**
    286      * Calls cleanup on attached states, do not call directly.
    287      */
    288     public void cleanup(){
    289         AppState[] array = getStates();
    290         for (AppState state : array){
    291             state.cleanup();
    292         }
    293     }
    294 }
    295