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