1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048 |
- /*
- * 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.Shared;
- using OpenSim.Region.ScriptEngine.Shared.Api;
- using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
- using OpenSim.Region.Framework.Scenes;
- namespace OpenSim.Region.ScriptEngine.Yengine
- {
- 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)
- {
- if (!m_eventCodeMap.TryGetValue(evt.EventName, out ScriptEventCode evc))
- return;
- if (!m_HaveEventHandlers[(int)evc]) // don't bother if we don't have such a handler in any state
- return;
- // Put event on end of event queue.
- bool startIt = false;
- bool wakeIt = false;
- lock(m_QueueLock)
- {
- // 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(m_IState != XMRInstState.CONSTRUCT)
- {
- if(!m_Running)
- {
- if(m_IState == XMRInstState.SUSPENDED)
- {
- if(evc == ScriptEventCode.state_entry && m_EventQueue.Count == 0)
- {
- LinkedListNode<EventParams> llns = new LinkedListNode<EventParams>(evt);
- m_EventQueue.AddFirst(llns);
- }
- }
- return;
- }
- }
- if(m_minEventDelay != 0)
- {
- switch (evc)
- {
- // ignore some events by time set by llMinEventDelay
- case ScriptEventCode.collision:
- case ScriptEventCode.land_collision:
- case ScriptEventCode.listen:
- case ScriptEventCode.not_at_target:
- case ScriptEventCode.not_at_rot_target:
- case ScriptEventCode.no_sensor:
- case ScriptEventCode.sensor:
- case ScriptEventCode.timer:
- case ScriptEventCode.touch:
- {
- double now = Util.GetTimeStamp();
- if (now < m_nextEventTime)
- return;
- m_nextEventTime = now + m_minEventDelay;
- break;
- }
- case ScriptEventCode.changed:
- {
- const int canignore = ~(CHANGED_SCALE | CHANGED_POSITION);
- int change = (int)evt.Params[0];
- if(change == 0) // what?
- return;
- if((change & canignore) == 0)
- {
- double now = Util.GetTimeStamp();
- if (now < m_nextEventTime)
- return;
- m_nextEventTime = now + m_minEventDelay;
- }
- break;
- }
- default:
- break;
- }
- }
- if(evc == ScriptEventCode.timer)
- {
- if (m_EventCounts[(int)evc] >= 1)
- return;
- m_EventCounts[(int)evc]++;
- m_EventQueue.AddLast(new LinkedListNode<EventParams>(evt));
- }
- else
- {
- if (m_EventCounts[(int)evc] >= MAXEVENTQUEUE)
- return;
- m_EventCounts[(int)evc]++;
- 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().Equals(UUID.ZeroString))
- {
- LinkedListNode<EventParams> lln2 = null;
- for(lln2 = m_EventQueue.First; lln2 != null; lln2 = lln2.Next)
- {
- EventParams evt2 = lln2.Value;
- m_eventCodeMap.TryGetValue(evt2.EventName, out ScriptEventCode evc2);
- 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);
- }
- }
- public void CancelEvent(string eventName)
- {
- if (!m_eventCodeMap.TryGetValue(eventName, out ScriptEventCode evc))
- return;
- lock (m_QueueLock)
- {
- if(m_EventQueue.Count == 0)
- return;
- LinkedListNode<EventParams> lln2 = null;
- for (lln2 = m_EventQueue.First; lln2 != null; lln2 = lln2.Next)
- {
- EventParams evt2 = lln2.Value;
- if(evt2.EventName.Equals(eventName))
- {
- m_EventQueue.Remove(lln2);
- if (evc >= 0 && m_EventCounts[(int)evc] > 0)
- m_EventCounts[(int)evc]--;
- }
- }
- }
- }
- // 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()
- {
- // someone may have called Suspend().
- //m_RunOnePhase = "check m_SuspendCount";
- if (m_SuspendCount > 0)
- {
- //m_RunOnePhase = "return is suspended";
- return XMRInstState.SUSPENDED;
- }
- 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;
- }
- // 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(15);
- //m_RunOnePhase = "return was locked";
- return XMRInstState.ONSLEEPQ;
- }
- try
- {
- //m_RunOnePhase = "check entry invariants";
- CheckRunLockInvariants(true);
- // Maybe it has been Disposed()
- if(m_Part == null || m_Part.Inventory == null)
- {
- //m_RunOnePhase = "runone saw it disposed";
- return XMRInstState.DISPOSED;
- }
- if(!m_Running)
- {
- //m_RunOnePhase = "return is not running";
- return XMRInstState.SUSPENDED;
- }
- Exception e = null;
- // Do some more of the last event if it didn't finish.
- 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 = 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;
- m_eventCodeMap.TryGetValue(evt.EventName, out evc);
- 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;
- }
- }
- finally
- {
- // If event handler completed, get rid of detect params.
- if (eventCode == ScriptEventCode.None)
- m_DetectParams = null;
- //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 bool lastActive = false;
- //private string lastRunPhase = "";
- public void CheckRunLockInvariants(bool throwIt)
- {
- // If not executing any event handler, there shouldn't be any saved stack frames.
- // If executing an event handler, there should be some saved stack frames.
- bool active = (stackFrames != null);
- if((active && (eventCode == ScriptEventCode.None)) ||
- (!active && (eventCode != ScriptEventCode.None)))
- {
- m_log.Error("CheckRunLockInvariants: script=" + m_DescName);
- m_log.Error("CheckRunLockInvariants: eventcode=" + eventCode.ToString() + ", active=" + active.ToString());
- //m_log.Error("CheckRunLockInvariants: m_RunOnePhase=" + m_RunOnePhase);
- //m_log.Error("CheckRunLockInvariants: lastec=" + lastEventCode + ", lastAct=" + lastActive + ", lastPhase=" + lastRunPhase);
- if(throwIt)
- throw new Exception("CheckRunLockInvariants: eventcode=" + eventCode.ToString() + ", active=" + active.ToString());
- }
- //lastEventCode = eventCode;
- //lastActive = active;
- //lastRunPhase = m_RunOnePhase;
- }
- /*
- * Start event handler.
- *
- * Input:
- * newEventCode = code of event to be processed
- * newEhArgs = 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 newEventCode, object[] newEhArgs)
- {
- // We use this.eventCode == ScriptEventCode.None to indicate we are idle.
- // So trying to execute ScriptEventCode.None might make a mess.
- if(newEventCode == ScriptEventCode.None)
- return new Exception("Can't process ScriptEventCode.None");
- // The microthread shouldn't be processing any event code.
- // These are assert checks so we throw them directly as exceptions.
- if (eventCode != ScriptEventCode.None)
- throw new Exception("still processing event " + this.eventCode.ToString());
- // Silly to even try if there is no handler defined for this event.
- if ((newEventCode >= 0) && (m_ObjCode.scriptEventHandlerTable[stateCode, (int)newEventCode] == null))
- return null;
- // 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.
- eventCode = newEventCode;
- ehArgs = newEhArgs;
- // 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.
- stackFrames = null;
- return StartEx();
- }
- /**
- * @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.
- ScriptEventCode curevent = eventCode;
- eventCode = ScriptEventCode.None;
- stackFrames = null;
- m_DetectParams = null;
- if (m_Part == null || m_Part.Inventory == null)
- {
- //we are gone and don't know it still
- m_SleepUntil = DateTime.MaxValue;
- return;
- }
- if (e is ScriptDeleteException)
- {
- // Script did something like llRemoveInventory(llGetScriptName());
- // ... to delete itself from the object.
- m_SleepUntil = DateTime.MaxValue;
- Verbose("[YEngine]: 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 if (e is OutOfHeapException)
- {
- // Some general script error.
- SendScriptErrorMessage(e, curevent);
- }
- else if (e is ScriptException)
- {
- // Some general script error.
- SendScriptErrorMessage(e, curevent);
- }
- else
- {
- // Some general script error.
- SendErrorMessage(e);
- }
- }
- private void SendScriptErrorMessage(Exception e, ScriptEventCode ev)
- {
- StringBuilder msg = new StringBuilder();
- bool toowner = false;
- msg.Append("YEngine: ");
- string evMessage = null;
- if (e != null && !string.IsNullOrEmpty(e.Message))
- {
- evMessage = e.Message;
- if (evMessage.StartsWith("(OWNER)"))
- {
- evMessage = evMessage.Substring(7);
- toowner = true;
- }
- if (e is OutOfHeapException)
- evMessage = "OutOfHeap: " + evMessage;
- msg.Append(evMessage);
- }
- msg.Append(" (script: ");
- msg.Append(m_Item.Name);
- msg.Append(" event: ");
- msg.Append(ev.ToString());
- msg.Append(" primID: ");
- msg.Append(m_Part.UUID.ToString());
- msg.Append(" at: <");
- 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(">) Script must be Reset to re-enable.\n");
- string msgst = msg.ToString();
- if (msgst.Length > 1000)
- msgst = msgst.Substring(0, 1000);
- if (toowner)
- {
- ScenePresence sp = m_Engine.World.GetScenePresence(m_Part.OwnerID);
- if (sp != null && !sp.IsNPC)
- m_Engine.World.SimChatToAgent(m_Part.OwnerID, Utils.StringToBytes(msgst), 0x7FFFFFFF, m_Part.AbsolutePosition,
- m_Part.Name, m_Part.UUID, false);
- }
- else
- m_Engine.World.SimChat(Utils.StringToBytes(msgst),
- ChatTypeEnum.DebugChannel, 0x7FFFFFFF,
- m_Part.AbsolutePosition,
- m_Part.Name, m_Part.UUID, false);
- m_log.Debug(string.Format(
- "[SCRIPT ERROR]: {0} (at event {1}, part {2} {3} at {4} in {5}",
- (string.IsNullOrEmpty(evMessage) ? "" : evMessage),
- ev.ToString(),
- m_Part.Name,
- m_Part.UUID,
- m_Part.AbsolutePosition,
- m_Part.ParentGroup.Scene.Name));
- m_SleepUntil = DateTime.MaxValue;
- }
- /**
- * @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("[YEngine]: 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);
- }
- // 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;
- case XMRInstState.SUSPENDED:
- break;
- 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.SUSPENDED)
- {
- if (m_IState != XMRInstState.RESETTING)
- throw new Exception("bad state");
- m_IState = XMRInstState.IDLE;
- }
- // Reset everything and queue up default's start_entry() event.
- ClearQueue();
- ResetLocked("external Reset");
- // Mark it idle now so it can get queued to process new stuff.
- 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";
- ReleaseControlsOrPermissions(true);
- m_Part.CollisionSound = UUID.Zero;
- if (m_XMRLSLApi != null)
- m_XMRLSLApi.llResetTime();
- //m_RunOnePhase = "ResetLocked: removing script";
- 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
- m_localsHeapUsed = 0;
- glblVars.Clear();
- // Tell next call to 'default state_entry()' to reset all global
- // vars to their initial values.
- doGblInit = true;
- // Throw away all its stack frames.
- // If the script is resetting itself, there shouldn't be any stack frames.
- // If the script is being reset by something else, we throw them away cuz we want to start from the beginning of an event handler.
- stackFrames = null;
- // 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.RemoveScriptTargets(m_ItemID);
- m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(0));
- PostEvent(EventParams.StateEntryParams);
- // Tell CheckRun() to let script run.
- suspendOnCheckRunHold = false;
- suspendOnCheckRunTemp = false;
- //m_RunOnePhase = "ResetLocked: reset complete";
- }
- private void ReleaseControlsOrPermissions(bool fullPermissions)
- {
- if(m_Part != null && m_Part.TaskInventory != null)
- {
- int permsMask;
- UUID permsGranter;
- m_Part.TaskInventory.LockItemsForWrite(true);
- if (!m_Part.TaskInventory.TryGetValue(m_ItemID, out TaskInventoryItem item))
- {
- m_Part.TaskInventory.LockItemsForWrite(false);
- return;
- }
- permsGranter = item.PermsGranter;
- permsMask = item.PermsMask;
- if(fullPermissions)
- {
- item.PermsGranter = UUID.Zero;
- item.PermsMask = 0;
- }
- else
- item.PermsMask = permsMask & ~(ScriptBaseClass.PERMISSION_TAKE_CONTROLS | ScriptBaseClass.PERMISSION_CONTROL_CAMERA);
- m_Part.TaskInventory.LockItemsForWrite(false);
- if ((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";
- suspendOnCheckRunTemp = false;
- switch(this.callMode)
- {
- // Now we are ready to suspend or resume.
- case CallMode_NORMAL:
- //m_CheckRunPhase = "suspending";
- callMode = XMRInstance.CallMode_SAVE;
- stackFrames = null;
- throw new StackHibernateException(); // does not return
- // 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.
- // the stack has been restored as a result of calling ResumeEx()
- // tell script code to process calls normally
- case CallMode_RESTORE:
- this.callMode = CallMode_NORMAL;
- break;
- default:
- {
- throw new Exception("callMode=" + callMode);
- }
- }
- //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_SuspendCount = 0;
- m_Suspended = false;
- m_DetachQuantum = 0;
- m_DetachReady.Set();
- 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_SuspendCount = 1;
- 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
- {
- }
- }
|