From dc21e8d5e2c830311271ac5cafb0d166a571a74e Mon Sep 17 00:00:00 2001 From: Adam Frisby Date: Thu, 8 May 2008 13:41:10 +0000 Subject: * Applying patch #1121 - Fixes for llListen() (Thanks Middlelink!) --- .../Region/Environment/Interfaces/IWorldComm.cs | 10 +- .../Modules/Scripting/WorldComm/WorldCommModule.cs | 598 ++++++++++++--------- 2 files changed, 333 insertions(+), 275 deletions(-) (limited to 'OpenSim/Region/Environment') diff --git a/OpenSim/Region/Environment/Interfaces/IWorldComm.cs b/OpenSim/Region/Environment/Interfaces/IWorldComm.cs index 8a63c54..c471e7f 100644 --- a/OpenSim/Region/Environment/Interfaces/IWorldComm.cs +++ b/OpenSim/Region/Environment/Interfaces/IWorldComm.cs @@ -33,14 +33,12 @@ namespace OpenSim.Region.Environment.Interfaces { public interface IWorldComm { - int Listen(uint LocalID, LLUUID itemID, LLUUID hostID, int channel, string name, string id, string msg); - void DeliverMessage(string sourceItemID, ChatTypeEnum type, int channel, string name, string msg); + int Listen(uint LocalID, LLUUID itemID, LLUUID hostID, int channel, string name, LLUUID id, string msg); + void DeliverMessage(ChatTypeEnum type, int channel, string name, LLUUID id, string msg); bool HasMessages(); ListenerInfo GetNextMessage(); - void ListenControl(int handle, int active); - void ListenRemove(int handle); + void ListenControl(LLUUID itemID, int handle, int active); + void ListenRemove(LLUUID itemID, int handle); void DeleteListener(LLUUID itemID); - uint PeekNextMessageLocalID(); - LLUUID PeekNextMessageItemID(); } } diff --git a/OpenSim/Region/Environment/Modules/Scripting/WorldComm/WorldCommModule.cs b/OpenSim/Region/Environment/Modules/Scripting/WorldComm/WorldCommModule.cs index e79b2bd..9121f7a 100644 --- a/OpenSim/Region/Environment/Modules/Scripting/WorldComm/WorldCommModule.cs +++ b/OpenSim/Region/Environment/Modules/Scripting/WorldComm/WorldCommModule.cs @@ -27,6 +27,7 @@ using System; using System.Collections; +using System.Collections.Generic; using libsecondlife; using Nini.Config; using OpenSim.Framework; @@ -60,6 +61,21 @@ using OpenSim.Region.Environment.Scenes; * be used to prevent in-world repeater loops. However, the * linden functions do not have this capability, so for now * thats the way it works. + * Instead it blocks messages originating from the same prim. + * (not Object!) + * + * For LSL compliance, note the following: + * (Tested again 1.21.1 on May 2, 2008) + * 1. 'id' has to be parsed into a LLUUID. None-UUID keys are + * to be replaced by the ZeroID key. (Well, TryParse does + * that for us. + * 2. Setting up an listen event from the same script, with the + * same filter settings (including step 1), returns the same + * handle as the original filter. + * 3. (TODO) handles should be script-local. Starting from 1. + * Might be actually easier to map the global handle into + * script-local handle in the ScriptEngine. Not sure if its + * worth the effort tho. * * **************************************************/ @@ -67,25 +83,39 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm { public class WorldCommModule : IRegionModule, IWorldComm { - private object CommListLock = new object(); - private object ListLock = new object(); private ListenerManager m_listenerManager; - private string m_name = "WorldCommModule"; private Queue m_pending; private Queue m_pendingQ; private Scene m_scene; - - public WorldCommModule() - { - } + private int m_whisperdistance = 10; + private int m_saydistance = 30; + private int m_shoutdistance = 100; #region IRegionModule Members public void Initialise(Scene scene, IConfigSource config) { + // wrap this in a try block so that defaults will work if + // the config file doesn't specify otherwise. + int maxlisteners = 1000; + int maxhandles = 64; + try + { + m_whisperdistance = config.Configs["Chat"].GetInt("whisper_distance", m_whisperdistance); + m_saydistance = config.Configs["Chat"].GetInt("say_distance", m_saydistance); + m_shoutdistance = config.Configs["Chat"].GetInt("shout_distance", m_shoutdistance); + maxlisteners = config.Configs["Chat"].GetInt("max_listens_per_region", maxlisteners); + maxhandles = config.Configs["Chat"].GetInt("max_listens_per_script", maxhandles); + } + catch (Exception) + { + } + if (maxlisteners < 1) maxlisteners = int.MaxValue; + if (maxhandles < 1) maxhandles = int.MaxValue; + m_scene = scene; m_scene.RegisterModuleInterface(this); - m_listenerManager = new ListenerManager(); + m_listenerManager = new ListenerManager(maxlisteners, maxhandles); m_scene.EventManager.OnNewClient += NewClient; m_pendingQ = new Queue(); m_pending = Queue.Synchronized(m_pendingQ); @@ -101,7 +131,7 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm public string Name { - get { return m_name; } + get { return "WorldCommModule"; } } public bool IsSharedModule @@ -113,170 +143,160 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm #region IWorldComm Members - public int Listen(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, string id, string msg) + /// + /// Create a listen event callback with the specified filters. + /// The parameters localID,itemID are needed to uniquely identify + /// the script during 'peek' time. Parameter hostID is needed to + /// determine the position of the script. + /// + /// localID of the script engine + /// UUID of the script engine + /// UUID of the SceneObjectPart + /// channel to listen on + /// name to filter on + /// key to filter on (user given, could be totally faked) + /// msg to filter on + /// number of the scripts handle + public int Listen(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, LLUUID id, string msg) { return m_listenerManager.AddListener(localID, itemID, hostID, channel, name, id, msg); } - public void ListenControl(int handle, int active) + /// + /// Sets the listen event with handle as active (active = TRUE) or inactive (active = FALSE). + /// The handle used is returned from Listen() + /// + /// UUID of the script engine + /// handle returned by Listen() + /// temp. activate or deactivate the Listen() + public void ListenControl(LLUUID itemID, int handle, int active) { - if (m_listenerManager != null) - { - if (active == 1) - m_listenerManager.Activate(handle); - else if (active == 0) - m_listenerManager.Dectivate(handle); - } + if (active == 1) + m_listenerManager.Activate(itemID, handle); + else if (active == 0) + m_listenerManager.Dectivate(itemID, handle); } - public void ListenRemove(int handle) + /// + /// Removes the listen event callback with handle + /// + /// UUID of the script engine + /// handle returned by Listen() + public void ListenRemove(LLUUID itemID, int handle) { - if (m_listenerManager != null) - { - m_listenerManager.Remove(handle); - } + m_listenerManager.Remove(itemID, handle); } + /// + /// Removes all listen event callbacks for the given itemID + /// (script engine) + /// + /// UUID of the script engine public void DeleteListener(LLUUID itemID) { - if (m_listenerManager != null) - { - m_listenerManager.DeleteListener(itemID); - } + m_listenerManager.DeleteListener(itemID); } - // This method scans nearby objects and determines if they are listeners, - // and if so if this message fits the filter. If it does, then - // enqueue the message for delivery to the objects listen event handler. - // Objects that do an llSay have their messages delivered here, and for - // nearby avatars, the SimChat function is used. - public void DeliverMessage(string sourceItemID, ChatTypeEnum type, int channel, string name, string msg) + /// + /// This method scans over the objects which registered an interest in listen callbacks. + /// For everyone it finds, it checks if it fits the given filter. If it does, then + /// enqueue the message for delivery to the objects listen event handler. + /// The enqueued ListenerInfo no longer has filter values, but the actually trigged values. + /// Objects that do an llSay have their messages delivered here and for nearby avatars, + /// the OnChatFromViewer event is used. + /// + /// type of delvery (whisper,say,shout or regionwide) + /// channel to sent on + /// name of sender (object or avatar) + /// key of sender (object or avatar) + /// msg to sent + public void DeliverMessage(ChatTypeEnum type, int channel, string name, LLUUID id, string msg) { SceneObjectPart source = null; ScenePresence avatar = null; - - source = m_scene.GetSceneObjectPart(new LLUUID(sourceItemID)); - if (source == null) - { - avatar = m_scene.GetScenePresence(new LLUUID(sourceItemID)); + LLVector3 position; + + source = m_scene.GetSceneObjectPart(id); + if (source != null) + position = source.AbsolutePosition; + else { + avatar = m_scene.GetScenePresence(id); + if (avatar != null) + position = avatar.AbsolutePosition; + else + // bail out early, given source could not be found + return; } - if ((avatar != null) || (source != null)) + + // Determine which listen event filters match the given set of arguments, this results + // in a limited set of listeners, each belonging a host. If the host is in range, add them + // to the pending queue. + foreach (ListenerInfo li in m_listenerManager.GetListeners(LLUUID.Zero, channel, name, id, msg)) { - // Loop through the objects in the scene - // If they are in proximity, then if they are - // listeners, if so add them to the pending queue + // Dont process if this message is from yourself! + if (li.GetHostID().Equals(id)) + continue; - foreach (ListenerInfo li in m_listenerManager.GetListeners()) - { - EntityBase sPart; + SceneObjectPart sPart = m_scene.GetSceneObjectPart(li.GetHostID()); + if (sPart == null) + continue; - m_scene.Entities.TryGetValue(li.GetHostID(), out sPart); + double dis = Util.GetDistanceTo(sPart.AbsolutePosition, position); + switch (type) + { + case ChatTypeEnum.Whisper: + if (dis < m_whisperdistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; - if (sPart != null) - { - double dis = 0; + case ChatTypeEnum.Say: + if (dis < m_saydistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; - if (source != null) - dis = Util.GetDistanceTo(sPart.AbsolutePosition, source.AbsolutePosition); - else - dis = Util.GetDistanceTo(sPart.AbsolutePosition, avatar.AbsolutePosition); + case ChatTypeEnum.Shout: + if (dis < m_shoutdistance) + { + lock (m_pending.SyncRoot) + { + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); + } + } + break; - switch (type) + case ChatTypeEnum.Region: + lock (m_pending.SyncRoot) { - case ChatTypeEnum.Whisper: - - if ((dis < 10) && (dis > -10)) - { - if (li.GetChannel() == channel) - { - ListenerInfo isListener = m_listenerManager.IsListenerMatch( - sourceItemID, sPart.UUID, channel, name, msg - ); - if (isListener != null) - { - lock (m_pending.SyncRoot) - { - m_pending.Enqueue(isListener); - } - } - } - } - break; - - case ChatTypeEnum.Say: - - if ((dis < 30) && (dis > -30)) - { - if (li.GetChannel() == channel) - { - ListenerInfo isListener = m_listenerManager.IsListenerMatch( - sourceItemID, sPart.UUID, channel, name, msg - ); - if (isListener != null) - { - lock (m_pending.SyncRoot) - { - m_pending.Enqueue(isListener); - } - } - } - } - break; - - case ChatTypeEnum.Shout: - if ((dis < 100) && (dis > -100)) - { - if (li.GetChannel() == channel) - { - ListenerInfo isListener = m_listenerManager.IsListenerMatch( - sourceItemID, sPart.UUID, channel, name, msg - ); - if (isListener != null) - { - lock (m_pending.SyncRoot) - { - m_pending.Enqueue(isListener); - } - } - } - } - break; - - case ChatTypeEnum.Broadcast: - // Dont process if this message is from itself! - if (li.GetHostID().ToString().Equals(sourceItemID) || - sPart.UUID.ToString().Equals(sourceItemID)) - continue; - - if (li.GetChannel() == channel) - { - ListenerInfo isListener = m_listenerManager.IsListenerMatch( - sourceItemID, sPart.UUID, channel, name, msg - ); - if (isListener != null) - { - lock (m_pending.SyncRoot) - { - m_pending.Enqueue(isListener); - } - } - } - - break; + m_pending.Enqueue(new ListenerInfo(li,name,id,msg)); } - } + break; } } } + /// + /// Are there any listen events ready to be dispatched? + /// + /// boolean indication public bool HasMessages() { - if (m_pending != null) - return (m_pending.Count > 0); - else - return false; + return (m_pending.Count > 0); } + /// + /// Pop the first availlable listen event from the queue + /// + /// ListenerInfo with filter filled in public ListenerInfo GetNextMessage() { ListenerInfo li = null; @@ -289,19 +309,9 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm return li; } - public uint PeekNextMessageLocalID() - { - return ((ListenerInfo) m_pending.Peek()).GetLocalID(); - } - - public LLUUID PeekNextMessageItemID() - { - return ((ListenerInfo) m_pending.Peek()).GetItemID(); - } - #endregion - public void NewClient(IClientAPI client) + private void NewClient(IClientAPI client) { client.OnChatFromViewer += DeliverClientMessage; } @@ -314,212 +324,267 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm private void DeliverClientMessage(Object sender, ChatFromViewerArgs e) { - DeliverMessage(e.Sender.AgentId.ToString(), - e.Type, e.Channel, + DeliverMessage(e.Type, + e.Channel, e.Sender.FirstName + " " + e.Sender.LastName, + e.Sender.AgentId, e.Message); } } public class ListenerManager { - //private Dictionary m_listeners; - private object ListenersLock = new object(); - private Hashtable m_listeners = Hashtable.Synchronized(new Hashtable()); - private int m_MaxListeners = 100; + private Dictionary> m_listeners = new Dictionary>(); + private int m_maxlisteners; + private int m_maxhandles; + private int m_curlisteners; + + public ListenerManager(int maxlisteners, int maxhandles) + { + m_maxlisteners = maxlisteners; + m_maxhandles = maxhandles; + m_curlisteners = 0; + } - public int AddListener(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, string id, string msg) + public int AddListener(uint localID, LLUUID itemID, LLUUID hostID, int channel, string name, LLUUID id, string msg) { - if (m_listeners.Count < m_MaxListeners) + // do we already have a match on this particular filter event? + List coll = GetListeners(itemID, channel, name, id, msg); + + if (coll.Count > 0) + { + // special case, called with same filter settings, return same handle + // (2008-05-02, tested on 1.21.1 server, still holds) + return coll[0].GetHandle(); + } + + if (m_curlisteners < m_maxlisteners) { - ListenerInfo isListener = IsListenerMatch(LLUUID.Zero.ToString(), itemID, channel, name, msg); + int newHandle = GetNewHandle(itemID); - if (isListener == null) + if (newHandle > 0) { - int newHandle = GetNewHandle(); + ListenerInfo li = new ListenerInfo(newHandle, localID, itemID, hostID, channel, name, id, msg); - if (newHandle > -1) + lock (m_listeners) { - ListenerInfo li = new ListenerInfo(localID, newHandle, itemID, hostID, channel, name, id, msg); - - lock (m_listeners.SyncRoot) + List listeners; + if (!m_listeners.TryGetValue(channel,out listeners)) { - m_listeners.Add(newHandle, li); + listeners = new List(); + m_listeners.Add(channel, listeners); } - - return newHandle; + listeners.Add(li); + m_curlisteners++; } + + return newHandle; } } - return -1; } - public void Remove(int handle) + public void Remove(LLUUID itemID, int handle) { - lock (m_listeners.SyncRoot) + lock (m_listeners) { - m_listeners.Remove(handle); + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle().Equals(handle)) + { + lis.Value.Remove(li); + if (lis.Value.Count == 0) + { + m_listeners.Remove(lis.Key); + m_curlisteners--; + } + // there should be only one, so we bail out early + return; + } + } + } } } public void DeleteListener(LLUUID itemID) { - ArrayList removedListeners = new ArrayList(); + List emptyChannels = new List(); + List removedListeners = new List(); - lock (m_listeners.SyncRoot) + lock (m_listeners) { - IDictionaryEnumerator en = m_listeners.GetEnumerator(); - while (en.MoveNext()) + foreach (KeyValuePair> lis in m_listeners) { - ListenerInfo li = (ListenerInfo) en.Value; - if (li.GetItemID().Equals(itemID)) + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID)) + { + // store them first, else the enumerated bails on us + removedListeners.Add(li); + } + } + foreach (ListenerInfo li in removedListeners) + { + lis.Value.Remove(li); + m_curlisteners--; + } + removedListeners.Clear(); + if (lis.Value.Count == 0) { - removedListeners.Add(li.GetHandle()); + // again, store first, remove later + emptyChannels.Add(lis.Key); } } - foreach (int handle in removedListeners) + foreach (int channel in emptyChannels) { - m_listeners.Remove(handle); + m_listeners.Remove(channel); } } } - private int GetNewHandle() + public void Activate(LLUUID itemID, int handle) { - for (int i = 0; i < int.MaxValue - 1; i++) + lock (m_listeners) { - if (!m_listeners.ContainsKey(i)) - return i; + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle() == handle) + { + li.Activate(); + // only one, bail out + return; + } + } + } } - - return -1; } - public bool IsListener(LLUUID hostID) + public void Dectivate(LLUUID itemID, int handle) { - foreach (ListenerInfo li in m_listeners.Values) + lock (m_listeners) { - if (li.GetHostID().Equals(hostID)) - return true; + foreach (KeyValuePair> lis in m_listeners) + { + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID) && li.GetHandle() == handle) + { + li.Deactivate(); + // only one, bail out + return; + } + } + } } - - return false; } - public void Activate(int handle) + // non-locked access, since its always called in the context of the lock + private int GetNewHandle(LLUUID itemID) { - if (m_listeners.ContainsKey(handle)) + List handles = new List(); + + // build a list of used keys for this specific itemID... + foreach (KeyValuePair> lis in m_listeners) { - lock (m_listeners.SyncRoot) - { - ListenerInfo li = (ListenerInfo) m_listeners[handle]; - li.Activate(); - } + foreach (ListenerInfo li in lis.Value) + { + if (li.GetItemID().Equals(itemID)) + handles.Add(li.GetHandle()); + } } - } - public void Dectivate(int handle) - { - if (m_listeners.ContainsKey(handle)) + // Note: 0 is NOT a valid handle for llListen() to return + for (int i = 1; i <= m_maxhandles; i++) { - ListenerInfo li = (ListenerInfo) m_listeners[handle]; - li.Deactivate(); + if (!handles.Contains(i)) + return i; } + + return -1; } // Theres probably a more clever and efficient way to // do this, maybe with regex. - public ListenerInfo IsListenerMatch(string sourceItemID, LLUUID listenerKey, int channel, string name, - string msg) + // PM2008: Ha, one could even be smart and define a specialized Enumerator. + public List GetListeners(LLUUID itemID, int channel, string name, LLUUID id, string msg) { - bool isMatch = true; - lock (m_listeners.SyncRoot) + List collection = new List(); + + lock (m_listeners) { - IDictionaryEnumerator en = m_listeners.GetEnumerator(); - while (en.MoveNext()) + List listeners; + if (!m_listeners.TryGetValue(channel,out listeners)) { - ListenerInfo li = (ListenerInfo) en.Value; + return collection; + } - if (li.IsActive()) + foreach (ListenerInfo li in listeners) + { + if (!li.IsActive()) { - if (li.GetHostID().Equals(listenerKey)) - { - if (channel == li.GetChannel()) - { - if ((li.GetID().ToString().Length > 0) && - (!li.GetID().Equals(LLUUID.Zero))) - { - if (!li.GetID().ToString().Equals(sourceItemID)) - { - isMatch = false; - } - } - if (isMatch && (li.GetName().Length > 0)) - { - if (li.GetName().Equals(name)) - { - isMatch = false; - } - } - if (isMatch) - { - return new ListenerInfo( - li.GetLocalID(), li.GetHandle(), li.GetItemID(), li.GetHostID(), - li.GetChannel(), name, li.GetID(), msg, new LLUUID(sourceItemID) - ); - } - } - } + continue; + } + if (!itemID.Equals(LLUUID.Zero) && !li.GetItemID().Equals(itemID)) + { + continue; + } + if (li.GetName().Length > 0 && !li.GetName().Equals(name)) + { + continue; + } + if (!li.GetID().Equals(LLUUID.Zero) && !li.GetID().Equals(id)) + { + continue; + } + if (li.GetMessage().Length > 0 && !li.GetMessage().Equals(msg)) + { + continue; } + collection.Add(li); } } - return null; - } - - public ICollection GetListeners() - { - return m_listeners.Values; + return collection; } } public class ListenerInfo { private bool m_active; // Listener is active or not - private int m_channel; // Channel private int m_handle; // Assigned handle of this listener + private uint m_localID; // Local ID from script engine + private LLUUID m_itemID; // ID of the host script engine private LLUUID m_hostID; // ID of the host/scene part + private int m_channel; // Channel private LLUUID m_id; // ID to filter messages from - private LLUUID m_itemID; // ID of the host script engine - private uint m_localID; // Local ID from script engine - private string m_message; // The message private string m_name; // Object name to filter messages from - private LLUUID m_sourceItemID; // ID of the scenePart or avatar source of the message + private string m_message; // The message - public ListenerInfo(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id, string message) + public ListenerInfo(int handle, uint localID, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id, string message) { - Initialise(localID, handle, ItemID, hostID, channel, name, id, message); + Initialise(handle, localID, ItemID, hostID, channel, name, id, message); } - public ListenerInfo(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id, - string message, LLUUID sourceItemID) + public ListenerInfo(ListenerInfo li, string name, LLUUID id, string message) { - Initialise(localID, handle, ItemID, hostID, channel, name, id, message); - m_sourceItemID = sourceItemID; + Initialise(li.m_handle, li.m_localID, li.m_itemID, li.m_hostID, li.m_channel, name, id, message); } - private void Initialise(uint localID, int handle, LLUUID ItemID, LLUUID hostID, int channel, string name, + private void Initialise(int handle, uint localID, LLUUID ItemID, LLUUID hostID, int channel, string name, LLUUID id, string message) { + m_active = true; m_handle = handle; - m_channel = channel; + m_localID = localID; m_itemID = ItemID; m_hostID = hostID; + m_channel = channel; m_name = name; m_id = id; m_message = message; - m_active = true; - m_localID = localID; } public LLUUID GetItemID() @@ -532,11 +597,6 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm return m_hostID; } - public LLUUID GetSourceItemID() - { - return m_sourceItemID; - } - public int GetChannel() { return m_channel; @@ -582,4 +642,4 @@ namespace OpenSim.Region.Environment.Modules.Scripting.WorldComm return m_id; } } -} \ No newline at end of file +} -- cgit v1.1