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
41
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
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 getWorldView().addEventListener(MutatorListObtained.class, new IWorldEventListener<MutatorListObtained>() {
80
81 public void notify(MutatorListObtained event) {
82 mutators = event.getMutators();
83 }
84 });
85
86
87
88 gameSpeed.addListener(new FlagListener<Double>() {
89
90 public void flagChanged(Double changedValue) {
91 getAct().act(new SetGameSpeed(changedValue));
92 }
93 });
94
95
96 getWorldView().addEventListener(MapListObtained.class, mapListListener = new IWorldEventListener<MapListObtained>() {
97
98 public void notify(MapListObtained event) {
99 maps = event.getMaps();
100
101 getAct().act(new StartPlayers(true, true, true));
102
103 mapLatch.countDown();
104 }
105
106 });
107
108 }
109
110
111
112
113
114
115
116
117
118 public UT2004AgentParameters getParams() {
119 return params;
120 }
121
122
123
124
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
161
162
163
164
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
332
333 public void connectNativeBot(String botName, String botType) {
334 super.connectNativeBot(botName, botType, 0);
335 };
336 }