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
44
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
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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82 getWorldView().addEventListener(MutatorListObtained.class, new IWorldEventListener<MutatorListObtained>() {
83
84 public void notify(MutatorListObtained event) {
85 mutators = event.getMutators();
86 }
87 });
88
89
90
91 gameSpeed.addListener(new FlagListener<Double>() {
92
93 public void flagChanged(Double changedValue) {
94 getAct().act(new SetGameSpeed(changedValue));
95 }
96 });
97
98
99 getWorldView().addEventListener(MapListObtained.class, mapListListener = new IWorldEventListener<MapListObtained>() {
100
101 public void notify(MapListObtained event) {
102 maps = event.getMaps();
103
104 getAct().act(new StartPlayers(true, true, true));
105
106 mapLatch.countDown();
107 }
108
109 });
110
111 }
112
113
114
115
116
117
118
119
120
121 public UDKAgentParameters getParams() {
122 return params;
123 }
124
125
126
127
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
151
152
153
154
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
164
165
166
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 }