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