ScriptManager.cs 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  1. /*
  2. * Copyright (c) Contributors, http://opensimulator.org/
  3. * See CONTRIBUTORS.TXT for a full list of copyright holders.
  4. *
  5. * Redistribution and use in source and binary forms, with or without
  6. * modification, are permitted provided that the following conditions are met:
  7. * * Redistributions of source code must retain the above copyright
  8. * notice, this list of conditions and the following disclaimer.
  9. * * Redistributions in binary form must reproduce the above copyright
  10. * notice, this list of conditions and the following disclaimer in the
  11. * documentation and/or other materials provided with the distribution.
  12. * * Neither the name of the OpenSim Project nor the
  13. * names of its contributors may be used to endorse or promote products
  14. * derived from this software without specific prior written permission.
  15. *
  16. * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
  17. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
  20. * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  23. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. */
  27. /* Original code: Tedd Hansen */
  28. using System;
  29. using System.Collections.Generic;
  30. using System.IO;
  31. using System.Reflection;
  32. using System.Runtime.Serialization.Formatters.Binary;
  33. using System.Threading;
  34. using libsecondlife;
  35. using OpenSim.Framework;
  36. using OpenSim.Grid.ScriptEngine.DotNetEngine.Compiler;
  37. using OpenSim.Grid.ScriptEngine.DotNetEngine.Compiler.LSL;
  38. using OpenSim.Region.Environment.Scenes;
  39. namespace OpenSim.Grid.ScriptEngine.DotNetEngine
  40. {
  41. /// <summary>
  42. /// Loads scripts
  43. /// Compiles them if necessary
  44. /// Execute functions for EventQueueManager (Sends them to script on other AppDomain for execution)
  45. /// </summary>
  46. [Serializable]
  47. public class ScriptManager
  48. {
  49. private static readonly log4net.ILog m_log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  50. #region Declares
  51. private Thread scriptLoadUnloadThread;
  52. private int scriptLoadUnloadThread_IdleSleepms = 100;
  53. private Queue<LoadStruct> loadQueue = new Queue<LoadStruct>();
  54. private Queue<UnloadStruct> unloadQueue = new Queue<UnloadStruct>();
  55. private struct LoadStruct
  56. {
  57. public uint localID;
  58. public LLUUID itemID;
  59. public string script;
  60. }
  61. private struct UnloadStruct
  62. {
  63. public uint localID;
  64. public LLUUID itemID;
  65. }
  66. // Object<string, Script<string, script>>
  67. // IMPORTANT: Types and MemberInfo-derived objects require a LOT of memory.
  68. // Instead use RuntimeTypeHandle, RuntimeFieldHandle and RunTimeHandle (IntPtr) instead!
  69. internal Dictionary<uint, Dictionary<LLUUID, LSL_BaseClass>> Scripts =
  70. new Dictionary<uint, Dictionary<LLUUID, LSL_BaseClass>>();
  71. public Scene World
  72. {
  73. get { return m_scriptEngine.World; }
  74. }
  75. #endregion
  76. #region Object init/shutdown
  77. private ScriptEngine m_scriptEngine;
  78. public ScriptManager(ScriptEngine scriptEngine)
  79. {
  80. m_scriptEngine = scriptEngine;
  81. AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
  82. scriptLoadUnloadThread = new Thread(ScriptLoadUnloadThreadLoop);
  83. scriptLoadUnloadThread.Name = "ScriptLoadUnloadThread";
  84. scriptLoadUnloadThread.IsBackground = true;
  85. scriptLoadUnloadThread.Priority = ThreadPriority.BelowNormal;
  86. scriptLoadUnloadThread.Start();
  87. }
  88. ~ScriptManager()
  89. {
  90. // Abort load/unload thread
  91. try
  92. {
  93. if (scriptLoadUnloadThread != null)
  94. {
  95. if (scriptLoadUnloadThread.IsAlive == true)
  96. {
  97. scriptLoadUnloadThread.Abort();
  98. scriptLoadUnloadThread.Join();
  99. }
  100. }
  101. }
  102. catch
  103. {
  104. }
  105. }
  106. #endregion
  107. #region Load / Unload scripts (Thread loop)
  108. private void ScriptLoadUnloadThreadLoop()
  109. {
  110. try
  111. {
  112. while (true)
  113. {
  114. if (loadQueue.Count == 0 && unloadQueue.Count == 0)
  115. Thread.Sleep(scriptLoadUnloadThread_IdleSleepms);
  116. if (loadQueue.Count > 0)
  117. {
  118. LoadStruct item = loadQueue.Dequeue();
  119. _StartScript(item.localID, item.itemID, item.script);
  120. }
  121. if (unloadQueue.Count > 0)
  122. {
  123. UnloadStruct item = unloadQueue.Dequeue();
  124. _StopScript(item.localID, item.itemID);
  125. }
  126. }
  127. }
  128. catch (ThreadAbortException tae)
  129. {
  130. string a = tae.ToString();
  131. a = "";
  132. // Expected
  133. }
  134. }
  135. #endregion
  136. #region Helper functions
  137. private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
  138. {
  139. //Console.WriteLine("ScriptManager.CurrentDomain_AssemblyResolve: " + args.Name);
  140. return Assembly.GetExecutingAssembly().FullName == args.Name ? Assembly.GetExecutingAssembly() : null;
  141. }
  142. #endregion
  143. #region Internal functions to keep track of script
  144. internal Dictionary<LLUUID, LSL_BaseClass>.KeyCollection GetScriptKeys(uint localID)
  145. {
  146. if (Scripts.ContainsKey(localID) == false)
  147. return null;
  148. Dictionary<LLUUID, LSL_BaseClass> Obj;
  149. Scripts.TryGetValue(localID, out Obj);
  150. return Obj.Keys;
  151. }
  152. internal LSL_BaseClass GetScript(uint localID, LLUUID itemID)
  153. {
  154. if (Scripts.ContainsKey(localID) == false)
  155. return null;
  156. Dictionary<LLUUID, LSL_BaseClass> Obj;
  157. Scripts.TryGetValue(localID, out Obj);
  158. if (Obj.ContainsKey(itemID) == false)
  159. return null;
  160. // Get script
  161. LSL_BaseClass Script;
  162. Obj.TryGetValue(itemID, out Script);
  163. return Script;
  164. }
  165. internal void SetScript(uint localID, LLUUID itemID, LSL_BaseClass Script)
  166. {
  167. // Create object if it doesn't exist
  168. if (Scripts.ContainsKey(localID) == false)
  169. {
  170. Scripts.Add(localID, new Dictionary<LLUUID, LSL_BaseClass>());
  171. }
  172. // Delete script if it exists
  173. Dictionary<LLUUID, LSL_BaseClass> Obj;
  174. Scripts.TryGetValue(localID, out Obj);
  175. if (Obj.ContainsKey(itemID) == true)
  176. Obj.Remove(itemID);
  177. // Add to object
  178. Obj.Add(itemID, Script);
  179. }
  180. internal void RemoveScript(uint localID, LLUUID itemID)
  181. {
  182. // Don't have that object?
  183. if (Scripts.ContainsKey(localID) == false)
  184. return;
  185. // Delete script if it exists
  186. Dictionary<LLUUID, LSL_BaseClass> Obj;
  187. Scripts.TryGetValue(localID, out Obj);
  188. if (Obj.ContainsKey(itemID) == true)
  189. Obj.Remove(itemID);
  190. }
  191. #endregion
  192. #region Start/Stop/Reset script
  193. /// <summary>
  194. /// Fetches, loads and hooks up a script to an objects events
  195. /// </summary>
  196. /// <param name="itemID"></param>
  197. /// <param name="localID"></param>
  198. public void StartScript(uint localID, LLUUID itemID, string Script)
  199. {
  200. LoadStruct ls = new LoadStruct();
  201. ls.localID = localID;
  202. ls.itemID = itemID;
  203. ls.script = Script;
  204. loadQueue.Enqueue(ls);
  205. }
  206. /// <summary>
  207. /// Disables and unloads a script
  208. /// </summary>
  209. /// <param name="localID"></param>
  210. /// <param name="itemID"></param>
  211. public void StopScript(uint localID, LLUUID itemID)
  212. {
  213. UnloadStruct ls = new UnloadStruct();
  214. ls.localID = localID;
  215. ls.itemID = itemID;
  216. unloadQueue.Enqueue(ls);
  217. }
  218. public void ResetScript(uint localID, LLUUID itemID)
  219. {
  220. string script = GetScript(localID, itemID).SourceCode;
  221. StopScript(localID, itemID);
  222. StartScript(localID, itemID, script);
  223. }
  224. private void _StartScript(uint localID, LLUUID itemID, string Script)
  225. {
  226. //IScriptHost root = host.GetRoot();
  227. Console.WriteLine("ScriptManager StartScript: localID: " + localID + ", itemID: " + itemID);
  228. // We will initialize and start the script.
  229. // It will be up to the script itself to hook up the correct events.
  230. string ScriptSource = "";
  231. SceneObjectPart m_host = World.GetSceneObjectPart(localID);
  232. try
  233. {
  234. // Create a new instance of the compiler (currently we don't want reuse)
  235. Compiler.LSL.Compiler LSLCompiler = new Compiler.LSL.Compiler();
  236. // Compile (We assume LSL)
  237. ScriptSource = LSLCompiler.CompileFromLSLText(Script);
  238. //Console.WriteLine("Compilation of " + FileName + " done");
  239. // * Insert yield into code
  240. ScriptSource = ProcessYield(ScriptSource);
  241. #if DEBUG
  242. long before;
  243. before = GC.GetTotalMemory(true);
  244. #endif
  245. LSL_BaseClass CompiledScript;
  246. CompiledScript = m_scriptEngine.m_AppDomainManager.LoadScript(ScriptSource);
  247. #if DEBUG
  248. Console.WriteLine("Script " + itemID + " occupies {0} bytes", GC.GetTotalMemory(true) - before);
  249. #endif
  250. CompiledScript.SourceCode = ScriptSource;
  251. // Add it to our script memstruct
  252. SetScript(localID, itemID, CompiledScript);
  253. // We need to give (untrusted) assembly a private instance of BuiltIns
  254. // this private copy will contain Read-Only FullitemID so that it can bring that on to the server whenever needed.
  255. LSL_BuiltIn_Commands LSLB = new LSL_BuiltIn_Commands(m_scriptEngine, m_host, localID, itemID);
  256. // Start the script - giving it BuiltIns
  257. CompiledScript.Start(LSLB);
  258. // Fire the first start-event
  259. m_scriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "state_entry", new object[] {});
  260. }
  261. catch (Exception e)
  262. {
  263. //m_scriptEngine.m_log.Error("[ScriptEngine]: Error compiling script: " + e.ToString());
  264. try
  265. {
  266. // DISPLAY ERROR INWORLD
  267. string text = "Error compiling script:\r\n" + e.Message.ToString();
  268. if (text.Length > 1500)
  269. text = text.Substring(0, 1500);
  270. World.SimChat(Helpers.StringToField(text), ChatTypeEnum.Say, 0, m_host.AbsolutePosition, m_host.Name, m_host.UUID);
  271. }
  272. catch (Exception e2)
  273. {
  274. m_scriptEngine.m_log.Error("[ScriptEngine]: Error displaying error in-world: " + e2.ToString());
  275. }
  276. }
  277. }
  278. private void _StopScript(uint localID, LLUUID itemID)
  279. {
  280. // Stop script
  281. Console.WriteLine("Stop script localID: " + localID + " LLUID: " + itemID.ToString());
  282. // Stop long command on script
  283. m_scriptEngine.m_LSLLongCmdHandler.RemoveScript(localID, itemID);
  284. LSL_BaseClass LSLBC = GetScript(localID, itemID);
  285. if (LSLBC == null)
  286. return;
  287. // TEMP: First serialize it
  288. //GetSerializedScript(localID, itemID);
  289. try
  290. {
  291. // Get AppDomain
  292. AppDomain ad = LSLBC.Exec.GetAppDomain();
  293. // Tell script not to accept new requests
  294. GetScript(localID, itemID).Exec.StopScript();
  295. // Remove from internal structure
  296. RemoveScript(localID, itemID);
  297. // Tell AppDomain that we have stopped script
  298. m_scriptEngine.m_AppDomainManager.StopScript(ad);
  299. }
  300. catch (Exception e)
  301. {
  302. Console.WriteLine("Exception stopping script localID: " + localID + " LLUID: " + itemID.ToString() +
  303. ": " + e.ToString());
  304. }
  305. }
  306. private string ProcessYield(string FileName)
  307. {
  308. // TODO: Create a new assembly and copy old but insert Yield Code
  309. //return TempDotNetMicroThreadingCodeInjector.TestFix(FileName);
  310. return FileName;
  311. }
  312. #endregion
  313. #region Perform event execution in script
  314. /// <summary>
  315. /// Execute a LL-event-function in Script
  316. /// </summary>
  317. /// <param name="localID">Object the script is located in</param>
  318. /// <param name="itemID">Script ID</param>
  319. /// <param name="FunctionName">Name of function</param>
  320. /// <param name="args">Arguments to pass to function</param>
  321. internal void ExecuteEvent(uint localID, LLUUID itemID, string FunctionName, object[] args)
  322. {
  323. // Execute a function in the script
  324. //m_scriptEngine.m_log.Info("[ScriptEngine]: Executing Function localID: " + localID + ", itemID: " + itemID + ", FunctionName: " + FunctionName);
  325. LSL_BaseClass Script = m_scriptEngine.m_ScriptManager.GetScript(localID, itemID);
  326. if (Script == null)
  327. return;
  328. // Must be done in correct AppDomain, so leaving it up to the script itself
  329. Script.Exec.ExecuteEvent(FunctionName, args);
  330. }
  331. #endregion
  332. #region Script serialization/deserialization
  333. public void GetSerializedScript(uint localID, LLUUID itemID)
  334. {
  335. // Serialize the script and return it
  336. // Should not be a problem
  337. FileStream fs = File.Create("SERIALIZED_SCRIPT_" + itemID);
  338. BinaryFormatter b = new BinaryFormatter();
  339. b.Serialize(fs, GetScript(localID, itemID));
  340. fs.Close();
  341. }
  342. public void PutSerializedScript(uint localID, LLUUID itemID)
  343. {
  344. // Deserialize the script and inject it into an AppDomain
  345. // How to inject into an AppDomain?
  346. }
  347. #endregion
  348. }
  349. }