diff options
Diffstat (limited to 'OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs')
-rw-r--r-- | OpenSim/Region/ScriptEngine/YEngine/XMRArray.cs | 572 |
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 | |||
28 | using System; | ||
29 | using System.Collections.Generic; | ||
30 | using System.IO; | ||
31 | using System.Text; | ||
32 | |||
33 | using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; | ||
34 | using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; | ||
35 | using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
36 | using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; | ||
37 | using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; | ||
38 | using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; | ||
39 | using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; | ||
40 | |||
41 | // This class exists in the main app domain | ||
42 | // | ||
43 | namespace 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 | } | ||