diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/YEngine/XMRInstRun.cs')
-rw-r--r-- | OpenSim/Region/ScriptEngine/YEngine/XMRInstRun.cs | 910 |
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 | |||
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.Shared; | ||
36 | using OpenSim.Region.ScriptEngine.Shared.Api; | ||
37 | using OpenSim.Region.ScriptEngine.Shared.ScriptBase; | ||
38 | using OpenSim.Region.Framework.Scenes; | ||
39 | using log4net; | ||
40 | |||
41 | using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; | ||
42 | using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; | ||
43 | using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
44 | using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; | ||
45 | using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; | ||
46 | using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
47 | using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; | ||
48 | |||
49 | namespace 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 | } | ||