View Javadoc

1   package cz.cuni.amis.pogamut.ut2004.bot.impl;
2   
3   import java.util.concurrent.TimeUnit;
4   import java.util.logging.Level;
5   import java.util.logging.Logger;
6   
7   import javax.management.InstanceAlreadyExistsException;
8   import javax.management.MBeanRegistrationException;
9   import javax.management.MBeanServer;
10  import javax.management.MalformedObjectNameException;
11  import javax.management.NotCompliantMBeanException;
12  import javax.management.ObjectName;
13  
14  import com.google.inject.Inject;
15  
16  import cz.cuni.amis.introspection.Folder;
17  import cz.cuni.amis.introspection.java.ReflectionObjectFolder;
18  import cz.cuni.amis.pogamut.base.agent.IAgentId;
19  import cz.cuni.amis.pogamut.base.agent.exceptions.AgentException;
20  import cz.cuni.amis.pogamut.base.agent.impl.AbstractAgent;
21  import cz.cuni.amis.pogamut.base.agent.jmx.AgentJMXComponents;
22  import cz.cuni.amis.pogamut.base.agent.jmx.adapter.AgentMBeanAdapter;
23  import cz.cuni.amis.pogamut.base.communication.command.IAct;
24  import cz.cuni.amis.pogamut.base.communication.worldview.IWorldView;
25  import cz.cuni.amis.pogamut.base.communication.worldview.event.IWorldEventListener;
26  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
27  import cz.cuni.amis.pogamut.base.communication.worldview.object.event.WorldObjectUpdatedEvent;
28  import cz.cuni.amis.pogamut.base.communication.worldview.react.EventReact;
29  import cz.cuni.amis.pogamut.base.component.bus.IComponentBus;
30  import cz.cuni.amis.pogamut.base.component.bus.event.BusAwareCountDownLatch;
31  import cz.cuni.amis.pogamut.base.component.exception.ComponentCantStartException;
32  import cz.cuni.amis.pogamut.base.utils.guice.AgentScoped;
33  import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
34  import cz.cuni.amis.pogamut.base3d.agent.AbstractAgent3D;
35  import cz.cuni.amis.pogamut.base3d.worldview.IVisionWorldView;
36  import cz.cuni.amis.pogamut.base3d.worldview.object.Location;
37  import cz.cuni.amis.pogamut.base3d.worldview.object.Rotation;
38  import cz.cuni.amis.pogamut.base3d.worldview.object.Velocity;
39  import cz.cuni.amis.pogamut.ut2004.bot.IUT2004Bot;
40  import cz.cuni.amis.pogamut.ut2004.bot.IUT2004BotController;
41  import cz.cuni.amis.pogamut.ut2004.bot.jmx.BotJMXMBeanAdapter;
42  import cz.cuni.amis.pogamut.ut2004.bot.params.UT2004BotParameters;
43  import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateHelloBotReceived;
44  import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateInited;
45  import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStatePassword;
46  import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateSendingInit;
47  import cz.cuni.amis.pogamut.ut2004.bot.state.impl.BotStateSpawned;
48  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Configuration;
49  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.DisconnectBot;
50  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Initialize;
51  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.PasswordReply;
52  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Ready;
53  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Respawn;
54  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.BotKilled;
55  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.ConfigChange;
56  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.EndMessage;
57  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.GameInfo;
58  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.HelloBotHandshake;
59  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.InitedMessage;
60  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Password;
61  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.Self;
62  import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.InitCommandRequest;
63  import cz.cuni.amis.pogamut.ut2004.communication.translator.shared.events.ReadyCommandRequest;
64  import cz.cuni.amis.utils.ExceptionToString;
65  import cz.cuni.amis.utils.NullCheck;
66  import cz.cuni.amis.utils.exception.PogamutException;
67  import cz.cuni.amis.utils.exception.PogamutJMXException;
68  
69  /**
70   * Ancestor of all UT2004 bots.
71   * <p><p>
72   * TODO: [comment me!]
73   *
74   * @author Jimmy
75   */
76  @AgentScoped
77  public class UT2004Bot<WORLD_VIEW extends IVisionWorldView, ACT extends IAct, CONTROLLER extends IUT2004BotController> extends AbstractAgent3D<WORLD_VIEW, ACT> implements IUT2004Bot {
78  
79      /**
80       * If specified - used for the construction of the PasswordReply in createPasswordReply() method.
81       */
82  	private CONTROLLER controller;
83  	
84  	/**
85  	 * Latch that is raised when {@link InitedMessage} comes.
86  	 */
87  	private BusAwareCountDownLatch endMessageLatch;
88  
89  	/**
90  	 * Whether the {@link IUT2004BotController#botStopped()} was called during the running-phase of the agent.
91  	 */
92  	private boolean botStoppedCalled = false;
93  	
94  	private EventReact<HelloBotHandshake> helloBotReaction;
95  
96  	/**
97  	 * Parameters passed into the constructor/factory/runner (by whatever means the agent has been started).
98  	 */
99  	private UT2004BotParameters params;
100 
101 	/**
102 	 * 
103 	 * @param agentId
104 	 * @param eventBus
105 	 * @param logger
106 	 * @param worldView due to Guice nature, this can't be templated with WORLD_VIEW - Guice can't use it as a key for the injection
107 	 * @param act due to Guice nature, this can't be templated with ACT - Guice can't use it as a key for the injection
108 	 * @param init due to Guice nature, this can't be templated with CONTROLLER - Guice can't use it as a key for the injection
109 	 */
110     @Inject
111     public UT2004Bot(UT2004BotParameters parameters, IComponentBus eventBus, IAgentLogger logger, IWorldView worldView, IAct act, IUT2004BotController init) {
112         super(parameters.getAgentId(), eventBus, logger, (WORLD_VIEW)worldView, (ACT)act);
113 
114         this.params = parameters;
115         this.controller = (CONTROLLER) init;
116         NullCheck.check(this.controller, "init");
117         if (log.isLoggable(Level.FINER)) log.finer("Initializing the controller...");
118         this.controller.initializeController(this);
119         if (log.isLoggable(Level.FINER)) log.finer("Preparing the controller...");
120         this.controller.prepareBot(this);
121         if (log.isLoggable(Level.FINE)) log.fine("Controller initialized.");
122         
123         helloBotReaction = new EventReact<HelloBotHandshake>(HelloBotHandshake.class, worldView) {
124 			@Override
125 			protected void react(HelloBotHandshake event) {
126 				if (event.isServerFull()) throw new ComponentCantStartException("Server is full.", UT2004Bot.this);
127 			}
128         };
129         
130         getWorldView().addEventListener(ReadyCommandRequest.class, readyCommandRequestListener);
131         getWorldView().addEventListener(InitCommandRequest.class, initCommandRequestListener);
132         getWorldView().addEventListener(Password.class, passwordRequestedListener);
133         getWorldView().addObjectListener(InitedMessage.class, WorldObjectUpdatedEvent.class, initedMessageListener);
134         getWorldView().addEventListener(BotKilled.class, killedListener);
135         // endListener must be attached inside startAgent() as it is removed from the worldview in the end
136         // and we want the bot to be restartable
137         
138         endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());        
139     }
140     
141     /**
142      * Returns the bot controller passed inside {@link UT2004Bot#AbstractUT2004Bot(IAgentId, IComponentBus, IAgentLogger, IVisionWorldView, IAct, IUT2004BotInitialization)}.
143      * @return
144      */
145     public CONTROLLER getController() {
146     	return controller;
147     }
148     
149     /**
150      * Returns parameters that were passed into the agent during the construction. 
151      * <p><p>
152      * This is a great place to parametrize your agent. Note that you may pass arbitrary subclass of {@link UT2004BotParameters}
153      * to the constructor/factory/runner and pick them up here.
154      * 
155      * @return parameters
156      */
157     public UT2004BotParameters getParams() {
158 		return params;
159 	}
160     
161     ////////  
162     //
163     // BOT CONTROL METHODS
164     //
165     ////////    
166 
167 	@Override
168 	protected void startAgent() {
169     	botStoppedCalled = false;
170     	super.startAgent();
171     	getWorldView().addEventListener(EndMessage.class, endListener);
172    		if (log.isLoggable(Level.INFO)) log.info("Waiting for the handshake to finish for 60s.");
173 		if (!endMessageLatch.await(60000, TimeUnit.MILLISECONDS)) {
174 			throw new ComponentCantStartException("The bot did not received first EndMessage in 60 seconds.", this);
175 		}
176 		if (log.isLoggable(Level.INFO)) log.info("Handshake finished.");
177     }
178 	
179 	@Override
180 	protected void startPausedAgent() {
181 		botStoppedCalled = false;
182 		super.startPausedAgent();
183 		getWorldView().addEventListener(EndMessage.class, endListener);
184    		if (log.isLoggable(Level.INFO)) log.info("Waiting for the handshake to finish for 60s.");
185 		if (!endMessageLatch.await(60000, TimeUnit.MILLISECONDS)) {
186 			throw new ComponentCantStartException("The bot did not received first EndMessage in 60 seconds.", this);
187 		}
188 		if (log.isLoggable(Level.INFO)) log.info("Handshake finished.");
189 	}
190     
191 	@Override
192 	protected void preStopAgent() {
193 		super.preStopAgent();
194 		try {
195 			tryDisconnect();
196     	} catch (Exception e) {
197 		}
198 	}
199 	
200     @Override
201     protected void stopAgent() {
202     	try {
203 	    	if (!botStoppedCalled) {
204 	    		botStoppedCalled = true;
205 	    		controller.botShutdown();	    		
206 	    	}	    	
207     	} finally {
208 			try {
209 				removeBotDisconnector();
210 			} finally {
211 				try {
212 					super.stopAgent();
213 				} finally {
214 					endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
215 				}
216 			}
217     	}
218     }
219     
220     @Override
221     protected void preKillAgent() {
222     	super.preKillAgent();
223     	try {
224 			tryDisconnect();
225     	} catch (Exception e) {
226 		}
227     }
228     
229     @Override
230     protected void killAgent() {
231        	try {
232 	    	if (!botStoppedCalled) {
233 	    		botStoppedCalled = true;
234 	    		controller.botShutdown();	    		
235 	    	}
236     	} finally {
237 			try {
238 				removeBotDisconnector();
239 			} finally {
240 				try {
241 					super.killAgent();
242 				} finally {
243 					endMessageLatch = new BusAwareCountDownLatch(1, getEventBus(), getWorldView());
244 				}
245 			}
246     	}
247     }
248     
249     /**
250      * Disconnector thread serves as a last resort for shutting down the bot inside GB2004 in case of JVM failures.
251      */
252     protected Thread botDisconnectorThread;
253     
254     /**
255      * Sends {@link DisconnectBot} commands to GB2004, eats up all exceptions.
256      */
257     protected void tryDisconnect() {
258     	try {
259     		DisconnectBot cmd = new DisconnectBot();
260     		try {
261     			log.info("Sending " + cmd + " to destroy bot inside UT2004.");
262     		} finally {
263     			getAct().act(cmd);
264     			Thread.sleep(1000);    			
265     		}
266     	} catch (Exception e) {
267     		log.warning(ExceptionToString.process("Failed to disconnect the bot, we hope that GB2004 will remove it when the socket gets closed.", e));
268     	}
269     }
270     
271     /**
272      * Initializes & registers {@link UT2004Bot#botDisconnectorThread} as a {@link Runtime#addShutdownHook(Thread)}.
273      */
274     protected void addBotDisconnector() {
275     	if (botDisconnectorThread == null) {
276     		botDisconnectorThread = new Thread(
277     			new Runnable() {
278 					@Override
279 					public void run() {
280 						tryDisconnect();
281 					}    				
282     			},
283     			getName() + "-Disconnector"
284     		);
285     		try {
286     			Runtime.getRuntime().addShutdownHook(botDisconnectorThread);
287     		} catch (Exception e) {
288     			throw new PogamutException("Failed to add BotDisconnectorThread as a JVM shutdown hook.", e, this);
289     		}
290     	}
291     }
292     
293     /**
294      * Removes {@link UT2004Bot#botDisconnectorThread} as a {@link Runtime#removeShutdownHook(Thread)} and nullify the field.
295      */
296     protected void removeBotDisconnector() {
297     	if (botDisconnectorThread != null) {
298     		try {
299     			Runtime.getRuntime().removeShutdownHook(botDisconnectorThread);
300     		} catch (Exception e) {
301     			log.warning(ExceptionToString.process("Failed to remove BotDisconnectorThread as a JVM shutdown hook.", e));
302     		}
303     		botDisconnectorThread = null;
304     	}
305     }
306 
307     // --------------
308     // -=-=-=-=-=-=-=
309     // READY LISTENER
310     // -=-=-=-=-=-=-=
311     // --------------
312     
313     /**
314      * This method is called whenever HelloBot message is parsed - the GameBots2004 is awaiting
315      * the bot to reply with Ready command to begin the handshake.
316      */
317     protected void readyCommandRequested() {
318         getAct().act(new Ready());
319     }
320     
321     /**
322      * Listener that is hooked to WorldView awaiting event ReadyCommandRequest calling
323      * setupWorldViewListeners() and then readyCommandRequested() method upon receiving the event.
324      */
325     private IWorldEventListener<ReadyCommandRequest> readyCommandRequestListener =
326             new IWorldEventListener<ReadyCommandRequest>() {
327 
328                 @Override
329                 public void notify(ReadyCommandRequest event) {
330                 	controller.getLog().setLevel(Level.ALL);
331                 	setState(new BotStateHelloBotReceived("GameBots2004 greeted us, adding custom listeners onto the worldview."));
332                     readyCommandRequested();
333                     setState(new BotStateHelloBotReceived("READY sent, handshaking."));
334                 }
335             };
336             
337     // --------------------
338     // -=-=-=-=-=-=-=-=-=-=
339     // INITIALIZER LISTENER
340     // -=-=-=-=-=-=-=-=-=-=
341     // --------------------
342 
343     /**
344      * This method is called whenever handshake with GameBots2004 is over - the GameBots2004 is awaiting
345      * the bot to reply with Ready command to begin the handshake. It calls setUpInit() method
346      * to obtains Initialize message that is then sent to GameBots2004.
347      * <p><p>
348      * Left as protected if you need to override it - but you probably wouldn't.
349      */
350     protected void initCommandRequested() {
351         Initialize initializeCommand = getController().getInitializeCommand();
352         if (initializeCommand == null) {
353         	throw new AgentException("getBotInit().getInitializeCommand() method returned null message, can't initialize the agent!", log, this);
354         }
355         if(initializeCommand.getName() == null) {
356             // set agent name shown in Unreal
357             initializeCommand.setName(getComponentId().getName().getFlag());
358         } else {
359             // override original name
360             getComponentId().getName().setFlag(initializeCommand.getName());
361         }
362         if (initializeCommand.getTeam() == null) {
363         	initializeCommand.setTeam(params.getTeam());
364         }
365         try {
366             // set the JMX name
367             initializeCommand.setJmx(getJMX().enableJMX());
368         } catch (Exception e) {
369             throw new PogamutJMXException("Error seting up JMX name of the agent.", e, log, this);
370         }
371 
372         getAct().act(initializeCommand);
373     }
374     
375     /**
376      * Listener that is hooked to WorldView awaiting event InitCommandRequest calling
377      * initCommandRequested() method upon receiving the event.
378      */
379     private IWorldEventListener<InitCommandRequest> initCommandRequestListener =
380             new IWorldEventListener<InitCommandRequest>() {
381                 @Override
382                 public void notify(InitCommandRequest event) {
383                 	setState(new BotStateSendingInit("Handshake over, sending INIT."));
384                 	initCommandRequested();
385                 	setState(new BotStateSendingInit("Handshake over, INIT sent."));
386                 }
387             };
388             
389     // -----------------
390     // -=-=-=-=-=-=-=-=-
391     // PASSWORD LISTENER
392     // -=-=-=-=-=-=-=-=-
393     // -----------------
394     
395     /**
396      * Listener that is hooked to WorldView awaiting event InitCommandRequest calling
397      * initCommandRequested() method upon receiving the event.
398      */
399     private IWorldEventListener<Password> passwordRequestedListener =
400             new IWorldEventListener<Password>() {
401                 @Override
402                 public void notify(Password event) {
403                     setState(new BotStatePassword("Password requested by the world."));
404                     PasswordReply passwordReply = getController().getPassword();
405                     if (passwordReply == null) {
406                     	if (log.isLoggable(Level.WARNING)) log.warning("createPasswordReply() returned null");
407                         passwordReply = new PasswordReply("");
408                     }
409                     if (log.isLoggable(Level.INFO)) log.info("Password required for the world, replying with '" + passwordReply.getPassword() + "'.");
410                     getAct().act(passwordReply);
411                     setState(new BotStatePassword("Password sent."));
412                 }
413             };
414 
415     // -----------------------
416     // -=-=-=-=-=-=-=-=-=-=-=-
417     // INITED MESSAGE LISTENER
418     // -=-=-=-=-=-=-=-=-=-=-=-
419     // -----------------------
420     /**
421      * Listener that is hooked to WorldView awaiting event InitedMessage calling
422      * botInitialized method upon receiving the event.
423      */
424     private IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>> initedMessageListener =
425             new IWorldObjectEventListener<InitedMessage, WorldObjectUpdatedEvent<InitedMessage>>() {
426 
427                 @Override
428                 public void notify(WorldObjectUpdatedEvent<InitedMessage> event) {
429                 	setState(new BotStateInited("InitedMessage received, calling botInitialized()."));
430                 	controller.botInitialized(getWorldView().getSingle(GameInfo.class), getWorldView().getSingle(ConfigChange.class), event.getObject());
431                     setState(new BotStateInited("Bot initialized."));                    
432                 }
433             };
434             
435 	// ---------------------------
436 	// -=-=-=-=-=-=-=-=-=-=-=-=-=-
437 	// BOT KILLED MESSAGE LISTENER
438 	// -=-=-=-=-=-=-=-=-=-=-=-=-=-
439 	// ---------------------------
440 	/**
441 	 * Listener that is hooked to WorldView awaiting event {@link BotKilled} calling
442 	 * botKilled method upon receiving the event.
443 	 */
444 	private IWorldEventListener<BotKilled> killedListener =
445 	        new IWorldEventListener<BotKilled>() {
446 	            @Override
447 	            public void notify(BotKilled event) {
448 	                getController().botKilled(event);
449 	            }
450 	        };
451 	        
452     // ------------------
453 	// -=-=-=-=--=-=-=-=-
454 	// FIRST END LISTENER
455 	// -=-=-=-=--=-=-=-=-
456 	// ------------------
457 	private IWorldEventListener<EndMessage> endListener = 
458 			new IWorldEventListener<EndMessage>() {
459 
460 				@Override
461 				public void notify(EndMessage event) {
462 					setState(new BotStateSpawned("First batch of informations received - calling botSpawned()."));
463 					controller.botFirstSpawn(getWorldView().getSingle(GameInfo.class), getWorldView().getSingle(ConfigChange.class), getWorldView().getSingle(InitedMessage.class), getWorldView().getSingle(Self.class));
464 					setState(new BotStateSpawned("botSpawned() finished, finalizing controller initialization..."));					
465 					controller.finishControllerInitialization();
466 					setState(new BotStateSpawned("finishControllerInitialization() finished, UT2004Bot is running."));
467 					getWorldView().removeEventListener(EndMessage.class, this);
468 					endMessageLatch.countDown();
469 				}
470 		
471 	};
472 
473     /**
474      * @return Location of the agent. Null if not set yet.
475      */
476     public Location getLocation() {
477         Self self = getWorldView().getSingle(Self.class);
478         if (self != null) {
479             return self.getLocation();
480         }
481         return null;
482     }
483 
484     /**
485      * @return Rotation of the agent. Null if not set yet.
486      */
487     public Rotation getRotation() {
488         Self self = getWorldView().getSingle(Self.class);
489         if (self != null) {
490             return self.getRotation();
491         }
492         return null;
493     }
494 
495     /**
496      * @return Velocity of the agent. Null if not set yet.
497      */
498     public Velocity getVelocity() {
499         Self self = getWorldView().getSingle(Self.class);
500         if (self != null) {
501             return self.getVelocity();
502         }
503         return null;
504     }
505 
506     public void respawn() throws PogamutException {
507         getAct().act(new Respawn());
508     }
509 
510     @Override
511     protected AgentJMXComponents createAgentJMX() {
512         return new AgentJMXComponents<IUT2004Bot>(this) {
513 
514             @Override
515             protected AgentMBeanAdapter createAgentMBean(ObjectName objectName, MBeanServer mbs) throws MalformedObjectNameException, InstanceAlreadyExistsException, InstanceAlreadyExistsException, MBeanRegistrationException, NotCompliantMBeanException {
516                 return new BotJMXMBeanAdapter(UT2004Bot.this, objectName, mbs);
517             }
518         };
519     }
520 
521     public void setBoolConfigure(BoolBotParam param, boolean value) {
522         try {
523             Configuration configuration = new Configuration();
524             // uff, copy all values manually
525             ConfigChange confCh = getWorldView().getSingle(ConfigChange.class);
526             configuration.copy(confCh);
527 
528             param.set(configuration, value);
529             param.setField(confCh, value);
530             getAct().act(configuration);
531         } catch (Exception ex) {
532         	// TODO: jimmy - co to je za logging?! ... mame log.severe() ...
533         	//       a nemel by se volat FATAL ERROR?! ... nebo tu vyjimku propagovat?
534             Logger.getLogger(UT2004Bot.class.getName()).log(Level.SEVERE, null, ex);
535         }
536     }
537 
538     public boolean getBoolConfigure(BoolBotParam param) {
539         try {
540             return param.get(getWorldView().getSingle(ConfigChange.class));
541         } catch (Exception ex) {
542         	// TODO: jimmy - co to je za logging?! ... mame log.severe() ...
543         	//     	 a nemel by se volat FATAL ERROR?! ... nebo tu vyjimku propagovat?
544             Logger.getLogger(UT2004Bot.class.getName()).log(Level.SEVERE, null, ex);
545             return false; // TODO
546         }
547     }
548 
549     @Override
550     protected Folder createIntrospection() {
551         return new ReflectionObjectFolder(AbstractAgent.INTROSPECTION_ROOT_NAME, controller);
552     }
553     
554     @Override
555     public WORLD_VIEW getWorldView() {
556     	return super.getWorldView();
557     }
558     
559 }