• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AppStateManager(Application app)94     public AppStateManager(Application app){
95         this.app = app;
96     }
97 
getInitializing()98     protected AppState[] getInitializing() {
99         synchronized (states){
100             return initializing.getArray();
101         }
102     }
103 
getTerminating()104     protected AppState[] getTerminating() {
105         synchronized (states){
106             return terminating.getArray();
107         }
108     }
109 
getStates()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      */
attach(AppState state)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      */
detach(AppState state)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      */
hasState(AppState state)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      */
getState(Class<T> stateClass)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 
initializePending()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 
terminatePending()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      */
update(float tpf)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      */
render(RenderManager rm)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      */
postRender()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      */
cleanup()288     public void cleanup(){
289         AppState[] array = getStates();
290         for (AppState state : array){
291             state.cleanup();
292         }
293     }
294 }
295