View Javadoc

1   package cz.cuni.amis.pogamut.ut2004multi.communication.module;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.HashSet;
6   import java.util.List;
7   import java.util.Map;
8   import java.util.Set;
9   
10  import cz.cuni.amis.pogamut.base.agent.IAgentId;
11  import cz.cuni.amis.pogamut.base.agent.utils.runner.impl.MultipleAgentRunner;
12  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObject;
13  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEvent;
14  import cz.cuni.amis.pogamut.base.communication.worldview.object.IWorldObjectEventListener;
15  import cz.cuni.amis.pogamut.base.communication.worldview.object.WorldObjectId;
16  import cz.cuni.amis.pogamut.base3d.worldview.IVisionWorldView;
17  import cz.cuni.amis.pogamut.multi.communication.worldview.object.ICompositeWorldObject;
18  import cz.cuni.amis.pogamut.multi.communication.worldview.object.ILocalWorldObject;
19  import cz.cuni.amis.pogamut.multi.communication.worldview.object.ISharedProperty;
20  import cz.cuni.amis.pogamut.multi.communication.worldview.object.ISharedWorldObject;
21  import cz.cuni.amis.pogamut.multi.communication.worldview.object.IStaticWorldObject;
22  import cz.cuni.amis.pogamut.multi.communication.worldview.property.PropertyId;
23  import cz.cuni.amis.pogamut.multi.utils.timekey.TimeKey;
24  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.UT2004CompositeObjectCreator;
25  import cz.cuni.amis.pogamut.ut2004.communication.messages.gbinfomessages.UT2004SharedObjectCreator;
26  import cz.cuni.amis.utils.concurrency.AtomicLongList;
27  import cz.cuni.amis.utils.exception.PogamutException;
28  import cz.cuni.amis.utils.maps.HashMapMap;
29  import cz.cuni.amis.utils.maps.SyncHashMap;
30  import cz.cuni.amis.utils.maps.WeakHashTriMap;
31  
32  /**
33   * Manages shared knowledge for agent teams.
34   * The class is managed - meaning only one instance can exist for each team.
35   * 
36   * Beware that this class can only be used when you are running all your agents from a single JVM.
37   * This means you have to used the new {@link MultipleAgentRunner} to run your team. Then each agent has to register with this
38   * database by calling {@link SharedKnowledgeDatabase#addAgent(IAgentId, IVisionWorldView, int)} with the specific parameters. 
39   * After that, the database should be ready for use.
40   * 
41   * @author srlok
42   * @author Jimmy
43   */
44  public class SharedKnowledgeDatabase {
45  
46  	/**
47  	 * Team number to instance.
48  	 */
49  	protected static Map<Integer, SharedKnowledgeDatabase> instances = new HashMap<Integer, SharedKnowledgeDatabase>();
50  	
51  	/**
52  	 * Returns the only instance of SharedKnowledgeDatabase for the specified team.
53  	 * @param team
54  	 * @return
55  	 */
56  	public static SharedKnowledgeDatabase get( int team )
57  	{
58  		synchronized(instances) {
59  			SharedKnowledgeDatabase instance = instances.get(team);
60  			if ( instance == null )
61  			{
62  				instance = new SharedKnowledgeDatabase(team);
63  				instances.put(team, instance);
64  			};
65  			return instance;
66  		}
67  	}
68  	
69  	//
70  	// IMPLEMENTATION
71  	//
72  
73  	/**
74  	 * Which agents are registered here mapped to their agent-numbers.
75  	 */
76  	protected SyncHashMap<IAgentId, Integer> registeredAgents = new SyncHashMap<IAgentId, Integer>();
77  	
78  	/**
79  	 * Team number for which the shared knowledge database was constructed for.
80  	 */
81  	protected int team;
82  	
83  	/**
84  	 * agentLockTimes[agentNumber] == lock time the agent owns (or -1 for no-lock)
85  	 */
86  	protected AtomicLongList agentLockTimes = new AtomicLongList(10, 10);	
87  	
88  	/**
89  	 * Maps the agentIds to their concrete worldViews - these are then used in getting the local object information
90  	 * and to attach the listeners
91  	 */
92  	protected Map<IAgentId, IVisionWorldView> agentWorldViews = new HashMap<IAgentId, IVisionWorldView>();
93  	
94  	/**
95  	 * Remembers all the listeners added for an agent, this is needed to properly remove them when a agent unregisters from the database
96  	 */
97  	protected Map<IAgentId, Set<IWorldObjectEventListener>> listeners = new HashMap<IAgentId, Set<IWorldObjectEventListener>>();
98  	
99  	/**
100 	 * Just holds the weakly referenced shared properties for the worldObjects
101 	 */
102 	protected WeakHashTriMap<TimeKey, WorldObjectId, PropertyId, ISharedProperty> sharedProperties = new WeakHashTriMap<TimeKey, WorldObjectId, PropertyId, ISharedProperty>();
103 	
104 	/**
105 	 * The most recent versions of sharedProperties
106 	 */
107 	protected HashMapMap< WorldObjectId, PropertyId, ISharedProperty> currSharedProperties = new HashMapMap< WorldObjectId, PropertyId, ISharedProperty>(); 
108 	
109 	/**
110 	 * Holds the last time when an update event was recieved from any agent for each object
111 	 */
112 	protected Map<WorldObjectId, Long> lastUpdateTime = new HashMap<WorldObjectId, Long>();
113 	
114 	/**
115 	 * Currently 'locked' timeKeys (with agents locking the keys) - we need to keep objects for those
116 	 */
117 	protected HashMap<TimeKey,Set<IAgentId>> heldKeys = new HashMap<TimeKey,Set<IAgentId>>();
118 	
119 	/**
120 	 * The current TimeKey handled by an agent
121 	 */
122 	protected Map<IAgentId, TimeKey> currentTimeKeys = new HashMap<IAgentId, TimeKey>();
123 	
124 	/**
125 	 * Classes for which the database processes events
126 	 */
127 	protected Set<Class> registeredClasses = new HashSet<Class>();
128 	
129 	protected SharedKnowledgeDatabase( int team )
130 	{
131 		this.team = team;
132 	}
133 	
134 	//
135 	//
136 	// TIMEKEY-related methods
137 	//
138 	//
139 	
140 	/**
141 	 * Locks the specified time and all greater times with the agentId
142 	 */
143 	protected void addTimeLock(TimeKey timeKey, IAgentId id)
144 	{
145 		Set<IAgentId> agentLocks = heldKeys.get(timeKey);
146 		if ( agentLocks == null )
147 		{
148 			agentLocks = new HashSet<IAgentId>();
149 			heldKeys.put(timeKey, agentLocks);
150 		}			
151 		//add the locks for all relevant timeKeys to prevent losing objects we might require in the future
152 		for ( TimeKey t : heldKeys.keySet() )
153 		{
154 			if ( t.getTime() >= timeKey.getTime())
155 			{
156 				heldKeys.get(t).add(id);
157 			}
158 		}
159 	}
160 	
161 	/**
162 	 * Removes a single lock for this timeKey, if it is the last, the whole lock is removed
163 	 * @param timeKey
164 	 * @param id
165 	 */
166 	protected void removeTimeLock( TimeKey timeKey, IAgentId id)
167 	{
168 		synchronized (heldKeys)
169 		{
170 			Set<IAgentId> agentLocks = heldKeys.get(timeKey);
171 			agentLocks.remove(id);
172 			if ( agentLocks.isEmpty() ) //this was the last lock
173 			{
174 				heldKeys.remove(timeKey);
175 			}
176 		}
177 	}
178 		
179 	/*
180 	 * MISC methods
181 	 */
182 	
183 	
184 	
185 	/*
186 	 * User functionality methods
187 	 */
188 	
189 	/**
190 	 * Registers an agent to the database - it will process it's relevant events from now on.
191 	 * @param id
192 	 * @param agentWorldView
193 	 * @param team
194 	 */
195 	public void addAgent( IAgentId id, IVisionWorldView agentWorldView, int team)
196 	{
197 		if (team != this.team)
198 		{
199 			throw new PogamutException("Trying to add an agent of different team than the one registered with this sharedKnowledgeDatabase.", this);
200 		}
201 		
202 		if ( registeredAgents.get(id) != null) {
203 			return;
204 		}
205 		
206 		registeredAgents.getWriteLock().lock();
207 		try {
208 			int unusedNumber = getUnusedAgentNumber();
209 			registeredAgents.getMap().put(id, unusedNumber);
210 		} finally {
211 			registeredAgents.getWriteLock().unlock();
212 		}
213 	}
214 	
215 	/**
216 	 * UNSYNC 
217 	 * @return
218 	 */
219 	private int getUnusedAgentNumber() {		
220 		List<Boolean> used = new ArrayList<Boolean>(registeredAgents.size()+1);
221 		for (Integer agentNumber : registeredAgents.getMap().values()) {
222 			used.set(agentNumber, true);
223 		}
224 		for (int i = 0; i < used.size(); ++i) {
225 			if (used.get(i) == null || !used.get(i)) {
226 				return i;
227 			}
228 		}
229 		// SHOULD NOT REACH HERE!
230 		throw new RuntimeException("registeredAgents corrupted!");
231 	}
232 
233 	/**
234 	 * Registers the provided class as a class of interest.
235 	 * The knowledge database will register listeners on all agent worldViews and will start processing events from
236 	 * the worldViews to collect shared information.
237 	 * @param c
238 	 */
239 	public void addObjectClass(Class c)
240 	{
241 		synchronized (registeredClasses)
242 		{
243 			registeredClasses.add(c);
244 			// TODO!!! registeredAgents.getReadLock().lock();
245 			for ( IAgentId id : registeredAgents.getMap().keySet())
246 			{
247 				addClassListener(agentWorldViews.get(id),c, id);
248 			}			
249 		}
250 	}
251 	
252 	/**
253 	 * Stops processing events for the specified class. If the class is not registered, no change is made.
254 	 * @param c class to process events for
255 	 * @return false if the class was not registered
256 	 */
257 	public boolean removeObjectClass(Class c)
258 	{
259 		synchronized (registeredClasses)
260 		{
261 			return registeredClasses.remove(c);
262 		}
263 	}
264 	
265 	/**
266 	 * Unregister the agent from the database -> the database will no longer process events from this agent.
267 	 * The method also removes all listeners created by the database on the agent's worldview.
268 	 * @param id
269 	 * @param team
270 	 * @return
271 	 */
272 	public boolean removeAgent( IAgentId id)
273 	{
274 		synchronized (registeredAgents)
275 		{
276 			// TODO!!! registeredAgents.getReadLock().lock();
277 			if ( !registeredAgents.getMap().containsKey(id) )
278 			{
279 				return false;
280 			}
281 			IVisionWorldView wv = agentWorldViews.get(id);
282 			Set<IWorldObjectEventListener> listenerSet = listeners.get(id);
283 			if ( listenerSet != null)
284 			{
285 				for (IWorldObjectEventListener listener : listenerSet )
286 				{
287 					wv.removeListener(listener);
288 				}
289 			}
290 			agentWorldViews.remove(id);
291 			registeredAgents.remove(id);
292 			listeners.remove(id);
293 			return true;
294 		}
295 	}
296 	
297 	/**
298 	 * Returns the specified object with the team shared knowledge put in
299 	 * @param id
300 	 * @param agentId
301 	 * @return
302 	 */
303 	public IWorldObject getObject(WorldObjectId id, IAgentId agentId)
304 	{
305 		synchronized (sharedProperties)
306 		{
307 			ICompositeWorldObject agentObject = (ICompositeWorldObject)agentWorldViews.get(agentId).get(id);
308 			if (agentObject == null)
309 			{
310 				return null;
311 			}
312 			ILocalWorldObject localPart = agentObject.getLocal();
313 			IStaticWorldObject staticPart = agentObject.getStatic();
314 			Map<PropertyId, ISharedProperty> properties = agentObject.getShared().getProperties();
315 			TimeKey timeKey = TimeKey.get(localPart.getSimTime());
316 			for ( PropertyId pId : currSharedProperties.get(id).keySet() ) //iterate through shared properties
317 			{
318 				ISharedProperty p = sharedProperties.get(timeKey, id, pId);
319 				if ( p == null )
320 				{
321 					p = currSharedProperties.get(id, pId);
322 				}
323 				properties.put(p.getPropertyId(), p);
324 			}
325 			Class msgClass = localPart.getCompositeClass();
326 			//now create the sharedObject
327 			ISharedWorldObject sharedPart = UT2004SharedObjectCreator.create( msgClass , id , properties.values());
328 			return UT2004CompositeObjectCreator.createObject(localPart, sharedPart, staticPart);
329 		}
330 	}
331 	
332 	/*
333 	 * HELPER methods servicing the inner functionality
334 	 */
335 	
336 	/**
337 	 * Helper method to add a single objectEventListener for Class c to a specific WorldView
338 	 * @param wv
339 	 * @param c
340 	 * @param agentId
341 	 */
342 	protected void addClassListener(IVisionWorldView wv, Class c, IAgentId agentId)
343 	{
344 		synchronized (listeners)
345 		{
346 			IWorldObjectEventListener listener =
347 				new AgentSpecificObjectEventListener<IWorldObject, IWorldObjectEvent<IWorldObject>>(agentId) {
348 					@Override
349 					public void notify(IWorldObjectEvent<IWorldObject> event) 
350 					{
351 						SharedKnowledgeDatabase.this.processObjEvent(event, this.agentId);						
352 					};
353 				};
354 			
355 			wv.addObjectListener(c, listener);
356 			
357 			Set<IWorldObjectEventListener> listenerSet = listeners.get(agentId);
358 			if ( listenerSet == null )
359 			{
360 				listenerSet = new HashSet<IWorldObjectEventListener>();
361 				listeners.put(agentId, listenerSet);
362 			};
363 			listenerSet.add(listener);
364 		}
365 	}
366 	
367 	/**
368 	 * Handles processing of an object event raised on any of the worldViews.
369 	 * This method will update all the shared properties correctly and also uses the event information to adjust the heldTimeKeys
370 	 * @param event
371 	 */
372 	protected void processObjEvent(IWorldObjectEvent<IWorldObject> event, IAgentId agentId)
373 	{
374 		//handle timeLocking first
375 		TimeKey currentTime = currentTimeKeys.get(agentId);
376 		if ( currentTime != null )
377 		{
378 			if ( currentTime.getTime() < event.getSimTime()) // NEW BATCH BEGINS... remove old one
379 			{
380 				TimeKey newTimeKey = TimeKey.get(event.getSimTime());
381 				//unlock old time
382 				this.removeTimeLock(currentTime, agentId);
383 				currentTimeKeys.put(agentId, newTimeKey);
384 				this.addTimeLock(newTimeKey, agentId);				
385 			}
386 		}
387 		else
388 		{
389 			TimeKey newTimeKey = TimeKey.get(event.getSimTime());
390 			currentTimeKeys.put(agentId, newTimeKey);
391 			this.addTimeLock(newTimeKey, agentId);				
392 		}
393 		
394 		WorldObjectId id = event.getId();		
395 		
396 		ISharedWorldObject sharedObj = ((ICompositeWorldObject)event.getObject()).getShared();
397 		
398 		long lastUpdateTime = -1;
399 		if ( this.lastUpdateTime.containsKey(id) )
400 		{
401 			lastUpdateTime = this.lastUpdateTime.get(id);
402 		}
403 		
404 		//we have recieved an update
405 		if ( sharedObj.getSimTime() >= lastUpdateTime ) //makes sense to update now
406 		{
407 			synchronized (sharedProperties)
408 			{
409 			
410 			
411 				for ( ISharedProperty p : sharedObj.getProperties().values())
412 				{
413 					if ( p.getValue() != null ) //some info in the property
414 					{
415 						PropertyId propertyId = p.getPropertyId();
416 						ISharedProperty old = currSharedProperties.get(id, propertyId);
417 						for ( TimeKey key : heldKeys.keySet() )
418 						{
419 							if ( sharedProperties.get(key, id, propertyId) == null )
420 							{
421 								sharedProperties.put(key,id, propertyId, p); //add a old copy
422 							}
423 						}
424 					}
425 				}
426 			}
427 		}
428 	}
429 }