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