/* * 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. */ /* Original code: Tedd Hansen */ using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Runtime.Serialization.Formatters.Binary; using System.Threading; using libsecondlife; using OpenSim.Framework; using OpenSim.Grid.ScriptEngine.DotNetEngine.Compiler; using OpenSim.Grid.ScriptEngine.DotNetEngine.Compiler.LSL; using OpenSim.Region.Environment.Scenes; namespace OpenSim.Grid.ScriptEngine.DotNetEngine { /// /// Loads scripts /// Compiles them if necessary /// Execute functions for EventQueueManager (Sends them to script on other AppDomain for execution) /// [Serializable] public class ScriptManager { private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); #region Declares private Thread scriptLoadUnloadThread; private int scriptLoadUnloadThread_IdleSleepms = 100; private Queue loadQueue = new Queue(); private Queue unloadQueue = new Queue(); private struct LoadStruct { public uint localID; public LLUUID itemID; public string script; } private struct UnloadStruct { public uint localID; public LLUUID itemID; } // Object> // IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory. // Instead use RuntimeTypeHandle, RuntimeFieldHandle and RunTimeHandle (IntPtr) instead! internal Dictionary> Scripts = new Dictionary>(); public Scene World { get { return m_scriptEngine.World; } } #endregion #region Object init/shutdown private ScriptEngine m_scriptEngine; public ScriptManager(ScriptEngine scriptEngine) { m_scriptEngine = scriptEngine; AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve); scriptLoadUnloadThread = new Thread(ScriptLoadUnloadThreadLoop); scriptLoadUnloadThread.Name = "ScriptLoadUnloadThread"; scriptLoadUnloadThread.IsBackground = true; scriptLoadUnloadThread.Priority = ThreadPriority.BelowNormal; scriptLoadUnloadThread.Start(); } ~ScriptManager() { // Abort load/unload thread try { if (scriptLoadUnloadThread != null) { if (scriptLoadUnloadThread.IsAlive == true) { scriptLoadUnloadThread.Abort(); scriptLoadUnloadThread.Join(); } } } catch { } } #endregion #region Load / Unload scripts (Thread loop) private void ScriptLoadUnloadThreadLoop() { try { while (true) { if (loadQueue.Count == 0 && unloadQueue.Count == 0) Thread.Sleep(scriptLoadUnloadThread_IdleSleepms); if (loadQueue.Count > 0) { LoadStruct item = loadQueue.Dequeue(); _StartScript(item.localID, item.itemID, item.script); } if (unloadQueue.Count > 0) { UnloadStruct item = unloadQueue.Dequeue(); _StopScript(item.localID, item.itemID); } } } catch (ThreadAbortException tae) { string a = tae.ToString(); a = ""; // Expected } } #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 Internal functions to keep track of script internal Dictionary.KeyCollection GetScriptKeys(uint localID) { if (Scripts.ContainsKey(localID) == false) return null; Dictionary Obj; Scripts.TryGetValue(localID, out Obj); return Obj.Keys; } internal LSL_BaseClass GetScript(uint localID, LLUUID itemID) { if (Scripts.ContainsKey(localID) == false) return null; Dictionary Obj; Scripts.TryGetValue(localID, out Obj); if (Obj.ContainsKey(itemID) == false) return null; // Get script LSL_BaseClass Script; Obj.TryGetValue(itemID, out Script); return Script; } internal void SetScript(uint localID, LLUUID itemID, LSL_BaseClass Script) { // 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); } internal void RemoveScript(uint localID, LLUUID 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 #region Start/Stop/Reset script /// /// Fetches, loads and hooks up a script to an objects events /// /// /// public void StartScript(uint localID, LLUUID itemID, string Script) { LoadStruct ls = new LoadStruct(); ls.localID = localID; ls.itemID = itemID; ls.script = Script; loadQueue.Enqueue(ls); } /// /// Disables and unloads a script /// /// /// public void StopScript(uint localID, LLUUID itemID) { UnloadStruct ls = new UnloadStruct(); ls.localID = localID; ls.itemID = itemID; unloadQueue.Enqueue(ls); } public void ResetScript(uint localID, LLUUID itemID) { string script = GetScript(localID, itemID).SourceCode; StopScript(localID, itemID); StartScript(localID, itemID, script); } private void _StartScript(uint localID, LLUUID itemID, string Script) { //IScriptHost root = host.GetRoot(); Console.WriteLine("ScriptManager StartScript: localID: " + localID + ", itemID: " + itemID); // We will initialize and start the script. // It will be up to the script itself to hook up the correct events. string ScriptSource = ""; SceneObjectPart m_host = World.GetSceneObjectPart(localID); try { // Create a new instance of the compiler (currently we don't want reuse) Compiler.LSL.Compiler LSLCompiler = new Compiler.LSL.Compiler(); // Compile (We assume LSL) ScriptSource = LSLCompiler.CompileFromLSLText(Script); //Console.WriteLine("Compilation of " + FileName + " done"); // * Insert yield into code ScriptSource = ProcessYield(ScriptSource); #if DEBUG long before; before = GC.GetTotalMemory(true); #endif LSL_BaseClass CompiledScript; CompiledScript = m_scriptEngine.m_AppDomainManager.LoadScript(ScriptSource); #if DEBUG Console.WriteLine("Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(true) - before); #endif CompiledScript.SourceCode = ScriptSource; // Add it to our script memstruct 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. LSL_BuiltIn_Commands LSLB = new LSL_BuiltIn_Commands(m_scriptEngine, m_host, localID, itemID); // Start the script - giving it BuiltIns CompiledScript.Start(LSLB); // Fire the first start-event m_scriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "state_entry", new object[] {}); } catch (Exception e) { //m_scriptEngine.m_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, 1500); World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0, m_host.AbsolutePosition, m_host.Name, m_host.UUID); } catch (Exception e2) { m_scriptEngine.m_log.Error("[ScriptEngine]: Error displaying error in-world: " + e2.ToString()); } } } private void _StopScript(uint localID, LLUUID itemID) { // Stop script Console.WriteLine("Stop script localID: " + localID + " LLUID: " + itemID.ToString()); // Stop long command on script m_scriptEngine.m_LSLLongCmdHandler.RemoveScript(localID, itemID); LSL_BaseClass LSLBC = GetScript(localID, itemID); if (LSLBC == null) return; // TEMP: First serialize it //GetSerializedScript(localID, itemID); try { // Get AppDomain AppDomain ad = LSLBC.Exec.GetAppDomain(); // Tell script not to accept new requests GetScript(localID, itemID).Exec.StopScript(); // Remove from internal structure RemoveScript(localID, itemID); // Tell AppDomain that we have stopped script m_scriptEngine.m_AppDomainManager.StopScript(ad); } catch (Exception e) { Console.WriteLine("Exception stopping script localID: " + localID + " LLUID: " + itemID.ToString() + ": " + e.ToString()); } } private string ProcessYield(string FileName) { // TODO: Create a new assembly and copy old but insert Yield Code //return TempDotNetMicroThreadingCodeInjector.TestFix(FileName); return FileName; } #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, LLUUID itemID, string FunctionName, object[] args) { // Execute a function in the script //m_scriptEngine.m_log.Info("[ScriptEngine]: Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName); LSL_BaseClass Script = m_scriptEngine.m_ScriptManager.GetScript(localID, itemID); if (Script == null) return; // Must be done in correct AppDomain, so leaving it up to the script itself Script.Exec.ExecuteEvent(FunctionName, args); } #endregion #region Script serialization/deserialization public void GetSerializedScript(uint localID, LLUUID 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, LLUUID itemID) { // Deserialize the script and inject it into an AppDomain // How to inject into an AppDomain? } #endregion } }