aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs
diff options
context:
space:
mode:
Diffstat (limited to 'OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs')
-rw-r--r--OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs572
1 files changed, 572 insertions, 0 deletions
diff --git a/OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs b/OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs
new file mode 100644
index 0000000..3d0525b
--- /dev/null
+++ b/OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs
@@ -0,0 +1,572 @@
1/*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28using System;
29using System.Collections.Generic;
30using System.IO;
31using System.Text;
32
33using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
34using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
35using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
36using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
37using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
38using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
39using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
40
41// This class exists in the main app domain
42//
43namespace OpenSim.Region.ScriptEngine.Yengine
44{
45 /**
46 * @brief Array objects.
47 */
48 public class XMR_Array
49 {
50 private const int EMPTYHEAP = 64;
51 private const int ENTRYHEAP = 24;
52
53 private bool enumrValid; // true: enumr set to return array[arrayValid]
54 // false: array[0..arrayValid-1] is all there is
55 private SortedDictionary<object, object> dnary;
56 private SortedDictionary<object, object>.Enumerator enumr;
57 // enumerator used to fill 'array' past arrayValid to end of dictionary
58 private int arrayValid; // number of elements in 'array' that have been filled in
59 private KeyValuePair<object, object>[] array; // list of kvp's that have been returned by ForEach() since last modification
60 private XMRInstAbstract inst; // script instance debited with heap use
61 private int heapUse; // current heap use debit amount
62
63 public static TokenTypeSDTypeDelegate countDelegate = new TokenTypeSDTypeDelegate(new TokenTypeInt(null), new TokenType[0]);
64 public static TokenTypeSDTypeDelegate clearDelegate = new TokenTypeSDTypeDelegate(new TokenTypeVoid(null), new TokenType[0]);
65 public static TokenTypeSDTypeDelegate indexDelegate = new TokenTypeSDTypeDelegate(new TokenTypeObject(null), new TokenType[] { new TokenTypeInt(null) });
66 public static TokenTypeSDTypeDelegate valueDelegate = new TokenTypeSDTypeDelegate(new TokenTypeObject(null), new TokenType[] { new TokenTypeInt(null) });
67
68 public XMR_Array(XMRInstAbstract inst)
69 {
70 this.inst = inst;
71 dnary = new SortedDictionary<object, object>(XMRArrayKeyComparer.singleton);
72 heapUse = inst.UpdateHeapUse(0, EMPTYHEAP);
73 }
74
75 ~XMR_Array()
76 {
77 heapUse = inst.UpdateHeapUse(heapUse, 0);
78 }
79
80 public static TokenType GetRValType(TokenName name)
81 {
82 if(name.val == "count")
83 return new TokenTypeInt(name);
84 if(name.val == "clear")
85 return clearDelegate;
86 if(name.val == "index")
87 return indexDelegate;
88 if(name.val == "value")
89 return valueDelegate;
90 return new TokenTypeVoid(name);
91 }
92
93 /**
94 * @brief Handle 'array[index]' syntax to get or set an element of the dictionary.
95 * Get returns null if element not defined, script sees type 'undef'.
96 * Setting an element to null removes it.
97 */
98 public object GetByKey(object key)
99 {
100 object val;
101 key = FixKey(key);
102 if(!dnary.TryGetValue(key, out val))
103 val = null;
104 return val;
105 }
106
107 public void SetByKey(object key, object value)
108 {
109 key = FixKey(key);
110
111 // Update heap use throwing an exception on failure
112 // before making any changes to the array.
113 int keysize = HeapTrackerObject.Size(key);
114 int newheapuse = heapUse;
115 object oldval;
116 if(dnary.TryGetValue(key, out oldval))
117 {
118 newheapuse -= keysize + HeapTrackerObject.Size(oldval);
119 }
120 if(value != null)
121 {
122 newheapuse += keysize + HeapTrackerObject.Size(value);
123 }
124 heapUse = inst.UpdateHeapUse(heapUse, newheapuse);
125
126 // Save new value in array, replacing one of same key if there.
127 // null means remove the value, ie, script did array[key] = undef.
128 if(value != null)
129 {
130 dnary[key] = value;
131 }
132 else
133 {
134 dnary.Remove(key);
135
136 // Shrink the enumeration array, but always leave at least one element.
137 if((array != null) && (dnary.Count < array.Length / 2))
138 {
139 Array.Resize<KeyValuePair<object, object>>(ref array, array.Length / 2);
140 }
141 }
142
143 // The enumeration array is invalid because the dictionary has been modified.
144 // Next time a ForEach() call happens, it will repopulate 'array' as elements are retrieved.
145 arrayValid = 0;
146 }
147
148 /**
149 * @brief Converts an 'object' type to array, key, list, string, but disallows null,
150 * as our language doesn't allow types other than 'object' to be null.
151 * Value types (float, rotation, etc) don't need explicit check for null as
152 * the C# runtime can't convert a null to a value type, and throws an exception.
153 * But for any reference type (array, key, etc) we must manually check for null.
154 */
155 public static XMR_Array Obj2Array(object obj)
156 {
157 if(obj == null)
158 throw new NullReferenceException();
159 return (XMR_Array)obj;
160 }
161 public static LSL_Key Obj2Key(object obj)
162 {
163 if(obj == null)
164 throw new NullReferenceException();
165 return (LSL_Key)obj;
166 }
167 public static LSL_List Obj2List(object obj)
168 {
169 if(obj == null)
170 throw new NullReferenceException();
171 return (LSL_List)obj;
172 }
173 public static LSL_String Obj2String(object obj)
174 {
175 if(obj == null)
176 throw new NullReferenceException();
177 return obj.ToString();
178 }
179
180 /**
181 * @brief remove all elements from the array.
182 * sets everything to its 'just constructed' state.
183 */
184 public void __pub_clear()
185 {
186 heapUse = inst.UpdateHeapUse(heapUse, EMPTYHEAP);
187 dnary.Clear();
188 enumrValid = false;
189 arrayValid = 0;
190 array = null;
191 }
192
193 /**
194 * @brief return number of elements in the array.
195 */
196 public int __pub_count()
197 {
198 return dnary.Count;
199 }
200
201 /**
202 * @brief Retrieve index (key) of an arbitrary element.
203 * @param number = number of the element (0 based)
204 * @returns null: array doesn't have that many elements
205 * else: index (key) for that element
206 */
207 public object __pub_index(int number)
208 {
209 return ForEach(number) ? UnfixKey(array[number].Key) : null;
210 }
211
212 /**
213 * @brief Retrieve value of an arbitrary element.
214 * @param number = number of the element (0 based)
215 * @returns null: array doesn't have that many elements
216 * else: value for that element
217 */
218 public object __pub_value(int number)
219 {
220 return ForEach(number) ? array[number].Value : null;
221 }
222
223 /**
224 * @brief Called in each iteration of a 'foreach' statement.
225 * @param number = index of element to retrieve (0 = first one)
226 * @returns false: element does not exist
227 * true: element exists
228 */
229 private bool ForEach(int number)
230 {
231 // If we don't have any array, we can't have ever done
232 // any calls here before, so allocate an array big enough
233 // and set everything else to the beginning.
234 if(array == null)
235 {
236 array = new KeyValuePair<object, object>[dnary.Count];
237 arrayValid = 0;
238 }
239
240 // If dictionary modified since last enumeration, get a new enumerator.
241 if(arrayValid == 0)
242 {
243 enumr = dnary.GetEnumerator();
244 enumrValid = true;
245 }
246
247 // Make sure we have filled the array up enough for requested element.
248 while((arrayValid <= number) && enumrValid && enumr.MoveNext())
249 {
250 if(arrayValid >= array.Length)
251 {
252 Array.Resize<KeyValuePair<object, object>>(ref array, dnary.Count);
253 }
254 array[arrayValid++] = enumr.Current;
255 }
256
257 // If we don't have that many elements, return end-of-array status.
258 return number < arrayValid;
259 }
260
261 /**
262 * @brief Transmit array out in such a way that it can be reconstructed,
263 * including any in-progress ForEach() enumerations.
264 */
265 public delegate void SendArrayObjDelegate(object graph);
266 public void SendArrayObj(SendArrayObjDelegate sendObj)
267 {
268 // Set the count then the elements themselves.
269 // UnfixKey() because sendObj doesn't handle XMRArrayListKeys.
270 sendObj(dnary.Count);
271 foreach(KeyValuePair<object, object> kvp in dnary)
272 {
273 sendObj(UnfixKey(kvp.Key));
274 sendObj(kvp.Value);
275 }
276 }
277
278 /**
279 * @brief Receive array in. Any previous contents are erased.
280 * Set up such that any enumeration in progress will resume
281 * at the exact spot and in the exact same order as they
282 * were in on the sending side.
283 */
284 public delegate object RecvArrayObjDelegate();
285 public void RecvArrayObj(RecvArrayObjDelegate recvObj)
286 {
287 heapUse = inst.UpdateHeapUse(heapUse, EMPTYHEAP);
288
289 // Cause any enumeration to refill the array from the sorted dictionary.
290 // Since it is a sorted dictionary, any enumerations will be in the same
291 // order as on the sending side.
292 arrayValid = 0;
293 enumrValid = false;
294
295 // Fill dictionary.
296 dnary.Clear();
297 int count = (int)recvObj();
298 while(--count >= 0)
299 {
300 object key = FixKey(recvObj());
301 object val = recvObj();
302 int htuse = HeapTrackerObject.Size(key) + HeapTrackerObject.Size(val);
303 heapUse = inst.UpdateHeapUse(heapUse, heapUse + htuse);
304 dnary.Add(key, val);
305 }
306 }
307
308 /**
309 * We want our index values to be of consistent type, otherwise we get things like (LSL_Integer)1 != (int)1.
310 * So strip off any LSL-ness from the types.
311 * We also deep-strip any given lists used as keys (multi-dimensional arrays).
312 */
313 public static object FixKey(object key)
314 {
315 if(key is LSL_Integer)
316 return (int)(LSL_Integer)key;
317 if(key is LSL_Float)
318 return (double)(LSL_Float)key;
319 if(key is LSL_Key)
320 return (string)(LSL_Key)key;
321 if(key is LSL_String)
322 return (string)(LSL_String)key;
323 if(key is LSL_List)
324 {
325 object[] data = ((LSL_List)key).Data;
326 if(data.Length == 1)
327 return FixKey(data[0]);
328 return new XMRArrayListKey((LSL_List)key);
329 }
330 return key; // int, double, string, LSL_Vector, LSL_Rotation, etc are ok as is
331 }
332
333 /**
334 * @brief When returning a key, such as for array.index(), we want to return the original
335 * LSL_List, not the sanitized one, as the script compiler expects an LSL_List.
336 * Any other sanitized types can remain as is (int, string, etc).
337 */
338 private static object UnfixKey(object key)
339 {
340 if(key is XMRArrayListKey)
341 key = ((XMRArrayListKey)key).GetOriginal();
342 return key;
343 }
344 }
345
346 public class XMRArrayKeyComparer: IComparer<object>
347 {
348
349 public static XMRArrayKeyComparer singleton = new XMRArrayKeyComparer();
350
351 /**
352 * @brief Compare two keys
353 */
354 public int Compare(object x, object y) // IComparer<object>
355 {
356 // Use short type name (eg, String, Int32, XMRArrayListKey) as most significant part of key.
357 string xtn = x.GetType().Name;
358 string ytn = y.GetType().Name;
359 int ctn = String.CompareOrdinal(xtn, ytn);
360 if(ctn != 0)
361 return ctn;
362
363 ComparerDelegate cd;
364 if(!comparers.TryGetValue(xtn, out cd))
365 {
366 throw new Exception("unsupported key type " + xtn);
367 }
368 return cd(x, y);
369 }
370
371 private delegate int ComparerDelegate(object a, object b);
372
373 private static Dictionary<string, ComparerDelegate> comparers = BuildComparers();
374
375 private static Dictionary<string, ComparerDelegate> BuildComparers()
376 {
377 Dictionary<string, ComparerDelegate> cmps = new Dictionary<string, ComparerDelegate>();
378 cmps.Add(typeof(double).Name, MyFloatComparer);
379 cmps.Add(typeof(int).Name, MyIntComparer);
380 cmps.Add(typeof(XMRArrayListKey).Name, MyListKeyComparer);
381 cmps.Add(typeof(LSL_Rotation).Name, MyRotationComparer);
382 cmps.Add(typeof(string).Name, MyStringComparer);
383 cmps.Add(typeof(LSL_Vector).Name, MyVectorComparer);
384 return cmps;
385 }
386
387 private static int MyFloatComparer(object a, object b)
388 {
389 double af = (double)a;
390 double bf = (double)b;
391 if(af < bf)
392 return -1;
393 if(af > bf)
394 return 1;
395 return 0;
396 }
397 private static int MyIntComparer(object a, object b)
398 {
399 return (int)a - (int)b;
400 }
401 private static int MyListKeyComparer(object a, object b)
402 {
403 XMRArrayListKey alk = (XMRArrayListKey)a;
404 XMRArrayListKey blk = (XMRArrayListKey)b;
405 return XMRArrayListKey.Compare(alk, blk);
406 }
407 private static int MyRotationComparer(object a, object b)
408 {
409 LSL_Rotation ar = (LSL_Rotation)a;
410 LSL_Rotation br = (LSL_Rotation)b;
411 if(ar.x < br.x)
412 return -1;
413 if(ar.x > br.x)
414 return 1;
415 if(ar.y < br.y)
416 return -1;
417 if(ar.y > br.y)
418 return 1;
419 if(ar.z < br.z)
420 return -1;
421 if(ar.z > br.z)
422 return 1;
423 if(ar.s < br.s)
424 return -1;
425 if(ar.s > br.s)
426 return 1;
427 return 0;
428 }
429 private static int MyStringComparer(object a, object b)
430 {
431 return String.CompareOrdinal((string)a, (string)b);
432 }
433 private static int MyVectorComparer(object a, object b)
434 {
435 LSL_Vector av = (LSL_Vector)a;
436 LSL_Vector bv = (LSL_Vector)b;
437 if(av.x < bv.x)
438 return -1;
439 if(av.x > bv.x)
440 return 1;
441 if(av.y < bv.y)
442 return -1;
443 if(av.y > bv.y)
444 return 1;
445 if(av.z < bv.z)
446 return -1;
447 if(av.z > bv.z)
448 return 1;
449 return 0;
450 }
451 }
452
453 /**
454 * @brief Lists used as keys must be sanitized first.
455 * List gets converted to an object[] and each element is converted from LSL_ types to system types where possible.
456 * And we also need an equality operator that compares the values of all elements of the list, not just the lengths.
457 * Note that just like LSL_Lists, we consider these objects to be immutable, so they can be directly used as keys in
458 * the dictionary as they don't ever change.
459 */
460 public class XMRArrayListKey
461 {
462 private LSL_List original;
463 private object[] cleaned;
464 private int length;
465 private int hashCode;
466
467 /**
468 * @brief Construct a sanitized object[] from a list.
469 * Also save the original list in case we need it later.
470 */
471 public XMRArrayListKey(LSL_List key)
472 {
473 original = key;
474 object[] given = key.Data;
475 int len = given.Length;
476 length = len;
477 cleaned = new object[len];
478 int hc = len;
479 for(int i = 0; i < len; i++)
480 {
481 object v = XMR_Array.FixKey(given[i]);
482 hc += hc + ((hc < 0) ? 1 : 0);
483 hc ^= v.GetHashCode();
484 cleaned[i] = v;
485 }
486 hashCode = hc;
487 }
488
489 /**
490 * @brief Get heap tracking size.
491 */
492 public int Size
493 {
494 get
495 {
496 return original.Size;
497 }
498 }
499
500 /**
501 * @brief See if the given object is an XMRArrayListKey and every value is equal to our own.
502 */
503 public override bool Equals(object o)
504 {
505 if(!(o is XMRArrayListKey))
506 return false;
507 XMRArrayListKey a = (XMRArrayListKey)o;
508 int len = a.length;
509 if(len != length)
510 return false;
511 if(a.hashCode != hashCode)
512 return false;
513 for(int i = 0; i < len; i++)
514 {
515 if(!cleaned[i].Equals(a.cleaned[i]))
516 return false;
517 }
518 return true;
519 }
520
521 /**
522 * @brief Get an hash code.
523 */
524 public override int GetHashCode()
525 {
526 return hashCode;
527 }
528
529 /**
530 * @brief Compare for key sorting.
531 */
532 public static int Compare(XMRArrayListKey x, XMRArrayListKey y)
533 {
534 int j = x.length - y.length;
535 if(j == 0)
536 {
537 for(int i = 0; i < x.length; i++)
538 {
539 object xo = x.cleaned[i];
540 object yo = y.cleaned[i];
541 j = XMRArrayKeyComparer.singleton.Compare(xo, yo);
542 if(j != 0)
543 break;
544 }
545 }
546 return j;
547 }
548
549 /**
550 * @brief Get the original LSL_List we were built from.
551 */
552 public LSL_List GetOriginal()
553 {
554 return original;
555 }
556
557 /**
558 * @brief Debugging
559 */
560 public override string ToString()
561 {
562 StringBuilder sb = new StringBuilder();
563 for(int i = 0; i < length; i++)
564 {
565 if(i > 0)
566 sb.Append(',');
567 sb.Append(cleaned[i].ToString());
568 }
569 return sb.ToString();
570 }
571 }
572}