OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
ScriptInstance.cs
Go to the documentation of this file.
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 OpenSimulator 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 
28 using System;
29 using System.Collections;
30 using System.Collections.Generic;
31 using System.Globalization;
32 using System.IO;
33 using System.Reflection;
34 using System.Runtime.Remoting;
35 using System.Runtime.Remoting.Lifetime;
36 using System.Security.Policy;
37 using System.Text;
38 using System.Threading;
39 using System.Xml;
40 using OpenMetaverse;
41 using log4net;
42 using Nini.Config;
43 using Amib.Threading;
44 using OpenSim.Framework;
45 using OpenSim.Region.CoreModules;
46 using OpenSim.Region.Framework.Scenes;
47 using OpenSim.Region.Framework.Interfaces;
48 using OpenSim.Region.ScriptEngine.Shared;
49 using OpenSim.Region.ScriptEngine.Shared.Api;
50 using OpenSim.Region.ScriptEngine.Shared.Api.Runtime;
51 using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
52 using OpenSim.Region.ScriptEngine.Shared.CodeTools;
53 using OpenSim.Region.ScriptEngine.Interfaces;
54 
55 using System.Diagnostics; //for [DebuggerNonUserCode]
56 
57 namespace OpenSim.Region.ScriptEngine.Shared.Instance
58 {
59  public class ScriptInstance : MarshalByRefObject, IScriptInstance
60  {
61  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
62 
63  public bool StatePersistedHere { get { return m_AttachedAvatar == UUID.Zero; } }
64 
71  private IScriptWorkItem m_CurrentWorkItem;
72 
73  private IScript m_Script;
74  private DetectParams[] m_DetectParams;
75  private bool m_TimerQueued;
76  private DateTime m_EventStart;
77  private bool m_InEvent;
78  private string m_assemblyPath;
79  private string m_dataPath;
80  private string m_CurrentEvent = String.Empty;
81  private bool m_InSelfDelete;
82  private int m_MaxScriptQueue;
83  private bool m_SaveState;
84  private int m_ControlEventsInQueue;
85  private int m_LastControlLevel;
86  private bool m_CollisionInQueue;
87  private bool m_StateChangeInProgress;
88 
89  // The following is for setting a minimum delay between events
90  private double m_minEventDelay;
91 
92  private long m_eventDelayTicks;
93  private long m_nextEventTimeTicks;
94  private bool m_startOnInit = true;
95  private UUID m_AttachedAvatar;
96  private StateSource m_stateSource;
97  private bool m_postOnRez;
98  private bool m_startedFromSavedState;
99  private UUID m_CurrentStateHash;
100  private UUID m_RegionID;
101 
102  public int DebugLevel { get; set; }
103 
104  public WaitHandle CoopWaitHandle { get; private set; }
105  public Stopwatch ExecutionTimer { get; private set; }
106 
107  public Dictionary<KeyValuePair<int, int>, KeyValuePair<int, int>> LineMap { get; set; }
108 
109  private Dictionary<string,IScriptApi> m_Apis = new Dictionary<string,IScriptApi>();
110 
111  public Object[] PluginData = new Object[0];
112 
118  public double MinEventDelay
119  {
120  get { return m_minEventDelay; }
121  set
122  {
123  if (value > 0.001)
124  m_minEventDelay = value;
125  else
126  m_minEventDelay = 0.0;
127 
128  m_eventDelayTicks = (long)(m_minEventDelay * 10000000L);
129  m_nextEventTimeTicks = DateTime.Now.Ticks;
130  }
131  }
132 
133  public bool Running
134  {
135  get { return m_running; }
136 
137  set
138  {
139  m_running = value;
140  if (m_running)
141  StayStopped = false;
142  }
143  }
144  private bool m_running;
145 
146  public bool Suspended
147  {
148  get { return m_Suspended; }
149 
150  set
151  {
152  // Need to do this inside a lock in order to avoid races with EventProcessor()
153  lock (m_Script)
154  {
155  bool wasSuspended = m_Suspended;
156  m_Suspended = value;
157 
158  if (wasSuspended && !m_Suspended)
159  {
160  lock (EventQueue)
161  {
162  // Need to place ourselves back in a work item if there are events to process
163  if (EventQueue.Count > 0 && Running && !ShuttingDown)
164  m_CurrentWorkItem = Engine.QueueEventHandler(this);
165  }
166  }
167  }
168  }
169  }
170  private bool m_Suspended;
171 
172  public bool ShuttingDown { get; set; }
173 
174  public string State { get; set; }
175 
176  public bool StayStopped { get; set; }
177 
178  public IScriptEngine Engine { get; private set; }
179 
180  public UUID AppDomain { get; set; }
181 
182  public SceneObjectPart Part { get; private set; }
183 
184  public string PrimName { get; private set; }
185 
186  public string ScriptName { get; private set; }
187 
188  public UUID ItemID { get; private set; }
189 
190  public UUID ObjectID { get { return Part.UUID; } }
191 
192  public uint LocalID { get { return Part.LocalId; } }
193 
194  public UUID RootObjectID { get { return Part.ParentGroup.UUID; } }
195 
196  public uint RootLocalID { get { return Part.ParentGroup.LocalId; } }
197 
198  public UUID AssetID { get; private set; }
199 
200  public Queue EventQueue { get; private set; }
201 
202  public long EventsQueued
203  {
204  get
205  {
206  lock (EventQueue)
207  return EventQueue.Count;
208  }
209  }
210 
211  public long EventsProcessed { get; private set; }
212 
213  public int StartParam { get; set; }
214 
215  public TaskInventoryItem ScriptTask { get; private set; }
216 
217  public DateTime TimeStarted { get; private set; }
218 
219  public MetricsCollectorTime ExecutionTime { get; private set; }
220 
221  private static readonly int MeasurementWindow = 30 * 1000; // show the *recent* time used by the script, to find currently active scripts
222 
223  private bool m_coopTermination;
224 
225  private EventWaitHandle m_coopSleepHandle;
226 
227  public void ClearQueue()
228  {
229  m_TimerQueued = false;
230  m_StateChangeInProgress = false;
231  EventQueue.Clear();
232  }
233 
236  int startParam, bool postOnRez,
237  int maxScriptQueue)
238  {
239  State = "default";
240  EventQueue = new Queue(32);
241  ExecutionTimer = new Stopwatch();
242 
243  Engine = engine;
244  Part = part;
245  ScriptTask = item;
246 
247  // This is currently only here to allow regression tests to get away without specifying any inventory
248  // item when they are testing script logic that doesn't require an item.
249  if (ScriptTask != null)
250  {
251  ScriptName = ScriptTask.Name;
252  ItemID = ScriptTask.ItemID;
253  AssetID = ScriptTask.AssetID;
254  }
255 
256  PrimName = part.ParentGroup.Name;
257  StartParam = startParam;
258  m_MaxScriptQueue = maxScriptQueue;
259  m_postOnRez = postOnRez;
260  m_AttachedAvatar = part.ParentGroup.AttachedAvatar;
261  m_RegionID = part.ParentGroup.Scene.RegionInfo.RegionID;
262 
263  m_SaveState = StatePersistedHere;
264 
265  ExecutionTime = new MetricsCollectorTime(MeasurementWindow, 10);
266 
267 // m_log.DebugFormat(
268 // "[SCRIPT INSTANCE]: Instantiated script instance {0} (id {1}) in part {2} (id {3}) in object {4} attached avatar {5} in {6}",
269 // ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, m_AttachedAvatar, Engine.World.Name);
270  }
271 
283  public bool Load(
284  IScript script, EventWaitHandle coopSleepHandle, string assemblyPath,
285  string dataPath, StateSource stateSource, bool coopTermination)
286  {
287  m_Script = script;
288  m_coopSleepHandle = coopSleepHandle;
289  m_assemblyPath = assemblyPath;
290  m_dataPath = dataPath;
291  m_stateSource = stateSource;
292  m_coopTermination = coopTermination;
293 
294  if (m_coopTermination)
295  CoopWaitHandle = coopSleepHandle;
296  else
297  CoopWaitHandle = null;
298 
299  ApiManager am = new ApiManager();
300 
301  foreach (string api in am.GetApis())
302  {
303  m_Apis[api] = am.CreateApi(api);
304  m_Apis[api].Initialize(Engine, Part, ScriptTask);
305  }
306 
307  try
308  {
309  foreach (KeyValuePair<string,IScriptApi> kv in m_Apis)
310  {
311  m_Script.InitApi(kv.Key, kv.Value);
312  }
313 
314  // // m_log.Debug("[Script] Script instance created");
315 
316  Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State));
317  }
318  catch (Exception e)
319  {
320  m_log.ErrorFormat(
321  "[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}",
322  ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, e.Message, e.StackTrace);
323 
324  return false;
325  }
326 
327  // For attachments, XEngine saves the state into a .state file when XEngine.SetXMLState() is called.
328  string savedState = Path.Combine(m_dataPath, ItemID.ToString() + ".state");
329 
330  if (File.Exists(savedState))
331  {
332  // m_log.DebugFormat(
333  // "[SCRIPT INSTANCE]: Found state for script {0} for {1} ({2}) at {3} in {4}",
334  // ItemID, savedState, Part.Name, Part.ParentGroup.Name, Part.ParentGroup.Scene.Name);
335 
336  string xml = String.Empty;
337 
338  try
339  {
340  FileInfo fi = new FileInfo(savedState);
341  int size = (int)fi.Length;
342  if (size < 512000)
343  {
344  using (FileStream fs = File.Open(savedState,
345  FileMode.Open, FileAccess.Read, FileShare.None))
346  {
347  Byte[] data = new Byte[size];
348  fs.Read(data, 0, size);
349 
350  xml = Encoding.UTF8.GetString(data);
351 
352  ScriptSerializer.Deserialize(xml, this);
353 
354  AsyncCommandManager.CreateFromData(Engine,
355  LocalID, ItemID, ObjectID,
356  PluginData);
357 
358  // m_log.DebugFormat("[Script] Successfully retrieved state for script {0}.{1}", PrimName, m_ScriptName);
359 
360  Part.SetScriptEvents(ItemID,
361  (int)m_Script.GetStateEventFlags(State));
362 
363  if (!Running)
364  m_startOnInit = false;
365 
366  Running = false;
367 
368  // we get new rez events on sim restart, too
369  // but if there is state, then we fire the change
370  // event
371 
372  // We loaded state, don't force a re-save
373  m_SaveState = false;
374  m_startedFromSavedState = true;
375  }
376 
377  // If this script is in an attachment then we no longer need the state file.
378  if (!StatePersistedHere)
379  RemoveState();
380  }
381  // else
382  // {
383  // m_log.WarnFormat(
384  // "[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.",
385  // ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState);
386  // }
387  }
388  catch (Exception e)
389  {
390  m_log.ErrorFormat(
391  "[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}",
392  ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name, savedState, xml, e.Message, e.StackTrace);
393  }
394  }
395  // else
396  // {
397  // m_log.DebugFormat(
398  // "[SCRIPT INSTANCE]: Did not find state for script {0} for {1} ({2}) at {3} in {4}",
399  // ItemID, savedState, Part.Name, Part.ParentGroup.Name, Part.ParentGroup.Scene.Name);
400  // }
401 
402  return true;
403  }
404 
405  public void Init()
406  {
407  if (ShuttingDown)
408  return;
409 
410  if (m_startedFromSavedState)
411  {
412  if (m_startOnInit)
413  Start();
414  if (m_postOnRez)
415  {
416  PostEvent(new EventParams("on_rez",
417  new Object[] {new LSL_Types.LSLInteger(StartParam)}, new DetectParams[0]));
418  }
419 
420  if (m_stateSource == StateSource.AttachedRez)
421  {
422  PostEvent(new EventParams("attach",
423  new object[] { new LSL_Types.LSLString(m_AttachedAvatar.ToString()) }, new DetectParams[0]));
424  }
425  else if (m_stateSource == StateSource.RegionStart)
426  {
427  //m_log.Debug("[Script] Posted changed(CHANGED_REGION_RESTART) to script");
428  PostEvent(new EventParams("changed",
429  new Object[] { new LSL_Types.LSLInteger((int)Changed.REGION_RESTART) }, new DetectParams[0]));
430  }
431  else if (m_stateSource == StateSource.PrimCrossing || m_stateSource == StateSource.Teleporting)
432  {
433  // CHANGED_REGION
434  PostEvent(new EventParams("changed",
435  new Object[] { new LSL_Types.LSLInteger((int)Changed.REGION) }, new DetectParams[0]));
436 
437  // CHANGED_TELEPORT
438  if (m_stateSource == StateSource.Teleporting)
439  PostEvent(new EventParams("changed",
440  new Object[] { new LSL_Types.LSLInteger((int)Changed.TELEPORT) }, new DetectParams[0]));
441  }
442  }
443  else
444  {
445  if (m_startOnInit)
446  Start();
447  PostEvent(new EventParams("state_entry",
448  new Object[0], new DetectParams[0]));
449  if (m_postOnRez)
450  {
451  PostEvent(new EventParams("on_rez",
452  new Object[] {new LSL_Types.LSLInteger(StartParam)}, new DetectParams[0]));
453  }
454 
455  if (m_stateSource == StateSource.AttachedRez)
456  {
457  PostEvent(new EventParams("attach",
458  new object[] { new LSL_Types.LSLString(m_AttachedAvatar.ToString()) }, new DetectParams[0]));
459  }
460 
461  }
462  }
463 
464  private void ReleaseControls()
465  {
466  SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID);
467 
468  if (part != null)
469  {
470  int permsMask;
471  UUID permsGranter;
472  part.TaskInventory.LockItemsForRead(true);
473  if (!part.TaskInventory.ContainsKey(ItemID))
474  {
475  part.TaskInventory.LockItemsForRead(false);
476  return;
477  }
478  permsGranter = part.TaskInventory[ItemID].PermsGranter;
479  permsMask = part.TaskInventory[ItemID].PermsMask;
480  part.TaskInventory.LockItemsForRead(false);
481 
482  if ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)
483  {
484  ScenePresence presence = Engine.World.GetScenePresence(permsGranter);
485  if (presence != null)
486  presence.UnRegisterControlEventsToScript(LocalID, ItemID);
487  }
488  }
489  }
490 
491  public void DestroyScriptInstance()
492  {
493  ReleaseControls();
494  AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID);
495  }
496 
497  public void RemoveState()
498  {
499  string savedState = Path.Combine(m_dataPath, ItemID.ToString() + ".state");
500 
501 // m_log.DebugFormat(
502 // "[SCRIPT INSTANCE]: Deleting state {0} for script {1} (id {2}) in part {3} (id {4}) in object {5} in {6}.",
503 // savedState, ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name);
504 
505  try
506  {
507  File.Delete(savedState);
508  }
509  catch (Exception e)
510  {
511  m_log.Warn(
512  string.Format(
513  "[SCRIPT INSTANCE]: Could not delete script state {0} for script {1} (id {2}) in part {3} (id {4}) in object {5} in {6}. Exception ",
514  savedState, ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name),
515  e);
516  }
517  }
518 
519  public void VarDump(Dictionary<string, object> vars)
520  {
521  // m_log.Info("Variable dump for script "+ ItemID.ToString());
522  // foreach (KeyValuePair<string, object> v in vars)
523  // {
524  // m_log.Info("Variable: "+v.Key+" = "+v.Value.ToString());
525  // }
526  }
527 
528  public void Start()
529  {
530  lock (EventQueue)
531  {
532  if (Running)
533  return;
534 
535  Running = true;
536 
537  TimeStarted = DateTime.Now;
538 
539  // Note: we don't reset ExecutionTime. The reason is that runaway scripts are stopped and restarted
540  // automatically, and we *do* want to show that they had high CPU in that case. If we had reset
541  // ExecutionTime here then runaway scripts, paradoxically, would never show up in the "Top Scripts" dialog.
542 
543  if (EventQueue.Count > 0)
544  {
545  if (m_CurrentWorkItem == null)
546  m_CurrentWorkItem = Engine.QueueEventHandler(this);
547  // else
548  // m_log.Error("[Script] Tried to start a script that was already queued");
549  }
550  }
551  }
552 
553  public bool Stop(int timeout, bool clearEventQueue = false)
554  {
555  if (DebugLevel >= 1)
556  m_log.DebugFormat(
557  "[SCRIPT INSTANCE]: Stopping script {0} {1} in {2} {3} with timeout {4} {5} {6}",
558  ScriptName, ItemID, PrimName, ObjectID, timeout, m_InSelfDelete, DateTime.Now.Ticks);
559 
560  IScriptWorkItem workItem;
561 
562  lock (EventQueue)
563  {
564  if (clearEventQueue)
565  ClearQueue();
566 
567  if (!Running)
568  return true;
569 
570  // If we're not running or waiting to run an event then we can safely stop.
571  if (m_CurrentWorkItem == null)
572  {
573  Running = false;
574  return true;
575  }
576 
577  // If we are waiting to run an event then we can try to cancel it.
578  if (m_CurrentWorkItem.Cancel())
579  {
580  m_CurrentWorkItem = null;
581  Running = false;
582  return true;
583  }
584 
585  workItem = m_CurrentWorkItem;
586  Running = false;
587  }
588 
589  // Wait for the current event to complete.
590  if (!m_InSelfDelete)
591  {
592  if (!m_coopTermination)
593  {
594  // If we're not co-operative terminating then try and wait for the event to complete before stopping
595  if (workItem.Wait(timeout))
596  return true;
597  }
598  else
599  {
600  if (DebugLevel >= 1)
601  m_log.DebugFormat(
602  "[SCRIPT INSTANCE]: Co-operatively stopping script {0} {1} in {2} {3}",
603  ScriptName, ItemID, PrimName, ObjectID);
604 
605  // This will terminate the event on next handle check by the script.
606  m_coopSleepHandle.Set();
607 
608  // For now, we will wait forever since the event should always cleanly terminate once LSL loop
609  // checking is implemented. May want to allow a shorter timeout option later.
610  if (workItem.Wait(Timeout.Infinite))
611  {
612  if (DebugLevel >= 1)
613  m_log.DebugFormat(
614  "[SCRIPT INSTANCE]: Co-operatively stopped script {0} {1} in {2} {3}",
615  ScriptName, ItemID, PrimName, ObjectID);
616 
617  return true;
618  }
619  }
620  }
621 
622  lock (EventQueue)
623  {
624  workItem = m_CurrentWorkItem;
625  }
626 
627  if (workItem == null)
628  return true;
629 
630  // If the event still hasn't stopped and we the stop isn't the result of script or object removal, then
631  // forcibly abort the work item (this aborts the underlying thread).
632  // Co-operative termination should never reach this point.
633  if (!m_InSelfDelete)
634  {
635  m_log.DebugFormat(
636  "[SCRIPT INSTANCE]: Aborting unstopped script {0} {1} in prim {2}, localID {3}, timeout was {4} ms",
637  ScriptName, ItemID, PrimName, LocalID, timeout);
638 
639  workItem.Abort();
640  }
641 
642  lock (EventQueue)
643  {
644  m_CurrentWorkItem = null;
645  }
646 
647  return true;
648  }
649 
650  [DebuggerNonUserCode] //Prevents the debugger from farting in this function
651  public void SetState(string state)
652  {
653  if (state == State)
654  return;
655 
656  EventParams lastTimerEv = null;
657 
658  lock (EventQueue)
659  {
660  // Remove all queued events, remembering the last timer event
661  while (EventQueue.Count > 0)
662  {
663  EventParams tempv = (EventParams)EventQueue.Dequeue();
664  if (tempv.EventName == "timer") lastTimerEv = tempv;
665  }
666 
667  // Post events
668  PostEvent(new EventParams("state_exit", new Object[0],
669  new DetectParams[0]));
670  PostEvent(new EventParams("state", new Object[] { state },
671  new DetectParams[0]));
672  PostEvent(new EventParams("state_entry", new Object[0],
673  new DetectParams[0]));
674 
675  // Requeue the timer event after the state changing events
676  if (lastTimerEv != null) EventQueue.Enqueue(lastTimerEv);
677 
678  // This will stop events from being queued and processed
679  // until the new state is started
680  m_StateChangeInProgress = true;
681  }
682 
683  throw new EventAbortException();
684  }
685 
693  public void PostEvent(EventParams data)
694  {
695 // m_log.DebugFormat("[Script] Posted event {2} in state {3} to {0}.{1}",
696 // PrimName, ScriptName, data.EventName, State);
697 
698  if (!Running)
699  return;
700 
701  // If min event delay is set then ignore any events untill the time has expired
702  // This currently only allows 1 event of any type in the given time period.
703  // This may need extending to allow for a time for each individual event type.
704  if (m_eventDelayTicks != 0)
705  {
706  if (DateTime.Now.Ticks < m_nextEventTimeTicks)
707  return;
708  m_nextEventTimeTicks = DateTime.Now.Ticks + m_eventDelayTicks;
709  }
710 
711  lock (EventQueue)
712  {
713  // The only events that persist across state changes are timers
714  if (m_StateChangeInProgress && data.EventName != "timer")
715  return;
716 
717  if (EventQueue.Count >= m_MaxScriptQueue)
718  return;
719 
720  if (data.EventName == "timer")
721  {
722  if (m_TimerQueued)
723  return;
724  m_TimerQueued = true;
725  }
726 
727  if (data.EventName == "control")
728  {
729  int held = ((LSL_Types.LSLInteger)data.Params[1]).value;
730  // int changed = ((LSL_Types.LSLInteger)data.Params[2]).value;
731 
732  // If the last message was a 0 (nothing held)
733  // and this one is also nothing held, drop it
734  //
735  if (m_LastControlLevel == held && held == 0)
736  return;
737 
738  // If there is one or more queued, then queue
739  // only changed ones, else queue unconditionally
740  //
741  if (m_ControlEventsInQueue > 0)
742  {
743  if (m_LastControlLevel == held)
744  return;
745  }
746 
747  m_LastControlLevel = held;
748  m_ControlEventsInQueue++;
749  }
750 
751  if (data.EventName == "collision")
752  {
753  if (m_CollisionInQueue)
754  return;
755  if (data.DetectParams == null)
756  return;
757 
758  m_CollisionInQueue = true;
759  }
760 
761  EventQueue.Enqueue(data);
762 
763  if (m_CurrentWorkItem == null)
764  {
765  m_CurrentWorkItem = Engine.QueueEventHandler(this);
766  }
767  }
768  }
769 
774  public object EventProcessor()
775  {
776  EventParams data = null;
777  // We check here as the thread stopping this instance from running may itself hold the m_Script lock.
778  if (!Running)
779  return 0;
780 
781  lock (m_Script)
782  {
783 // m_log.DebugFormat("[XEngine]: EventProcessor() invoked for {0}.{1}", PrimName, ScriptName);
784 
785  if (Suspended)
786  return 0;
787 
788  ExecutionTimer.Restart();
789 
790  try
791  {
792  return EventProcessorInt();
793  }
794  finally
795  {
796  ExecutionTimer.Stop();
797  ExecutionTime.AddSample(ExecutionTimer);
798  Part.ParentGroup.Scene.AddScriptExecutionTime(ExecutionTimer.ElapsedTicks);
799  }
800  }
801  }
802 
803  private object EventProcessorInt()
804  {
805  EventParams data = null;
806 
807  lock (EventQueue)
808  {
809  data = (EventParams)EventQueue.Dequeue();
810  if (data == null) // Shouldn't happen
811  {
812  if (EventQueue.Count > 0 && Running && !ShuttingDown)
813  {
814  m_CurrentWorkItem = Engine.QueueEventHandler(this);
815  }
816  else
817  {
818  m_CurrentWorkItem = null;
819  }
820  return 0;
821  }
822 
823  if (data.EventName == "timer")
824  m_TimerQueued = false;
825  if (data.EventName == "control")
826  {
827  if (m_ControlEventsInQueue > 0)
828  m_ControlEventsInQueue--;
829  }
830  if (data.EventName == "collision")
831  m_CollisionInQueue = false;
832  }
833 
834  if (DebugLevel >= 2)
835  m_log.DebugFormat(
836  "[SCRIPT INSTANCE]: Processing event {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}",
837  data.EventName,
838  ScriptName,
839  Part.Name,
840  Part.LocalId,
841  Part.ParentGroup.Name,
842  Part.ParentGroup.UUID,
843  Part.AbsolutePosition,
844  Part.ParentGroup.Scene.Name);
845 
846  m_DetectParams = data.DetectParams;
847 
848  if (data.EventName == "state") // Hardcoded state change
849  {
850  State = data.Params[0].ToString();
851 
852  if (DebugLevel >= 1)
853  m_log.DebugFormat(
854  "[SCRIPT INSTANCE]: Changing state to {0} for {1}/{2}({3})/{4}({5}) @ {6}/{7}",
855  State,
856  ScriptName,
857  Part.Name,
858  Part.LocalId,
859  Part.ParentGroup.Name,
860  Part.ParentGroup.UUID,
861  Part.AbsolutePosition,
862  Part.ParentGroup.Scene.Name);
863  AsyncCommandManager.StateChange(Engine,
864  LocalID, ItemID);
865  // we are effectively in the new state now, so we can resume queueing
866  // and processing other non-timer events
867  m_StateChangeInProgress = false;
868 
869  Part.SetScriptEvents(ItemID, (int)m_Script.GetStateEventFlags(State));
870  }
871  else
872  {
873  if (Engine.World.PipeEventsForScript(LocalID) ||
874  data.EventName == "control") // Don't freeze avies!
875  {
876  // m_log.DebugFormat("[Script] Delivered event {2} in state {3} to {0}.{1}",
877  // PrimName, ScriptName, data.EventName, State);
878 
879 
880  try
881  {
882  m_CurrentEvent = data.EventName;
883  m_EventStart = DateTime.Now;
884  m_InEvent = true;
885 
886  try
887  {
888  m_Script.ExecuteEvent(State, data.EventName, data.Params);
889  }
890  finally
891  {
892  m_InEvent = false;
893  m_CurrentEvent = String.Empty;
894  }
895 
896  if (m_SaveState)
897  {
898  // This will be the very first event we deliver
899  // (state_entry) in default state
900  //
901  SaveState();
902 
903  m_SaveState = false;
904  }
905  }
906  catch (Exception e)
907  {
908  // m_log.DebugFormat(
909  // "[SCRIPT] Exception in script {0} {1}: {2}{3}",
910  // ScriptName, ItemID, e.Message, e.StackTrace);
911 
912  if ((!(e is TargetInvocationException)
913  || (!(e.InnerException is SelfDeleteException)
914  && !(e.InnerException is ScriptDeleteException)
915  && !(e.InnerException is ScriptCoopStopException)))
916  && !(e is ThreadAbortException))
917  {
918  try
919  {
920  // DISPLAY ERROR INWORLD
921  string text = FormatException(e);
922 
923  if (text.Length > 1000)
924  text = text.Substring(0, 1000);
925  Engine.World.SimChat(Utils.StringToBytes(text),
926  ChatTypeEnum.DebugChannel, 2147483647,
927  Part.AbsolutePosition,
928  Part.Name, Part.UUID, false);
929 
930 
931  m_log.Debug(string.Format(
932  "[SCRIPT INSTANCE]: Runtime error in script {0} (event {1}), part {2} {3} at {4} in {5} ",
933  ScriptName,
934  data.EventName,
935  PrimName,
936  Part.UUID,
937  Part.AbsolutePosition,
938  Part.ParentGroup.Scene.Name),
939  e);
940  }
941  catch (Exception)
942  {
943  }
944  }
945  else if ((e is TargetInvocationException) && (e.InnerException is SelfDeleteException))
946  {
947  m_InSelfDelete = true;
948  Engine.World.DeleteSceneObject(Part.ParentGroup, false);
949  }
950  else if ((e is TargetInvocationException) && (e.InnerException is ScriptDeleteException))
951  {
952  m_InSelfDelete = true;
953  Part.Inventory.RemoveInventoryItem(ItemID);
954  }
955  else if ((e is TargetInvocationException) && (e.InnerException is ScriptCoopStopException))
956  {
957  if (DebugLevel >= 1)
958  m_log.DebugFormat(
959  "[SCRIPT INSTANCE]: Script {0}.{1} in event {2}, state {3} stopped co-operatively.",
960  PrimName, ScriptName, data.EventName, State);
961  }
962  }
963  }
964  }
965 
966  // If there are more events and we are currently running and not shutting down, then ask the
967  // script engine to run the next event.
968  lock (EventQueue)
969  {
970  // Increase processed events counter and prevent wrap;
971  if (++EventsProcessed == 1000000)
972  EventsProcessed = 100000;
973 
974  if ((EventsProcessed % 100000) == 0 && DebugLevel > 0)
975  {
976  m_log.DebugFormat("[SCRIPT INSTANCE]: Script \"{0}\" (Object \"{1}\" {2} @ {3}.{4}, Item ID {5}, Asset {6}) in event {7}: processed {8:n0} script events",
977  ScriptTask.Name,
978  Part.ParentGroup.Name, Part.ParentGroup.UUID, Part.ParentGroup.AbsolutePosition, Part.ParentGroup.Scene.Name,
979  ScriptTask.ItemID, ScriptTask.AssetID, data.EventName, EventsProcessed);
980  }
981 
982  if (EventQueue.Count > 0 && Running && !ShuttingDown)
983  {
984  m_CurrentWorkItem = Engine.QueueEventHandler(this);
985  }
986  else
987  {
988  m_CurrentWorkItem = null;
989  }
990  }
991 
992  m_DetectParams = null;
993 
994  return 0;
995  }
996 
997  public int EventTime()
998  {
999  if (!m_InEvent)
1000  return 0;
1001 
1002  return (DateTime.Now - m_EventStart).Seconds;
1003  }
1004 
1005  public void ResetScript(int timeout)
1006  {
1007  if (m_Script == null)
1008  return;
1009 
1010  bool running = Running;
1011 
1012  RemoveState();
1013  ReleaseControls();
1014 
1015  Stop(timeout);
1016  SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID);
1017  part.Inventory.GetInventoryItem(ItemID).PermsMask = 0;
1018  part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero;
1019  part.CollisionSound = UUID.Zero;
1020  AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID);
1021 
1022  m_TimerQueued = false;
1023  m_StateChangeInProgress = false;
1024  EventQueue.Clear();
1025 
1026  m_Script.ResetVars();
1027  StartParam = 0;
1028  State = "default";
1029 
1030 
1031  part.SetScriptEvents(ItemID,
1032  (int)m_Script.GetStateEventFlags(State));
1033  if (running)
1034  Start();
1035 
1036  m_SaveState = StatePersistedHere;
1037 
1038  PostEvent(new EventParams("state_entry",
1039  new Object[0], new DetectParams[0]));
1040  }
1041 
1042  [DebuggerNonUserCode] //Stops the VS debugger from farting in this function
1043  public void ApiResetScript()
1044  {
1045  // bool running = Running;
1046 
1047  RemoveState();
1048  ReleaseControls();
1049 
1050  m_Script.ResetVars();
1051  SceneObjectPart part = Engine.World.GetSceneObjectPart(LocalID);
1052  part.Inventory.GetInventoryItem(ItemID).PermsMask = 0;
1053  part.Inventory.GetInventoryItem(ItemID).PermsGranter = UUID.Zero;
1054  part.CollisionSound = UUID.Zero;
1055  AsyncCommandManager.RemoveScript(Engine, LocalID, ItemID);
1056 
1057  m_TimerQueued = false;
1058  m_StateChangeInProgress = false;
1059  EventQueue.Clear();
1060  m_Script.ResetVars();
1061  string oldState = State;
1062  StartParam = 0;
1063  State = "default";
1064 
1065  part.SetScriptEvents(ItemID,
1066  (int)m_Script.GetStateEventFlags(State));
1067 
1068  if (m_CurrentEvent != "state_entry" || oldState != "default")
1069  {
1070  m_SaveState = StatePersistedHere;
1071  PostEvent(new EventParams("state_entry",
1072  new Object[0], new DetectParams[0]));
1073  throw new EventAbortException();
1074  }
1075  }
1076 
1077  public Dictionary<string, object> GetVars()
1078  {
1079  if (m_Script != null)
1080  return m_Script.GetVars();
1081  else
1082  return new Dictionary<string, object>();
1083  }
1084 
1085  public void SetVars(Dictionary<string, object> vars)
1086  {
1087 // foreach (KeyValuePair<string, object> kvp in vars)
1088 // m_log.DebugFormat("[SCRIPT INSTANCE]: Setting var {0}={1}", kvp.Key, kvp.Value);
1089 
1090  m_Script.SetVars(vars);
1091  }
1092 
1094  {
1095  if (m_DetectParams == null)
1096  return null;
1097  if (idx < 0 || idx >= m_DetectParams.Length)
1098  return null;
1099 
1100  return m_DetectParams[idx];
1101  }
1102 
1103  public UUID GetDetectID(int idx)
1104  {
1105  if (m_DetectParams == null)
1106  return UUID.Zero;
1107  if (idx < 0 || idx >= m_DetectParams.Length)
1108  return UUID.Zero;
1109 
1110  return m_DetectParams[idx].Key;
1111  }
1112 
1113  public void SaveState()
1114  {
1115  if (!Running && !StayStopped)
1116  return;
1117 
1118  // We cannot call this inside the EventQueue lock since it will currently take AsyncCommandManager.staticLock.
1119  // This may already be held by AsyncCommandManager.DoOneCmdHandlerPass() which in turn can take EventQueue
1120  // lock via ScriptInstance.PostEvent().
1121  PluginData = AsyncCommandManager.GetSerializationData(Engine, ItemID);
1122 
1123  // We need to lock here to avoid any race with a thread that is removing this script.
1124  lock (EventQueue)
1125  {
1126  // Check again to avoid a race with a thread in Stop()
1127  if (!Running && !StayStopped)
1128  return;
1129 
1130  // If we're currently in an event, just tell it to save upon return
1131  //
1132  if (m_InEvent)
1133  {
1134  m_SaveState = true;
1135  return;
1136  }
1137 
1138  // m_log.DebugFormat(
1139  // "[SCRIPT INSTANCE]: Saving state for script {0} (id {1}) in part {2} (id {3}) in object {4} in {5}",
1140  // ScriptTask.Name, ScriptTask.ItemID, Part.Name, Part.UUID, Part.ParentGroup.Name, Engine.World.Name);
1141 
1142  string xml = ScriptSerializer.Serialize(this);
1143 
1144  // Compare hash of the state we just just created with the state last written to disk
1145  // If the state is different, update the disk file.
1146  UUID hash = UUID.Parse(Utils.MD5String(xml));
1147 
1148  if (hash != m_CurrentStateHash)
1149  {
1150  try
1151  {
1152  using (FileStream fs = File.Create(Path.Combine(m_dataPath, ItemID.ToString() + ".state")))
1153  {
1154  Byte[] buf = Util.UTF8NoBomEncoding.GetBytes(xml);
1155  fs.Write(buf, 0, buf.Length);
1156  }
1157  }
1158  catch(Exception)
1159  {
1160  // m_log.Error("Unable to save xml\n"+e.ToString());
1161  }
1162  //if (!File.Exists(Path.Combine(Path.GetDirectoryName(assembly), ItemID.ToString() + ".state")))
1163  //{
1164  // throw new Exception("Completed persistence save, but no file was created");
1165  //}
1166  m_CurrentStateHash = hash;
1167  }
1168 
1169  StayStopped = false;
1170  }
1171  }
1172 
1173  public IScriptApi GetApi(string name)
1174  {
1175  if (m_Apis.ContainsKey(name))
1176  {
1177 // m_log.DebugFormat("[SCRIPT INSTANCE]: Found api {0} in {1}@{2}", name, ScriptName, PrimName);
1178 
1179  return m_Apis[name];
1180  }
1181 
1182 // m_log.DebugFormat("[SCRIPT INSTANCE]: Did not find api {0} in {1}@{2}", name, ScriptName, PrimName);
1183 
1184  return null;
1185  }
1186 
1187  public override string ToString()
1188  {
1189  return String.Format("{0} {1} on {2}", ScriptName, ItemID, PrimName);
1190  }
1191 
1192  string FormatException(Exception e)
1193  {
1194  if (e.InnerException == null) // Not a normal runtime error
1195  return e.ToString();
1196 
1197  string message = "Runtime error:\n" + e.InnerException.StackTrace;
1198  string[] lines = message.Split(new char[] {'\n'});
1199 
1200  foreach (string line in lines)
1201  {
1202  if (line.Contains("SecondLife.Script"))
1203  {
1204  int idx = line.IndexOf(':');
1205  if (idx != -1)
1206  {
1207  string val = line.Substring(idx+1);
1208  int lineNum = 0;
1209  if (int.TryParse(val, out lineNum))
1210  {
1211  KeyValuePair<int, int> pos =
1212  Compiler.FindErrorPosition(
1213  lineNum, 0, LineMap);
1214 
1215  int scriptLine = pos.Key;
1216  int col = pos.Value;
1217  if (scriptLine == 0)
1218  scriptLine++;
1219  if (col == 0)
1220  col++;
1221  message = string.Format("Runtime error:\n" +
1222  "({0}): {1}", scriptLine - 1,
1223  e.InnerException.Message);
1224 
1225  return message;
1226  }
1227  }
1228  }
1229  }
1230 
1231  // m_log.ErrorFormat("Scripting exception:");
1232  // m_log.ErrorFormat(e.ToString());
1233 
1234  return e.ToString();
1235  }
1236 
1237  public string GetAssemblyName()
1238  {
1239  return m_assemblyPath;
1240  }
1241 
1242  public string GetXMLState()
1243  {
1244  bool run = Running;
1245  Stop(100);
1246  Running = run;
1247 
1248  // We should not be doing this, but since we are about to
1249  // dispose this, it really doesn't make a difference
1250  // This is meant to work around a Windows only race
1251  //
1252  m_InEvent = false;
1253 
1254  // Force an update of the in-memory plugin data
1255  //
1256  PluginData = AsyncCommandManager.GetSerializationData(Engine, ItemID);
1257 
1258  return ScriptSerializer.Serialize(this);
1259  }
1260 
1261  public UUID RegionID
1262  {
1263  get { return m_RegionID; }
1264  }
1265 
1266  public void Suspend()
1267  {
1268  Suspended = true;
1269  }
1270 
1271  public void Resume()
1272  {
1273  Suspended = false;
1274  }
1275  }
1276 
1286  public class XEngineEventWaitHandle : EventWaitHandle
1287  {
1288  public XEngineEventWaitHandle(bool initialState, EventResetMode mode) : base(initialState, mode) {}
1289 
1291  {
1292  return null;
1293  }
1294  }
1295 }
void SetVars(Dictionary< string, object > vars)
Handles LSL commands that takes long time and returns an event, for example timers, HTTP requests, etc.
XEngineEventWaitHandle(bool initialState, EventResetMode mode)
void VarDump(Dictionary< string, object > vars)
static void RemoveScript(IScriptEngine engine, uint localID, UUID itemID)
Remove a specific script (and all its pending commands)
Represents an item in a task inventory
A MetricsCollector for time spans.
void PostEvent(EventParams data)
Post an event to this script instance.
bool Load(IScript script, EventWaitHandle coopSleepHandle, string assemblyPath, string dataPath, StateSource stateSource, bool coopTermination)
Load the script from an assembly into an AppDomain.
Used to signal when the script is stopping in co-operation with the script engine (instead of through...
Definition: Helpers.cs:90
An interface for a script API module to communicate with the engine it's running under ...
ScriptInstance(IScriptEngine engine, SceneObjectPart part, TaskInventoryItem item, int startParam, bool postOnRez, int maxScriptQueue)
Interactive OpenSim region server
Definition: OpenSim.cs:55
Interface for interaction with a particular script instance
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger LSLInteger
Definition: CM_Constants.cs:31
object EventProcessor()
Process the next event queued for this script
bool Stop(int timeout, bool clearEventQueue=false)
Stop the script instance.
Holds all the data required to execute a scripting event.
Definition: Helpers.cs:281