View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.agent.module.sensor.visibility;
2   
3   import java.io.File;
4   import java.util.ArrayList;
5   import java.util.HashSet;
6   import java.util.List;
7   import java.util.Set;
8   import java.util.concurrent.ConcurrentLinkedQueue;
9   import java.util.concurrent.LinkedBlockingQueue;
10  import java.util.concurrent.RejectedExecutionException;
11  import java.util.concurrent.ThreadPoolExecutor;
12  import java.util.concurrent.TimeUnit;
13  import java.util.logging.Level;
14  
15  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateUp;
16  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
17  import cz.cuni.amis.pogamut.base.component.bus.event.BusAwareCountDownLatch;
18  import cz.cuni.amis.pogamut.base.utils.Pogamut;
19  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
20  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
21  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.visibility.model.VisibilityLocation;
22  import cz.cuni.amis.pogamut.ut2004.agent.module.sensor.visibility.model.VisibilityMatrix;
23  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.FastTrace;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.GetAllInvetories;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.GetAllNavPoints;
26  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.FastTraceResponse;
27  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPoint;
28  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.NavPointNeighbourLink;
29  import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.MapPointListObtained;
30  import cz.cuni.amis.pogamut.ut2004.factory.guice.remoteagent.UT2004ServerFactory;
31  import cz.cuni.amis.pogamut.ut2004.factory.guice.remoteagent.UT2004ServerModule;
32  import cz.cuni.amis.pogamut.ut2004.server.impl.UT2004Server;
33  import cz.cuni.amis.pogamut.ut2004.utils.UT2004ServerRunner;
34  import cz.cuni.amis.utils.StopWatch;
35  import cz.cuni.amis.utils.Tuple3;
36  import cz.cuni.amis.utils.flag.FlagInteger;
37  import cz.cuni.amis.utils.maps.MapWithKeyListeners;
38  import cz.cuni.amis.utils.maps.MapWithKeyListeners.KeyCreatedEvent;
39  import cz.cuni.amis.utils.token.Token;
40  import cz.cuni.amis.utils.token.Tokens;
41  
42  /**
43   * Object that is used to extract visibility information out of any UT2004 map via GameBots2004.
44   * 
45   * Just fire-up GB2004 server (having BIG NUMBER for time level in GameBots2004.ini)
46   * and executes {@link VisibilityCreator#main(String[])}.
47   * 
48   * @author Jimmy
49   */
50  public class VisibilityCreator {
51  	
52  	/**
53  	 * Min distance between {@link VisibilityLocation}s on one {@link NavPointNeighbourLink}.
54  	 */
55  	public static final int MATRIX_DENSITY = 100;
56  
57  	/**
58  	 * Second trace delta.
59  	 */
60  	public static final Location SECOND_TRACE_DELTA = new Location(0, 0, 50);
61  
62  	/**
63  	 * How many threads/fast traces to use for visibility-checking.
64  	 * Note that 10 is reasonable count, UT2004 server won't answer more parallel requests anyway.
65  	 */
66  	public static final int THREAD_COUNT = 10;
67  	
68  	private UT2004Server server;
69  	
70  	private LogCategory log;
71  
72  	public VisibilityCreator() {
73  		UT2004ServerModule serverModule = new UT2004ServerModule();
74  		UT2004ServerFactory serverFactory = new UT2004ServerFactory(serverModule);
75  		UT2004ServerRunner serverRunner = new UT2004ServerRunner(serverFactory);
76  		this.server = (UT2004Server) serverRunner.startAgent();	
77  		this.log = server.getLogger().getCategory(getClass().getSimpleName());
78  		this.log.setLevel(Level.INFO);
79  	}
80  	
81  	public VisibilityCreator(UT2004Server server) {
82  		this.server = server;
83  		this.log = server.getLogger().getCategory(getClass().getSimpleName());
84  		this.log.setLevel(Level.INFO);
85  	}
86  
87  	public UT2004Server getServer() {
88  		return server;
89  	}
90  
91  	public LogCategory getLog() {
92  		return log;
93  	}
94  
95  	public synchronized VisibilityMatrix create() {
96  		
97  		log.info("CREATING VISIBILITY MATRIX");
98  		
99  		StopWatch watch = new StopWatch();		
100 		
101 		checkServer();
102 		
103 		log.info("Time: " + watch.checkStr());
104 		
105 		MapPointListObtained mapPoints = obtainMapPoints();
106 		
107 		log.info("Time: " + watch.checkStr());
108 		
109 		List<Tuple3<Location, NavPoint, NavPointNeighbourLink>> locations = generateLocations(mapPoints);
110 		
111 		log.info("Time: " + watch.checkStr());
112 		
113 		VisibilityMatrix matrix = precreateMatrix(locations);
114 		
115 		log.info("Time: " + watch.checkStr());
116 		
117 		fillMatrix(matrix);
118 		
119 		log.info("Time: " + watch.checkStr());
120 		
121 		log.info("Visibility stats: " + matrix.getVisiblePairCount() + " / " + matrix.getPairCount() + " visible pairs");
122 		
123 		return matrix;
124 	}
125 	
126 	public VisibilityMatrix createAndSave(File targetDirectory) {
127 		VisibilityMatrix matrix = create();
128 		
129 		log.info("SAVING VISIBILITY MATRIX");
130 		
131 		StopWatch watch = new StopWatch();
132 		
133 		save(matrix, targetDirectory);
134 		
135 		log.info("Time: " + watch.checkStr());
136 		
137 		return matrix;
138 	}
139 		
140 	//
141 	// CHECKING SERVER
142 	//
143 
144 	private void checkServer() {
145 		log.info("Checking server...");
146 		if (!server.inState(IAgentStateUp.class)){
147 			log.info("Server is not running, starting server...");
148 			server.start();
149 			log.info("Server started.");
150 		} else {
151 			log.info("Server running.");
152 		}
153 		log.info("Map name: " + server.getMapName());
154 	}
155 
156 	//
157 	// OBTAINING NAVPOINTS
158 	//
159 	
160 	private MapPointListObtained mapPointsTemp;
161 	private BusAwareCountDownLatch mapPointsLatch;
162 	
163 	private IWorldEventListener<MapPointListObtained> mapPointsListener = new IWorldEventListener<MapPointListObtained>() {
164 
165 		@Override
166 		public void notify(MapPointListObtained event) {
167 			VisibilityCreator.this.mapPointsTemp = event;
168 			server.getWorldView().removeEventListener(MapPointListObtained.class, this);
169 			mapPointsLatch.countDown();
170 		}
171 		
172 	};
173 
174 	private MapPointListObtained obtainMapPoints() {
175 		log.info("Getting navpoints...");
176 		
177 		mapPointsLatch = new BusAwareCountDownLatch(1, server.getEventBus(), server.getWorldView());		
178 		server.getWorldView().addEventListener(MapPointListObtained.class, mapPointsListener);
179 		server.getAct().act(new GetAllNavPoints());
180 		server.getAct().act(new GetAllInvetories());
181 		
182 		mapPointsLatch.await();
183 		mapPointsLatch = null;
184 		
185 		log.info("Navpoints obtained, total " + mapPointsTemp.getNavPoints().size() + " navpoints in map.");
186 		
187 		return mapPointsTemp;
188 	}
189 
190 	// 
191 	// GENERATING LOCATIONS
192 	// 
193 	
194 	private List<Tuple3<Location, NavPoint, NavPointNeighbourLink>> generateLocations(MapPointListObtained mapPoints) {
195 		log.info("Generating visibility locations...");
196 		
197 		if (mapPoints.getNavPoints() == null || mapPoints.getNavPoints().size() == 0) {
198 			throw new RuntimeException("No navpoints in map?");
199 		}
200 		
201 		Set<NavPoint> finished = new HashSet<NavPoint>();
202 		
203 		Set<NavPoint> pending = new HashSet<NavPoint>();
204 		pending.addAll(mapPoints.getNavPoints().values());
205 		
206 		List<Tuple3<Location, NavPoint, NavPointNeighbourLink>> result = new ArrayList<Tuple3<Location, NavPoint, NavPointNeighbourLink>>(mapPoints.getNavPoints().size()*3);
207 		
208 		while (pending.size() > 0) {
209 			NavPoint from = pending.iterator().next();
210 			pending.remove(from);
211 			
212 			// ADD NAVPOINT as VISIBILITY POINT 
213 			result.add(new Tuple3<Location, NavPoint, NavPointNeighbourLink>(from.getLocation(), from, null));
214 			
215 			for (NavPointNeighbourLink link : from.getOutgoingEdges().values()) {
216 				NavPoint to = link.getToNavPoint();
217 				if (finished.contains(to)) continue;
218 				
219 				List<Location> locations = getLocationsBetween(from, to);				
220 				for (Location location : locations) {
221 					result.add(new Tuple3<Location, NavPoint, NavPointNeighbourLink>(location, null, link));
222 				}
223 			}				
224 			
225 			finished.add(from);
226 		}
227 		
228 		log.info("Done, generated " + (result.size()) + " unique locations for visibility matrix.");
229 		log.info("Matrix " + result.size() + "x" + result.size() + " == " + (result.size()*result.size()) + " bits == " + (result.size()*result.size()/8) + " bytes.");
230 		
231 		return result;
232 	}
233 	
234 	/**
235 	 * Add locations between 'from' and 'to' ... excluding points 'from' and 'to'.
236 	 * @param from
237 	 * @param to
238 	 * @return
239 	 */
240 	private List<Location> getLocationsBetween(NavPoint from, NavPoint to) {
241 		double distance = from.getLocation().getDistance(to.getLocation());
242 		int parts = ((int)Math.round(distance)) / MATRIX_DENSITY;
243 		
244 		if (parts <= 0) {
245 			// NAVPOINTS ARE TOO CLOSE TO EACH OTHER
246 			return new ArrayList<Location>(0);
247 		}
248 		
249 		double oneLength = distance / ((double)parts);
250 		Location vector = to.getLocation().sub(from.getLocation()).getNormalized().scale(oneLength);
251 		
252 		List<Location> result = new ArrayList<Location>(parts-1);
253 		
254 		// DO NOT ADD "from" LOCATION as that is added separately in generateLocations()
255 		//result.add(from.getLocation());
256 		
257 		// ADD PARTS IN-BETWEEN from / to
258 		for (int i = 1; i < parts; ++i) {
259 			result.add(from.getLocation().add(vector.scale(i)));
260 		}
261 		
262 		// DO NOT ADD "to" LOCATION as that will be added when "TO" navpoint will be examined
263 		//result.add(to.getLocation());
264 		
265 		return result;
266 	}
267 
268 	//
269 	// CREATING MATRIX
270 	//
271 	
272 	private VisibilityMatrix precreateMatrix(List<Tuple3<Location, NavPoint, NavPointNeighbourLink>> locations) {
273 		
274 		log.info("Pre-creating visibility matrix, filling visibility-locations...");
275 		
276 		VisibilityMatrix result = new VisibilityMatrix(server.getMapName(), locations.size());
277 		
278 		int i = 0;
279 		for (Tuple3<Location, NavPoint, NavPointNeighbourLink> location : locations) {
280 			Location loc = location.getFirst();
281 			NavPoint np = location.getSecond();
282 			NavPointNeighbourLink link = location.getThird();
283 			
284 			VisibilityLocation vLoc = new VisibilityLocation();
285 			vLoc.x = loc.x;
286 			vLoc.y = loc.y;
287 			vLoc.z = loc.z;
288 			
289 			if (np != null) {
290 				vLoc.navPoint = np;
291 				vLoc.navPoint1Id = getNavPointId(server.getMapName(), np);
292 			} else
293 			if (link != null) {
294 				vLoc.link = link;
295 				vLoc.navPoint1Id = getNavPointId(server.getMapName(), link.getFromNavPoint());
296 				vLoc.navPoint2Id = getNavPointId(server.getMapName(), link.getToNavPoint());
297 			} else {
298 				throw new RuntimeException("Tuple3 has neither NavPoint nor Link information, invalid.");
299 			}
300 			
301 			result.getLocations().put(i, vLoc);
302 			
303 			++i;
304 		}
305 		
306 		log.info("Visibility matrix pre-created.");
307 		
308 		return result;
309 	}
310 
311 	private String getNavPointId(String mapName, NavPoint np) {
312 		String id = np.getId().getStringId();
313 		String result = id.substring(mapName.length()+1); 
314 		return result;
315 	}
316 	
317 	//
318 	// FILLING MATRIX
319 	// 
320 	
321 	private int totalRaycast;
322 	private int jobsGenerated;
323 	private FlagInteger jobsCompleted = new FlagInteger(0);
324 	private MapWithKeyListeners<Token, FastTraceResponse> fastTraceResponses;
325 	private VisibilityMatrix matrix;
326 	
327 	private void fillMatrix(VisibilityMatrix matrix) {
328 		
329 		log.info("Gathering visibility information...");
330 		
331 		this.matrix = matrix; 
332 		jobsGenerated = 0;
333 		jobsCompleted.setFlag(0);
334 		fastTraceResponses = new MapWithKeyListeners<Token, FastTraceResponse>();
335 		
336 		totalRaycast = (matrix.getLocations().size()) * (matrix.getLocations().size()-1) / 2;
337 		
338 		log.info("Estimated number of raycasts to perform: " + totalRaycast);
339 
340 		log.info("Registering FastTraceResponse listener...");
341 		server.getWorldView().addEventListener(FastTraceResponse.class, fastTraceListener);
342 		
343 		try {
344 		
345 			log.info("Starting thread pool executor...");
346 			
347 			ThreadPoolExecutor executor = new ThreadPoolExecutor(THREAD_COUNT, THREAD_COUNT, 5000, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(1000));
348 			
349 			try {
350 				log.info("Generating jobs...");
351 				
352 				int locations = matrix.getLocations().size();
353 				for (int i = 0; i < locations; ++i) {
354 					
355 					synchronized(matrix) {
356 						matrix.getMatrix().set(i, i); // DIAGONAL
357 					}
358 					
359 					for (int j = i+1; j < locations; ++j) {
360 						TraceJob job = new TraceJob(i, matrix.getLocation(i), j, matrix.getLocation(j));	
361 						++jobsGenerated;
362 						if (jobsGenerated % 1000 == 0) {
363 							log.info("Generated " + jobsGenerated + " / " + totalRaycast + " jobs generated...");
364 						}
365 						
366 						// SUBMIT THE JOB
367 						while (true) {
368 							try {
369 								executor.execute(job);
370 								// JOB ACCEPTED
371 								break;
372 							} catch (RejectedExecutionException e1) {
373 								// BUFFER IS FULL 
374 								try {
375 									Thread.sleep(5000);
376 								} catch (InterruptedException e2) {
377 									throw new RuntimeException("Interrupted while asleep.", e2);
378 								}
379 							} 
380 						}	
381 					}
382 				}
383 				
384 				log.info("Generated all " + jobsGenerated + " jobs, waiting their completion...");
385 				
386 				jobsCompleted.waitFor(new Integer[]{jobsGenerated});
387 				
388 				log.info("All " + jobsGenerated + " jobs finished!");
389 				
390 			} finally {
391 				executor.shutdownNow();
392 			}
393 			
394 		} finally {
395 			try {
396 				log.info("Removing FastTraceResponse listener...");
397 			} finally {
398 				try {
399 					server.getWorldView().removeEventListener(FastTraceResponse.class, fastTraceListener);
400 				} finally {
401 					this.matrix = null;
402 				}
403 			}
404 		}
405 		
406 		log.info("Visibility information gathered.");	
407 	}
408 	
409 	private class TraceJob implements Runnable {
410 		
411 		private VisibilityLocation from;
412 		private VisibilityLocation to;
413 		private int indexFrom;
414 		private int indexTo;
415 
416 		public TraceJob(int indexFrom, VisibilityLocation from, int indexTo,  VisibilityLocation to) {
417 			this.indexFrom = indexFrom;
418 			this.from = from;
419 			this.indexTo = indexTo;
420 			this.to = to;
421 		}
422 		
423 		@Override
424 		public void run() {
425 			// SET AS NOT-VISIBLE
426 			synchronized(matrix) {
427 				matrix.getMatrix().unset(indexFrom, indexTo);
428 				matrix.getMatrix().unset(indexTo, indexFrom);
429 			}
430 			
431 			final Token id1 = getNextFastTraceId();
432 			final Token id2 = getNextFastTraceId();
433 			
434 			final BusAwareCountDownLatch latch = new BusAwareCountDownLatch(2, server.getEventBus(), server.getWorldView()); 
435 			
436 			final FastTrace fastTrace1 = new FastTrace().setId(id1.getToken()).setFrom(from.getLocation()).setTo(to.getLocation());
437 			final FastTrace fastTrace2 = new FastTrace().setId(id2.getToken()).setFrom(from.getLocation().add(SECOND_TRACE_DELTA)).setTo(to.getLocation().add(SECOND_TRACE_DELTA));
438 			
439 			MapWithKeyListeners.IKeyCreatedListener<Token, FastTraceResponse> listener = new MapWithKeyListeners.IKeyCreatedListener<Token, FastTraceResponse>() {
440 				@Override
441 				public void notify(KeyCreatedEvent<Token, FastTraceResponse> event) {
442 					synchronized(fastTraceResponses) {
443 						fastTraceResponses.remove(event.getKey());
444 					}
445 					if (!event.getValue().isResult()) {
446 						synchronized(matrix) {
447 							// no hit == VISIBLE!!!
448 							matrix.getMatrix().set(indexFrom, indexTo);
449 							matrix.getMatrix().set(indexTo, indexFrom);
450 						}
451 						while (latch.getCount() > 0) latch.countDown();
452 					} else {
453 						latch.countDown();
454 						if (latch.getCount() > 0) {
455 							// try to perform second trace to validate NON-VISIBILITY
456 							server.getAct().act(fastTrace2);
457 						}
458 					}					
459 				}
460 			};
461 			
462 			fastTraceResponses.addWeakListener(id1, listener);
463 			fastTraceResponses.addWeakListener(id2, listener);
464 			
465 			// TRACE 1 ... second one is made only if first indicates "not visible"
466 			server.getAct().act(fastTrace1);			
467 			
468 			latch.await();
469 			
470 			fastTraceResponses.removeListener(id1, listener);
471 			fastTraceResponses.removeListener(id2, listener);
472 			
473 			returnId(id1);
474 			returnId(id2);
475 			
476 			jobsCompleted.increment(1);
477 			int num = jobsCompleted.getFlag();
478 			if (num % 50 == 0) {
479 				log.info("Raycast " + num + " / " + totalRaycast);
480 			}
481 			if (num % 1000 == 0) {
482 				log.info("Existing IDs: " + nextId);
483 			}
484 		}
485 		
486 	}
487 
488 	private IWorldEventListener<FastTraceResponse> fastTraceListener = new IWorldEventListener<FastTraceResponse>() {
489 
490 		@Override
491 		public void notify(FastTraceResponse event) {
492 			fastTraceResponses.put(Tokens.get(event.getId()), event);			
493 		}
494 		
495 	};
496 	
497 	private Object nextIdMutex = new Object();
498 	private int nextId = 0;
499 	private ConcurrentLinkedQueue<Token> availableTokens = new ConcurrentLinkedQueue<Token>();
500 	
501 	private Token getNextFastTraceId() {
502 		try {
503 			if (availableTokens.size() > 0) {
504 				return availableTokens.remove();
505 			}
506 		} catch (Exception e) {			
507 		}		
508 		int myId;
509 		synchronized(nextIdMutex) {
510 			myId = nextId++;			
511 		}
512 		return Tokens.get("FTID-" + myId);
513 	}
514 	
515 	private void returnId(Token token) {
516 		availableTokens.add(token);
517 	}
518 	
519 	//
520 	// SAVING MATRIX
521 	//
522 	
523 	private void save(VisibilityMatrix matrix, File targetDirectory) {
524 		
525 		log.info("Saving visibility matrix into: " + targetDirectory.getAbsolutePath());
526 		
527 		if (targetDirectory.exists()) {
528 			if (targetDirectory.isFile()) {
529 				throw new RuntimeException("'targetDirectory' points to " + targetDirectory.getAbsolutePath() + " which is FILE not DIRECTORY");
530 			} else
531 			if (!targetDirectory.isDirectory()) {
532 				throw new RuntimeException("'targetDirectory' points to " + targetDirectory.getAbsolutePath() + " which is not DIRECTORY");
533 			}
534 		} else {
535 			if (!targetDirectory.mkdirs()) {
536 				throw new RuntimeException("Failed to create 'targetDirectory' -> " + targetDirectory.getAbsolutePath());
537 			}
538 		}
539 		
540 		matrix.save(targetDirectory);
541 		
542 		log.info("Visibility matrix saved.");
543 	}
544 
545 	/**
546 	 * Initializes {@link VisibilityCreator}, connects it to the localhost:3001 (localhost GB2004 server connection)
547 	 * and calls {@link VisibilityCreator#createAndSave(File)} method. Note that it may take HUGE AMOUNT of time
548 	 * to fill up visibility matrix for given level. 
549 	 * 
550 	 * Be sure to have "map time" in GameBots2004.ini set to BIG NUMBER.
551 	 * 
552 	 * @param args
553 	 */
554 	public static void main(String[] args) {
555 		VisibilityCreator creator = new VisibilityCreator();
556 		try {
557 			creator.createAndSave(new File("."));
558 		} finally {
559 			try {
560 				creator.getServer().stop();
561 			} finally {
562 				Pogamut.getPlatform().close();
563 			}
564 		}
565 	}
566 	
567 }