diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs')
-rw-r--r-- | OpenSim/Region/ScriptEngine/XMREngine/XMRInstRun.cs | 1056 |
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 | |||
28 | using System; | ||
29 | using System.Threading; | ||
30 | using System.Collections.Generic; | ||
31 | using System.Text; | ||
32 | using OpenMetaverse; | ||
33 | using OpenSim.Framework; | ||
34 | using OpenSim.Region.Framework.Interfaces; | ||
35 | using OpenSim.Region.ScriptEngine.Interfaces; | ||
36 | using OpenSim.Region.ScriptEngine.Shared; | ||
37 | using OpenSim.Region.ScriptEngine.Shared.Api; | ||
38 | using OpenSim.Region.ScriptEngine.Shared.ScriptBase; | ||
39 | using OpenSim.Region.ScriptEngine.XMREngine; | ||
40 | using OpenSim.Region.Framework.Scenes; | ||
41 | using log4net; | ||
42 | |||
43 | using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; | ||
44 | using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; | ||
45 | using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
46 | using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; | ||
47 | using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; | ||
48 | using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
49 | using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; | ||
50 | |||
51 | namespace 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 | } | ||