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