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 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
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
168
169
170
171
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
339
340 public void connectNativeBot(String botName, String botType) {
341 super.connectNativeBot(botName, botType, 0);
342 };
343 }