OpenSimMirror/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs

1057 lines
42 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.Threading;
using System.Collections.Generic;
using System.Text;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.ScriptEngine.Interfaces;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
using OpenSim.Region.ScriptEngine.XMREngine;
using OpenSim.Region.Framework.Scenes;
using log4net;
using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
namespace OpenSim.Region.ScriptEngine.XMREngine
{
public partial class XMRInstance
{
/************************************************************************************\
* This module contains these externally useful methods: *
* PostEvent() - queues an event to script and wakes script thread to process it *
* RunOne() - runs script for a time slice or until it volunteers to give up cpu *
* CallSEH() - runs in the microthread to call the event handler *
\************************************************************************************/
/**
* @brief This can be called in any thread (including the script thread itself)
* to queue event to script for processing.
*/
public void PostEvent(EventParams evt)
{
ScriptEventCode evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
evt.EventName);
/*
* Put event on end of event queue.
*/
bool startIt = false;
bool wakeIt = false;
lock (m_QueueLock)
{
bool construct = (m_IState == XMRInstState.CONSTRUCT);
/*
* Ignore event if we don't even have such an handler in any state.
* We can't be state-specific here because state might be different
* by the time this event is dequeued and delivered to the script.
*/
if (!construct && // make sure m_HaveEventHandlers is filled in
((uint)evc < (uint)m_HaveEventHandlers.Length) &&
!m_HaveEventHandlers[(int)evc]) { // don't bother if we don't have such a handler in any state
return;
}
/*
* Not running means we ignore any incoming events.
* But queue if still constructing because m_Running is not yet valid.
*/
if (!m_Running && !construct) {
return;
}
/*
* Only so many of each event type allowed to queue.
*/
if ((uint)evc < (uint)m_EventCounts.Length) {
int maxAllowed = MAXEVENTQUEUE;
if (evc == ScriptEventCode.timer) maxAllowed = 1;
if (m_EventCounts[(int)evc] >= maxAllowed)
{
return;
}
m_EventCounts[(int)evc] ++;
}
/*
* Put event on end of instance's event queue.
*/
LinkedListNode<EventParams> lln = new LinkedListNode<EventParams>(evt);
switch (evc) {
/*
* These need to go first. The only time we manually
* queue them is for the default state_entry() and we
* need to make sure they go before any attach() events
* so the heapLimit value gets properly initialized.
*/
case ScriptEventCode.state_entry: {
m_EventQueue.AddFirst(lln);
break;
}
/*
* The attach event sneaks to the front of the queue.
* This is needed for quantum limiting to work because
* we want the attach(NULL_KEY) event to come in front
* of all others so the m_DetachQuantum won't run out
* before attach(NULL_KEY) is executed.
*/
case ScriptEventCode.attach:
{
if (evt.Params[0].ToString() == UUID.Zero.ToString())
{
LinkedListNode<EventParams> lln2 = null;
for (lln2 = m_EventQueue.First; lln2 != null; lln2 = lln2.Next)
{
EventParams evt2 = lln2.Value;
ScriptEventCode evc2 = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
evt2.EventName);
if ((evc2 != ScriptEventCode.state_entry) &&
(evc2 != ScriptEventCode.attach)) break;
}
if (lln2 == null)
m_EventQueue.AddLast(lln);
else
m_EventQueue.AddBefore(lln2, lln);
/* If we're detaching, limit the qantum. This will also
* cause the script to self-suspend after running this
* event
*/
m_DetachReady.Reset();
m_DetachQuantum = 100;
}
else
m_EventQueue.AddLast(lln);
break;
}
/*
* All others just go on end in the order queued.
*/
default:
{
m_EventQueue.AddLast(lln);
break;
}
}
/*
* If instance is idle (ie, not running or waiting to run),
* flag it to be on m_StartQueue as we are about to do so.
* Flag it now before unlocking so another thread won't try
* to do the same thing right now.
* Dont' flag it if it's still suspended!
*/
if ((m_IState == XMRInstState.IDLE) && !m_Suspended)
{
m_IState = XMRInstState.ONSTARTQ;
startIt = true;
}
/*
* If instance is sleeping (ie, possibly in xmrEventDequeue),
* wake it up if event is in the mask.
*/
if ((m_SleepUntil > DateTime.UtcNow) && !m_Suspended)
{
int evc1 = (int)evc;
int evc2 = evc1 - 32;
if ((((uint)evc1 < (uint)32) && (((m_SleepEventMask1 >> evc1) & 1) != 0)) ||
(((uint)evc2 < (uint)32) && (((m_SleepEventMask2 >> evc2) & 1) != 0)))
{
wakeIt = true;
}
}
}
/*
* If transitioned from IDLE->ONSTARTQ, actually go insert it
* on m_StartQueue and give the RunScriptThread() a wake-up.
*/
if (startIt)
m_Engine.QueueToStart(this);
/*
* Likewise, if the event mask triggered a wake, wake it up.
*/
if (wakeIt)
{
m_SleepUntil = DateTime.MinValue;
m_Engine.WakeFromSleep(this);
}
}
/*
* This is called in the script thread to step script until it calls
* CheckRun(). It returns what the instance's next state should be,
* ONSLEEPQ, ONYIELDQ, SUSPENDED or FINISHED.
*/
public XMRInstState RunOne()
{
DateTime now = DateTime.UtcNow;
m_SliceStart = Util.GetTimeStampMS();
/*
* If script has called llSleep(), don't do any more until time is
* up.
*/
m_RunOnePhase = "check m_SleepUntil";
if (m_SleepUntil > now)
{
m_RunOnePhase = "return is sleeping";
return XMRInstState.ONSLEEPQ;
}
/*
* Also, someone may have called Suspend().
*/
m_RunOnePhase = "check m_SuspendCount";
if (m_SuspendCount > 0)
{
m_RunOnePhase = "return is suspended";
return XMRInstState.SUSPENDED;
}
/*
* Make sure we aren't being migrated in or out and prevent that
* whilst we are in here. If migration has it locked, don't call
* back right away, delay a bit so we don't get in infinite loop.
*/
m_RunOnePhase = "lock m_RunLock";
if (!Monitor.TryEnter (m_RunLock))
{
m_SleepUntil = now.AddMilliseconds(3);
m_RunOnePhase = "return was locked";
return XMRInstState.ONSLEEPQ;
}
try
{
m_RunOnePhase = "check entry invariants";
CheckRunLockInvariants(true);
Exception e = null;
/*
* Maybe we have been disposed.
*/
m_RunOnePhase = "check disposed";
if (microthread == null)
{
m_RunOnePhase = "return disposed";
return XMRInstState.DISPOSED;
}
/*
* Do some more of the last event if it didn't finish.
*/
else if (eventCode != ScriptEventCode.None)
{
lock (m_QueueLock)
{
if (m_DetachQuantum > 0 && --m_DetachQuantum == 0)
{
m_Suspended = true;
m_DetachReady.Set();
m_RunOnePhase = "detach quantum went zero";
CheckRunLockInvariants(true);
return XMRInstState.FINISHED;
}
}
m_RunOnePhase = "resume old event handler";
m_LastRanAt = now;
m_InstEHSlice ++;
callMode = CallMode_NORMAL;
e = microthread.ResumeEx ();
}
/*
* Otherwise, maybe we can dequeue a new event and start
* processing it.
*/
else
{
m_RunOnePhase = "lock event queue";
EventParams evt = null;
ScriptEventCode evc = ScriptEventCode.None;
lock (m_QueueLock)
{
/* We can't get here unless the script has been resumed
* after creation, then suspended again, and then had
* an event posted to it. We just pretend there is no
* event int he queue and let the normal mechanics
* carry out the suspension. A Resume will handle the
* restarting gracefully. This is taking the easy way
* out and may be improved in the future.
*/
if (m_Suspended)
{
m_RunOnePhase = "m_Suspended is set";
CheckRunLockInvariants(true);
return XMRInstState.FINISHED;
}
m_RunOnePhase = "dequeue event";
if (m_EventQueue.First != null)
{
evt = m_EventQueue.First.Value;
evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
evt.EventName);
if (m_DetachQuantum > 0)
{
if (evc != ScriptEventCode.attach)
{
/*
* This is the case where the attach event
* has completed and another event is queued
* Stop it from running and suspend
*/
m_Suspended = true;
m_DetachReady.Set();
m_DetachQuantum = 0;
m_RunOnePhase = "nothing to do #3";
CheckRunLockInvariants(true);
return XMRInstState.FINISHED;
}
}
m_EventQueue.RemoveFirst();
if (evc >= 0)
m_EventCounts[(int)evc] --;
}
/*
* If there is no event to dequeue, don't run this script
* until another event gets queued.
*/
if (evt == null)
{
if (m_DetachQuantum > 0)
{
/*
* This will happen if the attach event has run
* and exited with time slice left.
*/
m_Suspended = true;
m_DetachReady.Set();
m_DetachQuantum = 0;
}
m_RunOnePhase = "nothing to do #4";
CheckRunLockInvariants(true);
return XMRInstState.FINISHED;
}
}
/*
* Dequeued an event, so start it going until it either
* finishes or it calls CheckRun().
*/
m_RunOnePhase = "start event handler";
m_DetectParams = evt.DetectParams;
m_LastRanAt = now;
m_InstEHEvent ++;
e = StartEventHandler (evc, evt.Params);
}
m_RunOnePhase = "done running";
m_CPUTime += DateTime.UtcNow.Subtract(now).TotalMilliseconds;
/*
* Maybe it puqued.
*/
if (e != null)
{
m_RunOnePhase = "handling exception " + e.Message;
HandleScriptException(e);
m_RunOnePhase = "return had exception " + e.Message;
CheckRunLockInvariants(true);
return XMRInstState.FINISHED;
}
/*
* If event handler completed, get rid of detect params.
*/
if (this.eventCode == ScriptEventCode.None)
{
m_DetectParams = null;
}
}
finally
{
m_RunOnePhase += "; checking exit invariants and unlocking";
CheckRunLockInvariants(false);
Monitor.Exit(m_RunLock);
}
/*
* Cycle script through the yield queue and call it back asap.
*/
m_RunOnePhase = "last return";
return XMRInstState.ONYIELDQ;
}
/**
* @brief Immediately after taking m_RunLock or just before releasing it, check invariants.
*/
private ScriptEventCode lastEventCode = ScriptEventCode.None;
private int lastActive = 0;
private string lastRunPhase = "";
public void CheckRunLockInvariants(bool throwIt)
{
/*
* If not executing any event handler, active should be 0 indicating the microthread stack is not in use.
* If executing an event handler, active should be -1 indicating stack is in use but suspended.
*/
IScriptUThread uth = microthread;
if (uth != null) {
int active = uth.Active ();
ScriptEventCode ec = this.eventCode;
if (((ec == ScriptEventCode.None) && (active != 0)) ||
((ec != ScriptEventCode.None) && (active >= 0))) {
Console.WriteLine("CheckRunLockInvariants: script=" + m_DescName);
Console.WriteLine("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString());
Console.WriteLine("CheckRunLockInvariants: m_RunOnePhase=" + m_RunOnePhase);
Console.WriteLine("CheckRunLockInvariants: lastec=" + lastEventCode + ", lastAct=" + lastActive + ", lastPhase=" + lastRunPhase);
if (throwIt) {
throw new Exception("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString());
}
}
lastEventCode = ec;
lastActive = active;
lastRunPhase = m_RunOnePhase;
}
}
/*
* Start event handler.
*
* Input:
* eventCode = code of event to be processed
* ehArgs = arguments for the event handler
*
* Caution:
* It is up to the caller to make sure ehArgs[] is correct for
* the particular event handler being called. The first thing
* a script event handler method does is to unmarshall the args
* from ehArgs[] and will throw an array bounds or cast exception
* if it can't.
*/
private Exception StartEventHandler (ScriptEventCode eventCode, object[] ehArgs)
{
/*
* We use this.eventCode == ScriptEventCode.None to indicate we are idle.
* So trying to execute ScriptEventCode.None might make a mess.
*/
if (eventCode == ScriptEventCode.None)
return new Exception ("Can't process ScriptEventCode.None");
/*
* Silly to even try if there is no handler defined for this event.
*/
if ((eventCode >= 0) && (m_ObjCode.scriptEventHandlerTable[this.stateCode,(int)eventCode] == null))
return null;
/*
* The microthread shouldn't be processing any event code.
* These are assert checks so we throw them directly as exceptions.
*/
if (this.eventCode != ScriptEventCode.None)
throw new Exception ("still processing event " + this.eventCode.ToString ());
int active = microthread.Active ();
if (active != 0)
throw new Exception ("microthread is active " + active.ToString ());
/*
* Save eventCode so we know what event handler to run in the microthread.
* And it also marks us busy so we can't be started again and this event lost.
*/
this.eventCode = eventCode;
this.ehArgs = ehArgs;
/*
* This calls ScriptUThread.Main() directly, and returns when Main() [indirectly]
* calls Suspend() or when Main() returns, whichever occurs first.
* Setting stackFrames = null means run the event handler from the beginning
* without doing any stack frame restores first.
*/
this.stackFrames = null;
Exception e;
e = microthread.StartEx ();
return e;
}
/**
* @brief There was an exception whilst starting/running a script event handler.
* Maybe we handle it directly or just print an error message.
*/
private void HandleScriptException(Exception e)
{
/*
* The script threw some kind of exception that was not caught at
* script level, so the script is no longer running an event handler.
*/
eventCode = ScriptEventCode.None;
if (e is ScriptDeleteException)
{
/*
* Script did something like llRemoveInventory(llGetScriptName());
* ... to delete itself from the object.
*/
m_SleepUntil = DateTime.MaxValue;
Verbose ("[XMREngine]: script self-delete {0}", m_ItemID);
m_Part.Inventory.RemoveInventoryItem(m_ItemID);
}
else if (e is ScriptDieException)
{
/*
* Script did an llDie()
*/
m_RunOnePhase = "dying...";
m_SleepUntil = DateTime.MaxValue;
m_Engine.World.DeleteSceneObject(m_Part.ParentGroup, false);
}
else if (e is ScriptResetException)
{
/*
* Script did an llResetScript().
*/
m_RunOnePhase = "resetting...";
ResetLocked("HandleScriptResetException");
}
else
{
/*
* Some general script error.
*/
SendErrorMessage(e);
}
return;
}
/**
* @brief There was an exception running script event handler.
* Display error message and disable script (in a way
* that the script can be reset to be restarted).
*/
private void SendErrorMessage(Exception e)
{
StringBuilder msg = new StringBuilder();
msg.Append ("[XMREngine]: Exception while running ");
msg.Append (m_ItemID);
msg.Append ('\n');
/*
* Add exception message.
*/
string des = e.Message;
des = (des == null) ? "" : (": " + des);
msg.Append (e.GetType ().Name + des + "\n");
/*
* Tell script owner what to do.
*/
msg.Append ("Prim: <");
msg.Append (m_Part.Name);
msg.Append (">, Script: <");
msg.Append (m_Item.Name);
msg.Append (">, Location: ");
msg.Append (m_Engine.World.RegionInfo.RegionName);
msg.Append (" <");
Vector3 pos = m_Part.AbsolutePosition;
msg.Append ((int) Math.Floor (pos.X));
msg.Append (',');
msg.Append ((int) Math.Floor (pos.Y));
msg.Append (',');
msg.Append ((int) Math.Floor (pos.Z));
msg.Append (">\nScript must be Reset to re-enable.\n");
/*
* Display full exception message in log.
*/
m_log.Info (msg.ToString() + XMRExceptionStackString (e), e);
/*
* Give script owner the stack dump.
*/
msg.Append (XMRExceptionStackString (e));
/*
* Send error message to owner.
* Suppress internal code stack trace lines.
*/
string msgst = msg.ToString();
if (!msgst.EndsWith ("\n")) msgst += '\n';
int j = 0;
StringBuilder imstr = new StringBuilder ();
for (int i = 0; (i = msgst.IndexOf ('\n', i)) >= 0; j = ++ i) {
string line = msgst.Substring (j, i - j);
if (line.StartsWith ("at ")) {
if (line.StartsWith ("at (wrapper")) continue; // at (wrapper ...
int k = line.LastIndexOf (".cs:"); // ... .cs:linenumber
if (Int32.TryParse (line.Substring (k + 4), out k)) continue;
}
this.llOwnerSay (line);
imstr.Append (line);
imstr.Append ('\n');
}
/*
* Send as instant message in case user not online.
* Code modelled from llInstantMessage().
*/
IMessageTransferModule transferModule = m_Engine.World.RequestModuleInterface<IMessageTransferModule>();
if (transferModule != null) {
UUID friendTransactionID = UUID.Random();
GridInstantMessage gim = new GridInstantMessage();
gim.fromAgentID = new Guid (m_Part.UUID.ToString());
gim.toAgentID = new Guid (m_Part.OwnerID.ToString ());
gim.imSessionID = new Guid(friendTransactionID.ToString());
gim.timestamp = (uint)Util.UnixTimeSinceEpoch();
gim.message = imstr.ToString ();
gim.dialog = (byte)19; // messgage from script
gim.fromGroup = false;
gim.offline = (byte)0;
gim.ParentEstateID = 0;
gim.Position = pos;
gim.RegionID = m_Engine.World.RegionInfo.RegionID.Guid;
gim.binaryBucket = Util.StringToBytes256(
"{0}/{1}/{2}/{3}",
m_Engine.World.RegionInfo.RegionName,
(int)Math.Floor(pos.X),
(int)Math.Floor(pos.Y),
(int)Math.Floor(pos.Z));
transferModule.SendInstantMessage(gim, delegate(bool success) {});
}
/*
* Say script is sleeping for a very long time.
* Reset() is able to cancel this sleeping.
*/
m_SleepUntil = DateTime.MaxValue;
}
/**
* @brief The user clicked the Reset Script button.
* We want to reset the script to a never-has-ever-run-before state.
*/
public void Reset()
{
checkstate:
XMRInstState iState = m_IState;
switch (iState) {
/*
* If it's really being constructed now, that's about as reset as we get.
*/
case XMRInstState.CONSTRUCT: {
return;
}
/*
* If it's idle, that means it is ready to receive a new event.
* So we lock the event queue to prevent another thread from taking
* it out of idle, verify that it is still in idle then transition
* it to resetting so no other thread will touch it.
*/
case XMRInstState.IDLE: {
lock (m_QueueLock) {
if (m_IState == XMRInstState.IDLE) {
m_IState = XMRInstState.RESETTING;
break;
}
}
goto checkstate;
}
/*
* If it's on the start queue, that means it is about to dequeue an
* event and start processing it. So we lock the start queue so it
* can't be started and transition it to resetting so no other thread
* will touch it.
*/
case XMRInstState.ONSTARTQ: {
lock (m_Engine.m_StartQueue) {
if (m_IState == XMRInstState.ONSTARTQ) {
m_Engine.m_StartQueue.Remove(this);
m_IState = XMRInstState.RESETTING;
break;
}
}
goto checkstate;
}
/*
* If it's running, tell CheckRun() to suspend the thread then go back
* to see what it got transitioned to.
*/
case XMRInstState.RUNNING: {
suspendOnCheckRunHold = true;
lock (m_QueueLock) { }
goto checkstate;
}
/*
* If it's sleeping, remove it from sleep queue and transition it to
* resetting so no other thread will touch it.
*/
case XMRInstState.ONSLEEPQ: {
lock (m_Engine.m_SleepQueue) {
if (m_IState == XMRInstState.ONSLEEPQ) {
m_Engine.m_SleepQueue.Remove(this);
m_IState = XMRInstState.RESETTING;
break;
}
}
goto checkstate;
}
/*
* It was just removed from the sleep queue and is about to be put
* on the yield queue (ie, is being woken up).
* Let that thread complete transition and try again.
*/
case XMRInstState.REMDFROMSLPQ: {
Sleep (10);
goto checkstate;
}
/*
* If it's yielding, remove it from yield queue and transition it to
* resetting so no other thread will touch it.
*/
case XMRInstState.ONYIELDQ: {
lock (m_Engine.m_YieldQueue) {
if (m_IState == XMRInstState.ONYIELDQ) {
m_Engine.m_YieldQueue.Remove(this);
m_IState = XMRInstState.RESETTING;
break;
}
}
goto checkstate;
}
/*
* If it just finished running something, let that thread transition it
* to its next state then check again.
*/
case XMRInstState.FINISHED: {
Sleep (10);
goto checkstate;
}
/*
* If it's disposed, that's about as reset as it gets.
*/
case XMRInstState.DISPOSED: {
return;
}
/*
* Some other thread is already resetting it, let it finish.
*/
case XMRInstState.RESETTING: {
return;
}
default: throw new Exception("bad state");
}
/*
* This thread transitioned the instance to RESETTING so reset it.
*/
lock (m_RunLock) {
CheckRunLockInvariants(true);
/*
* No other thread should have transitioned it from RESETTING.
*/
if (m_IState != XMRInstState.RESETTING) throw new Exception("bad state");
/*
* If the microthread is active, that means it has call frame
* context that we don't want. Throw it out and get a fresh one.
*/
if (microthread.Active () < 0) {
microthread.Dispose ();
microthread = (IScriptUThread)m_Engine.uThreadCtor.Invoke (new object[] { this });
}
/*
* Mark it idle now so it can get queued to process new stuff.
*/
m_IState = XMRInstState.IDLE;
/*
* Reset everything and queue up default's start_entry() event.
*/
ClearQueue();
ResetLocked("external Reset");
CheckRunLockInvariants(true);
}
}
private void ClearQueueExceptLinkMessages()
{
lock (m_QueueLock) {
EventParams[] linkMessages = new EventParams[m_EventQueue.Count];
int n = 0;
foreach (EventParams evt2 in m_EventQueue) {
if (evt2.EventName == "link_message") {
linkMessages[n++] = evt2;
}
}
m_EventQueue.Clear();
for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0;
for (int i = 0; i < n; i ++) {
m_EventQueue.AddLast(linkMessages[i]);
}
m_EventCounts[(int)ScriptEventCode.link_message] = n;
}
}
private void ClearQueue()
{
lock (m_QueueLock)
{
m_EventQueue.Clear(); // no events queued
for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0;
}
}
/**
* @brief The script called llResetScript() while it was running and
* has suspended. We want to reset the script to a never-has-
* ever-run-before state.
*
* Caller must have m_RunLock locked so we know script isn't
* running.
*/
private void ResetLocked(string from)
{
m_RunOnePhase = "ResetLocked: releasing controls";
ReleaseControls();
m_RunOnePhase = "ResetLocked: removing script";
m_Part.Inventory.GetInventoryItem(m_ItemID).PermsMask = 0;
m_Part.Inventory.GetInventoryItem(m_ItemID).PermsGranter = UUID.Zero;
IUrlModule urlModule = m_Engine.World.RequestModuleInterface<IUrlModule>();
if (urlModule != null)
urlModule.ScriptRemoved(m_ItemID);
AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID);
m_RunOnePhase = "ResetLocked: clearing current event";
this.eventCode = ScriptEventCode.None; // not processing an event
m_DetectParams = null; // not processing an event
m_SleepUntil = DateTime.MinValue; // not doing llSleep()
m_ResetCount ++; // has been reset once more
/*
* Tell next call to 'default state_entry()' to reset all global
* vars to their initial values.
*/
doGblInit = true;
/*
* Set script to 'default' state and queue call to its
* 'state_entry()' event handler.
*/
m_RunOnePhase = "ResetLocked: posting default:state_entry() event";
stateCode = 0;
m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(0));
PostEvent(new EventParams("state_entry",
zeroObjectArray,
zeroDetectParams));
/*
* Tell CheckRun() to let script run.
*/
suspendOnCheckRunHold = false;
suspendOnCheckRunTemp = false;
m_RunOnePhase = "ResetLocked: reset complete";
}
private void ReleaseControls()
{
if (m_Part != null)
{
bool found;
int permsMask;
UUID permsGranter;
try {
permsGranter = m_Part.TaskInventory[m_ItemID].PermsGranter;
permsMask = m_Part.TaskInventory[m_ItemID].PermsMask;
found = true;
} catch {
permsGranter = UUID.Zero;
permsMask = 0;
found = false;
}
if (found && ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)) {
ScenePresence presence = m_Engine.World.GetScenePresence(permsGranter);
if (presence != null) {
presence.UnRegisterControlEventsToScript(m_LocalID, m_ItemID);
}
}
}
}
/**
* @brief The script code should call this routine whenever it is
* convenient to perform a migation or switch microthreads.
*/
public override void CheckRunWork ()
{
if(!suspendOnCheckRunHold && ! suspendOnCheckRunTemp)
{
if(Util.GetTimeStampMS() - m_SliceStart < 60.0)
return;
suspendOnCheckRunTemp = true;
}
m_CheckRunPhase = "entered";
/*
* Stay stuck in this loop as long as something wants us suspended.
*/
while (suspendOnCheckRunHold || suspendOnCheckRunTemp)
{
m_CheckRunPhase = "top of while";
/*
* See if MigrateOutEventHandler() has been called.
* If so, dump our stack to stackFrames and unwind.
*/
if (this.captureStackFrames)
{
/*
* Puque our stack to the output stream.
* But otherwise, our state remains intact.
*/
m_CheckRunPhase = "saving";
this.callMode = CallMode_SAVE;
this.stackFrames = null;
throw new StackCaptureException ();
}
/*
* We get here when the script state has been read in by MigrateInEventHandler().
* Since the stack is completely restored at this point, any subsequent calls
* within the functions should do their normal processing instead of trying to
* restore their state.
*/
if (this.callMode == CallMode_RESTORE)
{
stackFramesRestored = true;
this.callMode = CallMode_NORMAL;
}
/*
* Now we are ready to suspend the microthread.
* This is like a longjmp() to the most recent StartEx() or ResumeEx()
* with a simultaneous setjmp() so ResumeEx() can longjmp() back here.
*/
m_CheckRunPhase = "suspending";
suspendOnCheckRunTemp = false;
microthread.Hiber ();
m_CheckRunPhase = "resumed";
}
m_CheckRunPhase = "returning";
/*
* Upon return from CheckRun() it should always be the case that the script is
* going to process calls normally, neither saving nor restoring stack frame state.
*/
if (callMode != CallMode_NORMAL) throw new Exception ("bad callMode " + callMode);
}
/**
* @brief Allow script to dequeue events.
*/
public void ResumeIt()
{
lock (m_QueueLock)
{
m_Suspended = false;
if ((m_EventQueue != null) &&
(m_EventQueue.First != null) &&
(m_IState == XMRInstState.IDLE)) {
m_IState = XMRInstState.ONSTARTQ;
m_Engine.QueueToStart(this);
}
m_HasRun = true;
}
}
/**
* @brief Block script from dequeuing events.
*/
public void SuspendIt()
{
lock (m_QueueLock)
{
m_Suspended = true;
}
}
}
/**
* @brief Thrown by CheckRun() to unwind the script stack, capturing frames to
* instance.stackFrames as it unwinds. We don't want scripts to be able
* to intercept this exception as it would block the stack capture
* functionality.
*/
public class StackCaptureException : Exception, IXMRUncatchable { }
}