View Javadoc

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