View Javadoc

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