/* * 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.Globalization; using System.IO; using System.Reflection; using System.Runtime.Remoting; using System.Runtime.Remoting.Lifetime; using System.Security.Policy; using System.Text; using System.Threading; using System.Xml; using OpenMetaverse; using log4net; using Nini.Config; using Amib.Threading; using OpenSim.Framework; using OpenSim.Region.CoreModules; using OpenSim.Region.Framework.Scenes; using OpenSim.Region.Framework.Interfaces; using OpenSim.Region.ScriptEngine.Shared; using OpenSim.Region.ScriptEngine.Shared.Api; using OpenSim.Region.ScriptEngine.Shared.Api.Runtime; using OpenSim.Region.ScriptEngine.Shared.ScriptBase; using OpenSim.Region.ScriptEngine.Shared.CodeTools; using OpenSim.Region.ScriptEngine.Interfaces; namespace OpenSim.Region.ScriptEngine.Shared.Instance { public class ScriptInstance : MarshalByRefObject, IScriptInstance { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); /// <summary> /// The current work item if an event for this script is running or waiting to run, /// </summary> /// <remarks> /// Null if there is no running or waiting to run event. Must be changed only under an EventQueue lock. /// </remarks> private IScriptWorkItem m_CurrentWorkItem; private IScript m_Script; private DetectParams[] m_DetectParams; private bool m_TimerQueued; private DateTime m_EventStart; private bool m_InEvent; private string m_Assembly; private string m_CurrentEvent = String.Empty; private bool m_InSelfDelete; private int m_MaxScriptQueue; private bool m_SaveState = true; private int m_ControlEventsInQueue; private int m_LastControlLevel; private bool m_CollisionInQueue; // The following is for setting a minimum delay between events private double m_minEventDelay; private long m_eventDelayTicks; private long m_nextEventTimeTicks; private bool m_startOnInit = true; private UUID m_AttachedAvatar; private StateSource m_stateSource; private bool m_postOnRez; private bool m_startedFromSavedState; private UUID m_CurrentStateHash; private UUID m_RegionID; public int DebugLevel { get; set; } public Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> LineMap { get; set; } private Dictionary<string,IScriptApi> m_Apis = new Dictionary<string,IScriptApi>(); public Object[] PluginData = new Object[0]; /// <summary> /// Used by llMinEventDelay to suppress events happening any faster than this speed. /// This currently restricts all events in one go. Not sure if each event type has /// its own check so take the simple route first. /// </summary> public double MinEventDelay { get { return m_minEventDelay; } set { if (value > 0.001) m_minEventDelay = value; else m_minEventDelay = 0.0; m_eventDelayTicks = (long)(m_minEventDelay * 10000000L); m_nextEventTimeTicks = DateTime.Now.Ticks; } } public bool Running { get; set; } public bool Suspended { get { return m_Suspended; } set { // Need to do this inside a lock in order to avoid races with EventProcessor() lock (m_Script) { bool wasSuspended = m_Suspended; m_Suspended = value; if (wasSuspended && !m_Suspended) { lock (EventQueue) { // Need to place ourselves back in a work item if there are events to process if (EventQueue.Count > 0 && Running && !ShuttingDown) m_CurrentWorkItem = Engine.QueueEventHandler(this); } } } } } private bool m_Suspended; public bool ShuttingDown { get; set; } public string State { get; set; } public IScriptEngine Engine { get; private set; } public UUID AppDomain { get; set; } public SceneObjectPart Part { get; private set; } public string PrimName { get; private set; } public string ScriptName { get; private set; } public UUID ItemID { get; private set; } public UUID ObjectID { get { return Part.UUID; } } public uint LocalID { get { return Part.LocalId; } } public UUID RootObjectID { get { return Part.ParentGroup.UUID; } } public uint RootLocalID { get { return Part.ParentGroup.LocalId; } } public UUID AssetID { get; private set; } public Queue EventQueue { get; private set; } public long EventsQueued { get { lock (EventQueue) return EventQueue.Count; } } public long EventsProcessed { get; private set; } public int StartParam { get; set; } public TaskInventoryItem ScriptTask { get; private set; } public DateTime TimeStarted { get; private set; } public long MeasurementPeriodTickStart { get; private set; } public long MeasurementPeriodExecutionTime { get; private set; } public static readonly long MaxMeasurementPeriod = 30 * TimeSpan.TicksPerMinute; private bool m_coopTermination; private EventWaitHandle m_coopSleepHandle; public void ClearQueue() { m_TimerQueued = false; EventQueue.Clear(); } public ScriptInstance( IScriptEngine engine, SceneObjectPart part, TaskInventoryItem item, int startParam, bool postOnRez, int maxScriptQueue) { State = "default"; EventQueue = new Queue(32); Engine = engine; Part = part; ScriptTask = item; // This is currently only here to allow regression tests to get away without specifying any inventory // item when they are testing script logic that doesn't require an item. if (ScriptTask != null) { ScriptName = ScriptTask.Name; ItemID = ScriptTask.ItemID; AssetID = ScriptTask.AssetID; } PrimName = part.ParentGroup.Name; StartParam = startParam; m_MaxScriptQueue = maxScriptQueue; m_postOnRez = postOnRez; m_AttachedAvatar = Part.ParentGroup.AttachedAvatar; m_RegionID = Part.ParentGroup.Scene.RegionInfo.RegionID; if (Engine.Config.GetString("ScriptStopStrategy", "abort") == "co-op") { m_coopTermination = true; m_coopSleepHandle = new XEngineEventWaitHandle(false, EventResetMode.AutoReset); } } /// <summary> /// Load the script from an assembly into an AppDomain. /// </summary> /// <param name='dom'></param> /// <param name='assembly'></param> /// <param name='stateSource'></param> /// <returns>false if load failed, true if suceeded</returns> public bool Load(AppDomain dom, string assembly, StateSource stateSource) { m_Assembly = assembly; m_stateSource = stateSource; ApiManager am = new ApiManager(); foreach (string api in am.GetApis()) { m_Apis[api] = am.CreateApi(api); m_Apis[api].Initialize(Engine, Part, ScriptTask, m_coopSleepHandle); } try { object[] constructorParams; Assembly scriptAssembly = dom.Load(Path.GetFileNameWithoutExtension(assembly)); Type scriptType = scriptAssembly.GetType("SecondLife.XEngineScript"); if (scriptType != null) { constructorParams = new object[] { m_coopSleepHandle }; } else if (!m_coopTermination) { scriptType = scriptAssembly.GetType("SecondLife.Script"); constructorParams = null; } else { m_log.ErrorFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. You must remove all existing {6}* script DLL files before using enabling co-op termination" + ", either by setting DeleteScriptsOnStartup = true in [XEngine] for one run" + " or by deleting these files manually.", ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, assembly); return false; } // m_log.DebugFormat( // "[SCRIPT INSTANCE]: Looking to load {0} from assembly {1} in {2}", // scriptType.FullName, Path.GetFileNameWithoutExtension(assembly), Engine.World.Name); if (dom != System.AppDomain.CurrentDomain) m_Script = (IScript)dom.CreateInstanceAndUnwrap( Path.GetFileNameWithoutExtension(assembly), scriptType.FullName, false, BindingFlags.Default, null, constructorParams, null, null, null); else m_Script = (IScript)scriptAssembly.CreateInstance( scriptType.FullName, false, BindingFlags.Default, null, constructorParams, null, null); //ILease lease = (ILease)RemotingServices.GetLifetimeService(m_Script as ScriptBaseClass); //RemotingServices.GetLifetimeService(m_Script as ScriptBaseClass); // lease.Register(this); } catch (Exception e) { m_log.ErrorFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Error loading assembly {6}. Exception {7}{8}", ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, assembly, e.Message, e.StackTrace); return false; } try { foreach (KeyValuePair<string,IScriptApi> kv in m_Apis) { m_Script.InitApi(kv.Key, kv.Value); } // // m_log.Debug("[Script] Script instance created"); Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); } catch (Exception e) { m_log.ErrorFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Error initializing script instance. Exception {6}{7}", ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, e.Message, e.StackTrace); return false; } m_SaveState = true; string savedState = Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state"); if (File.Exists(savedState)) { string xml = String.Empty; try { FileInfo fi = new FileInfo(savedState); int size = (int)fi.Length; if (size < 512000) { using (FileStream fs = File.Open(savedState, FileMode.Open, FileAccess.Read, FileShare.None)) { Byte[] data = new Byte[size]; fs.Read(data, 0, size); xml = Encoding.UTF8.GetString(data); ScriptSerializer.Deserialize(xml, this); AsyncCommandManager.CreateFromData(Engine, LocalID, ItemID, ObjectID, PluginData); // m_log.DebugFormat("[Script] Successfully retrieved state for script {0}.{1}", PrimName, m_ScriptName); Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); if (!Running) m_startOnInit = false; Running = false; // we get new rez events on sim restart, too // but if there is state, then we fire the change // event // We loaded state, don't force a re-save m_SaveState = false; m_startedFromSavedState = true; } } else { m_log.WarnFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Unable to load script state file {6}. Memory limit exceeded.", ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState); } } catch (Exception e) { m_log.ErrorFormat( "[SCRIPT INSTANCE]: Not starting script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}. Unable to load script state file {6}. XML is {7}. Exception {8}{9}", ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState, xml, e.Message, e.StackTrace); } } // else // { // ScenePresence presence = Engine.World.GetScenePresence(part.OwnerID); // if (presence != null && (!postOnRez)) // presence.ControllingClient.SendAgentAlertMessage("Compile successful", false); // } return true; } public void Init() { if (ShuttingDown) return; if (m_startedFromSavedState) { if (m_startOnInit) Start(); if (m_postOnRez) { PostEvent(new EventParams("on_rez", new Object[] {new LSL_Types.LSLInteger(StartParam)}, new DetectParams[0])); } if (m_stateSource == StateSource.AttachedRez) { PostEvent(new EventParams("attach", new object[] { new LSL_Types.LSLString(m_AttachedAvatar.ToString()) }, new DetectParams[0])); } else if (m_stateSource == StateSource.RegionStart) { //m_log.Debug("[Script] Posted changed(CHANGED_REGION_RESTART) to script"); PostEvent(new EventParams("changed", new Object[] { new LSL_Types.LSLInteger((int)Changed.REGION_RESTART) }, new DetectParams[0])); } else if (m_stateSource == StateSource.PrimCrossing || m_stateSource == StateSource.Teleporting) { // CHANGED_REGION PostEvent(new EventParams("changed", new Object[] { new LSL_Types.LSLInteger((int)Changed.REGION) }, new DetectParams[0])); // CHANGED_TELEPORT if (m_stateSource == StateSource.Teleporting) PostEvent(new EventParams("changed", new Object[] { new LSL_Types.LSLInteger((int)Changed.TELEPORT) }, new DetectParams[0])); } } else { if (m_startOnInit) Start(); PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); if (m_postOnRez) { PostEvent(new EventParams("on_rez", new Object[] {new LSL_Types.LSLInteger(StartParam)}, new DetectParams[0])); } if (m_stateSource == StateSource.AttachedRez) { PostEvent(new EventParams("attach", new object[] { new LSL_Types.LSLString(m_AttachedAvatar.ToString()) }, new DetectParams[0])); } } } private void ReleaseControls() { int permsMask; UUID permsGranter; lock (Part.TaskInventory) { if (!Part.TaskInventory.ContainsKey(ItemID)) return; permsGranter = Part.TaskInventory[ItemID].PermsGranter; permsMask = Part.TaskInventory[ItemID].PermsMask; } if ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0) { ScenePresence presence = Engine.World.GetScenePresence(permsGranter); if (presence != null) presence.UnRegisterControlEventsToScript(LocalID, ItemID); } } public void DestroyScriptInstance() { ReleaseControls(); AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); } public void RemoveState() { string savedState = Path.Combine(Path.GetDirectoryName(m_Assembly), ItemID.ToString() + ".state"); try { File.Delete(savedState); } catch (Exception e) { m_log.Warn( string.Format( "[SCRIPT INSTANCE]: Could not delete script state {0} for script {1} (id {2}) in part {3} (id {4}) in object {5} in {6}. Exception ", savedState, ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name), e); } } public void VarDump(Dictionary<string, object> vars) { // m_log.Info("Variable dump for script "+ ItemID.ToString()); // foreach (KeyValuePair<string, object> v in vars) // { // m_log.Info("Variable: "+v.Key+" = "+v.Value.ToString()); // } } public void Start() { lock (EventQueue) { if (Running) return; Running = true; TimeStarted = DateTime.Now; MeasurementPeriodTickStart = Util.EnvironmentTickCount(); MeasurementPeriodExecutionTime = 0; if (EventQueue.Count > 0) { if (m_CurrentWorkItem == null) m_CurrentWorkItem = Engine.QueueEventHandler(this); // else // m_log.Error("[Script] Tried to start a script that was already queued"); } } } public bool Stop(int timeout) { if (DebugLevel >= 1) m_log.DebugFormat( "[SCRIPT INSTANCE]: Stopping script {0} {1} in {2} {3} with timeout {4} {5} {6}", ScriptName, ItemID, PrimName, ObjectID, timeout, m_InSelfDelete, DateTime.Now.Ticks); IScriptWorkItem workItem; lock (EventQueue) { if (!Running) return true; // If we're not running or waiting to run an event then we can safely stop. if (m_CurrentWorkItem == null) { Running = false; return true; } // If we are waiting to run an event then we can try to cancel it. if (m_CurrentWorkItem.Cancel()) { m_CurrentWorkItem = null; Running = false; return true; } workItem = m_CurrentWorkItem; Running = false; } // Wait for the current event to complete. if (!m_InSelfDelete) { if (!m_coopTermination) { // If we're not co-operative terminating then try and wait for the event to complete before stopping if (workItem.Wait(timeout)) return true; } else { if (DebugLevel >= 1) m_log.DebugFormat( "[SCRIPT INSTANCE]: Co-operatively stopping script {0} {1} in {2} {3}", ScriptName, ItemID, PrimName, ObjectID); // This will terminate the event on next handle check by the script. m_coopSleepHandle.Set(); // For now, we will wait forever since the event should always cleanly terminate once LSL loop // checking is implemented. May want to allow a shorter timeout option later. if (workItem.Wait(Timeout.Infinite)) { if (DebugLevel >= 1) m_log.DebugFormat( "[SCRIPT INSTANCE]: Co-operatively stopped script {0} {1} in {2} {3}", ScriptName, ItemID, PrimName, ObjectID); return true; } } } lock (EventQueue) { workItem = m_CurrentWorkItem; } if (workItem == null) return true; // If the event still hasn't stopped and we the stop isn't the result of script or object removal, then // forcibly abort the work item (this aborts the underlying thread). // Co-operative termination should never reach this point. if (!m_InSelfDelete) { m_log.DebugFormat( "[SCRIPT INSTANCE]: Aborting unstopped script {0} {1} in prim {2}, localID {3}, timeout was {4} ms", ScriptName, ItemID, PrimName, LocalID, timeout); workItem.Abort(); } lock (EventQueue) { m_CurrentWorkItem = null; } return true; } public void SetState(string state) { if (state == State) return; PostEvent(new EventParams("state_exit", new Object[0], new DetectParams[0])); PostEvent(new EventParams("state", new Object[] { state }, new DetectParams[0])); PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); throw new EventAbortException(); } /// <summary> /// Post an event to this script instance. /// </summary> /// <remarks> /// The request to run the event is sent /// </remarks> /// <param name="data"></param> public void PostEvent(EventParams data) { // m_log.DebugFormat("[Script] Posted event {2} in state {3} to {0}.{1}", // PrimName, ScriptName, data.EventName, State); if (!Running) return; // If min event delay is set then ignore any events untill the time has expired // This currently only allows 1 event of any type in the given time period. // This may need extending to allow for a time for each individual event type. if (m_eventDelayTicks != 0) { if (DateTime.Now.Ticks < m_nextEventTimeTicks) return; m_nextEventTimeTicks = DateTime.Now.Ticks + m_eventDelayTicks; } lock (EventQueue) { if (EventQueue.Count >= m_MaxScriptQueue) return; if (data.EventName == "timer") { if (m_TimerQueued) return; m_TimerQueued = true; } if (data.EventName == "control") { int held = ((LSL_Types.LSLInteger)data.Params[1]).value; // int changed = ((LSL_Types.LSLInteger)data.Params[2]).value; // If the last message was a 0 (nothing held) // and this one is also nothing held, drop it // if (m_LastControlLevel == held && held == 0) return; // If there is one or more queued, then queue // only changed ones, else queue unconditionally // if (m_ControlEventsInQueue > 0) { if (m_LastControlLevel == held) return; } m_LastControlLevel = held; m_ControlEventsInQueue++; } if (data.EventName == "collision") { if (m_CollisionInQueue) return; if (data.DetectParams == null) return; m_CollisionInQueue = true; } EventQueue.Enqueue(data); if (m_CurrentWorkItem == null) { m_CurrentWorkItem = Engine.QueueEventHandler(this); } } } /// <summary> /// Process the next event queued for this script /// </summary> /// <returns></returns> public object EventProcessor() { // We check here as the thread stopping this instance from running may itself hold the m_Script lock. if (!Running) return 0; lock (m_Script) { // m_log.DebugFormat("[XEngine]: EventProcessor() invoked for {0}.{1}", PrimName, ScriptName); if (Suspended) return 0; EventParams data = null; lock (EventQueue) { data = (EventParams)EventQueue.Dequeue(); if (data == null) // Shouldn't happen { if (EventQueue.Count > 0 && Running && !ShuttingDown) { m_CurrentWorkItem = Engine.QueueEventHandler(this); } else { m_CurrentWorkItem = null; } return 0; } if (data.EventName == "timer") m_TimerQueued = false; if (data.EventName == "control") { if (m_ControlEventsInQueue > 0) m_ControlEventsInQueue--; } if (data.EventName == "collision") m_CollisionInQueue = false; } if (DebugLevel >= 2) m_log.DebugFormat( "[SCRIPT INSTANCE]: Processing event {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", data.EventName, ScriptName, Part.Name, Part.LocalId, Part.ParentGroup.Name, Part.ParentGroup.UUID, Part.AbsolutePosition, Part.ParentGroup.Scene.Name); m_DetectParams = data.DetectParams; if (data.EventName == "state") // Hardcoded state change { State = data.Params[0].ToString(); if (DebugLevel >= 1) m_log.DebugFormat( "[SCRIPT INSTANCE]: Changing state to {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}", State, ScriptName, Part.Name, Part.LocalId, Part.ParentGroup.Name, Part.ParentGroup.UUID, Part.AbsolutePosition, Part.ParentGroup.Scene.Name); AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); } else { if (Engine.World.PipeEventsForScript(LocalID) || data.EventName == "control") // Don't freeze avies! { // m_log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}", // PrimName, ScriptName, data.EventName, State); try { m_CurrentEvent = data.EventName; m_EventStart = DateTime.Now; m_InEvent = true; int start = Util.EnvironmentTickCount(); // Reset the measurement period when we reach the end of the current one. if (start - MeasurementPeriodTickStart > MaxMeasurementPeriod) MeasurementPeriodTickStart = start; m_Script.ExecuteEvent(State, data.EventName, data.Params); MeasurementPeriodExecutionTime += Util.EnvironmentTickCount() - start; m_InEvent = false; m_CurrentEvent = String.Empty; if (m_SaveState) { // This will be the very first event we deliver // (state_entry) in default state // SaveState(m_Assembly); m_SaveState = false; } } catch (Exception e) { // m_log.DebugFormat( // "[SCRIPT] Exception in script {0} {1}: {2}{3}", // ScriptName, ItemID, e.Message, e.StackTrace); m_InEvent = false; m_CurrentEvent = String.Empty; if ((!(e is TargetInvocationException) || (!(e.InnerException is SelfDeleteException) && !(e.InnerException is ScriptDeleteException) && !(e.InnerException is ScriptCoopStopException))) && !(e is ThreadAbortException)) { try { // DISPLAY ERROR INWORLD string text = FormatException(e); if (text.Length > 1000) text = text.Substring(0, 1000); Engine.World.SimChat(Utils.StringToBytes(text), ChatTypeEnum.DebugChannel, 2147483647, Part.AbsolutePosition, Part.Name, Part.UUID, false); m_log.DebugFormat( "[SCRIPT INSTANCE]: Runtime error in script {0}, part {1} {2} at {3} in {4}, displayed error {5}, actual exception {6}", ScriptName, PrimName, Part.UUID, Part.AbsolutePosition, Part.ParentGroup.Scene.Name, text.Replace("\n", "\\n"), e.InnerException); } catch (Exception) { } // catch (Exception e2) // LEGIT: User Scripting // { // m_log.Error("[SCRIPT]: "+ // "Error displaying error in-world: " + // e2.ToString()); // m_log.Error("[SCRIPT]: " + // "Errormessage: Error compiling script:\r\n" + // e.ToString()); // } } else if ((e is TargetInvocationException) && (e.InnerException is SelfDeleteException)) { m_InSelfDelete = true; Engine.World.DeleteSceneObject(Part.ParentGroup, false); } else if ((e is TargetInvocationException) && (e.InnerException is ScriptDeleteException)) { m_InSelfDelete = true; Part.Inventory.RemoveInventoryItem(ItemID); } else if ((e is TargetInvocationException) && (e.InnerException is ScriptCoopStopException)) { if (DebugLevel >= 1) m_log.DebugFormat( "[SCRIPT INSTANCE]: Script {0}.{1} in event {2}, state {3} stopped co-operatively.", PrimName, ScriptName, data.EventName, State); } } } } // If there are more events and we are currently running and not shutting down, then ask the // script engine to run the next event. lock (EventQueue) { EventsProcessed++; if (EventQueue.Count > 0 && Running && !ShuttingDown) { m_CurrentWorkItem = Engine.QueueEventHandler(this); } else { m_CurrentWorkItem = null; } } m_DetectParams = null; return 0; } } public int EventTime() { if (!m_InEvent) return 0; return (DateTime.Now - m_EventStart).Seconds; } public void ResetScript(int timeout) { if (m_Script == null) return; bool running = Running; RemoveState(); ReleaseControls(); Stop(timeout); Part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; Part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); EventQueue.Clear(); m_Script.ResetVars(); State = "default"; Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); if (running) Start(); m_SaveState = true; PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); } public void ApiResetScript() { // bool running = Running; RemoveState(); ReleaseControls(); m_Script.ResetVars(); Part.Inventory.GetInventoryItem(ItemID).PermsMask = 0; Part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero; AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID); EventQueue.Clear(); m_Script.ResetVars(); State = "default"; Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State)); if (m_CurrentEvent != "state_entry") { m_SaveState = true; PostEvent(new EventParams("state_entry", new Object[0], new DetectParams[0])); throw new EventAbortException(); } } public Dictionary<string, object> GetVars() { if (m_Script != null) return m_Script.GetVars(); else return new Dictionary<string, object>(); } public void SetVars(Dictionary<string, object> vars) { m_Script.SetVars(vars); } public DetectParams GetDetectParams(int idx) { if (m_DetectParams == null) return null; if (idx < 0 || idx >= m_DetectParams.Length) return null; return m_DetectParams[idx]; } public UUID GetDetectID(int idx) { if (m_DetectParams == null) return UUID.Zero; if (idx < 0 || idx >= m_DetectParams.Length) return UUID.Zero; return m_DetectParams[idx].Key; } public void SaveState(string assembly) { // If we're currently in an event, just tell it to save upon return // if (m_InEvent) { m_SaveState = true; return; } PluginData = AsyncCommandManager.GetSerializationData(Engine, ItemID); string xml = ScriptSerializer.Serialize(this); // Compare hash of the state we just just created with the state last written to disk // If the state is different, update the disk file. UUID hash = UUID.Parse(Utils.MD5String(xml)); if (hash != m_CurrentStateHash) { try { FileStream fs = File.Create(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state")); Byte[] buf = Util.UTF8NoBomEncoding.GetBytes(xml); fs.Write(buf, 0, buf.Length); fs.Close(); } catch(Exception) { // m_log.Error("Unable to save xml\n"+e.ToString()); } //if (!File.Exists(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state"))) //{ // throw new Exception("Completed persistence save, but no file was created"); //} m_CurrentStateHash = hash; } } public IScriptApi GetApi(string name) { if (m_Apis.ContainsKey(name)) { // m_log.DebugFormat("[SCRIPT INSTANCE]: Found api {0} in {1}@{2}", name, ScriptName, PrimName); return m_Apis[name]; } // m_log.DebugFormat("[SCRIPT INSTANCE]: Did not find api {0} in {1}@{2}", name, ScriptName, PrimName); return null; } public override string ToString() { return String.Format("{0} {1} on {2}", ScriptName, ItemID, PrimName); } string FormatException(Exception e) { if (e.InnerException == null) // Not a normal runtime error 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" + "({0}): {1}", scriptLine - 1, e.InnerException.Message); return message; } } } } // m_log.ErrorFormat("Scripting exception:"); // m_log.ErrorFormat(e.ToString()); return e.ToString(); } public string GetAssemblyName() { return m_Assembly; } public string GetXMLState() { bool run = Running; Stop(100); Running = run; // We should not be doing this, but since we are about to // dispose this, it really doesn't make a difference // This is meant to work around a Windows only race // m_InEvent = false; // Force an update of the in-memory plugin data // PluginData = AsyncCommandManager.GetSerializationData(Engine, ItemID); return ScriptSerializer.Serialize(this); } public UUID RegionID { get { return m_RegionID; } } public void Suspend() { Suspended = true; } public void Resume() { Suspended = false; } } /// <summary> /// Xengine event wait handle. /// </summary> /// <remarks> /// This class exists becase XEngineScriptBase gets a reference to this wait handle. We need to make sure that /// when scripts are running in different AppDomains the lease does not expire. /// FIXME: Like LSL_Api, etc., this effectively leaks memory since the GC will never collect it. To avoid this, /// proper remoting sponsorship needs to be implemented across the board. /// </remarks> public class XEngineEventWaitHandle : EventWaitHandle { public XEngineEventWaitHandle(bool initialState, EventResetMode mode) : base(initialState, mode) {} public override Object InitializeLifetimeService() { return null; } } }