aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs')
-rw-r--r--OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs1056
1 files changed, 1056 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs
new file mode 100644
index 0000000..9654b01
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs
@@ -0,0 +1,1056 @@
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
28using System;
29using System.Threading;
30using System.Collections.Generic;
31using System.Text;
32using OpenMetaverse;
33using OpenSim.Framework;
34using OpenSim.Region.Framework.Interfaces;
35using OpenSim.Region.ScriptEngine.Interfaces;
36using OpenSim.Region.ScriptEngine.Shared;
37using OpenSim.Region.ScriptEngine.Shared.Api;
38using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
39using OpenSim.Region.ScriptEngine.XMREngine;
40using OpenSim.Region.Framework.Scenes;
41using log4net;
42
43using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
44using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
45using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
46using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
47using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
48using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
49using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
50
51namespace OpenSim.Region.ScriptEngine.XMREngine
52{
53 public partial class XMRInstance
54 {
55 /************************************************************************************\
56 * This module contains these externally useful methods: *
57 * PostEvent() - queues an event to script and wakes script thread to process it *
58 * RunOne() - runs script for a time slice or until it volunteers to give up cpu *
59 * CallSEH() - runs in the microthread to call the event handler *
60 \************************************************************************************/
61
62 /**
63 * @brief This can be called in any thread (including the script thread itself)
64 * to queue event to script for processing.
65 */
66 public void PostEvent(EventParams evt)
67 {
68 ScriptEventCode evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
69 evt.EventName);
70
71 /*
72 * Put event on end of event queue.
73 */
74 bool startIt = false;
75 bool wakeIt = false;
76 lock (m_QueueLock)
77 {
78 bool construct = (m_IState == XMRInstState.CONSTRUCT);
79
80 /*
81 * Ignore event if we don't even have such an handler in any state.
82 * We can't be state-specific here because state might be different
83 * by the time this event is dequeued and delivered to the script.
84 */
85 if (!construct && // make sure m_HaveEventHandlers is filled in
86 ((uint)evc < (uint)m_HaveEventHandlers.Length) &&
87 !m_HaveEventHandlers[(int)evc]) { // don't bother if we don't have such a handler in any state
88 return;
89 }
90
91 /*
92 * Not running means we ignore any incoming events.
93 * But queue if still constructing because m_Running is not yet valid.
94 */
95 if (!m_Running && !construct) {
96 return;
97 }
98
99 /*
100 * Only so many of each event type allowed to queue.
101 */
102 if ((uint)evc < (uint)m_EventCounts.Length) {
103 int maxAllowed = MAXEVENTQUEUE;
104 if (evc == ScriptEventCode.timer) maxAllowed = 1;
105 if (m_EventCounts[(int)evc] >= maxAllowed)
106 {
107 return;
108 }
109 m_EventCounts[(int)evc] ++;
110 }
111
112 /*
113 * Put event on end of instance's event queue.
114 */
115 LinkedListNode<EventParams> lln = new LinkedListNode<EventParams>(evt);
116 switch (evc) {
117
118 /*
119 * These need to go first. The only time we manually
120 * queue them is for the default state_entry() and we
121 * need to make sure they go before any attach() events
122 * so the heapLimit value gets properly initialized.
123 */
124 case ScriptEventCode.state_entry: {
125 m_EventQueue.AddFirst(lln);
126 break;
127 }
128
129 /*
130 * The attach event sneaks to the front of the queue.
131 * This is needed for quantum limiting to work because
132 * we want the attach(NULL_KEY) event to come in front
133 * of all others so the m_DetachQuantum won't run out
134 * before attach(NULL_KEY) is executed.
135 */
136 case ScriptEventCode.attach:
137 {
138 if (evt.Params[0].ToString() == UUID.Zero.ToString())
139 {
140 LinkedListNode<EventParams> lln2 = null;
141 for (lln2 = m_EventQueue.First; lln2 != null; lln2 = lln2.Next)
142 {
143 EventParams evt2 = lln2.Value;
144 ScriptEventCode evc2 = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
145 evt2.EventName);
146 if ((evc2 != ScriptEventCode.state_entry) &&
147 (evc2 != ScriptEventCode.attach)) break;
148 }
149 if (lln2 == null)
150 m_EventQueue.AddLast(lln);
151 else
152 m_EventQueue.AddBefore(lln2, lln);
153 /* If we're detaching, limit the qantum. This will also
154 * cause the script to self-suspend after running this
155 * event
156 */
157
158 m_DetachReady.Reset();
159 m_DetachQuantum = 100;
160 }
161 else
162 m_EventQueue.AddLast(lln);
163 break;
164 }
165
166 /*
167 * All others just go on end in the order queued.
168 */
169 default:
170 {
171 m_EventQueue.AddLast(lln);
172 break;
173 }
174 }
175
176 /*
177 * If instance is idle (ie, not running or waiting to run),
178 * flag it to be on m_StartQueue as we are about to do so.
179 * Flag it now before unlocking so another thread won't try
180 * to do the same thing right now.
181 * Dont' flag it if it's still suspended!
182 */
183 if ((m_IState == XMRInstState.IDLE) && !m_Suspended)
184 {
185 m_IState = XMRInstState.ONSTARTQ;
186 startIt = true;
187 }
188
189 /*
190 * If instance is sleeping (ie, possibly in xmrEventDequeue),
191 * wake it up if event is in the mask.
192 */
193 if ((m_SleepUntil > DateTime.UtcNow) && !m_Suspended)
194 {
195 int evc1 = (int)evc;
196 int evc2 = evc1 - 32;
197 if ((((uint)evc1 < (uint)32) && (((m_SleepEventMask1 >> evc1) & 1) != 0)) ||
198 (((uint)evc2 < (uint)32) && (((m_SleepEventMask2 >> evc2) & 1) != 0)))
199 {
200 wakeIt = true;
201 }
202 }
203 }
204
205 /*
206 * If transitioned from IDLE->ONSTARTQ, actually go insert it
207 * on m_StartQueue and give the RunScriptThread() a wake-up.
208 */
209 if (startIt)
210 m_Engine.QueueToStart(this);
211
212 /*
213 * Likewise, if the event mask triggered a wake, wake it up.
214 */
215 if (wakeIt)
216 {
217 m_SleepUntil = DateTime.MinValue;
218 m_Engine.WakeFromSleep(this);
219 }
220 }
221
222 /*
223 * This is called in the script thread to step script until it calls
224 * CheckRun(). It returns what the instance's next state should be,
225 * ONSLEEPQ, ONYIELDQ, SUSPENDED or FINISHED.
226 */
227 public XMRInstState RunOne()
228 {
229 DateTime now = DateTime.UtcNow;
230 m_SliceStart = Util.GetTimeStampMS();
231
232 /*
233 * If script has called llSleep(), don't do any more until time is
234 * up.
235 */
236 m_RunOnePhase = "check m_SleepUntil";
237 if (m_SleepUntil > now)
238 {
239 m_RunOnePhase = "return is sleeping";
240 return XMRInstState.ONSLEEPQ;
241 }
242
243 /*
244 * Also, someone may have called Suspend().
245 */
246 m_RunOnePhase = "check m_SuspendCount";
247 if (m_SuspendCount > 0)
248 {
249 m_RunOnePhase = "return is suspended";
250 return XMRInstState.SUSPENDED;
251 }
252
253 /*
254 * Make sure we aren't being migrated in or out and prevent that
255 * whilst we are in here. If migration has it locked, don't call
256 * back right away, delay a bit so we don't get in infinite loop.
257 */
258 m_RunOnePhase = "lock m_RunLock";
259 if (!Monitor.TryEnter (m_RunLock))
260 {
261 m_SleepUntil = now.AddMilliseconds(3);
262 m_RunOnePhase = "return was locked";
263 return XMRInstState.ONSLEEPQ;
264 }
265 try
266 {
267 m_RunOnePhase = "check entry invariants";
268 CheckRunLockInvariants(true);
269 Exception e = null;
270
271 /*
272 * Maybe we have been disposed.
273 */
274 m_RunOnePhase = "check disposed";
275 if (microthread == null)
276 {
277 m_RunOnePhase = "return disposed";
278 return XMRInstState.DISPOSED;
279 }
280
281 /*
282 * Do some more of the last event if it didn't finish.
283 */
284 else if (eventCode != ScriptEventCode.None)
285 {
286 lock (m_QueueLock)
287 {
288 if (m_DetachQuantum > 0 && --m_DetachQuantum == 0)
289 {
290 m_Suspended = true;
291 m_DetachReady.Set();
292 m_RunOnePhase = "detach quantum went zero";
293 CheckRunLockInvariants(true);
294 return XMRInstState.FINISHED;
295 }
296 }
297
298 m_RunOnePhase = "resume old event handler";
299 m_LastRanAt = now;
300 m_InstEHSlice ++;
301 callMode = CallMode_NORMAL;
302 e = microthread.ResumeEx ();
303 }
304
305 /*
306 * Otherwise, maybe we can dequeue a new event and start
307 * processing it.
308 */
309 else
310 {
311 m_RunOnePhase = "lock event queue";
312 EventParams evt = null;
313 ScriptEventCode evc = ScriptEventCode.None;
314
315 lock (m_QueueLock)
316 {
317
318 /* We can't get here unless the script has been resumed
319 * after creation, then suspended again, and then had
320 * an event posted to it. We just pretend there is no
321 * event int he queue and let the normal mechanics
322 * carry out the suspension. A Resume will handle the
323 * restarting gracefully. This is taking the easy way
324 * out and may be improved in the future.
325 */
326
327 if (m_Suspended)
328 {
329 m_RunOnePhase = "m_Suspended is set";
330 CheckRunLockInvariants(true);
331 return XMRInstState.FINISHED;
332 }
333
334 m_RunOnePhase = "dequeue event";
335 if (m_EventQueue.First != null)
336 {
337 evt = m_EventQueue.First.Value;
338 evc = (ScriptEventCode)Enum.Parse (typeof (ScriptEventCode),
339 evt.EventName);
340 if (m_DetachQuantum > 0)
341 {
342 if (evc != ScriptEventCode.attach)
343 {
344 /*
345 * This is the case where the attach event
346 * has completed and another event is queued
347 * Stop it from running and suspend
348 */
349 m_Suspended = true;
350 m_DetachReady.Set();
351 m_DetachQuantum = 0;
352 m_RunOnePhase = "nothing to do #3";
353 CheckRunLockInvariants(true);
354 return XMRInstState.FINISHED;
355 }
356 }
357 m_EventQueue.RemoveFirst();
358 if (evc >= 0)
359 m_EventCounts[(int)evc] --;
360 }
361
362 /*
363 * If there is no event to dequeue, don't run this script
364 * until another event gets queued.
365 */
366 if (evt == null)
367 {
368 if (m_DetachQuantum > 0)
369 {
370 /*
371 * This will happen if the attach event has run
372 * and exited with time slice left.
373 */
374 m_Suspended = true;
375 m_DetachReady.Set();
376 m_DetachQuantum = 0;
377 }
378 m_RunOnePhase = "nothing to do #4";
379 CheckRunLockInvariants(true);
380 return XMRInstState.FINISHED;
381 }
382 }
383
384 /*
385 * Dequeued an event, so start it going until it either
386 * finishes or it calls CheckRun().
387 */
388 m_RunOnePhase = "start event handler";
389 m_DetectParams = evt.DetectParams;
390 m_LastRanAt = now;
391 m_InstEHEvent ++;
392 e = StartEventHandler (evc, evt.Params);
393 }
394 m_RunOnePhase = "done running";
395 m_CPUTime += DateTime.UtcNow.Subtract(now).TotalMilliseconds;
396
397 /*
398 * Maybe it puqued.
399 */
400 if (e != null)
401 {
402 m_RunOnePhase = "handling exception " + e.Message;
403 HandleScriptException(e);
404 m_RunOnePhase = "return had exception " + e.Message;
405 CheckRunLockInvariants(true);
406 return XMRInstState.FINISHED;
407 }
408
409 /*
410 * If event handler completed, get rid of detect params.
411 */
412 if (this.eventCode == ScriptEventCode.None)
413 {
414 m_DetectParams = null;
415 }
416 }
417 finally
418 {
419 m_RunOnePhase += "; checking exit invariants and unlocking";
420 CheckRunLockInvariants(false);
421 Monitor.Exit(m_RunLock);
422 }
423
424 /*
425 * Cycle script through the yield queue and call it back asap.
426 */
427 m_RunOnePhase = "last return";
428 return XMRInstState.ONYIELDQ;
429 }
430
431 /**
432 * @brief Immediately after taking m_RunLock or just before releasing it, check invariants.
433 */
434 private ScriptEventCode lastEventCode = ScriptEventCode.None;
435 private int lastActive = 0;
436 private string lastRunPhase = "";
437
438 public void CheckRunLockInvariants(bool throwIt)
439 {
440 /*
441 * If not executing any event handler, active should be 0 indicating the microthread stack is not in use.
442 * If executing an event handler, active should be -1 indicating stack is in use but suspended.
443 */
444 IScriptUThread uth = microthread;
445 if (uth != null) {
446 int active = uth.Active ();
447 ScriptEventCode ec = this.eventCode;
448 if (((ec == ScriptEventCode.None) && (active != 0)) ||
449 ((ec != ScriptEventCode.None) && (active >= 0))) {
450 Console.WriteLine("CheckRunLockInvariants: script=" + m_DescName);
451 Console.WriteLine("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString());
452 Console.WriteLine("CheckRunLockInvariants: m_RunOnePhase=" + m_RunOnePhase);
453 Console.WriteLine("CheckRunLockInvariants: lastec=" + lastEventCode + ", lastAct=" + lastActive + ", lastPhase=" + lastRunPhase);
454 if (throwIt) {
455 throw new Exception("CheckRunLockInvariants: eventcode=" + ec.ToString() + ", active=" + active.ToString());
456 }
457 }
458 lastEventCode = ec;
459 lastActive = active;
460 lastRunPhase = m_RunOnePhase;
461 }
462 }
463
464 /*
465 * Start event handler.
466 *
467 * Input:
468 * eventCode = code of event to be processed
469 * ehArgs = arguments for the event handler
470 *
471 * Caution:
472 * It is up to the caller to make sure ehArgs[] is correct for
473 * the particular event handler being called. The first thing
474 * a script event handler method does is to unmarshall the args
475 * from ehArgs[] and will throw an array bounds or cast exception
476 * if it can't.
477 */
478 private Exception StartEventHandler (ScriptEventCode eventCode, object[] ehArgs)
479 {
480 /*
481 * We use this.eventCode == ScriptEventCode.None to indicate we are idle.
482 * So trying to execute ScriptEventCode.None might make a mess.
483 */
484 if (eventCode == ScriptEventCode.None)
485 return new Exception ("Can't process ScriptEventCode.None");
486
487 /*
488 * Silly to even try if there is no handler defined for this event.
489 */
490 if ((eventCode >= 0) && (m_ObjCode.scriptEventHandlerTable[this.stateCode,(int)eventCode] == null))
491 return null;
492
493 /*
494 * The microthread shouldn't be processing any event code.
495 * These are assert checks so we throw them directly as exceptions.
496 */
497 if (this.eventCode != ScriptEventCode.None)
498 throw new Exception ("still processing event " + this.eventCode.ToString ());
499
500 int active = microthread.Active ();
501 if (active != 0)
502 throw new Exception ("microthread is active " + active.ToString ());
503
504 /*
505 * Save eventCode so we know what event handler to run in the microthread.
506 * And it also marks us busy so we can't be started again and this event lost.
507 */
508 this.eventCode = eventCode;
509 this.ehArgs = ehArgs;
510
511 /*
512 * This calls ScriptUThread.Main() directly, and returns when Main() [indirectly]
513 * calls Suspend() or when Main() returns, whichever occurs first.
514 * Setting stackFrames = null means run the event handler from the beginning
515 * without doing any stack frame restores first.
516 */
517 this.stackFrames = null;
518 Exception e;
519 e = microthread.StartEx ();
520 return e;
521 }
522
523
524 /**
525 * @brief There was an exception whilst starting/running a script event handler.
526 * Maybe we handle it directly or just print an error message.
527 */
528 private void HandleScriptException(Exception e)
529 {
530 /*
531 * The script threw some kind of exception that was not caught at
532 * script level, so the script is no longer running an event handler.
533 */
534 eventCode = ScriptEventCode.None;
535
536 if (e is ScriptDeleteException)
537 {
538 /*
539 * Script did something like llRemoveInventory(llGetScriptName());
540 * ... to delete itself from the object.
541 */
542 m_SleepUntil = DateTime.MaxValue;
543 Verbose ("[XMREngine]: script self-delete {0}", m_ItemID);
544 m_Part.Inventory.RemoveInventoryItem(m_ItemID);
545 }
546 else if (e is ScriptDieException)
547 {
548 /*
549 * Script did an llDie()
550 */
551 m_RunOnePhase = "dying...";
552 m_SleepUntil = DateTime.MaxValue;
553 m_Engine.World.DeleteSceneObject(m_Part.ParentGroup, false);
554 }
555 else if (e is ScriptResetException)
556 {
557 /*
558 * Script did an llResetScript().
559 */
560 m_RunOnePhase = "resetting...";
561 ResetLocked("HandleScriptResetException");
562 }
563 else
564 {
565 /*
566 * Some general script error.
567 */
568 SendErrorMessage(e);
569 }
570 return;
571 }
572
573 /**
574 * @brief There was an exception running script event handler.
575 * Display error message and disable script (in a way
576 * that the script can be reset to be restarted).
577 */
578 private void SendErrorMessage(Exception e)
579 {
580 StringBuilder msg = new StringBuilder();
581
582 msg.Append ("[XMREngine]: Exception while running ");
583 msg.Append (m_ItemID);
584 msg.Append ('\n');
585
586 /*
587 * Add exception message.
588 */
589 string des = e.Message;
590 des = (des == null) ? "" : (": " + des);
591 msg.Append (e.GetType ().Name + des + "\n");
592
593 /*
594 * Tell script owner what to do.
595 */
596 msg.Append ("Prim: <");
597 msg.Append (m_Part.Name);
598 msg.Append (">, Script: <");
599 msg.Append (m_Item.Name);
600 msg.Append (">, Location: ");
601 msg.Append (m_Engine.World.RegionInfo.RegionName);
602 msg.Append (" <");
603 Vector3 pos = m_Part.AbsolutePosition;
604 msg.Append ((int) Math.Floor (pos.X));
605 msg.Append (',');
606 msg.Append ((int) Math.Floor (pos.Y));
607 msg.Append (',');
608 msg.Append ((int) Math.Floor (pos.Z));
609 msg.Append (">\nScript must be Reset to re-enable.\n");
610
611 /*
612 * Display full exception message in log.
613 */
614 m_log.Info (msg.ToString() + XMRExceptionStackString (e), e);
615
616 /*
617 * Give script owner the stack dump.
618 */
619 msg.Append (XMRExceptionStackString (e));
620
621 /*
622 * Send error message to owner.
623 * Suppress internal code stack trace lines.
624 */
625 string msgst = msg.ToString();
626 if (!msgst.EndsWith ("\n")) msgst += '\n';
627 int j = 0;
628 StringBuilder imstr = new StringBuilder ();
629 for (int i = 0; (i = msgst.IndexOf ('\n', i)) >= 0; j = ++ i) {
630 string line = msgst.Substring (j, i - j);
631 if (line.StartsWith ("at ")) {
632 if (line.StartsWith ("at (wrapper")) continue; // at (wrapper ...
633 int k = line.LastIndexOf (".cs:"); // ... .cs:linenumber
634 if (Int32.TryParse (line.Substring (k + 4), out k)) continue;
635 }
636 this.llOwnerSay (line);
637 imstr.Append (line);
638 imstr.Append ('\n');
639 }
640
641 /*
642 * Send as instant message in case user not online.
643 * Code modelled from llInstantMessage().
644 */
645 IMessageTransferModule transferModule = m_Engine.World.RequestModuleInterface<IMessageTransferModule>();
646 if (transferModule != null) {
647 UUID friendTransactionID = UUID.Random();
648 GridInstantMessage gim = new GridInstantMessage();
649 gim.fromAgentID = new Guid (m_Part.UUID.ToString());
650 gim.toAgentID = new Guid (m_Part.OwnerID.ToString ());
651 gim.imSessionID = new Guid(friendTransactionID.ToString());
652 gim.timestamp = (uint)Util.UnixTimeSinceEpoch();
653 gim.message = imstr.ToString ();
654 gim.dialog = (byte)19; // messgage from script
655 gim.fromGroup = false;
656 gim.offline = (byte)0;
657 gim.ParentEstateID = 0;
658 gim.Position = pos;
659 gim.RegionID = m_Engine.World.RegionInfo.RegionID.Guid;
660 gim.binaryBucket = Util.StringToBytes256(
661 "{0}/{1}/{2}/{3}",
662 m_Engine.World.RegionInfo.RegionName,
663 (int)Math.Floor(pos.X),
664 (int)Math.Floor(pos.Y),
665 (int)Math.Floor(pos.Z));
666 transferModule.SendInstantMessage(gim, delegate(bool success) {});
667 }
668
669 /*
670 * Say script is sleeping for a very long time.
671 * Reset() is able to cancel this sleeping.
672 */
673 m_SleepUntil = DateTime.MaxValue;
674 }
675
676 /**
677 * @brief The user clicked the Reset Script button.
678 * We want to reset the script to a never-has-ever-run-before state.
679 */
680 public void Reset()
681 {
682 checkstate:
683 XMRInstState iState = m_IState;
684 switch (iState) {
685
686 /*
687 * If it's really being constructed now, that's about as reset as we get.
688 */
689 case XMRInstState.CONSTRUCT: {
690 return;
691 }
692
693 /*
694 * If it's idle, that means it is ready to receive a new event.
695 * So we lock the event queue to prevent another thread from taking
696 * it out of idle, verify that it is still in idle then transition
697 * it to resetting so no other thread will touch it.
698 */
699 case XMRInstState.IDLE: {
700 lock (m_QueueLock) {
701 if (m_IState == XMRInstState.IDLE) {
702 m_IState = XMRInstState.RESETTING;
703 break;
704 }
705 }
706 goto checkstate;
707 }
708
709 /*
710 * If it's on the start queue, that means it is about to dequeue an
711 * event and start processing it. So we lock the start queue so it
712 * can't be started and transition it to resetting so no other thread
713 * will touch it.
714 */
715 case XMRInstState.ONSTARTQ: {
716 lock (m_Engine.m_StartQueue) {
717 if (m_IState == XMRInstState.ONSTARTQ) {
718 m_Engine.m_StartQueue.Remove(this);
719 m_IState = XMRInstState.RESETTING;
720 break;
721 }
722 }
723 goto checkstate;
724 }
725
726 /*
727 * If it's running, tell CheckRun() to suspend the thread then go back
728 * to see what it got transitioned to.
729 */
730 case XMRInstState.RUNNING: {
731 suspendOnCheckRunHold = true;
732 lock (m_QueueLock) { }
733 goto checkstate;
734 }
735
736 /*
737 * If it's sleeping, remove it from sleep queue and transition it to
738 * resetting so no other thread will touch it.
739 */
740 case XMRInstState.ONSLEEPQ: {
741 lock (m_Engine.m_SleepQueue) {
742 if (m_IState == XMRInstState.ONSLEEPQ) {
743 m_Engine.m_SleepQueue.Remove(this);
744 m_IState = XMRInstState.RESETTING;
745 break;
746 }
747 }
748 goto checkstate;
749 }
750
751 /*
752 * It was just removed from the sleep queue and is about to be put
753 * on the yield queue (ie, is being woken up).
754 * Let that thread complete transition and try again.
755 */
756 case XMRInstState.REMDFROMSLPQ: {
757 Sleep (10);
758 goto checkstate;
759 }
760
761 /*
762 * If it's yielding, remove it from yield queue and transition it to
763 * resetting so no other thread will touch it.
764 */
765 case XMRInstState.ONYIELDQ: {
766 lock (m_Engine.m_YieldQueue) {
767 if (m_IState == XMRInstState.ONYIELDQ) {
768 m_Engine.m_YieldQueue.Remove(this);
769 m_IState = XMRInstState.RESETTING;
770 break;
771 }
772 }
773 goto checkstate;
774 }
775
776 /*
777 * If it just finished running something, let that thread transition it
778 * to its next state then check again.
779 */
780 case XMRInstState.FINISHED: {
781 Sleep (10);
782 goto checkstate;
783 }
784
785 /*
786 * If it's disposed, that's about as reset as it gets.
787 */
788 case XMRInstState.DISPOSED: {
789 return;
790 }
791
792 /*
793 * Some other thread is already resetting it, let it finish.
794 */
795 case XMRInstState.RESETTING: {
796 return;
797 }
798
799 default: throw new Exception("bad state");
800 }
801
802 /*
803 * This thread transitioned the instance to RESETTING so reset it.
804 */
805 lock (m_RunLock) {
806 CheckRunLockInvariants(true);
807
808 /*
809 * No other thread should have transitioned it from RESETTING.
810 */
811 if (m_IState != XMRInstState.RESETTING) throw new Exception("bad state");
812
813 /*
814 * If the microthread is active, that means it has call frame
815 * context that we don't want. Throw it out and get a fresh one.
816 */
817 if (microthread.Active () < 0) {
818 microthread.Dispose ();
819 microthread = (IScriptUThread)m_Engine.uThreadCtor.Invoke (new object[] { this });
820 }
821
822 /*
823 * Mark it idle now so it can get queued to process new stuff.
824 */
825 m_IState = XMRInstState.IDLE;
826
827 /*
828 * Reset everything and queue up default's start_entry() event.
829 */
830 ClearQueue();
831 ResetLocked("external Reset");
832
833 CheckRunLockInvariants(true);
834 }
835 }
836
837 private void ClearQueueExceptLinkMessages()
838 {
839 lock (m_QueueLock) {
840 EventParams[] linkMessages = new EventParams[m_EventQueue.Count];
841 int n = 0;
842 foreach (EventParams evt2 in m_EventQueue) {
843 if (evt2.EventName == "link_message") {
844 linkMessages[n++] = evt2;
845 }
846 }
847
848 m_EventQueue.Clear();
849 for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0;
850
851 for (int i = 0; i < n; i ++) {
852 m_EventQueue.AddLast(linkMessages[i]);
853 }
854
855 m_EventCounts[(int)ScriptEventCode.link_message] = n;
856 }
857 }
858
859 private void ClearQueue()
860 {
861 lock (m_QueueLock)
862 {
863 m_EventQueue.Clear(); // no events queued
864 for (int i = m_EventCounts.Length; -- i >= 0;) m_EventCounts[i] = 0;
865 }
866 }
867
868 /**
869 * @brief The script called llResetScript() while it was running and
870 * has suspended. We want to reset the script to a never-has-
871 * ever-run-before state.
872 *
873 * Caller must have m_RunLock locked so we know script isn't
874 * running.
875 */
876 private void ResetLocked(string from)
877 {
878 m_RunOnePhase = "ResetLocked: releasing controls";
879 ReleaseControls();
880
881 m_RunOnePhase = "ResetLocked: removing script";
882 m_Part.Inventory.GetInventoryItem(m_ItemID).PermsMask = 0;
883 m_Part.Inventory.GetInventoryItem(m_ItemID).PermsGranter = UUID.Zero;
884 IUrlModule urlModule = m_Engine.World.RequestModuleInterface<IUrlModule>();
885 if (urlModule != null)
886 urlModule.ScriptRemoved(m_ItemID);
887
888 AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID);
889
890 m_RunOnePhase = "ResetLocked: clearing current event";
891 this.eventCode = ScriptEventCode.None; // not processing an event
892 m_DetectParams = null; // not processing an event
893 m_SleepUntil = DateTime.MinValue; // not doing llSleep()
894 m_ResetCount ++; // has been reset once more
895
896 /*
897 * Tell next call to 'default state_entry()' to reset all global
898 * vars to their initial values.
899 */
900 doGblInit = true;
901
902 /*
903 * Set script to 'default' state and queue call to its
904 * 'state_entry()' event handler.
905 */
906 m_RunOnePhase = "ResetLocked: posting default:state_entry() event";
907 stateCode = 0;
908 m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(0));
909 PostEvent(new EventParams("state_entry",
910 zeroObjectArray,
911 zeroDetectParams));
912
913 /*
914 * Tell CheckRun() to let script run.
915 */
916 suspendOnCheckRunHold = false;
917 suspendOnCheckRunTemp = false;
918 m_RunOnePhase = "ResetLocked: reset complete";
919 }
920
921 private void ReleaseControls()
922 {
923 if (m_Part != null)
924 {
925 bool found;
926 int permsMask;
927 UUID permsGranter;
928
929 try {
930 permsGranter = m_Part.TaskInventory[m_ItemID].PermsGranter;
931 permsMask = m_Part.TaskInventory[m_ItemID].PermsMask;
932 found = true;
933 } catch {
934 permsGranter = UUID.Zero;
935 permsMask = 0;
936 found = false;
937 }
938
939 if (found && ((permsMask & ScriptBaseClass.PERMISSION_TAKE_CONTROLS) != 0)) {
940 ScenePresence presence = m_Engine.World.GetScenePresence(permsGranter);
941 if (presence != null) {
942 presence.UnRegisterControlEventsToScript(m_LocalID, m_ItemID);
943 }
944 }
945 }
946 }
947
948 /**
949 * @brief The script code should call this routine whenever it is
950 * convenient to perform a migation or switch microthreads.
951 */
952 public override void CheckRunWork ()
953 {
954 if(!suspendOnCheckRunHold && ! suspendOnCheckRunTemp)
955 {
956 if(Util.GetTimeStampMS() - m_SliceStart < 60.0)
957 return;
958 suspendOnCheckRunTemp = true;
959 }
960
961 m_CheckRunPhase = "entered";
962
963 /*
964 * Stay stuck in this loop as long as something wants us suspended.
965 */
966 while (suspendOnCheckRunHold || suspendOnCheckRunTemp)
967 {
968 m_CheckRunPhase = "top of while";
969
970 /*
971 * See if MigrateOutEventHandler() has been called.
972 * If so, dump our stack to stackFrames and unwind.
973 */
974 if (this.captureStackFrames)
975 {
976
977 /*
978 * Puque our stack to the output stream.
979 * But otherwise, our state remains intact.
980 */
981 m_CheckRunPhase = "saving";
982 this.callMode = CallMode_SAVE;
983 this.stackFrames = null;
984 throw new StackCaptureException ();
985 }
986
987 /*
988 * We get here when the script state has been read in by MigrateInEventHandler().
989 * Since the stack is completely restored at this point, any subsequent calls
990 * within the functions should do their normal processing instead of trying to
991 * restore their state.
992 */
993 if (this.callMode == CallMode_RESTORE)
994 {
995 stackFramesRestored = true;
996 this.callMode = CallMode_NORMAL;
997 }
998
999 /*
1000 * Now we are ready to suspend the microthread.
1001 * This is like a longjmp() to the most recent StartEx() or ResumeEx()
1002 * with a simultaneous setjmp() so ResumeEx() can longjmp() back here.
1003 */
1004 m_CheckRunPhase = "suspending";
1005 suspendOnCheckRunTemp = false;
1006 microthread.Hiber ();
1007 m_CheckRunPhase = "resumed";
1008 }
1009
1010 m_CheckRunPhase = "returning";
1011
1012 /*
1013 * Upon return from CheckRun() it should always be the case that the script is
1014 * going to process calls normally, neither saving nor restoring stack frame state.
1015 */
1016 if (callMode != CallMode_NORMAL) throw new Exception ("bad callMode " + callMode);
1017 }
1018
1019 /**
1020 * @brief Allow script to dequeue events.
1021 */
1022 public void ResumeIt()
1023 {
1024 lock (m_QueueLock)
1025 {
1026 m_Suspended = false;
1027 if ((m_EventQueue != null) &&
1028 (m_EventQueue.First != null) &&
1029 (m_IState == XMRInstState.IDLE)) {
1030 m_IState = XMRInstState.ONSTARTQ;
1031 m_Engine.QueueToStart(this);
1032 }
1033 m_HasRun = true;
1034 }
1035 }
1036
1037 /**
1038 * @brief Block script from dequeuing events.
1039 */
1040 public void SuspendIt()
1041 {
1042 lock (m_QueueLock)
1043 {
1044 m_Suspended = true;
1045 }
1046 }
1047 }
1048
1049 /**
1050 * @brief Thrown by CheckRun() to unwind the script stack, capturing frames to
1051 * instance.stackFrames as it unwinds. We don't want scripts to be able
1052 * to intercept this exception as it would block the stack capture
1053 * functionality.
1054 */
1055 public class StackCaptureException : Exception, IXMRUncatchable { }
1056}