View Javadoc

1   package cz.cuni.amis.pogamut.udk.server.impl;
2   
3   import java.io.IOException;
4   import java.util.concurrent.CountDownLatch;
5   import java.util.concurrent.Future;
6   import java.util.concurrent.TimeUnit;
7   import java.util.logging.Level;
8   
9   import com.google.inject.Inject;
10  
11  import cz.cuni.amis.pogamut.base.agent.state.level0.IAgentState;
12  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateDown;
13  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateGoingUp;
14  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateUp;
15  import cz.cuni.amis.pogamut.base.communication.command.IAct;
16  import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnection;
17  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
18  import cz.cuni.amis.pogamut.base.communication.worldview.event.WorldEventFuture;
19  import cz.cuni.amis.pogamut.base.component.bus.IComponentBus;
20  import cz.cuni.amis.pogamut.base.component.bus.event.BusAwareCountDownLatch;
21  import cz.cuni.amis.pogamut.base.component.exception.ComponentCantStartException;
22  import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
23  import cz.cuni.amis.pogamut.udk.agent.params.UDKAgentParameters;
24  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.ChangeMap;
25  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.SetGameSpeed;
26  import cz.cuni.amis.pogamut.udk.communication.messages.gbcommands.StartPlayers;
27  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.MapChange;
28  import cz.cuni.amis.pogamut.udk.communication.messages.gbinfomessages.PlayerJoinsGame;
29  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.MapListObtained;
30  import cz.cuni.amis.pogamut.udk.communication.translator.shared.events.MutatorListObtained;
31  import cz.cuni.amis.pogamut.udk.communication.worldview.UDKWorldView;
32  import cz.cuni.amis.pogamut.udk.server.IUDKServer;
33  import cz.cuni.amis.pogamut.udk.server.exception.MapChangeException;
34  import cz.cuni.amis.pogamut.udk.utils.UDKWrapper;
35  import cz.cuni.amis.utils.Job;
36  import cz.cuni.amis.utils.exception.PogamutInterruptedException;
37  import cz.cuni.amis.utils.flag.FlagListener;
38  
39  public class UDKServer extends AbstractUDKServer<UDKWorldView, IAct> implements IUDKServer {
40  	
41  	/**
42  	 * How many times we're going to try to connect to the GB2004
43  	 * before declaring that the change-map has failed.
44  	 */
45  	public static final int MAX_CHANGING_MAP_ATTEMPTS = 20;
46  	public static final int MAP_CHANGE_CONNECT_INTERVAL_MILLIS = 1000;
47  	
48  
49  	private volatile BusAwareCountDownLatch mapLatch = null;
50  	
51  	protected IWorldEventListener<PlayerJoinsGame> playerJoinsListener = null;
52      protected IWorldEventListener<MapListObtained> mapListListener = null;
53      
54      /**
55  	 * Parameters passed into the constructor/factory/runner (by whatever means the agent has been started).
56  	 */
57  	private UDKAgentParameters params;
58  	
59      @Inject
60      public UDKServer(UDKAgentParameters params, IAgentLogger agentLogger, IComponentBus bus, SocketConnection connection, UDKWorldView worldView, IAct act) {
61          super(params.getAgentId(), agentLogger, bus, connection, worldView, act);
62          this.params = params;
63          mapLatch = new BusAwareCountDownLatch(1, getEventBus(), worldView);
64          
65          // place where to hook listeners!
66          
67  
68          // TODO change the players list on the fly
69         
70          /*getWorldView().addListener(PlayerJoinsGame.class, playerJoinsListener = new WorldEventListener<PlayerJoinsGame>() {
71  
72          public void notify(PlayerJoinsGame event) {
73          // TODO
74          players.add(null);
75          }
76          });
77           */
78          // TODO player left
79  
80          // mutators list
81          getWorldView().addEventListener(MutatorListObtained.class, new IWorldEventListener<MutatorListObtained>() {
82  
83              public void notify(MutatorListObtained event) {
84                  mutators = event.getMutators();
85              }
86          });
87  
88          // TODO where to get gamespeed?
89          // gamespeed
90          gameSpeed.addListener(new FlagListener<Double>() {
91  
92              public void flagChanged(Double changedValue) {
93                  getAct().act(new SetGameSpeed(changedValue));
94              }
95          });
96  
97          // maps
98          getWorldView().addEventListener(MapListObtained.class, mapListListener = new IWorldEventListener<MapListObtained>() {
99  
100             public void notify(MapListObtained event) {
101             	maps = event.getMaps();
102                 // first send command
103                 getAct().act(new StartPlayers(true, true, true));
104                 // than rise the latch to continue with server starting
105                 mapLatch.countDown();
106             }
107             
108         });
109         
110     }
111     
112     /**
113      * Returns parameters that were passed into the agent during the construction. 
114      * <p><p>
115      * This is a great place to parametrize your agent. Note that you may pass arbitrary subclass of {@link UDKAgentParameters}
116      * to the constructor/factory/runner and pick them up here.
117      * 
118      * @return parameters
119      */
120     public UDKAgentParameters getParams() {
121 		return params;
122 	}
123 
124 	////////  
125     //
126     // SERVER CONTROL METHODS
127     //
128     ////////
129     @Override
130     protected void startAgent() {
131         super.startAgent();
132         boolean succeded;
133         if (log.isLoggable(Level.INFO)) log.info("Waiting for the map list to arrive...");
134         succeded = mapLatch.await(60000, TimeUnit.MILLISECONDS);
135         if (!succeded) {
136             throw new ComponentCantStartException("The server did not received maps in 60 seconds.", this);
137         } else {
138         	if (log.isLoggable(Level.INFO)) log.info("Maps received.");
139         }        
140     }
141      
142 	protected void reset() {
143     	super.reset();
144     	mapLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
145     }
146 
147     //////
148 	////////
149 	// MAP CHANGING
150 	////////
151 	//////
152 	
153 	// WARNING: very fragile feature - it depends on the UT2004 behavior + exact handling of start/stop that is happening outside UT2004Server object
154 	
155 	protected Object changingMapMutex = new Object();
156 	protected boolean changingMap = false;
157 	protected int changingMapAttempt = 0;
158 	protected String targetMap = null;
159 	protected MapChangeFuture mapChangeFuture = null;
160 	
161 	@Override
162 	public Future<Boolean> setGameMap(String map) throws MapChangeException {
163 		try {
164 			synchronized(changingMapMutex) {
165 				if (!inState(IAgentStateUp.class)) {
166 					throw new MapChangeException("Can't change map as we're not connected to GB2004 server.", this);
167 				}
168 				
169 				if (log.isLoggable(Level.WARNING)) log.warning("Changing map to '" + map + "'");
170 				
171 				WorldEventFuture<MapChange> mapChangeLatch = new WorldEventFuture<MapChange>(getWorldView(), MapChange.class);
172 				changingMap = true;
173 				changingMapAttempt = 0;
174 				targetMap = map;				
175 				mapChangeFuture = new MapChangeFuture();
176 				
177 				getAct().act(new ChangeMap().setMapName(map));
178 				
179 				if (mapChangeLatch.get(20000, TimeUnit.MILLISECONDS) == null) {
180 					throw new MapChangeException("ChangeMap sent but GB2004 failed to response with MapChange message in 20sec.", this);
181 				}				
182 				
183 				return this.mapChangeFuture; 
184 			}			
185 		} catch (Exception e) {
186 			throw new MapChangeException("Can't change map to " + map + ".", e);
187 		}
188 		
189 	}
190 		
191 	public class MapChangeFuture implements Future<Boolean> {
192 
193 		boolean canceled = false;
194 		Boolean success = null;
195 		CountDownLatch doneLatch = new CountDownLatch(1);		
196 		
197 		IAgentState lastState = null;
198 		
199 		FlagListener<IAgentState> listener = new FlagListener<IAgentState>() {
200 			
201 			@Override
202 			public void flagChanged(IAgentState changedValue) {
203 				if (lastState != null && lastState.getClass().isAssignableFrom(changedValue.getClass())) {
204 					return;
205 				}
206 				lastState = changedValue;
207 				if (changedValue instanceof IAgentStateGoingUp) {
208 					++changingMapAttempt;
209 					if (log.isLoggable(Level.WARNING)) log.warning("Map change attempt: " + changingMapAttempt + " / " + MAX_CHANGING_MAP_ATTEMPTS);
210 				} else
211 				if (changedValue instanceof IAgentStateDown) {
212 					if (changingMapAttempt >= MAX_CHANGING_MAP_ATTEMPTS) {
213 						synchronized(changingMapMutex) {
214 							changingMap = false;
215 							changingMapAttempt = 0;
216 							targetMap = null;
217 							mapChangeFuture = null;
218 							
219 							success = false;
220 							doneLatch.countDown();
221 							getState().removeListener(this);
222 						}
223 					} else {
224 						Job<Boolean> restartServer = new MapChangeRestartServerJob();
225 						restartServer.startJob();
226 					}
227 				} else 
228 				if (changedValue instanceof IAgentStateUp) {
229 					if (getMapName() == null || !getMapName().equalsIgnoreCase(targetMap)) {
230 						if (log.isLoggable(Level.WARNING)) log.warning("Reconnected to GB2004 but the map was not changed to '" + targetMap + "' yet.");
231 						Job<Boolean> restartServer = new MapChangeRestartServerJob();
232 						restartServer.startJob();
233 					} else {
234 						success = true;
235 						doneLatch.countDown();
236 						getState().removeListener(this);
237 					}
238 				}
239 				
240 			}
241 			
242 		};
243 		
244 		protected MapChangeFuture() {
245 			getState().addListener(listener);
246 		}
247 		
248 		public void restartServer() {
249 			new MapChangeRestartServerJob().startJob();
250 		}
251 
252 		@Override
253 		public boolean cancel(boolean arg0) {
254 			synchronized(changingMapMutex) {
255 				changingMap = false;
256 				changingMapAttempt = 0;
257 				targetMap = null;
258 				mapChangeFuture = null;
259 				
260 				success = false;
261 				canceled = true;
262 				doneLatch.countDown();
263 			}
264 			return false;
265 		}
266 
267 		@Override
268 		public Boolean get() {
269 			try {
270 				doneLatch.await();
271 			} catch (InterruptedException e) {
272 				new PogamutInterruptedException("Interrupted while waiting for the map change to finish.", e);
273 			}
274 			return success;
275 		}
276 
277 		@Override
278 		public Boolean get(long arg0, TimeUnit arg1) {
279 			try {
280 				doneLatch.await(arg0, arg1);
281 			} catch (InterruptedException e) {
282 				new PogamutInterruptedException("Interrupted while waiting for the map change to finish.", e);
283 			}			
284 			return success;
285 		}
286 
287 		@Override
288 		public boolean isCancelled() {
289 			return canceled;
290 		}
291 
292 		@Override
293 		public boolean isDone() {
294 			return doneLatch.getCount() <= 0;
295 		}
296 		
297 	}
298 
299 	private class MapChangeRestartServerJob extends Job<Boolean> {
300 		
301 		@Override
302 		protected void job() throws Exception {
303 			try {
304 				UDKServer.this.stop();
305 			} catch (Exception e) {
306 				UDKServer.this.kill();
307 			}
308 			try {
309 				Thread.sleep(MAP_CHANGE_CONNECT_INTERVAL_MILLIS);
310 			} catch (Exception e) {									
311 			}
312 			UDKServer.this.start();
313 			setResult(true);
314 		}
315 			
316 	};
317 }