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