/* * 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 OpenSim 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.Reflection; using log4net; using OpenMetaverse; using OpenSim.Framework; using OpenSim.Region.Environment.Scenes; using OpenSim.Region.ScriptEngine.Common; using OpenSim.Region.ScriptEngine.Shared; using OpenSim.Region.ScriptEngine.Shared.Api; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; namespace OpenSim.Region.ScriptEngine.DotNetEngine { public class ScriptManager { private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); #region Declares private Thread scriptLoadUnloadThread; private static Thread staticScriptLoadUnloadThread; // private int scriptLoadUnloadThread_IdleSleepms; private Queue LUQueue = new Queue(); private static bool PrivateThread; private int LoadUnloadMaxQueueSize; private Object scriptLock = new Object(); private bool m_started = false; private Dictionary detparms = new Dictionary(); // Load/Unload structure private struct LUStruct { public uint localID; public UUID itemID; public string script; public LUType Action; public int startParam; public bool postOnRez; } private enum LUType { Unknown = 0, Load = 1, Unload = 2 } // Xantor 20080525: Keep a list of compiled scripts this session for reuse public Dictionary scriptList = new Dictionary(); // Object> // IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory. // Instead use RuntimeTypeHandle, RuntimeFieldHandle and RunTimeHandle (IntPtr) instead! public Dictionary> Scripts = new Dictionary>(); public Scene World { get { return m_scriptEngine.World; } } #endregion private Compiler.LSL.Compiler LSLCompiler; public void Initialize() { // Create our compiler LSLCompiler = new Compiler.LSL.Compiler(m_scriptEngine); } // KEEP TRACK OF SCRIPTS //internal Dictionary> Scripts = new Dictionary>(); // LOAD SCRIPT // UNLOAD SCRIPT // PROVIDE SCRIPT WITH ITS INTERFACE TO OpenSim public void _StartScript(uint localID, UUID itemID, string Script, int startParam, bool postOnRez) { m_log.DebugFormat( "[{0}]: ScriptManager StartScript: localID: {1}, itemID: {2}", m_scriptEngine.ScriptEngineName, localID, itemID); //IScriptHost root = host.GetRoot(); // We will initialize and start the script. // It will be up to the script itself to hook up the correct events. string CompiledScriptFile = String.Empty; SceneObjectPart m_host = World.GetSceneObjectPart(localID); if (null == m_host) { m_log.ErrorFormat( "[{0}]: Could not find scene object part corresponding to localID {1} to start script", m_scriptEngine.ScriptEngineName, localID); return; } // Xantor 20080525: I need assetID here to see if we already compiled this one previously UUID assetID = UUID.Zero; TaskInventoryItem taskInventoryItem = new TaskInventoryItem(); if (m_host.TaskInventory.TryGetValue(itemID, out taskInventoryItem)) assetID = taskInventoryItem.AssetID; try { // Xantor 20080525 see if we already compiled this script this session, stop incessant recompiling on // scriptreset, spawning of objects with embedded scripts etc. if (scriptList.TryGetValue(assetID, out CompiledScriptFile)) { m_log.InfoFormat("[SCRIPT]: Found existing compile of assetID {0}: {1}", assetID, CompiledScriptFile); } else { // Compile (We assume LSL) CompiledScriptFile = LSLCompiler.PerformScriptCompile(Script); // Xantor 20080525 Save compiled scriptfile for later use m_log.InfoFormat("[SCRIPT]: Compiled assetID {0}: {1}", assetID, CompiledScriptFile); scriptList.Add(assetID, CompiledScriptFile); } //#if DEBUG //long before; //before = GC.GetTotalMemory(true); // This force a garbage collect that freezes some windows plateforms //#endif IScript CompiledScript; CompiledScript = m_scriptEngine.m_AppDomainManager.LoadScript(CompiledScriptFile); //#if DEBUG //m_scriptEngine.Log.DebugFormat("[" + m_scriptEngine.ScriptEngineName + "]: Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(true) - before); //#endif CompiledScript.Source = Script; CompiledScript.StartParam = startParam; // Add it to our script memstruct m_scriptEngine.m_ScriptManager.SetScript(localID, itemID, CompiledScript); // We need to give (untrusted) assembly a private instance of BuiltIns // this private copy will contain Read-Only FullitemID so that it can bring that on to the server whenever needed. // OSSL_BuilIn_Commands LSLB = new OSSL_BuilIn_Commands(m_scriptEngine, m_host, localID, itemID); LSL_Api LSL = new LSL_Api(); OSSL_Api OSSL = new OSSL_Api(); LSL.Initialize(m_scriptEngine, m_host, localID, itemID); OSSL.Initialize(m_scriptEngine, m_host, localID, itemID); // Start the script - giving it BuiltIns CompiledScript.InitApi("LSL", LSL); CompiledScript.InitApi("OSSL", OSSL); // Fire the first start-event int eventFlags = m_scriptEngine.m_ScriptManager.GetStateEventFlags(localID, itemID); m_host.SetScriptEvents(itemID, eventFlags); m_scriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "state_entry", new DetectParams[0], new object[] { }); if (postOnRez) { m_scriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "on_rez", new DetectParams[0], new object[] { new LSL_Types.LSLInteger(startParam) }); } } catch (Exception e) // LEGIT: User Scripting { //m_scriptEngine.Log.Error("[ScriptEngine]: Error compiling script: " + e.ToString()); try { // DISPLAY ERROR INWORLD string text = "Error compiling script:\r\n" + e.Message.ToString(); if (text.Length > 1500) text = text.Substring(0, 1499); // 0-1499 is 1500 characters World.SimChat(Utils.StringToBytes(text), ChatTypeEnum.DebugChannel, 2147483647, m_host.AbsolutePosition, m_host.Name, m_host.UUID, false); } catch (Exception e2) // LEGIT: User Scripting { m_scriptEngine.Log.Error("[" + m_scriptEngine.ScriptEngineName + "]: Error displaying error in-world: " + e2.ToString()); m_scriptEngine.Log.Error("[" + m_scriptEngine.ScriptEngineName + "]: " + "Errormessage: Error compiling script:\r\n" + e.Message.ToString()); } } } public void _StopScript(uint localID, UUID itemID) { IScript LSLBC = GetScript(localID, itemID); if (LSLBC == null) return; // Stop long command on script AsyncCommandManager.RemoveScript(m_scriptEngine, localID, itemID); // TEMP: First serialize it //GetSerializedScript(localID, itemID); try { // Get AppDomain AppDomain ad = LSLBC.Exec.GetAppDomain(); // Tell script not to accept new requests m_scriptEngine.m_ScriptManager.GetScript(localID, itemID).Exec.StopScript(); // Remove from internal structure m_scriptEngine.m_ScriptManager.RemoveScript(localID, itemID); // Tell AppDomain that we have stopped script m_scriptEngine.m_AppDomainManager.StopScript(ad); } catch (Exception e) // LEGIT: User Scripting { m_scriptEngine.Log.Error("[" + m_scriptEngine.ScriptEngineName + "]: Exception stopping script localID: " + localID + " LLUID: " + itemID.ToString() + ": " + e.ToString()); } } public void ReadConfig() { // scriptLoadUnloadThread_IdleSleepms = m_scriptEngine.ScriptConfigSource.GetInt("ScriptLoadUnloadLoopms", 30); // TODO: Requires sharing of all ScriptManagers to single thread PrivateThread = true; // m_scriptEngine.ScriptConfigSource.GetBoolean("PrivateScriptLoadUnloadThread", false); LoadUnloadMaxQueueSize = m_scriptEngine.ScriptConfigSource.GetInt("LoadUnloadMaxQueueSize", 100); } #region Object init/shutdown public ScriptEngine m_scriptEngine; public ScriptManager(ScriptEngine scriptEngine) { m_scriptEngine = scriptEngine; } public void Setup() { ReadConfig(); Initialize(); } public void Start() { m_started = true; AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); // // CREATE THREAD // Private or shared // if (PrivateThread) { // Assign one thread per region //scriptLoadUnloadThread = StartScriptLoadUnloadThread(); } else { // Shared thread - make sure one exist, then assign it to the private if (staticScriptLoadUnloadThread == null) { //staticScriptLoadUnloadThread = StartScriptLoadUnloadThread(); } scriptLoadUnloadThread = staticScriptLoadUnloadThread; } } // TODO: unused // private static int privateThreadCount = 0; // private Thread StartScriptLoadUnloadThread() // { // Thread t = new Thread(ScriptLoadUnloadThreadLoop); // string name = "ScriptLoadUnloadThread:"; // if (PrivateThread) // { // name += "Private:" + privateThreadCount; // privateThreadCount++; // } // else // { // name += "Shared"; // } // t.Name = name; // t.IsBackground = true; // t.Priority = ThreadPriority.Normal; // t.Start(); // OpenSim.Framework.ThreadTracker.Add(t); // return t; // } ~ScriptManager() { // Abort load/unload thread try { //PleaseShutdown = true; //Thread.Sleep(100); if (scriptLoadUnloadThread != null && scriptLoadUnloadThread.IsAlive == true) { scriptLoadUnloadThread.Abort(); //scriptLoadUnloadThread.Join(); } } catch { } } #endregion #region Load / Unload scripts (Thread loop) // TODO: unused // private void ScriptLoadUnloadThreadLoop() // { // try // { // while (true) // { // if (LUQueue.Count == 0) // Thread.Sleep(scriptLoadUnloadThread_IdleSleepms); // //if (PleaseShutdown) // // return; // DoScriptLoadUnload(); // } // } // catch (ThreadAbortException tae) // { // string a = tae.ToString(); // a = String.Empty; // // Expected // } // } public void DoScriptLoadUnload() { if (!m_started) return; lock (LUQueue) { if (LUQueue.Count > 0) { m_scriptEngine.Log.InfoFormat("[{0}]: Loading script", m_scriptEngine.ScriptEngineName); LUStruct item = LUQueue.Dequeue(); if (item.Action == LUType.Unload) { _StopScript(item.localID, item.itemID); RemoveScript(item.localID, item.itemID); } else if (item.Action == LUType.Load) { _StartScript(item.localID, item.itemID, item.script, item.startParam, item.postOnRez); } } } } #endregion #region Helper functions private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) { //Console.WriteLine("ScriptManager.CurrentDomain_AssemblyResolve: " + args.Name); return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null; } #endregion #region Start/Stop/Reset script // private readonly Object startStopLock = new Object(); /// /// Fetches, loads and hooks up a script to an objects events /// /// /// public void StartScript(uint localID, UUID itemID, string Script, int startParam, bool postOnRez) { lock (LUQueue) { if ((LUQueue.Count >= LoadUnloadMaxQueueSize) && m_started) { m_scriptEngine.Log.Error("[" + m_scriptEngine.ScriptEngineName + "]: ERROR: Load/unload queue item count is at " + LUQueue.Count + ". Config variable \"LoadUnloadMaxQueueSize\" is set to " + LoadUnloadMaxQueueSize + ", so ignoring new script."); return; } LUStruct ls = new LUStruct(); ls.localID = localID; ls.itemID = itemID; ls.script = Script; ls.Action = LUType.Load; ls.startParam = startParam; ls.postOnRez = postOnRez; LUQueue.Enqueue(ls); m_scriptEngine.Log.InfoFormat("[{0}]: Queued script for load", m_scriptEngine.ScriptEngineName); } } /// /// Disables and unloads a script /// /// /// public void StopScript(uint localID, UUID itemID) { LUStruct ls = new LUStruct(); ls.localID = localID; ls.itemID = itemID; ls.Action = LUType.Unload; ls.startParam = 0; ls.postOnRez = false; lock (LUQueue) { LUQueue.Enqueue(ls); } } // Create a new instance of the compiler (reuse) //private Compiler.LSL.Compiler LSLCompiler = new Compiler.LSL.Compiler(); #endregion #region Perform event execution in script /// /// Execute a LL-event-function in Script /// /// Object the script is located in /// Script ID /// Name of function /// Arguments to pass to function internal void ExecuteEvent(uint localID, UUID itemID, string FunctionName, DetectParams[] qParams, object[] args) { //cfk 2-7-08 dont need this right now and the default Linux build has DEBUG defined ///#if DEBUG /// Console.WriteLine("ScriptEngine: Inside ExecuteEvent for event " + FunctionName); ///#endif // Execute a function in the script //m_scriptEngine.Log.Info("[" + ScriptEngineName + "]: Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); //ScriptBaseInterface Script = (ScriptBaseInterface)GetScript(localID, itemID); IScript Script = GetScript(localID, itemID); if (Script == null) { return; } //cfk 2-7-08 dont need this right now and the default Linux build has DEBUG defined ///#if DEBUG /// Console.WriteLine("ScriptEngine: Executing event: " + FunctionName); ///#endif // Must be done in correct AppDomain, so leaving it up to the script itself detparms[Script] = qParams; Script.Exec.ExecuteEvent(FunctionName, args); detparms.Remove(Script); } public uint GetLocalID(UUID itemID) { foreach (KeyValuePair > k in Scripts) { if (k.Value.ContainsKey(itemID)) return k.Key; } return 0; } public int GetStateEventFlags(uint localID, UUID itemID) { // Console.WriteLine("GetStateEventFlags for <" + localID + "," + itemID + ">"); try { IScript Script = GetScript(localID, itemID); if (Script == null) { return 0; } ExecutorBase.scriptEvents evflags = Script.Exec.GetStateEventFlags(); return (int)evflags; } catch (Exception) { } return 0; } #endregion #region Internal functions to keep track of script public List GetScriptKeys(uint localID) { if (Scripts.ContainsKey(localID) == false) return new List(); Dictionary Obj; Scripts.TryGetValue(localID, out Obj); return new List(Obj.Keys); } public IScript GetScript(uint localID, UUID itemID) { lock (scriptLock) { IScript Script = null; if (Scripts.ContainsKey(localID) == false) return null; Dictionary Obj; Scripts.TryGetValue(localID, out Obj); if (Obj.ContainsKey(itemID) == false) return null; // Get script Obj.TryGetValue(itemID, out Script); return Script; } } public void SetScript(uint localID, UUID itemID, IScript Script) { lock (scriptLock) { // Create object if it doesn't exist if (Scripts.ContainsKey(localID) == false) { Scripts.Add(localID, new Dictionary()); } // Delete script if it exists Dictionary Obj; Scripts.TryGetValue(localID, out Obj); if (Obj.ContainsKey(itemID) == true) Obj.Remove(itemID); // Add to object Obj.Add(itemID, Script); } } public void RemoveScript(uint localID, UUID itemID) { if (localID == 0) localID = GetLocalID(itemID); // Don't have that object? if (Scripts.ContainsKey(localID) == false) return; // Delete script if it exists Dictionary Obj; Scripts.TryGetValue(localID, out Obj); if (Obj.ContainsKey(itemID) == true) Obj.Remove(itemID); } #endregion public void ResetScript(uint localID, UUID itemID) { IScript s = GetScript(localID, itemID); string script = s.Source; StopScript(localID, itemID); SceneObjectPart part = World.GetSceneObjectPart(localID); part.GetInventoryItem(itemID).PermsMask = 0; part.GetInventoryItem(itemID).PermsGranter = UUID.Zero; StartScript(localID, itemID, script, s.StartParam, false); } #region Script serialization/deserialization public void GetSerializedScript(uint localID, UUID itemID) { // Serialize the script and return it // Should not be a problem FileStream fs = File.Create("SERIALIZED_SCRIPT_" + itemID); BinaryFormatter b = new BinaryFormatter(); b.Serialize(fs, GetScript(localID, itemID)); fs.Close(); } public void PutSerializedScript(uint localID, UUID itemID) { // Deserialize the script and inject it into an AppDomain // How to inject into an AppDomain? } #endregion ///// ///// If set to true then threads and stuff should try to make a graceful exit ///// //public bool PleaseShutdown //{ // get { return _PleaseShutdown; } // set { _PleaseShutdown = value; } //} //private bool _PleaseShutdown = false; public DetectParams[] GetDetectParams(IScript script) { if (detparms.ContainsKey(script)) return detparms[script]; return null; } } }