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         init();
139     }
140     
141     @Override
142     protected void startPausedAgent() {
143     	super.startPausedAgent();
144     	boolean succeded;
145         if (log.isLoggable(Level.INFO)) log.info("Waiting for the map list to arrive...");
146         succeded = mapLatch.await(60000, TimeUnit.MILLISECONDS);
147         if (!succeded) {
148             throw new ComponentCantStartException("The server did not received maps in 60 seconds.", this);
149         } else {
150         	if (log.isLoggable(Level.INFO)) log.info("Maps received.");
151         }    
152     }
153     
154     /**
155      * Hook for users (descendants of this class) to fill-in initialization code, any listeners hooked here should be removed inside {@link UT2004Server#reset()}.
156      */
157     protected void init() {    	
158     }
159      
160 	protected void reset() {
161     	super.reset();
162     	mapLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
163     }
164 
165     //////
166 	////////
167 	// MAP CHANGING
168 	////////
169 	//////
170 	
171 	// WARNING: very fragile feature - it depends on the UT2004 behavior + exact handling of start/stop that is happening outside UT2004Server object
172 	
173 	protected Object changingMapMutex = new Object();
174 	protected boolean changingMap = false;
175 	protected int changingMapAttempt = 0;
176 	protected String targetMap = null;
177 	protected MapChangeFuture mapChangeFuture = null;
178 	
179 	@Override
180 	public Future<Boolean> setGameMap(String map) throws MapChangeException {
181 		try {
182 			synchronized(changingMapMutex) {
183 				if (!inState(IAgentStateUp.class)) {
184 					throw new MapChangeException("Can't change map as we're not connected to GB2004 server.", this);
185 				}
186 				
187 				if (log.isLoggable(Level.WARNING)) log.warning("Changing map to '" + map + "'");
188 				
189 				WorldEventFuture<MapChange> mapChangeLatch = new WorldEventFuture<MapChange>(getWorldView(), MapChange.class);
190 				changingMap = true;
191 				changingMapAttempt = 0;
192 				targetMap = map;				
193 				mapChangeFuture = new MapChangeFuture();
194 				
195 				getAct().act(new ChangeMap().setMapName(map));
196 				
197 				if (mapChangeLatch.get(20000, TimeUnit.MILLISECONDS) == null) {
198 					throw new MapChangeException("ChangeMap sent but GB2004 failed to response with MapChange message in 20sec.", this);
199 				}				
200 				
201 				return this.mapChangeFuture; 
202 			}			
203 		} catch (Exception e) {
204 			throw new MapChangeException("Can't change map to " + map + ".", e);
205 		}
206 		
207 	}
208 		
209 	public class MapChangeFuture implements Future<Boolean> {
210 
211 		boolean canceled = false;
212 		Boolean success = null;
213 		CountDownLatch doneLatch = new CountDownLatch(1);		
214 		
215 		IAgentState lastState = null;
216 		
217 		FlagListener<IAgentState> listener = new FlagListener<IAgentState>() {
218 			
219 			@Override
220 			public void flagChanged(IAgentState changedValue) {
221 				if (lastState != null && lastState.getClass().isAssignableFrom(changedValue.getClass())) {
222 					return;
223 				}
224 				lastState = changedValue;
225 				if (changedValue instanceof IAgentStateGoingUp) {
226 					++changingMapAttempt;
227 					if (log.isLoggable(Level.WARNING)) log.warning("Map change attempt: " + changingMapAttempt + " / " + MAX_CHANGING_MAP_ATTEMPTS);
228 				} else
229 				if (changedValue instanceof IAgentStateDown) {
230 					if (changingMapAttempt >= MAX_CHANGING_MAP_ATTEMPTS) {
231 						synchronized(changingMapMutex) {
232 							changingMap = false;
233 							changingMapAttempt = 0;
234 							targetMap = null;
235 							mapChangeFuture = null;
236 							
237 							success = false;
238 							doneLatch.countDown();
239 							getState().removeListener(this);
240 						}
241 					} else {
242 						Job<Boolean> restartServer = new MapChangeRestartServerJob();
243 						restartServer.startJob();
244 					}
245 				} else 
246 				if (changedValue instanceof IAgentStateUp) {
247 					if (getMapName() == null || !getMapName().equalsIgnoreCase(targetMap)) {
248 						if (log.isLoggable(Level.WARNING)) log.warning("Reconnected to GB2004 but the map was not changed to '" + targetMap + "' yet.");
249 						Job<Boolean> restartServer = new MapChangeRestartServerJob();
250 						restartServer.startJob();
251 					} else {
252 						success = true;
253 						doneLatch.countDown();
254 						getState().removeListener(this);
255 					}
256 				}
257 				
258 			}
259 			
260 		};
261 		
262 		protected MapChangeFuture() {
263 			getState().addListener(listener);
264 		}
265 		
266 		public void restartServer() {
267 			new MapChangeRestartServerJob().startJob();
268 		}
269 
270 		@Override
271 		public boolean cancel(boolean arg0) {
272 			synchronized(changingMapMutex) {
273 				changingMap = false;
274 				changingMapAttempt = 0;
275 				targetMap = null;
276 				mapChangeFuture = null;
277 				
278 				success = false;
279 				canceled = true;
280 				doneLatch.countDown();
281 			}
282 			return false;
283 		}
284 
285 		@Override
286 		public Boolean get() {
287 			try {
288 				doneLatch.await();
289 			} catch (InterruptedException e) {
290 				new PogamutInterruptedException("Interrupted while waiting for the map change to finish.", e);
291 			}
292 			return success;
293 		}
294 
295 		@Override
296 		public Boolean get(long arg0, TimeUnit arg1) {
297 			try {
298 				doneLatch.await(arg0, arg1);
299 			} catch (InterruptedException e) {
300 				new PogamutInterruptedException("Interrupted while waiting for the map change to finish.", e);
301 			}			
302 			return success;
303 		}
304 
305 		@Override
306 		public boolean isCancelled() {
307 			return canceled;
308 		}
309 
310 		@Override
311 		public boolean isDone() {
312 			return doneLatch.getCount() <= 0;
313 		}
314 		
315 	}
316 
317 	private class MapChangeRestartServerJob extends Job<Boolean> {
318 		
319 		@Override
320 		protected void job() throws Exception {
321 			try {
322 				UT2004Server.this.stop();
323 			} catch (Exception e) {
324 				UT2004Server.this.kill();
325 			}
326 			try {
327 				Thread.sleep(MAP_CHANGE_CONNECT_INTERVAL_MILLIS);
328 			} catch (Exception e) {									
329 			}
330 			UT2004Server.this.start();
331 			setResult(true);
332 		}
333 			
334 	}
335 
336 	@Override
337 	/**
338 	 * <b>WARNING</b> use this only when no team needs to be specified
339 	 */
340 	public void connectNativeBot(String botName, String botType) {
341 		super.connectNativeBot(botName, botType, 0);		
342 	};
343 }