1
2
3
4
5 package cz.cuni.amis.pogamut.udk.utils;
6
7 import java.io.BufferedReader;
8 import java.io.File;
9 import java.io.IOException;
10 import java.io.InputStream;
11 import java.io.InputStreamReader;
12 import java.io.Serializable;
13 import java.util.Timer;
14 import java.util.TimerTask;
15 import java.util.concurrent.CountDownLatch;
16 import java.util.logging.Level;
17 import java.util.logging.Logger;
18 import java.util.regex.Matcher;
19 import java.util.regex.Pattern;
20
21 import cz.cuni.amis.pogamut.base.agent.impl.AgentId;
22 import cz.cuni.amis.pogamut.base.communication.connection.impl.socket.SocketConnectionAddress;
23 import cz.cuni.amis.pogamut.base.utils.Pogamut;
24 import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
25 import cz.cuni.amis.pogamut.base.utils.logging.LogPublisher;
26 import cz.cuni.amis.pogamut.udk.factory.direct.remoteagent.UDKServerFactory;
27 import cz.cuni.amis.pogamut.udk.server.IUDKServer;
28 import cz.cuni.amis.pogamut.udk.server.exception.UCCStartException;
29 import cz.cuni.amis.utils.exception.PogamutException;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63 public class UCCWrapper {
64
65
66
67
68 public static class UCCWrapperConf implements Serializable {
69
70 String mapName = "DM-Deck";
71 String gameBotsPack = "GameBotsUDK";
72 String gameType = "BotDeathMatch";
73 String mutators = "";
74 String options = "";
75 boolean startOnUnusedPort = true;
76 transient Logger log = null;
77
78
79
80
81
82 public UCCWrapperConf setStartOnUnusedPort(boolean startOnUnusedPort) {
83 this.startOnUnusedPort = startOnUnusedPort;
84 return this;
85 }
86
87
88
89
90
91 public UCCWrapperConf setGameBotsPack(String gameBotsPack) {
92 this.gameBotsPack = gameBotsPack;
93 return this;
94 }
95
96 public UCCWrapperConf setMapName(String mapName) {
97 this.mapName = mapName;
98 return this;
99 }
100
101
102
103
104
105 public UCCWrapperConf setGameType(String gameType) {
106 this.gameType = gameType;
107 return this;
108 }
109
110
111
112
113
114 public UCCWrapperConf setOptions(String options) {
115 this.options = options;
116 return this;
117 }
118
119
120
121
122
123 public UCCWrapperConf setLogger(Logger log) {
124 this.log = log;
125 return this;
126 }
127 }
128
129
130 public static final long DEFAULT_START_TIMEOUT = 2 * 60 * 1000;
131
132
133
134 protected LogCategory uccLog;
135 protected static int fileCounter = 0;
136 Process uccProcess = null;
137
138 protected int gbPort = -1;
139
140 protected int controlPort = -1;
141
142 protected int observerPort = -1;
143 protected IUDKServer utServer = null;
144
145 protected static final int basePort = 39782;
146 protected static Integer nextUccWrapperUID = 0;
147
148 protected int uccWrapperUID = 0;
149 protected String unrealHome = null;
150 protected UCCWrapperConf configuration = null;
151
152 protected long startingTimeout;
153
154
155
156
157
158
159
160
161 public Logger getLogger() {
162 return uccLog;
163 }
164
165
166
167
168 public IUDKServer getUTServer() {
169 stopCheck();
170 if (utServer == null) {
171 UDKServerFactory factory = new UDKServerFactory();
172 UDKServerRunner serverRunner = new UDKServerRunner(factory, "NBUTServer", "localhost", controlPort);
173 utServer = serverRunner.startAgent();
174 }
175 return utServer;
176 }
177
178 protected String getUnrealHome() {
179 if (unrealHome == null) {
180 return Pogamut.getPlatform().getProperty(PogamutUDKProperty.POGAMUT_UNREAL_HOME.getKey());
181 } else {
182 return unrealHome;
183 }
184 }
185
186 public UCCWrapper(UCCWrapperConf configuration){
187 this(configuration, DEFAULT_START_TIMEOUT);
188 }
189
190 public UCCWrapper(UCCWrapperConf configuration, long startingTimeout) throws UCCStartException {
191 this.startingTimeout = startingTimeout;
192 uccLog = new LogCategory("Wrapper");
193 uccLog.addHandler(new LogPublisher.ConsolePublisher(new AgentId("UCC")));
194 if (configuration.log != null) {
195 uccLog.setParent(configuration.log);
196 }
197 this.configuration = configuration;
198 uccWrapperUID = nextUccWrapperUID++;
199 initUCCWrapper();
200 Runtime.getRuntime().addShutdownHook(shutDownHook);
201 }
202
203
204
205 Thread shutDownHook = new Thread("UCC wrapper finalizer") {
206
207 @Override
208 public void run() {
209 UCCWrapper.this.stopNoWaiting();
210 }
211 };
212
213 private boolean isWindows() {
214 return System.getProperty("os.name").contains("Windows");
215 }
216
217
218
219
220 protected class StreamSink extends Thread {
221
222 protected InputStream os = null;
223
224 public StreamSink(InputStream os) {
225 setName("UCC Stream handler");
226 this.os = os;
227 }
228
229 protected void handleInput(String str) {
230 if (uccLog.isLoggable(Level.INFO)) uccLog.info("ID" + uccWrapperUID + " " + str);
231 }
232
233 @Override
234 public void run() {
235 BufferedReader stdInput = new BufferedReader(new InputStreamReader(os));
236
237 String s = null;
238 try {
239 while ((s = stdInput.readLine()) != null) {
240 handleInput(s);
241 }
242 os.close();
243 } catch (IOException ex) {
244
245
246
247 }
248 }
249 }
250
251
252
253
254 public class ScannerSink extends StreamSink {
255
256
257 public UCCStartException exception = null;
258
259 public ScannerSink(InputStream is) {
260 super(is);
261 timer.schedule(task = new TimerTask() {
262
263 @Override
264 public void run() {
265 exception = new UCCStartException("Starting timed out. Ports weren't bound in the required time (" + startingTimeout + " ms).", this);
266 timer.cancel();
267 serverStartedLatch.countDown();
268 }
269 }, startingTimeout);
270 }
271 public CountDownLatch serverStartedLatch = new CountDownLatch(1);
272 public int controlPort = -1;
273 public int botsPort = -1;
274
275
276
277
278
279 Timer timer = new Timer("UDK start timeout");
280 TimerTask task = null;
281
282 private final String defaultPatternStart = "";
283 private final Pattern portPattern = Pattern.compile(defaultPatternStart + "BotServerPort:(\\d*) ControlServerPort:(\\d*)");
284 private final Pattern commandletNotFoundPattern = Pattern.compile(defaultPatternStart + "Commandlet server not found");
285 private final Pattern mapNotFoundPattern = Pattern.compile(defaultPatternStart + "No maplist entries found matching the current command line.*");
286 private final Pattern matchStartedPattern = Pattern.compile(defaultPatternStart + "START MATCH");
287
288 @Override
289 protected void handleInput(String str) {
290 super.handleInput(str);
291 if (serverStartedLatch.getCount() != 0) {
292
293
294 Matcher matcher = portPattern.matcher(str);
295 if (matcher.find()) {
296 botsPort = Integer.parseInt(matcher.group(1));
297 controlPort = Integer.parseInt(matcher.group(2));
298
299 }
300
301 matcher = commandletNotFoundPattern.matcher(str);
302 if (matcher.find()) {
303 exception = new UCCStartException("UDK failed to start due to: Commandlet server not found.", this);
304 raiseLatch();
305 }
306
307 matcher = mapNotFoundPattern.matcher(str);
308 if (matcher.find()) {
309 exception = new UCCStartException("UDK failed to start due to: Map not found.", this);
310 raiseLatch();
311 }
312
313 matcher = matchStartedPattern.matcher(str);
314 if (matcher.find()) {
315
316 raiseLatch();
317 }
318 }
319
320 }
321
322 protected void raiseLatch() {
323 timer.cancel();
324 task.cancel();
325 serverStartedLatch.countDown();
326 }
327 }
328 public static long stamp = System.currentTimeMillis();
329
330 private void cleanupAfterException(){
331 if(uccProcess != null){
332 uccProcess.destroy();
333 }
334 }
335
336 protected void initUCCWrapper() throws UCCStartException {
337 try {
338
339 String id = System.currentTimeMillis() + "a" + fileCounter++;
340 String fileWithPorts = "GBports" + id;
341 String udkHomePath = getUnrealHome();
342 String binariesPath = udkHomePath + File.separator + "Binaries";
343
344
345
346 String uccFile = "Win32" + File.separator + "UDK.com";
347 String execStr = binariesPath + File.separator + uccFile;
348
349
350 String postOptions = "";
351 String preOptions = "";
352 if (!isWindows()) {
353 postOptions = " -nohomedir";
354 preOptions = binariesPath + File.separator + "Win32" + File.separator + "UDK.com";
355 execStr = Pogamut.getPlatform().getProperty("WINE", "wine");
356 }
357
358 String portsSetting = configuration.startOnUnusedPort ? "?PortsLog=" + fileWithPorts + "?bRandomPorts=true" : "";
359
360 ProcessBuilder procBuilder = new ProcessBuilder(execStr, preOptions, "server",
361 configuration.mapName
362 + "?game=" + configuration.gameBotsPack + "." + configuration.gameType
363 + portsSetting + configuration.options + postOptions);
364 procBuilder.directory(new File(binariesPath));
365
366 uccProcess = procBuilder.start();
367 ScannerSink scanner = new ScannerSink(uccProcess.getInputStream());
368 scanner.start();
369 new StreamSink(uccProcess.getErrorStream()).start();
370
371 scanner.serverStartedLatch.await();
372 if (scanner.exception != null) {
373
374 uccProcess.destroy();
375 throw scanner.exception;
376 }
377
378 controlPort = scanner.controlPort;
379 gbPort = scanner.botsPort;
380 Thread.sleep(5000);
381 } catch (InterruptedException ex) {
382 cleanupAfterException();
383 throw new UCCStartException("Interrupted.", ex);
384 } catch (IOException ex) {
385 cleanupAfterException();
386 throw new UCCStartException("IO Exception.", ex);
387 }
388 }
389
390
391
392
393
394 public Process getProcess() {
395 return uccProcess;
396 }
397
398 protected boolean stopped = false;
399
400 private synchronized void stopNoWaiting() {
401 if (!isWindows()) {
402
403
404
405 String[] command1 = new String[2];
406 command1[0] = "killall";
407 command1[1] = "UDK.com";
408
409 String[] command2 = new String[2];
410 command2[0] = "killall";
411 command2[1] = "UDK.exe";
412
413 try {
414 Runtime.getRuntime().exec(command1);
415 Runtime.getRuntime().exec(command2);
416 } catch (IOException ex) {
417 uccLog.log(Level.SEVERE, "Could not kill the UDK process: " + ex, ex);
418 }
419 } else {
420
421 String[] command = new String[4];
422 command[0] = "taskkill";
423 command[1] = "/F";
424 command[2] = "/IM";
425 command[3] = "UDK.*";
426
427
428 try {
429 Runtime.getRuntime().exec(command);
430 } catch (IOException ex) {
431 uccLog.log(Level.SEVERE, "Could not kill the UDK with taskkill: " + ex, ex);
432 }
433
434 }
435
436
437
438 if(uccProcess != null){
439
440
441
442
443
444
445
446
447
448
449
450
451 uccProcess.destroy();
452 }
453
454
455 uccProcess = null;
456 Runtime.getRuntime().removeShutdownHook(shutDownHook);
457 }
458
459
460
461
462
463 public synchronized void stop() {
464 stopped = true;
465 if (uccProcess != null) {
466 stopNoWaiting();
467 try {
468 Thread.sleep(1000);
469
470 } catch (InterruptedException e) {
471
472 }
473 }
474 }
475
476
477
478
479 public int getBotPort() {
480 stopCheck();
481 return gbPort;
482 }
483
484
485
486
487 public int getObserverPort() {
488 stopCheck();
489 return observerPort;
490 }
491
492
493
494
495 public int getControlPort() {
496 stopCheck();
497 return controlPort;
498 }
499
500 protected void stopCheck() {
501 if (stopped) {
502 throw new PogamutException("UCC already stopped.", this);
503 }
504 }
505
506 public String getHost() {
507 return "localhost";
508 }
509
510 public SocketConnectionAddress getBotAddress() {
511 return new SocketConnectionAddress(getHost(), getBotPort());
512 }
513
514 public SocketConnectionAddress getServerAddress() {
515 return new SocketConnectionAddress(getHost(), getControlPort());
516 }
517
518 public SocketConnectionAddress getObserverAddress() {
519 return new SocketConnectionAddress(getHost(), getObserverPort());
520 }
521
522 }