clockwerk-opensim – Blame information for rev 1
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | vero | 1 | /* |
2 | * Copyright (c) Contributors, http://opensimulator.org/ |
||
3 | * See CONTRIBUTORS.TXT for a full list of copyright holders. |
||
4 | * |
||
5 | * Redistribution and use in source and binary forms, with or without |
||
6 | * modification, are permitted provided that the following conditions are met: |
||
7 | * * Redistributions of source code must retain the above copyright |
||
8 | * notice, this list of conditions and the following disclaimer. |
||
9 | * * Redistributions in binary form must reproduce the above copyright |
||
10 | * notice, this list of conditions and the following disclaimer in the |
||
11 | * documentation and/or other materials provided with the distribution. |
||
12 | * * Neither the name of the OpenSimulator Project nor the |
||
13 | * names of its contributors may be used to endorse or promote products |
||
14 | * derived from this software without specific prior written permission. |
||
15 | * |
||
16 | * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY |
||
17 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||
18 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||
19 | * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY |
||
20 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||
21 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||
22 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
||
23 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||
24 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||
25 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
||
26 | */ |
||
27 | |||
28 | using System; |
||
29 | using System.Collections; |
||
30 | using System.Collections.Generic; |
||
31 | using System.Text.RegularExpressions; |
||
32 | |||
33 | using Nini.Config; |
||
34 | using Mono.Addins; |
||
35 | |||
36 | using OpenMetaverse; |
||
37 | |||
38 | using OpenSim.Framework; |
||
39 | using OpenSim.Region.Framework.Interfaces; |
||
40 | using OpenSim.Region.Framework.Scenes; |
||
41 | |||
42 | // using log4net; |
||
43 | // using System.Reflection; |
||
44 | |||
45 | |||
46 | /***************************************************** |
||
47 | * |
||
48 | * WorldCommModule |
||
49 | * |
||
50 | * |
||
51 | * Holding place for world comms - basically llListen |
||
52 | * function implementation. |
||
53 | * |
||
54 | * lLListen(integer channel, string name, key id, string msg) |
||
55 | * The name, id, and msg arguments specify the filtering |
||
56 | * criteria. You can pass the empty string |
||
57 | * (or NULL_KEY for id) for these to set a completely |
||
58 | * open filter; this causes the listen() event handler to be |
||
59 | * invoked for all chat on the channel. To listen only |
||
60 | * for chat spoken by a specific object or avatar, |
||
61 | * specify the name and/or id arguments. To listen |
||
62 | * only for a specific command, specify the |
||
63 | * (case-sensitive) msg argument. If msg is not empty, |
||
64 | * listener will only hear strings which are exactly equal |
||
65 | * to msg. You can also use all the arguments to establish |
||
66 | * the most restrictive filtering criteria. |
||
67 | * |
||
68 | * It might be useful for each listener to maintain a message |
||
69 | * digest, with a list of recent messages by UUID. This can |
||
70 | * be used to prevent in-world repeater loops. However, the |
||
71 | * linden functions do not have this capability, so for now |
||
72 | * thats the way it works. |
||
73 | * Instead it blocks messages originating from the same prim. |
||
74 | * (not Object!) |
||
75 | * |
||
76 | * For LSL compliance, note the following: |
||
77 | * (Tested again 1.21.1 on May 2, 2008) |
||
78 | * 1. 'id' has to be parsed into a UUID. None-UUID keys are |
||
79 | * to be replaced by the ZeroID key. (Well, TryParse does |
||
80 | * that for us. |
||
81 | * 2. Setting up an listen event from the same script, with the |
||
82 | * same filter settings (including step 1), returns the same |
||
83 | * handle as the original filter. |
||
84 | * 3. (TODO) handles should be script-local. Starting from 1. |
||
85 | * Might be actually easier to map the global handle into |
||
86 | * script-local handle in the ScriptEngine. Not sure if its |
||
87 | * worth the effort tho. |
||
88 | * |
||
89 | * **************************************************/ |
||
90 | |||
91 | namespace OpenSim.Region.CoreModules.Scripting.WorldComm |
||
92 | { |
||
93 | [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WorldCommModule")] |
||
94 | public class WorldCommModule : IWorldComm, INonSharedRegionModule |
||
95 | { |
||
96 | // private static readonly ILog m_log = |
||
97 | // LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); |
||
98 | |||
99 | private ListenerManager m_listenerManager; |
||
100 | private Queue m_pending; |
||
101 | private Queue m_pendingQ; |
||
102 | private Scene m_scene; |
||
103 | private int m_whisperdistance = 10; |
||
104 | private int m_saydistance = 20; |
||
105 | private int m_shoutdistance = 100; |
||
106 | |||
107 | #region INonSharedRegionModule Members |
||
108 | |||
109 | public void Initialise(IConfigSource config) |
||
110 | { |
||
111 | // wrap this in a try block so that defaults will work if |
||
112 | // the config file doesn't specify otherwise. |
||
113 | int maxlisteners = 1000; |
||
114 | int maxhandles = 64; |
||
115 | try |
||
116 | { |
||
117 | m_whisperdistance = config.Configs["Chat"].GetInt( |
||
118 | "whisper_distance", m_whisperdistance); |
||
119 | m_saydistance = config.Configs["Chat"].GetInt( |
||
120 | "say_distance", m_saydistance); |
||
121 | m_shoutdistance = config.Configs["Chat"].GetInt( |
||
122 | "shout_distance", m_shoutdistance); |
||
123 | maxlisteners = config.Configs["LL-Functions"].GetInt( |
||
124 | "max_listens_per_region", maxlisteners); |
||
125 | maxhandles = config.Configs["LL-Functions"].GetInt( |
||
126 | "max_listens_per_script", maxhandles); |
||
127 | } |
||
128 | catch (Exception) |
||
129 | { |
||
130 | } |
||
131 | if (maxlisteners < 1) maxlisteners = int.MaxValue; |
||
132 | if (maxhandles < 1) maxhandles = int.MaxValue; |
||
133 | m_listenerManager = new ListenerManager(maxlisteners, maxhandles); |
||
134 | m_pendingQ = new Queue(); |
||
135 | m_pending = Queue.Synchronized(m_pendingQ); |
||
136 | } |
||
137 | |||
138 | public void PostInitialise() |
||
139 | { |
||
140 | } |
||
141 | |||
142 | public void AddRegion(Scene scene) |
||
143 | { |
||
144 | m_scene = scene; |
||
145 | m_scene.RegisterModuleInterface<IWorldComm>(this); |
||
146 | m_scene.EventManager.OnChatFromClient += DeliverClientMessage; |
||
147 | m_scene.EventManager.OnChatBroadcast += DeliverClientMessage; |
||
148 | } |
||
149 | |||
150 | public void RegionLoaded(Scene scene) { } |
||
151 | |||
152 | public void RemoveRegion(Scene scene) |
||
153 | { |
||
154 | if (scene != m_scene) |
||
155 | return; |
||
156 | |||
157 | m_scene.UnregisterModuleInterface<IWorldComm>(this); |
||
158 | m_scene.EventManager.OnChatBroadcast -= DeliverClientMessage; |
||
159 | m_scene.EventManager.OnChatBroadcast -= DeliverClientMessage; |
||
160 | } |
||
161 | |||
162 | public void Close() |
||
163 | { |
||
164 | } |
||
165 | |||
166 | public string Name |
||
167 | { |
||
168 | get { return "WorldCommModule"; } |
||
169 | } |
||
170 | |||
171 | public Type ReplaceableInterface { get { return null; } } |
||
172 | |||
173 | #endregion |
||
174 | |||
175 | #region IWorldComm Members |
||
176 | |||
177 | public int ListenerCount |
||
178 | { |
||
179 | get |
||
180 | { |
||
181 | return m_listenerManager.ListenerCount; |
||
182 | } |
||
183 | } |
||
184 | |||
185 | /// <summary> |
||
186 | /// Create a listen event callback with the specified filters. |
||
187 | /// The parameters localID,itemID are needed to uniquely identify |
||
188 | /// the script during 'peek' time. Parameter hostID is needed to |
||
189 | /// determine the position of the script. |
||
190 | /// </summary> |
||
191 | /// <param name="localID">localID of the script engine</param> |
||
192 | /// <param name="itemID">UUID of the script engine</param> |
||
193 | /// <param name="hostID">UUID of the SceneObjectPart</param> |
||
194 | /// <param name="channel">channel to listen on</param> |
||
195 | /// <param name="name">name to filter on</param> |
||
196 | /// <param name="id"> |
||
197 | /// key to filter on (user given, could be totally faked) |
||
198 | /// </param> |
||
199 | /// <param name="msg">msg to filter on</param> |
||
200 | /// <returns>number of the scripts handle</returns> |
||
201 | public int Listen(uint localID, UUID itemID, UUID hostID, int channel, |
||
202 | string name, UUID id, string msg) |
||
203 | { |
||
204 | return m_listenerManager.AddListener(localID, itemID, hostID, |
||
205 | channel, name, id, msg); |
||
206 | } |
||
207 | |||
208 | /// <summary> |
||
209 | /// Create a listen event callback with the specified filters. |
||
210 | /// The parameters localID,itemID are needed to uniquely identify |
||
211 | /// the script during 'peek' time. Parameter hostID is needed to |
||
212 | /// determine the position of the script. |
||
213 | /// </summary> |
||
214 | /// <param name="localID">localID of the script engine</param> |
||
215 | /// <param name="itemID">UUID of the script engine</param> |
||
216 | /// <param name="hostID">UUID of the SceneObjectPart</param> |
||
217 | /// <param name="channel">channel to listen on</param> |
||
218 | /// <param name="name">name to filter on</param> |
||
219 | /// <param name="id"> |
||
220 | /// key to filter on (user given, could be totally faked) |
||
221 | /// </param> |
||
222 | /// <param name="msg">msg to filter on</param> |
||
223 | /// <param name="regexBitfield"> |
||
224 | /// Bitfield indicating which strings should be processed as regex. |
||
225 | /// </param> |
||
226 | /// <returns>number of the scripts handle</returns> |
||
227 | public int Listen(uint localID, UUID itemID, UUID hostID, int channel, |
||
228 | string name, UUID id, string msg, int regexBitfield) |
||
229 | { |
||
230 | return m_listenerManager.AddListener(localID, itemID, hostID, |
||
231 | channel, name, id, msg, regexBitfield); |
||
232 | } |
||
233 | |||
234 | /// <summary> |
||
235 | /// Sets the listen event with handle as active (active = TRUE) or inactive (active = FALSE). |
||
236 | /// The handle used is returned from Listen() |
||
237 | /// </summary> |
||
238 | /// <param name="itemID">UUID of the script engine</param> |
||
239 | /// <param name="handle">handle returned by Listen()</param> |
||
240 | /// <param name="active">temp. activate or deactivate the Listen()</param> |
||
241 | public void ListenControl(UUID itemID, int handle, int active) |
||
242 | { |
||
243 | if (active == 1) |
||
244 | m_listenerManager.Activate(itemID, handle); |
||
245 | else if (active == 0) |
||
246 | m_listenerManager.Dectivate(itemID, handle); |
||
247 | } |
||
248 | |||
249 | /// <summary> |
||
250 | /// Removes the listen event callback with handle |
||
251 | /// </summary> |
||
252 | /// <param name="itemID">UUID of the script engine</param> |
||
253 | /// <param name="handle">handle returned by Listen()</param> |
||
254 | public void ListenRemove(UUID itemID, int handle) |
||
255 | { |
||
256 | m_listenerManager.Remove(itemID, handle); |
||
257 | } |
||
258 | |||
259 | /// <summary> |
||
260 | /// Removes all listen event callbacks for the given itemID |
||
261 | /// (script engine) |
||
262 | /// </summary> |
||
263 | /// <param name="itemID">UUID of the script engine</param> |
||
264 | public void DeleteListener(UUID itemID) |
||
265 | { |
||
266 | m_listenerManager.DeleteListener(itemID); |
||
267 | } |
||
268 | |||
269 | |||
270 | protected static Vector3 CenterOfRegion = new Vector3(128, 128, 20); |
||
271 | |||
272 | public void DeliverMessage(ChatTypeEnum type, int channel, string name, UUID id, string msg) |
||
273 | { |
||
274 | Vector3 position; |
||
275 | SceneObjectPart source; |
||
276 | ScenePresence avatar; |
||
277 | |||
278 | if ((source = m_scene.GetSceneObjectPart(id)) != null) |
||
279 | position = source.AbsolutePosition; |
||
280 | else if ((avatar = m_scene.GetScenePresence(id)) != null) |
||
281 | position = avatar.AbsolutePosition; |
||
282 | else if (ChatTypeEnum.Region == type) |
||
283 | position = CenterOfRegion; |
||
284 | else |
||
285 | return; |
||
286 | |||
287 | DeliverMessage(type, channel, name, id, msg, position); |
||
288 | } |
||
289 | |||
290 | /// <summary> |
||
291 | /// This method scans over the objects which registered an interest in listen callbacks. |
||
292 | /// For everyone it finds, it checks if it fits the given filter. If it does, then |
||
293 | /// enqueue the message for delivery to the objects listen event handler. |
||
294 | /// The enqueued ListenerInfo no longer has filter values, but the actually trigged values. |
||
295 | /// Objects that do an llSay have their messages delivered here and for nearby avatars, |
||
296 | /// the OnChatFromClient event is used. |
||
297 | /// </summary> |
||
298 | /// <param name="type">type of delvery (whisper,say,shout or regionwide)</param> |
||
299 | /// <param name="channel">channel to sent on</param> |
||
300 | /// <param name="name">name of sender (object or avatar)</param> |
||
301 | /// <param name="id">key of sender (object or avatar)</param> |
||
302 | /// <param name="msg">msg to sent</param> |
||
303 | public void DeliverMessage(ChatTypeEnum type, int channel, |
||
304 | string name, UUID id, string msg, Vector3 position) |
||
305 | { |
||
306 | // m_log.DebugFormat("[WorldComm] got[2] type {0}, channel {1}, name {2}, id {3}, msg {4}", |
||
307 | // type, channel, name, id, msg); |
||
308 | |||
309 | // Determine which listen event filters match the given set of arguments, this results |
||
310 | // in a limited set of listeners, each belonging a host. If the host is in range, add them |
||
311 | // to the pending queue. |
||
312 | foreach (ListenerInfo li |
||
313 | in m_listenerManager.GetListeners(UUID.Zero, channel, |
||
314 | name, id, msg)) |
||
315 | { |
||
316 | // Dont process if this message is from yourself! |
||
317 | if (li.GetHostID().Equals(id)) |
||
318 | continue; |
||
319 | |||
320 | SceneObjectPart sPart = m_scene.GetSceneObjectPart( |
||
321 | li.GetHostID()); |
||
322 | if (sPart == null) |
||
323 | continue; |
||
324 | |||
325 | double dis = Util.GetDistanceTo(sPart.AbsolutePosition, |
||
326 | position); |
||
327 | switch (type) |
||
328 | { |
||
329 | case ChatTypeEnum.Whisper: |
||
330 | if (dis < m_whisperdistance) |
||
331 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
332 | break; |
||
333 | |||
334 | case ChatTypeEnum.Say: |
||
335 | if (dis < m_saydistance) |
||
336 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
337 | break; |
||
338 | |||
339 | case ChatTypeEnum.Shout: |
||
340 | if (dis < m_shoutdistance) |
||
341 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
342 | break; |
||
343 | |||
344 | case ChatTypeEnum.Region: |
||
345 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
346 | break; |
||
347 | } |
||
348 | } |
||
349 | } |
||
350 | |||
351 | /// <summary> |
||
352 | /// Delivers the message to a scene entity. |
||
353 | /// </summary> |
||
354 | /// <param name='target'> |
||
355 | /// Target. |
||
356 | /// </param> |
||
357 | /// <param name='channel'> |
||
358 | /// Channel. |
||
359 | /// </param> |
||
360 | /// <param name='name'> |
||
361 | /// Name. |
||
362 | /// </param> |
||
363 | /// <param name='id'> |
||
364 | /// Identifier. |
||
365 | /// </param> |
||
366 | /// <param name='msg'> |
||
367 | /// Message. |
||
368 | /// </param> |
||
369 | public void DeliverMessageTo(UUID target, int channel, Vector3 pos, |
||
370 | string name, UUID id, string msg) |
||
371 | { |
||
372 | // Is id an avatar? |
||
373 | ScenePresence sp = m_scene.GetScenePresence(target); |
||
374 | |||
375 | if (sp != null) |
||
376 | { |
||
377 | // ignore if a child agent this is restricted to inside one |
||
378 | // region |
||
379 | if (sp.IsChildAgent) |
||
380 | return; |
||
381 | |||
382 | // Send message to the avatar. |
||
383 | // Channel zero only goes to the avatar |
||
384 | // non zero channel messages only go to the attachments |
||
385 | if (channel == 0) |
||
386 | { |
||
387 | m_scene.SimChatToAgent(target, Utils.StringToBytes(msg), |
||
388 | pos, name, id, false); |
||
389 | } |
||
390 | else |
||
391 | { |
||
392 | List<SceneObjectGroup> attachments = sp.GetAttachments(); |
||
393 | if (attachments.Count == 0) |
||
394 | return; |
||
395 | |||
396 | // Get uuid of attachments |
||
397 | List<UUID> targets = new List<UUID>(); |
||
398 | foreach (SceneObjectGroup sog in attachments) |
||
399 | { |
||
400 | if (!sog.IsDeleted) |
||
401 | targets.Add(sog.UUID); |
||
402 | } |
||
403 | |||
404 | // Need to check each attachment |
||
405 | foreach (ListenerInfo li |
||
406 | in m_listenerManager.GetListeners(UUID.Zero, |
||
407 | channel, name, id, msg)) |
||
408 | { |
||
409 | if (li.GetHostID().Equals(id)) |
||
410 | continue; |
||
411 | |||
412 | if (m_scene.GetSceneObjectPart( |
||
413 | li.GetHostID()) == null) |
||
414 | { |
||
415 | continue; |
||
416 | } |
||
417 | |||
418 | if (targets.Contains(li.GetHostID())) |
||
419 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
420 | } |
||
421 | } |
||
422 | |||
423 | return; |
||
424 | } |
||
425 | |||
426 | // No avatar found so look for an object |
||
427 | foreach (ListenerInfo li |
||
428 | in m_listenerManager.GetListeners(UUID.Zero, channel, |
||
429 | name, id, msg)) |
||
430 | { |
||
431 | // Dont process if this message is from yourself! |
||
432 | if (li.GetHostID().Equals(id)) |
||
433 | continue; |
||
434 | |||
435 | SceneObjectPart sPart = m_scene.GetSceneObjectPart( |
||
436 | li.GetHostID()); |
||
437 | if (sPart == null) |
||
438 | continue; |
||
439 | |||
440 | if (li.GetHostID().Equals(target)) |
||
441 | { |
||
442 | QueueMessage(new ListenerInfo(li, name, id, msg)); |
||
443 | break; |
||
444 | } |
||
445 | } |
||
446 | |||
447 | return; |
||
448 | } |
||
449 | |||
450 | protected void QueueMessage(ListenerInfo li) |
||
451 | { |
||
452 | lock (m_pending.SyncRoot) |
||
453 | { |
||
454 | m_pending.Enqueue(li); |
||
455 | } |
||
456 | } |
||
457 | |||
458 | /// <summary> |
||
459 | /// Are there any listen events ready to be dispatched? |
||
460 | /// </summary> |
||
461 | /// <returns>boolean indication</returns> |
||
462 | public bool HasMessages() |
||
463 | { |
||
464 | return (m_pending.Count > 0); |
||
465 | } |
||
466 | |||
467 | /// <summary> |
||
468 | /// Pop the first availlable listen event from the queue |
||
469 | /// </summary> |
||
470 | /// <returns>ListenerInfo with filter filled in</returns> |
||
471 | public IWorldCommListenerInfo GetNextMessage() |
||
472 | { |
||
473 | ListenerInfo li = null; |
||
474 | |||
475 | lock (m_pending.SyncRoot) |
||
476 | { |
||
477 | li = (ListenerInfo)m_pending.Dequeue(); |
||
478 | } |
||
479 | |||
480 | return li; |
||
481 | } |
||
482 | |||
483 | #endregion |
||
484 | |||
485 | /******************************************************************** |
||
486 | * |
||
487 | * Listener Stuff |
||
488 | * |
||
489 | * *****************************************************************/ |
||
490 | |||
491 | private void DeliverClientMessage(Object sender, OSChatMessage e) |
||
492 | { |
||
493 | if (null != e.Sender) |
||
494 | { |
||
495 | DeliverMessage(e.Type, e.Channel, e.Sender.Name, |
||
496 | e.Sender.AgentId, e.Message, e.Position); |
||
497 | } |
||
498 | else |
||
499 | { |
||
500 | DeliverMessage(e.Type, e.Channel, e.From, UUID.Zero, |
||
501 | e.Message, e.Position); |
||
502 | } |
||
503 | } |
||
504 | |||
505 | public Object[] GetSerializationData(UUID itemID) |
||
506 | { |
||
507 | return m_listenerManager.GetSerializationData(itemID); |
||
508 | } |
||
509 | |||
510 | public void CreateFromData(uint localID, UUID itemID, UUID hostID, |
||
511 | Object[] data) |
||
512 | { |
||
513 | m_listenerManager.AddFromData(localID, itemID, hostID, data); |
||
514 | } |
||
515 | } |
||
516 | |||
517 | public class ListenerManager |
||
518 | { |
||
519 | private Dictionary<int, List<ListenerInfo>> m_listeners = |
||
520 | new Dictionary<int, List<ListenerInfo>>(); |
||
521 | private int m_maxlisteners; |
||
522 | private int m_maxhandles; |
||
523 | private int m_curlisteners; |
||
524 | |||
525 | /// <summary> |
||
526 | /// Total number of listeners |
||
527 | /// </summary> |
||
528 | public int ListenerCount |
||
529 | { |
||
530 | get |
||
531 | { |
||
532 | lock (m_listeners) |
||
533 | return m_listeners.Count; |
||
534 | } |
||
535 | } |
||
536 | |||
537 | public ListenerManager(int maxlisteners, int maxhandles) |
||
538 | { |
||
539 | m_maxlisteners = maxlisteners; |
||
540 | m_maxhandles = maxhandles; |
||
541 | m_curlisteners = 0; |
||
542 | } |
||
543 | |||
544 | public int AddListener(uint localID, UUID itemID, UUID hostID, |
||
545 | int channel, string name, UUID id, string msg) |
||
546 | { |
||
547 | return AddListener(localID, itemID, hostID, channel, name, id, |
||
548 | msg, 0); |
||
549 | } |
||
550 | |||
551 | public int AddListener(uint localID, UUID itemID, UUID hostID, |
||
552 | int channel, string name, UUID id, string msg, |
||
553 | int regexBitfield) |
||
554 | { |
||
555 | // do we already have a match on this particular filter event? |
||
556 | List<ListenerInfo> coll = GetListeners(itemID, channel, name, id, |
||
557 | msg); |
||
558 | |||
559 | if (coll.Count > 0) |
||
560 | { |
||
561 | // special case, called with same filter settings, return same |
||
562 | // handle (2008-05-02, tested on 1.21.1 server, still holds) |
||
563 | return coll[0].GetHandle(); |
||
564 | } |
||
565 | |||
566 | if (m_curlisteners < m_maxlisteners) |
||
567 | { |
||
568 | lock (m_listeners) |
||
569 | { |
||
570 | int newHandle = GetNewHandle(itemID); |
||
571 | |||
572 | if (newHandle > 0) |
||
573 | { |
||
574 | ListenerInfo li = new ListenerInfo(newHandle, localID, |
||
575 | itemID, hostID, channel, name, id, msg, |
||
576 | regexBitfield); |
||
577 | |||
578 | List<ListenerInfo> listeners; |
||
579 | if (!m_listeners.TryGetValue( |
||
580 | channel, out listeners)) |
||
581 | { |
||
582 | listeners = new List<ListenerInfo>(); |
||
583 | m_listeners.Add(channel, listeners); |
||
584 | } |
||
585 | listeners.Add(li); |
||
586 | m_curlisteners++; |
||
587 | |||
588 | return newHandle; |
||
589 | } |
||
590 | } |
||
591 | } |
||
592 | return -1; |
||
593 | } |
||
594 | |||
595 | public void Remove(UUID itemID, int handle) |
||
596 | { |
||
597 | lock (m_listeners) |
||
598 | { |
||
599 | foreach (KeyValuePair<int, List<ListenerInfo>> lis |
||
600 | in m_listeners) |
||
601 | { |
||
602 | foreach (ListenerInfo li in lis.Value) |
||
603 | { |
||
604 | if (li.GetItemID().Equals(itemID) && |
||
605 | li.GetHandle().Equals(handle)) |
||
606 | { |
||
607 | lis.Value.Remove(li); |
||
608 | if (lis.Value.Count == 0) |
||
609 | { |
||
610 | m_listeners.Remove(lis.Key); |
||
611 | m_curlisteners--; |
||
612 | } |
||
613 | // there should be only one, so we bail out early |
||
614 | return; |
||
615 | } |
||
616 | } |
||
617 | } |
||
618 | } |
||
619 | } |
||
620 | |||
621 | public void DeleteListener(UUID itemID) |
||
622 | { |
||
623 | List<int> emptyChannels = new List<int>(); |
||
624 | List<ListenerInfo> removedListeners = new List<ListenerInfo>(); |
||
625 | |||
626 | lock (m_listeners) |
||
627 | { |
||
628 | foreach (KeyValuePair<int, List<ListenerInfo>> lis |
||
629 | in m_listeners) |
||
630 | { |
||
631 | foreach (ListenerInfo li in lis.Value) |
||
632 | { |
||
633 | if (li.GetItemID().Equals(itemID)) |
||
634 | { |
||
635 | // store them first, else the enumerated bails on |
||
636 | // us |
||
637 | removedListeners.Add(li); |
||
638 | } |
||
639 | } |
||
640 | foreach (ListenerInfo li in removedListeners) |
||
641 | { |
||
642 | lis.Value.Remove(li); |
||
643 | m_curlisteners--; |
||
644 | } |
||
645 | removedListeners.Clear(); |
||
646 | if (lis.Value.Count == 0) |
||
647 | { |
||
648 | // again, store first, remove later |
||
649 | emptyChannels.Add(lis.Key); |
||
650 | } |
||
651 | } |
||
652 | foreach (int channel in emptyChannels) |
||
653 | { |
||
654 | m_listeners.Remove(channel); |
||
655 | } |
||
656 | } |
||
657 | } |
||
658 | |||
659 | public void Activate(UUID itemID, int handle) |
||
660 | { |
||
661 | lock (m_listeners) |
||
662 | { |
||
663 | foreach (KeyValuePair<int, List<ListenerInfo>> lis |
||
664 | in m_listeners) |
||
665 | { |
||
666 | foreach (ListenerInfo li in lis.Value) |
||
667 | { |
||
668 | if (li.GetItemID().Equals(itemID) && |
||
669 | li.GetHandle() == handle) |
||
670 | { |
||
671 | li.Activate(); |
||
672 | // only one, bail out |
||
673 | return; |
||
674 | } |
||
675 | } |
||
676 | } |
||
677 | } |
||
678 | } |
||
679 | |||
680 | public void Dectivate(UUID itemID, int handle) |
||
681 | { |
||
682 | lock (m_listeners) |
||
683 | { |
||
684 | foreach (KeyValuePair<int, List<ListenerInfo>> lis |
||
685 | in m_listeners) |
||
686 | { |
||
687 | foreach (ListenerInfo li in lis.Value) |
||
688 | { |
||
689 | if (li.GetItemID().Equals(itemID) && |
||
690 | li.GetHandle() == handle) |
||
691 | { |
||
692 | li.Deactivate(); |
||
693 | // only one, bail out |
||
694 | return; |
||
695 | } |
||
696 | } |
||
697 | } |
||
698 | } |
||
699 | } |
||
700 | |||
701 | /// <summary> |
||
702 | /// non-locked access, since its always called in the context of the |
||
703 | /// lock |
||
704 | /// </summary> |
||
705 | /// <param name="itemID"></param> |
||
706 | /// <returns></returns> |
||
707 | private int GetNewHandle(UUID itemID) |
||
708 | { |
||
709 | List<int> handles = new List<int>(); |
||
710 | |||
711 | // build a list of used keys for this specific itemID... |
||
712 | foreach (KeyValuePair<int, List<ListenerInfo>> lis in m_listeners) |
||
713 | { |
||
714 | foreach (ListenerInfo li in lis.Value) |
||
715 | { |
||
716 | if (li.GetItemID().Equals(itemID)) |
||
717 | handles.Add(li.GetHandle()); |
||
718 | } |
||
719 | } |
||
720 | |||
721 | // Note: 0 is NOT a valid handle for llListen() to return |
||
722 | for (int i = 1; i <= m_maxhandles; i++) |
||
723 | { |
||
724 | if (!handles.Contains(i)) |
||
725 | return i; |
||
726 | } |
||
727 | |||
728 | return -1; |
||
729 | } |
||
730 | |||
731 | /// These are duplicated from ScriptBaseClass |
||
732 | /// http://opensimulator.org/mantis/view.php?id=6106#c21945 |
||
733 | #region Constants for the bitfield parameter of osListenRegex |
||
734 | |||
735 | /// <summary> |
||
736 | /// process name parameter as regex |
||
737 | /// </summary> |
||
738 | public const int OS_LISTEN_REGEX_NAME = 0x1; |
||
739 | |||
740 | /// <summary> |
||
741 | /// process message parameter as regex |
||
742 | /// </summary> |
||
743 | public const int OS_LISTEN_REGEX_MESSAGE = 0x2; |
||
744 | |||
745 | #endregion |
||
746 | |||
747 | /// <summary> |
||
748 | /// Get listeners matching the input parameters. |
||
749 | /// </summary> |
||
750 | /// <remarks> |
||
751 | /// Theres probably a more clever and efficient way to do this, maybe |
||
752 | /// with regex. |
||
753 | /// PM2008: Ha, one could even be smart and define a specialized |
||
754 | /// Enumerator. |
||
755 | /// </remarks> |
||
756 | /// <param name="itemID"></param> |
||
757 | /// <param name="channel"></param> |
||
758 | /// <param name="name"></param> |
||
759 | /// <param name="id"></param> |
||
760 | /// <param name="msg"></param> |
||
761 | /// <returns></returns> |
||
762 | public List<ListenerInfo> GetListeners(UUID itemID, int channel, |
||
763 | string name, UUID id, string msg) |
||
764 | { |
||
765 | List<ListenerInfo> collection = new List<ListenerInfo>(); |
||
766 | |||
767 | lock (m_listeners) |
||
768 | { |
||
769 | List<ListenerInfo> listeners; |
||
770 | if (!m_listeners.TryGetValue(channel, out listeners)) |
||
771 | { |
||
772 | return collection; |
||
773 | } |
||
774 | |||
775 | foreach (ListenerInfo li in listeners) |
||
776 | { |
||
777 | if (!li.IsActive()) |
||
778 | { |
||
779 | continue; |
||
780 | } |
||
781 | if (!itemID.Equals(UUID.Zero) && |
||
782 | !li.GetItemID().Equals(itemID)) |
||
783 | { |
||
784 | continue; |
||
785 | } |
||
786 | if (li.GetName().Length > 0 && ( |
||
787 | ((li.RegexBitfield & OS_LISTEN_REGEX_NAME) != OS_LISTEN_REGEX_NAME && !li.GetName().Equals(name)) || |
||
788 | ((li.RegexBitfield & OS_LISTEN_REGEX_NAME) == OS_LISTEN_REGEX_NAME && !Regex.IsMatch(name, li.GetName())) |
||
789 | )) |
||
790 | { |
||
791 | continue; |
||
792 | } |
||
793 | if (!li.GetID().Equals(UUID.Zero) && !li.GetID().Equals(id)) |
||
794 | { |
||
795 | continue; |
||
796 | } |
||
797 | if (li.GetMessage().Length > 0 && ( |
||
798 | ((li.RegexBitfield & OS_LISTEN_REGEX_MESSAGE) != OS_LISTEN_REGEX_MESSAGE && !li.GetMessage().Equals(msg)) || |
||
799 | ((li.RegexBitfield & OS_LISTEN_REGEX_MESSAGE) == OS_LISTEN_REGEX_MESSAGE && !Regex.IsMatch(msg, li.GetMessage())) |
||
800 | )) |
||
801 | { |
||
802 | continue; |
||
803 | } |
||
804 | collection.Add(li); |
||
805 | } |
||
806 | } |
||
807 | return collection; |
||
808 | } |
||
809 | |||
810 | public Object[] GetSerializationData(UUID itemID) |
||
811 | { |
||
812 | List<Object> data = new List<Object>(); |
||
813 | |||
814 | lock (m_listeners) |
||
815 | { |
||
816 | foreach (List<ListenerInfo> list in m_listeners.Values) |
||
817 | { |
||
818 | foreach (ListenerInfo l in list) |
||
819 | { |
||
820 | if (l.GetItemID() == itemID) |
||
821 | data.AddRange(l.GetSerializationData()); |
||
822 | } |
||
823 | } |
||
824 | } |
||
825 | return (Object[])data.ToArray(); |
||
826 | } |
||
827 | |||
828 | public void AddFromData(uint localID, UUID itemID, UUID hostID, |
||
829 | Object[] data) |
||
830 | { |
||
831 | int idx = 0; |
||
832 | Object[] item = new Object[6]; |
||
833 | int dataItemLength = 6; |
||
834 | |||
835 | while (idx < data.Length) |
||
836 | { |
||
837 | dataItemLength = (idx + 7 == data.Length || (idx + 7 < data.Length && data[idx + 7] is bool)) ? 7 : 6; |
||
838 | item = new Object[dataItemLength]; |
||
839 | Array.Copy(data, idx, item, 0, dataItemLength); |
||
840 | |||
841 | ListenerInfo info = |
||
842 | ListenerInfo.FromData(localID, itemID, hostID, item); |
||
843 | |||
844 | lock (m_listeners) |
||
845 | { |
||
846 | if (!m_listeners.ContainsKey((int)item[2])) |
||
847 | { |
||
848 | m_listeners.Add((int)item[2], |
||
849 | new List<ListenerInfo>()); |
||
850 | } |
||
851 | m_listeners[(int)item[2]].Add(info); |
||
852 | } |
||
853 | |||
854 | idx += dataItemLength; |
||
855 | } |
||
856 | } |
||
857 | } |
||
858 | |||
859 | public class ListenerInfo : IWorldCommListenerInfo |
||
860 | { |
||
861 | /// <summary> |
||
862 | /// Listener is active or not |
||
863 | /// </summary> |
||
864 | private bool m_active; |
||
865 | |||
866 | /// <summary> |
||
867 | /// Assigned handle of this listener |
||
868 | /// </summary> |
||
869 | private int m_handle; |
||
870 | |||
871 | /// <summary> |
||
872 | /// Local ID from script engine |
||
873 | /// </summary> |
||
874 | private uint m_localID; |
||
875 | |||
876 | /// <summary> |
||
877 | /// ID of the host script engine |
||
878 | /// </summary> |
||
879 | private UUID m_itemID; |
||
880 | |||
881 | /// <summary> |
||
882 | /// ID of the host/scene part |
||
883 | /// </summary> |
||
884 | private UUID m_hostID; |
||
885 | |||
886 | /// <summary> |
||
887 | /// Channel |
||
888 | /// </summary> |
||
889 | private int m_channel; |
||
890 | |||
891 | /// <summary> |
||
892 | /// ID to filter messages from |
||
893 | /// </summary> |
||
894 | private UUID m_id; |
||
895 | |||
896 | /// <summary> |
||
897 | /// Object name to filter messages from |
||
898 | /// </summary> |
||
899 | private string m_name; |
||
900 | |||
901 | /// <summary> |
||
902 | /// The message |
||
903 | /// </summary> |
||
904 | private string m_message; |
||
905 | |||
906 | public ListenerInfo(int handle, uint localID, UUID ItemID, |
||
907 | UUID hostID, int channel, string name, UUID id, |
||
908 | string message) |
||
909 | { |
||
910 | Initialise(handle, localID, ItemID, hostID, channel, name, id, |
||
911 | message, 0); |
||
912 | } |
||
913 | |||
914 | public ListenerInfo(int handle, uint localID, UUID ItemID, |
||
915 | UUID hostID, int channel, string name, UUID id, |
||
916 | string message, int regexBitfield) |
||
917 | { |
||
918 | Initialise(handle, localID, ItemID, hostID, channel, name, id, |
||
919 | message, regexBitfield); |
||
920 | } |
||
921 | |||
922 | public ListenerInfo(ListenerInfo li, string name, UUID id, |
||
923 | string message) |
||
924 | { |
||
925 | Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, |
||
926 | li.m_channel, name, id, message, 0); |
||
927 | } |
||
928 | |||
929 | public ListenerInfo(ListenerInfo li, string name, UUID id, |
||
930 | string message, int regexBitfield) |
||
931 | { |
||
932 | Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, |
||
933 | li.m_channel, name, id, message, regexBitfield); |
||
934 | } |
||
935 | |||
936 | private void Initialise(int handle, uint localID, UUID ItemID, |
||
937 | UUID hostID, int channel, string name, UUID id, |
||
938 | string message, int regexBitfield) |
||
939 | { |
||
940 | m_active = true; |
||
941 | m_handle = handle; |
||
942 | m_localID = localID; |
||
943 | m_itemID = ItemID; |
||
944 | m_hostID = hostID; |
||
945 | m_channel = channel; |
||
946 | m_name = name; |
||
947 | m_id = id; |
||
948 | m_message = message; |
||
949 | RegexBitfield = regexBitfield; |
||
950 | } |
||
951 | |||
952 | public Object[] GetSerializationData() |
||
953 | { |
||
954 | Object[] data = new Object[7]; |
||
955 | |||
956 | data[0] = m_active; |
||
957 | data[1] = m_handle; |
||
958 | data[2] = m_channel; |
||
959 | data[3] = m_name; |
||
960 | data[4] = m_id; |
||
961 | data[5] = m_message; |
||
962 | data[6] = RegexBitfield; |
||
963 | |||
964 | return data; |
||
965 | } |
||
966 | |||
967 | public static ListenerInfo FromData(uint localID, UUID ItemID, |
||
968 | UUID hostID, Object[] data) |
||
969 | { |
||
970 | ListenerInfo linfo = new ListenerInfo((int)data[1], localID, |
||
971 | ItemID, hostID, (int)data[2], (string)data[3], |
||
972 | (UUID)data[4], (string)data[5]); |
||
973 | linfo.m_active = (bool)data[0]; |
||
974 | if (data.Length >= 7) |
||
975 | { |
||
976 | linfo.RegexBitfield = (int)data[6]; |
||
977 | } |
||
978 | |||
979 | return linfo; |
||
980 | } |
||
981 | |||
982 | public UUID GetItemID() |
||
983 | { |
||
984 | return m_itemID; |
||
985 | } |
||
986 | |||
987 | public UUID GetHostID() |
||
988 | { |
||
989 | return m_hostID; |
||
990 | } |
||
991 | |||
992 | public int GetChannel() |
||
993 | { |
||
994 | return m_channel; |
||
995 | } |
||
996 | |||
997 | public uint GetLocalID() |
||
998 | { |
||
999 | return m_localID; |
||
1000 | } |
||
1001 | |||
1002 | public int GetHandle() |
||
1003 | { |
||
1004 | return m_handle; |
||
1005 | } |
||
1006 | |||
1007 | public string GetMessage() |
||
1008 | { |
||
1009 | return m_message; |
||
1010 | } |
||
1011 | |||
1012 | public string GetName() |
||
1013 | { |
||
1014 | return m_name; |
||
1015 | } |
||
1016 | |||
1017 | public bool IsActive() |
||
1018 | { |
||
1019 | return m_active; |
||
1020 | } |
||
1021 | |||
1022 | public void Deactivate() |
||
1023 | { |
||
1024 | m_active = false; |
||
1025 | } |
||
1026 | |||
1027 | public void Activate() |
||
1028 | { |
||
1029 | m_active = true; |
||
1030 | } |
||
1031 | |||
1032 | public UUID GetID() |
||
1033 | { |
||
1034 | return m_id; |
||
1035 | } |
||
1036 | |||
1037 | public int RegexBitfield { get; private set; } |
||
1038 | } |
||
1039 | } |