/* * 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 lln = new LinkedListNode(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 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(); 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(); 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 { } }