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