View Javadoc

1   /**
2    * BaseUnrealEnvironment, an implementation of the environment interface standard that 
3    * facilitates the connection between GOAL and the UT2004 engine. 
4    * 
5    * Copyright (C) 2012 BaseUnrealEnvironment authors.
6    * 
7    * This program is free software: you can redistribute it and/or modify it under
8    * the terms of the GNU General Public License as published by the Free Software
9    * Foundation, either version 3 of the License, or (at your option) any later
10   * version.
11   * 
12   * This program is distributed in the hope that it will be useful, but WITHOUT
13   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15   * details.
16   * 
17   * You should have received a copy of the GNU General Public License along with
18   * this program. If not, see <http://www.gnu.org/licenses/>.
19   */
20  package nl.tudelft.goal.unreal.environment;
21  
22  import java.net.URI;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  
28  import nl.tudelft.goal.EIS2Java.environment.AbstractEnvironment;
29  import nl.tudelft.goal.EIS2Java.handlers.ActionHandler;
30  import nl.tudelft.goal.EIS2Java.handlers.AllPerceptPerceptHandler;
31  import nl.tudelft.goal.EIS2Java.handlers.DefaultActionHandler;
32  import nl.tudelft.goal.EIS2Java.handlers.PerceptHandler;
33  import nl.tudelft.goal.EIS2Java.translation.Translator;
34  import nl.tudelft.goal.unreal.messages.BotParameters;
35  import nl.tudelft.goal.unreal.messages.EnvironmentParameters;
36  import nl.tudelft.goal.unreal.messages.Parameters;
37  import nl.tudelft.goal.unreal.translators.AgentIdTranslator;
38  import nl.tudelft.goal.unreal.translators.LevelTranslator;
39  import nl.tudelft.goal.unreal.translators.LocationTranslator;
40  import nl.tudelft.goal.unreal.translators.RotationTranslator;
41  import nl.tudelft.goal.unreal.translators.SkinTranslator;
42  import nl.tudelft.goal.unreal.translators.StringListTranslator;
43  import nl.tudelft.goal.unreal.translators.TeamTranslator;
44  import nl.tudelft.goal.unreal.translators.URITranslator;
45  import nl.tudelft.pogamut.base.server.ReconnectingServerDefinition;
46  import nl.tudelft.pogamut.ut2004.server.UTServerDefinition;
47  import cz.cuni.amis.pogamut.base.agent.IAgentId;
48  import cz.cuni.amis.pogamut.base.agent.exceptions.AgentException;
49  import cz.cuni.amis.pogamut.base.agent.impl.AgentId;
50  import cz.cuni.amis.pogamut.base.agent.state.level0.IAgentState;
51  import cz.cuni.amis.pogamut.base.agent.state.level1.IAgentStateDown;
52  import cz.cuni.amis.pogamut.base.communication.command.IAct;
53  import cz.cuni.amis.pogamut.base.component.IComponent;
54  import cz.cuni.amis.pogamut.base.utils.Pogamut;
55  import cz.cuni.amis.pogamut.base.utils.logging.AgentLogger;
56  import cz.cuni.amis.pogamut.base.utils.logging.IAgentLogger;
57  import cz.cuni.amis.pogamut.base.utils.logging.LogCategory;
58  import cz.cuni.amis.pogamut.base3d.worldview.IVisionWorldView;
59  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004Bot;
60  import cz.cuni.amis.pogamut.ut2004.bot.impl.UT2004BotController;
61  import cz.cuni.amis.pogamut.ut2004.bot.params.UT2004BotParameters;
62  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbcommands.Pause;
63  import cz.cuni.amis.pogamut.ut2004.server.IUT2004Server;
64  import cz.cuni.amis.pogamut.ut2004.utils.UT2004BotRunner;
65  import cz.cuni.amis.utils.exception.PogamutException;
66  import cz.cuni.amis.utils.flag.FlagListener;
67  import eis.exceptions.EntityException;
68  import eis.exceptions.ManagementException;
69  import eis.exceptions.RelationException;
70  import eis.iilang.Action;
71  import eis.iilang.EnvironmentState;
72  import eis.iilang.Parameter;
73  
74  @SuppressWarnings("rawtypes")
75  public abstract class AbstractUnrealEnvironment extends SimpleTransitioningEnvironment implements IComponent {
76  
77  	/**
78  	 * Generated serialVersionUID.
79  	 */
80  	private static final long serialVersionUID = 6786623950045095814L;
81  	private final IAgentId id;
82  
83  	// Manager of logs.
84  	private final IAgentLogger environmentLogger;
85  	// Actual logger
86  	protected final LogCategory log;
87  
88  	// Agent state listeners
89  	private final Map<String, AgentDownListener> agentDownListeners;
90  
91  	// Parameters provided on init send to bots
92  	protected BotParameters botParameters;
93  	protected EnvironmentParameters environmentParameters;
94  
95  	// Connection to the ut Server. Can be used to pause/resume the game.
96  	private ReconnectingServerDefinition<IUT2004Server> utServerConnection;
97  
98  	/**
99  	 * Constructs the Unreal Environment. The environment won't be ready until
100 	 * until is has has been initialized.
101 	 * 
102 	 */
103 	public AbstractUnrealEnvironment() {
104 		id = new AgentId(getName());
105 		agentDownListeners = new HashMap<String, AgentDownListener>();
106 		environmentLogger = new AgentLogger(id);
107 		environmentLogger.addDefaultConsoleHandler();
108 		log = environmentLogger.getCategory(this);
109 		log.info("Environment has been created.");
110 		log.addConsoleHandler();
111 
112 		// Register own translators.
113 		Translator translator = Translator.getInstance();
114 		translator.registerParameter2JavaTranslator(new AgentIdTranslator());
115 		translator.registerParameter2JavaTranslator(new LevelTranslator());
116 		translator.registerParameter2JavaTranslator(new SkinTranslator());
117 		translator.registerParameter2JavaTranslator(new StringListTranslator());
118 		translator.registerParameter2JavaTranslator(new TeamTranslator());
119 		translator.registerParameter2JavaTranslator(new URITranslator());
120 		translator.registerParameter2JavaTranslator(new LocationTranslator());
121 		translator.registerParameter2JavaTranslator(new RotationTranslator());
122 
123 		// Register translators required by the bot controller.
124 		registerTranslators();
125 	}
126 
127 	protected abstract void registerTranslators();
128 
129 	/**
130 	 * Provides an unique identifier for this component for use by loggers.
131 	 */
132 
133 	@Override
134 	public IAgentId getComponentId() {
135 		return id;
136 	}
137 
138 	/**
139 	 * Returns a string representation of the environment based on ID of the
140 	 * environment.
141 	 * 
142 	 * @return a representation of the environment.
143 	 */
144 	@Override
145 	public String toString() {
146 		return simplefyID(getComponentId());
147 	}
148 
149 	public String getName() {
150 		return "UnrealGoal Environment for EIS" + requiredVersion();
151 	}
152 
153 	protected synchronized void initializeEnvironment(Map<String, Parameter> parameters) throws ManagementException {
154 		assert botParameters == null;
155 		assert environmentParameters == null;
156 
157 		try {
158 			botParameters = new BotParameters(parameters, environmentLogger);
159 			environmentParameters = new EnvironmentParameters(parameters, environmentLogger);
160 		} catch (UnrealEnvironmentException e) {
161 			// clean up
162 			botParameters = null;
163 			environmentParameters = null;
164 			// and throw
165 			log.severe("Invalid parameters: " + e);
166 			throw new ManagementException("Invalid parameters.", e);
167 		}
168 
169 		// Set defaults for environment and bot
170 		environmentParameters.assignDefaults(EnvironmentParameters.getDefaults(environmentLogger));
171 		botParameters.assignDefaults(BotParameters.getDefaults(environmentLogger));
172 
173 		// Set log level
174 		// TODO: using same level as bot for now, might be different.
175 		log.setLevel(environmentParameters.getLogLevel());
176 
177 		// Set up (future) connection the UT server. Connecting is done later.
178 		utServerConnection = new ReconnectingServerDefinition<IUT2004Server>(new UTServerDefinition());
179 	}
180 
181 	protected synchronized void connectEnvironment() throws ManagementException {
182 		assert botParameters != null;
183 		assert environmentParameters != null;
184 
185 		// 1. Connect to UT server
186 		URI utServerURI = environmentParameters.getUTServer();
187 		if (utServerURI != null) {
188 			log.info("Connecting to the control server at " + utServerURI + " .");
189 			utServerConnection.setUri(utServerURI);
190 		} else {
191 			log.info("No address for the ut control server was provided. The environment will not try to connect to the control server.");
192 		}
193 
194 		// 2. Set names on bot parameters.
195 		List<BotParameters> agentParameters = new ArrayList<BotParameters>();
196 		for (String name : environmentParameters.getBotNames()) {
197 			BotParameters parameter = new BotParameters(botParameters, environmentLogger);
198 			parameter.setAgentId(name);
199 			agentParameters.add(parameter);
200 		}
201 
202 		// 3. Start bots.
203 		BotParameters[] agentParameterArray = new BotParameters[agentParameters.size()];
204 		agentParameterArray = agentParameters.toArray(agentParameterArray);
205 		startAgents(agentParameterArray);
206 
207 	}
208 
209 	protected abstract Class<? extends UT2004BotController> getControlerClass();
210 
211 	private synchronized void startAgents(BotParameters... parameters) throws ManagementException {
212 
213 		// 1. Don't add bots if the environment has been killed.
214 		if (getState() == EnvironmentState.KILLED) {
215 			return;
216 		}
217 
218 		// 2. Unpause the game. Can't add agents to paused game.
219 		IUT2004Server server = utServerConnection.getServerFlag().getFlag();
220 		if (server != null) {
221 			Pause resume = new Pause(false, false);
222 			server.getAct().act(resume);
223 
224 			log.info("The server has been unpaused. Required to allow adding bots.");
225 		} else {
226 			log.warning("We are not connected to ut server and could not unpause the environment.");
227 		}
228 
229 		// 3. Setting defaults again, this function may be called from else
230 		// where.
231 		for (BotParameters botParameters : parameters) {
232 			botParameters.assignDefaults(BotParameters.getDefaults(environmentLogger));
233 		}
234 
235 		// 4. TODO: Using defaults here because pogamut can't load properties
236 		// when used inside a Jar.
237 
238 		UT2004BotRunner<UT2004Bot<IVisionWorldView, IAct, UT2004BotController>, UT2004BotParameters> runner = new UT2004BotRunner<UT2004Bot<IVisionWorldView, IAct, UT2004BotController>, UT2004BotParameters>(
239 				getControlerClass(), Parameters.DEFAULT_NAME, Parameters.LOCAL_HOST, Parameters.BOT_SERVER_PORT);
240 
241 		// 5. Launch bots using the parameters
242 		runner.setLog(log);
243 		List<UT2004Bot<IVisionWorldView, IAct, UT2004BotController>> agents;
244 		try {
245 			agents = runner.startAgents(parameters);
246 		} catch (PogamutException e) {
247 			throw new ManagementException(
248 			// Adding exception as String. While Pogamut exceptions
249 			// themselves are properly serializable, their contents may not be.
250 					"Pogmut was unable to start all agents. Cause: " + e.toString());
251 		}
252 
253 		// 6. Notify EIS of new entity.
254 		try {
255 			for (UT2004Bot<IVisionWorldView, IAct, UT2004BotController> agent : agents) {
256 				String simpleID = simplefyID(agent.getComponentId());
257 				UT2004BotController controller = agent.getController();
258 				registerEntity(simpleID, "bot", controller, createActionHandler(controller), createPerceptHandler(controller));
259 			}
260 		} catch (EntityException e) {
261 
262 			// Clean up agents if they could not be registered.
263 			for (UT2004Bot<IVisionWorldView, IAct, UT2004BotController> agent : agents) {
264 				agent.stop();
265 			}
266 
267 			throw new ManagementException("Unable to register entity", e);
268 		}
269 
270 		// 7. Add bot dead listeners
271 		for (UT2004Bot<IVisionWorldView, IAct, UT2004BotController> agent : agents) {
272 			String simpleID = simplefyID(agent.getComponentId());
273 			agentDownListeners.put(simpleID, new AgentDownListener(simpleID, agent));
274 		}
275 
276 		// 8. Check if bots are still alive. Throw out if not.
277 		for (UT2004Bot<IVisionWorldView, IAct, UT2004BotController> agent : agents) {
278 			if (agent.inState(IAgentStateDown.class)) {
279 				String simpleID = simplefyID(agent.getComponentId());
280 				agentDownListeners.get(simpleID).removeListener();
281 				synchronizedDeleteEntity(simpleID);
282 			}
283 		}
284 
285 		// 9. Everything aokay!
286 
287 	}
288 
289 	protected abstract PerceptHandler createPerceptHandler(UT2004BotController controller ) throws EntityException;
290 
291 	protected abstract ActionHandler createActionHandler(UT2004BotController controller) throws EntityException;
292 
293 	/**
294 	 * Simplifies a given string representation of an AgentID by removing the
295 	 * UUID making it human readable.
296 	 * 
297 	 * By the specs of the agentID this should still result in a unique but
298 	 * readable ID.
299 	 * 
300 	 * @param iAgentId
301 	 * @return the agentID without the UUID.
302 	 */
303 	private String simplefyID(IAgentId agentID) {
304 		String token = agentID.getToken();
305 		int index = token.lastIndexOf('/');
306 		// If the expected separator could not be found, use the whole string.
307 		if (index < 0) {
308 			log.severe("Could not find UUID seperator in Agent ID: " + token);
309 			return token;
310 
311 		}
312 
313 		return token.substring(0, index);
314 	}
315 
316 	protected void startEnvironment() throws ManagementException {
317 
318 		IUT2004Server server = utServerConnection.getServerFlag().getFlag();
319 
320 		if (server != null) {
321 			Pause resume = new Pause(false, false);
322 			server.getAct().act(resume);
323 
324 			log.info("The environment has been started.");
325 		} else {
326 			log.warning("We are not connected to ut server and could not start the environment.");
327 		}
328 	}
329 
330 	protected void pauseEvironment() {
331 
332 		IUT2004Server server = utServerConnection.getServerFlag().getFlag();
333 
334 		if (server != null) {
335 			Pause pause = new Pause(true, false);
336 			server.getAct().act(pause);
337 			log.info("The environment has been paused.");
338 		} else {
339 			log.warning("We are not connected to ut server and could not pause the environment.");
340 		}
341 	}
342 
343 	protected synchronized void killEnvironment() {
344 		// 1. Unpause the environment.
345 		IUT2004Server server = utServerConnection.getServerFlag().getFlag();
346 		if (server != null) {
347 			Pause resume = new Pause(false, false);
348 			server.getAct().act(resume);
349 
350 			log.info("The environment has been unpaused.");
351 		} else {
352 			log.warning("We are not connected to ut server and could not unpause the environment.");
353 		}
354 
355 		// 2. Shut down all bots.
356 		for (String id : getEntities()) {
357 			@SuppressWarnings("unchecked")
358 			UT2004BotController<UT2004Bot> controller = ((UT2004BotController<UT2004Bot>) getEntity(id));
359 			UT2004Bot bot = controller.getBot();
360 
361 			try {
362 				agentDownListeners.get(id).removeListener();
363 				bot.stop();
364 				log.info(bot.getName() + " has been stopped");
365 			} catch (AgentException e) {
366 				// When a bot can not be stopped it will be killed.
367 				log.info(bot.getName() + " has been killed", e);
368 			}
369 		}
370 
371 		// 3. Close the connection to the utServer.
372 		utServerConnection.stopServer();
373 
374 		// 4. Clear up bots and server
375 		botParameters = null;
376 		environmentParameters = null;
377 
378 		// 5. Stop pogamut platform. Prevents memory leak see #1727
379 		Pogamut.getPlatform().close();
380 	}
381 
382 	/**
383 	 * Synchronized version of {@link AbstractEnvironment.deleteEntity}. Used by
384 	 * {@link AgentDownListener} to removed agents that have shut down.
385 	 * 
386 	 * @param entity
387 	 *            to remove.
388 	 */
389 	protected synchronized void synchronizedDeleteEntity(String name) {
390 		try {
391 			deleteEntity(name);
392 		} catch (RelationException e) {
393 			// TODO: This relationship exception is no longer thrown.
394 			log.severe("Could not delete entity " + name);
395 		} catch (EntityException e) {
396 			// TODO: This should be replaced by an assertion in the default
397 			// implementation of EIS.
398 			log.severe("Could not delete entity " + name + ", it was already deleted.");
399 		}
400 	}
401 
402 	/**
403 	 * Monitors the the agent state, if the agent goes down it is removed from
404 	 * the environment.
405 	 * 
406 	 * @author M.P. Korstanje
407 	 * 
408 	 */
409 	private class AgentDownListener implements FlagListener<IAgentState> {
410 
411 		private final String key;
412 		private final UT2004Bot agent;
413 
414 		public AgentDownListener(String key, UT2004Bot agent) {
415 			this.key = key;
416 			this.agent = agent;
417 			this.agent.getState().addStrongListener(this);
418 		}
419 
420 		@Override
421 		public void flagChanged(IAgentState state) {
422 			if (state instanceof IAgentStateDown) {
423 				removeListener();
424 				synchronizedDeleteEntity(key);
425 			}
426 		}
427 
428 		public void removeListener() {
429 			agent.getState().removeListener(this);
430 			agentDownListeners.remove(key);
431 		}
432 	}
433 
434 	@Override
435 	protected boolean isSupportedByEnvironment(Action arg0) {
436 		return true;
437 	}
438 
439 	@Override
440 	protected boolean isSupportedByType(Action arg0, String arg1) {
441 		return true;
442 	}
443 
444 }