opensim-development – Blame information for rev 1

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