From 6dd923b01d6864ffcb17030c9de17224f45b4c2a Mon Sep 17 00:00:00 2001 From: Tedd Hansen Date: Fri, 5 Oct 2007 19:56:44 +0000 Subject: Some more work on new ScriptEngine. --- .../ScriptEngine/DotNetEngine/EventQueueManager.cs | 321 +++++++++++++++++++++ 1 file changed, 321 insertions(+) create mode 100644 OpenSim/Grid/ScriptEngine/DotNetEngine/EventQueueManager.cs (limited to 'OpenSim/Grid/ScriptEngine/DotNetEngine/EventQueueManager.cs') diff --git a/OpenSim/Grid/ScriptEngine/DotNetEngine/EventQueueManager.cs b/OpenSim/Grid/ScriptEngine/DotNetEngine/EventQueueManager.cs new file mode 100644 index 0000000..8ee005a --- /dev/null +++ b/OpenSim/Grid/ScriptEngine/DotNetEngine/EventQueueManager.cs @@ -0,0 +1,321 @@ +/* +* Copyright (c) Contributors, http://opensimulator.org/ +* See CONTRIBUTORS.TXT for a full list of copyright holders. +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions are met: +* * Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* * Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in the +* documentation and/or other materials provided with the distribution. +* * Neither the name of the OpenSim Project nor the +* names of its contributors may be used to endorse or promote products +* derived from this software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS AND ANY +* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY +* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +* +*/ +/* Original code: Tedd Hansen */ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using System.Reflection; +using OpenSim.Region.Environment.Scenes.Scripting; +using libsecondlife; +using OpenSim.Grid.ScriptEngine.DotNetEngine.Compiler.LSL; + +namespace OpenSim.Grid.ScriptEngine.DotNetEngine +{ + /// + /// EventQueueManager handles event queues + /// Events are queued and executed in separate thread + /// + [Serializable] + class EventQueueManager + { + /// + /// List of threads processing event queue + /// + private List eventQueueThreads = new List(); + private object queueLock = new object(); // Mutex lock object + /// + /// How many ms to sleep if queue is empty + /// + private int nothingToDoSleepms = 50; + /// + /// How many threads to process queue with + /// + private int numberOfThreads = 2; + /// + /// Queue containing events waiting to be executed + /// + private Queue eventQueue = new Queue(); + /// + /// Queue item structure + /// + private struct QueueItemStruct + { + public uint localID; + public LLUUID itemID; + public string functionName; + public object[] param; + } + + /// + /// List of localID locks for mutex processing of script events + /// + private List objectLocks = new List(); + private object tryLockLock = new object(); // Mutex lock object + + private ScriptEngine m_ScriptEngine; + public EventQueueManager(ScriptEngine _ScriptEngine) + { + m_ScriptEngine = _ScriptEngine; + + // + // Start event queue processing threads (worker threads) + // + for (int ThreadCount = 0; ThreadCount <= numberOfThreads; ThreadCount++) + { + Thread EventQueueThread = new Thread(EventQueueThreadLoop); + eventQueueThreads.Add(EventQueueThread); + EventQueueThread.IsBackground = true; + EventQueueThread.Priority = ThreadPriority.BelowNormal; + EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount; + EventQueueThread.Start(); + } + } + ~EventQueueManager() + { + + // Kill worker threads + foreach (Thread EventQueueThread in new System.Collections.ArrayList(eventQueueThreads)) + { + if (EventQueueThread != null && EventQueueThread.IsAlive == true) + { + try + { + EventQueueThread.Abort(); + EventQueueThread.Join(); + } + catch (Exception) + { + //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Exception killing worker thread: " + e.ToString()); + } + } + } + eventQueueThreads.Clear(); + // Todo: Clean up our queues + eventQueue.Clear(); + + } + + /// + /// Queue processing thread loop + /// + private void EventQueueThreadLoop() + { + //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Worker thread spawned"); + try + { + QueueItemStruct BlankQIS = new QueueItemStruct(); + while (true) + { + try + { + QueueItemStruct QIS = BlankQIS; + bool GotItem = false; + + if (eventQueue.Count == 0) + { + // Nothing to do? Sleep a bit waiting for something to do + Thread.Sleep(nothingToDoSleepms); + } + else + { + // Something in queue, process + //myScriptEngine.m_logger.Verbose("ScriptEngine", "Processing event for localID: " + QIS.localID + ", itemID: " + QIS.itemID + ", FunctionName: " + QIS.FunctionName); + + // OBJECT BASED LOCK - TWO THREADS WORKING ON SAME OBJECT IS NOT GOOD + lock (queueLock) + { + GotItem = false; + for (int qc = 0; qc < eventQueue.Count; qc++) + { + // Get queue item + QIS = eventQueue.Dequeue(); + + // Check if object is being processed by someone else + if (TryLock(QIS.localID) == false) + { + // Object is already being processed, requeue it + eventQueue.Enqueue(QIS); + } + else + { + // We have lock on an object and can process it + GotItem = true; + break; + } + } // go through queue + } // lock + + if (GotItem == true) + { + // Execute function + try + { + m_ScriptEngine.m_ScriptManager.ExecuteEvent(QIS.localID, QIS.itemID, QIS.functionName, QIS.param); + } + catch (Exception e) + { + // DISPLAY ERROR INWORLD + string text = "Error executing script function \"" + QIS.functionName + "\":\r\n"; + if (e.InnerException != null) + { // Send inner exception + text += e.InnerException.Message.ToString(); + } + else + { // Send normal + text += e.Message.ToString(); + } + try + { + if (text.Length > 1500) + text = text.Substring(0, 1500); + IScriptHost m_host = m_ScriptEngine.World.GetSceneObjectPart(QIS.localID); + //if (m_host != null) + //{ + m_ScriptEngine.World.SimChat(Helpers.StringToField(text), 1, 0, m_host.AbsolutePosition, m_host.Name, m_host.UUID); + } catch { + //} + //else + //{ + // T oconsole + Console.WriteLine("Unable to send text in-world:\r\n" + text); + } + + } + finally + { + ReleaseLock(QIS.localID); + } + } + + } // Something in queue + } catch (ThreadAbortException tae) { + throw tae; + } catch (Exception e) { + Console.WriteLine("Exception in EventQueueThreadLoop: " + e.ToString()); + } + } // while + } // try + catch (ThreadAbortException) + { + //myScriptEngine.Log.Verbose("ScriptEngine", "EventQueueManager Worker thread killed: " + tae.Message); + } + } + + /// + /// Try to get a mutex lock on localID + /// + /// + /// + private bool TryLock(uint localID) + { + lock (tryLockLock) + { + if (objectLocks.Contains(localID) == true) + { + return false; + } + else + { + objectLocks.Add(localID); + return true; + } + } + } + + /// + /// Release mutex lock on localID + /// + /// + private void ReleaseLock(uint localID) + { + lock (tryLockLock) + { + if (objectLocks.Contains(localID) == true) + { + objectLocks.Remove(localID); + } + } + } + + + /// + /// Add event to event execution queue + /// + /// + /// Name of the function, will be state + "_event_" + FunctionName + /// Array of parameters to match event mask + public void AddToObjectQueue(uint localID, string FunctionName, object[] param) + { + // Determine all scripts in Object and add to their queue + //myScriptEngine.m_logger.Verbose("ScriptEngine", "EventQueueManager Adding localID: " + localID + ", FunctionName: " + FunctionName); + + + // Do we have any scripts in this object at all? If not, return + if (m_ScriptEngine.m_ScriptManager.Scripts.ContainsKey(localID) == false) + { + //Console.WriteLine("Event \"" + FunctionName + "\" for localID: " + localID + ". No scripts found on this localID."); + return; + } + + Dictionary.KeyCollection scriptKeys = m_ScriptEngine.m_ScriptManager.GetScriptKeys(localID); + + foreach ( LLUUID itemID in scriptKeys ) + { + // Add to each script in that object + // TODO: Some scripts may not subscribe to this event. Should we NOT add it? Does it matter? + AddToScriptQueue(localID, itemID, FunctionName, param); + } + + } + + /// + /// Add event to event execution queue + /// + /// + /// + /// Name of the function, will be state + "_event_" + FunctionName + /// Array of parameters to match event mask + public void AddToScriptQueue(uint localID, LLUUID itemID, string FunctionName, object[] param) + { + lock (queueLock) + { + // Create a structure and add data + QueueItemStruct QIS = new QueueItemStruct(); + QIS.localID = localID; + QIS.itemID = itemID; + QIS.functionName = FunctionName; + QIS.param = param; + + // Add it to queue + eventQueue.Enqueue(QIS); + } + } + + } +} -- cgit v1.1