430 lines
17 KiB
C#
430 lines
17 KiB
C#
/*
|
|
* 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 OpenSimulator 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.
|
|
*/
|
|
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Reflection;
|
|
using System.Text.RegularExpressions;
|
|
using System.Threading;
|
|
using System.Globalization;
|
|
using OpenMetaverse;
|
|
using log4net;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Region.Framework.Scenes;
|
|
using OpenSim.Region.Framework.Scenes.Scripting;
|
|
using OpenSim.Region.ScriptEngine.Shared;
|
|
using OpenSim.Region.ScriptEngine.Shared.CodeTools;
|
|
|
|
namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
|
{
|
|
// Because every thread needs some data set for it
|
|
// (time started to execute current function), it will do its work
|
|
// within a class
|
|
public class EventQueueThreadClass
|
|
{
|
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
// How many ms to sleep if queue is empty
|
|
private static int nothingToDoSleepms;// = 50;
|
|
private static ThreadPriority MyThreadPriority;
|
|
|
|
public long LastExecutionStarted;
|
|
public bool InExecution = false;
|
|
public bool KillCurrentScript = false;
|
|
|
|
//private EventQueueManager eventQueueManager;
|
|
public Thread EventQueueThread;
|
|
private static int ThreadCount = 0;
|
|
|
|
private string ScriptEngineName = "ScriptEngine.Common";
|
|
|
|
public EventQueueThreadClass()//EventQueueManager eqm
|
|
{
|
|
CultureInfo USCulture = new CultureInfo("en-US");
|
|
Thread.CurrentThread.CurrentCulture = USCulture;
|
|
|
|
//eventQueueManager = eqm;
|
|
ReadConfig();
|
|
Start();
|
|
}
|
|
|
|
~EventQueueThreadClass()
|
|
{
|
|
Stop();
|
|
}
|
|
|
|
public void ReadConfig()
|
|
{
|
|
lock (ScriptEngine.ScriptEngines)
|
|
{
|
|
foreach (ScriptEngine m_ScriptEngine in
|
|
ScriptEngine.ScriptEngines)
|
|
{
|
|
ScriptEngineName = m_ScriptEngine.ScriptEngineName;
|
|
nothingToDoSleepms =
|
|
m_ScriptEngine.ScriptConfigSource.GetInt(
|
|
"SleepTimeIfNoScriptExecutionMs", 50);
|
|
|
|
string pri = m_ScriptEngine.ScriptConfigSource.GetString(
|
|
"ScriptThreadPriority", "BelowNormal");
|
|
|
|
switch (pri.ToLower())
|
|
{
|
|
case "lowest":
|
|
MyThreadPriority = ThreadPriority.Lowest;
|
|
break;
|
|
case "belownormal":
|
|
MyThreadPriority = ThreadPriority.BelowNormal;
|
|
break;
|
|
case "normal":
|
|
MyThreadPriority = ThreadPriority.Normal;
|
|
break;
|
|
case "abovenormal":
|
|
MyThreadPriority = ThreadPriority.AboveNormal;
|
|
break;
|
|
case "highest":
|
|
MyThreadPriority = ThreadPriority.Highest;
|
|
break;
|
|
default:
|
|
MyThreadPriority = ThreadPriority.BelowNormal;
|
|
m_log.Error(
|
|
"[ScriptEngine.DotNetEngine]: Unknown "+
|
|
"priority type \"" + pri +
|
|
"\" in config file. Defaulting to "+
|
|
"\"BelowNormal\".");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// Now set that priority
|
|
if (EventQueueThread != null)
|
|
if (EventQueueThread.IsAlive)
|
|
EventQueueThread.Priority = MyThreadPriority;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start thread
|
|
/// </summary>
|
|
private void Start()
|
|
{
|
|
EventQueueThread = new Thread(EventQueueThreadLoop);
|
|
EventQueueThread.IsBackground = true;
|
|
|
|
EventQueueThread.Priority = MyThreadPriority;
|
|
EventQueueThread.Name = "EventQueueManagerThread_" + ThreadCount;
|
|
EventQueueThread.Start();
|
|
ThreadTracker.Add(EventQueueThread);
|
|
|
|
// Look at this... Don't you wish everyone did that solid
|
|
// coding everywhere? :P
|
|
|
|
if (ThreadCount == int.MaxValue)
|
|
ThreadCount = 0;
|
|
|
|
ThreadCount++;
|
|
}
|
|
|
|
public void Stop()
|
|
{
|
|
if (EventQueueThread != null && EventQueueThread.IsAlive == true)
|
|
{
|
|
try
|
|
{
|
|
EventQueueThread.Abort(); // Send abort
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
}
|
|
}
|
|
|
|
private EventQueueManager.QueueItemStruct BlankQIS =
|
|
new EventQueueManager.QueueItemStruct();
|
|
|
|
private ScriptEngine lastScriptEngine;
|
|
private uint lastLocalID;
|
|
private UUID lastItemID;
|
|
|
|
// Queue processing thread loop
|
|
private void EventQueueThreadLoop()
|
|
{
|
|
CultureInfo USCulture = new CultureInfo("en-US");
|
|
Thread.CurrentThread.CurrentCulture = USCulture;
|
|
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
DoProcessQueue();
|
|
}
|
|
}
|
|
catch (ThreadAbortException)
|
|
{
|
|
m_log.Info("[" + ScriptEngineName +
|
|
"]: ThreadAbortException while executing "+
|
|
"function.");
|
|
}
|
|
catch (SelfDeleteException) // Must delete SOG
|
|
{
|
|
SceneObjectPart part =
|
|
lastScriptEngine.World.GetSceneObjectPart(
|
|
lastLocalID);
|
|
if (part != null && part.ParentGroup != null)
|
|
lastScriptEngine.World.DeleteSceneObject(
|
|
part.ParentGroup, false);
|
|
}
|
|
catch (ScriptDeleteException) // Must delete item
|
|
{
|
|
SceneObjectPart part =
|
|
lastScriptEngine.World.GetSceneObjectPart(
|
|
lastLocalID);
|
|
if (part != null && part.ParentGroup != null)
|
|
part.Inventory.RemoveInventoryItem(lastItemID);
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
m_log.ErrorFormat("[{0}]: Exception {1} thrown", ScriptEngineName, e.GetType().ToString());
|
|
throw e;
|
|
}
|
|
}
|
|
}
|
|
catch (ThreadAbortException)
|
|
{
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
// TODO: Let users in the sim and those entering it and possibly an external watchdog know what has happened
|
|
m_log.ErrorFormat(
|
|
"[{0}]: Event queue thread terminating with exception. PLEASE REBOOT YOUR SIM - SCRIPT EVENTS WILL NOT WORK UNTIL YOU DO. Exception is {1}",
|
|
ScriptEngineName, e);
|
|
}
|
|
}
|
|
|
|
public void DoProcessQueue()
|
|
{
|
|
foreach (ScriptEngine m_ScriptEngine in
|
|
new ArrayList(ScriptEngine.ScriptEngines))
|
|
{
|
|
lastScriptEngine = m_ScriptEngine;
|
|
|
|
EventQueueManager.QueueItemStruct QIS = BlankQIS;
|
|
bool GotItem = false;
|
|
|
|
//if (PleaseShutdown)
|
|
// return;
|
|
|
|
if (m_ScriptEngine.m_EventQueueManager == null ||
|
|
m_ScriptEngine.m_EventQueueManager.eventQueue == null)
|
|
continue;
|
|
|
|
if (m_ScriptEngine.m_EventQueueManager.eventQueue.Count == 0)
|
|
{
|
|
// Nothing to do? Sleep a bit waiting for something to do
|
|
Thread.Sleep(nothingToDoSleepms);
|
|
}
|
|
else
|
|
{
|
|
// Something in queue, process
|
|
|
|
// OBJECT BASED LOCK - TWO THREADS WORKING ON SAME
|
|
// OBJECT IS NOT GOOD
|
|
lock (m_ScriptEngine.m_EventQueueManager.eventQueue)
|
|
{
|
|
GotItem = false;
|
|
for (int qc = 0; qc < m_ScriptEngine.m_EventQueueManager.eventQueue.Count; qc++)
|
|
{
|
|
// Get queue item
|
|
QIS = m_ScriptEngine.m_EventQueueManager.eventQueue.Dequeue();
|
|
|
|
// Check if object is being processed by
|
|
// someone else
|
|
if (m_ScriptEngine.m_EventQueueManager.TryLock(
|
|
QIS.localID) == false)
|
|
{
|
|
// Object is already being processed, requeue it
|
|
m_ScriptEngine.m_EventQueueManager.
|
|
eventQueue.Enqueue(QIS);
|
|
}
|
|
else
|
|
{
|
|
// We have lock on an object and can process it
|
|
GotItem = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (GotItem == true)
|
|
{
|
|
// Execute function
|
|
try
|
|
{
|
|
// Only pipe event if land supports it.
|
|
if (m_ScriptEngine.World.PipeEventsForScript(
|
|
QIS.localID))
|
|
{
|
|
lastLocalID = QIS.localID;
|
|
lastItemID = QIS.itemID;
|
|
LastExecutionStarted = DateTime.Now.Ticks;
|
|
KillCurrentScript = false;
|
|
InExecution = true;
|
|
m_ScriptEngine.m_ScriptManager.ExecuteEvent(
|
|
QIS.localID,
|
|
QIS.itemID,
|
|
QIS.functionName,
|
|
QIS.llDetectParams,
|
|
QIS.param);
|
|
|
|
InExecution = false;
|
|
}
|
|
}
|
|
catch (TargetInvocationException tie)
|
|
{
|
|
Exception e = tie.InnerException;
|
|
|
|
if (e is SelfDeleteException) // Forward it
|
|
throw e;
|
|
|
|
InExecution = false;
|
|
string text = FormatException(tie, QIS.LineMap);
|
|
|
|
// DISPLAY ERROR INWORLD
|
|
|
|
// if (e.InnerException != null)
|
|
// {
|
|
// // Send inner exception
|
|
// string line = " (unknown line)";
|
|
// Regex rx = new Regex(@"SecondLife\.Script\..+[\s:](?<line>\d+)\.?\r?$", RegexOptions.Compiled);
|
|
// if (rx.Match(e.InnerException.ToString()).Success)
|
|
// line = " (line " + rx.Match(e.InnerException.ToString()).Result("${line}") + ")";
|
|
// text += e.InnerException.Message.ToString() + line;
|
|
// }
|
|
// else
|
|
// {
|
|
// text += "\r\n";
|
|
// // Send normal
|
|
// text += e.Message.ToString();
|
|
// }
|
|
// if (KillCurrentScript)
|
|
// text += "\r\nScript will be deactivated!";
|
|
|
|
try
|
|
{
|
|
if (text.Length >= 1100)
|
|
text = text.Substring(0, 1099);
|
|
IScriptHost m_host =
|
|
m_ScriptEngine.World.GetSceneObjectPart(QIS.localID);
|
|
m_ScriptEngine.World.SimChat(
|
|
Utils.StringToBytes(text),
|
|
ChatTypeEnum.DebugChannel, 2147483647,
|
|
m_host.AbsolutePosition,
|
|
m_host.Name, m_host.UUID, false);
|
|
}
|
|
catch (Exception)
|
|
{
|
|
m_log.Error("[" +
|
|
ScriptEngineName + "]: " +
|
|
"Unable to send text in-world:\r\n" +
|
|
text);
|
|
}
|
|
finally
|
|
{
|
|
// So we are done sending message in-world
|
|
if (KillCurrentScript)
|
|
{
|
|
m_ScriptEngine.m_EventQueueManager.
|
|
m_ScriptEngine.m_ScriptManager.
|
|
StopScript(
|
|
QIS.localID, QIS.itemID);
|
|
}
|
|
}
|
|
}
|
|
catch (Exception)
|
|
{
|
|
throw;
|
|
}
|
|
finally
|
|
{
|
|
InExecution = false;
|
|
m_ScriptEngine.m_EventQueueManager.ReleaseLock(
|
|
QIS.localID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
string FormatException(Exception e, Dictionary<KeyValuePair<int,int>,
|
|
KeyValuePair<int,int>> LineMap)
|
|
{
|
|
if (e.InnerException == null)
|
|
return e.ToString();
|
|
|
|
string message = "Runtime error:\n" + e.InnerException.StackTrace;
|
|
string[] lines = message.Split(new char[] {'\n'});
|
|
|
|
foreach (string line in lines)
|
|
{
|
|
if (line.Contains("SecondLife.Script"))
|
|
{
|
|
int idx = line.IndexOf(':');
|
|
if (idx != -1)
|
|
{
|
|
string val = line.Substring(idx+1);
|
|
int lineNum = 0;
|
|
if (int.TryParse(val, out lineNum))
|
|
{
|
|
KeyValuePair<int, int> pos =
|
|
Compiler.FindErrorPosition(
|
|
lineNum, 0, LineMap);
|
|
|
|
int scriptLine = pos.Key;
|
|
int col = pos.Value;
|
|
if (scriptLine == 0)
|
|
scriptLine++;
|
|
if (col == 0)
|
|
col++;
|
|
message = string.Format("Runtime error:\n" +
|
|
"Line ({0}): {1}", scriptLine - 1,
|
|
e.InnerException.Message);
|
|
|
|
return message;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return message;
|
|
}
|
|
}
|
|
}
|