123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572 |
- /*
- * Copyright (c) Contributors, http://opensimulator.org/
- * See CONTRIBUTORS.TXT for a full list of copyright holders.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the OpenSimulator Project nor the
- * names of its contributors may be used to endorse or promote products
- * derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
- * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
- * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Text;
- using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat;
- using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger;
- using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
- using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list;
- using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion;
- using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString;
- using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3;
- // This class exists in the main app domain
- //
- namespace OpenSim.Region.ScriptEngine.Yengine
- {
- /**
- * @brief Array objects.
- */
- public class XMR_Array
- {
- private const int EMPTYHEAP = 64;
- private const int ENTRYHEAP = 24;
- private bool enumrValid; // true: enumr set to return array[arrayValid]
- // false: array[0..arrayValid-1] is all there is
- private SortedDictionary<object, object> dnary;
- private SortedDictionary<object, object>.Enumerator enumr;
- // enumerator used to fill 'array' past arrayValid to end of dictionary
- private int arrayValid; // number of elements in 'array' that have been filled in
- private KeyValuePair<object, object>[] array; // list of kvp's that have been returned by ForEach() since last modification
- private XMRInstAbstract inst; // script instance debited with heap use
- private int heapUse; // current heap use debit amount
- public static TokenTypeSDTypeDelegate countDelegate = new TokenTypeSDTypeDelegate(new TokenTypeInt(null), new TokenType[0]);
- public static TokenTypeSDTypeDelegate clearDelegate = new TokenTypeSDTypeDelegate(new TokenTypeVoid(null), new TokenType[0]);
- public static TokenTypeSDTypeDelegate indexDelegate = new TokenTypeSDTypeDelegate(new TokenTypeObject(null), new TokenType[] { new TokenTypeInt(null) });
- public static TokenTypeSDTypeDelegate valueDelegate = new TokenTypeSDTypeDelegate(new TokenTypeObject(null), new TokenType[] { new TokenTypeInt(null) });
- public XMR_Array(XMRInstAbstract inst)
- {
- this.inst = inst;
- dnary = new SortedDictionary<object, object>(XMRArrayKeyComparer.singleton);
- heapUse = inst.UpdateHeapUse(0, EMPTYHEAP);
- }
- ~XMR_Array()
- {
- heapUse = inst.UpdateHeapUse(heapUse, 0);
- }
- public static TokenType GetRValType(TokenName name)
- {
- if(name.val == "count")
- return new TokenTypeInt(name);
- if(name.val == "clear")
- return clearDelegate;
- if(name.val == "index")
- return indexDelegate;
- if(name.val == "value")
- return valueDelegate;
- return new TokenTypeVoid(name);
- }
- /**
- * @brief Handle 'array[index]' syntax to get or set an element of the dictionary.
- * Get returns null if element not defined, script sees type 'undef'.
- * Setting an element to null removes it.
- */
- public object GetByKey(object key)
- {
- object val;
- key = FixKey(key);
- if(!dnary.TryGetValue(key, out val))
- val = null;
- return val;
- }
- public void SetByKey(object key, object value)
- {
- key = FixKey(key);
- // Update heap use throwing an exception on failure
- // before making any changes to the array.
- int keysize = HeapTrackerObject.Size(key);
- int newheapuse = heapUse;
- object oldval;
- if(dnary.TryGetValue(key, out oldval))
- {
- newheapuse -= keysize + HeapTrackerObject.Size(oldval);
- }
- if(value != null)
- {
- newheapuse += keysize + HeapTrackerObject.Size(value);
- }
- heapUse = inst.UpdateHeapUse(heapUse, newheapuse);
- // Save new value in array, replacing one of same key if there.
- // null means remove the value, ie, script did array[key] = undef.
- if(value != null)
- {
- dnary[key] = value;
- }
- else
- {
- dnary.Remove(key);
- // Shrink the enumeration array, but always leave at least one element.
- if((array != null) && (dnary.Count < array.Length / 2))
- {
- Array.Resize<KeyValuePair<object, object>>(ref array, array.Length / 2);
- }
- }
- // The enumeration array is invalid because the dictionary has been modified.
- // Next time a ForEach() call happens, it will repopulate 'array' as elements are retrieved.
- arrayValid = 0;
- }
- /**
- * @brief Converts an 'object' type to array, key, list, string, but disallows null,
- * as our language doesn't allow types other than 'object' to be null.
- * Value types (float, rotation, etc) don't need explicit check for null as
- * the C# runtime can't convert a null to a value type, and throws an exception.
- * But for any reference type (array, key, etc) we must manually check for null.
- */
- public static XMR_Array Obj2Array(object obj)
- {
- if(obj == null)
- throw new NullReferenceException();
- return (XMR_Array)obj;
- }
- public static LSL_Key Obj2Key(object obj)
- {
- if(obj == null)
- throw new NullReferenceException();
- return (LSL_Key)obj;
- }
- public static LSL_List Obj2List(object obj)
- {
- if(obj == null)
- throw new NullReferenceException();
- return (LSL_List)obj;
- }
- public static LSL_String Obj2String(object obj)
- {
- if(obj == null)
- throw new NullReferenceException();
- return obj.ToString();
- }
- /**
- * @brief remove all elements from the array.
- * sets everything to its 'just constructed' state.
- */
- public void __pub_clear()
- {
- heapUse = inst.UpdateHeapUse(heapUse, EMPTYHEAP);
- dnary.Clear();
- enumrValid = false;
- arrayValid = 0;
- array = null;
- }
- /**
- * @brief return number of elements in the array.
- */
- public int __pub_count()
- {
- return dnary.Count;
- }
- /**
- * @brief Retrieve index (key) of an arbitrary element.
- * @param number = number of the element (0 based)
- * @returns null: array doesn't have that many elements
- * else: index (key) for that element
- */
- public object __pub_index(int number)
- {
- return ForEach(number) ? UnfixKey(array[number].Key) : null;
- }
- /**
- * @brief Retrieve value of an arbitrary element.
- * @param number = number of the element (0 based)
- * @returns null: array doesn't have that many elements
- * else: value for that element
- */
- public object __pub_value(int number)
- {
- return ForEach(number) ? array[number].Value : null;
- }
- /**
- * @brief Called in each iteration of a 'foreach' statement.
- * @param number = index of element to retrieve (0 = first one)
- * @returns false: element does not exist
- * true: element exists
- */
- private bool ForEach(int number)
- {
- // If we don't have any array, we can't have ever done
- // any calls here before, so allocate an array big enough
- // and set everything else to the beginning.
- if(array == null)
- {
- array = new KeyValuePair<object, object>[dnary.Count];
- arrayValid = 0;
- }
- // If dictionary modified since last enumeration, get a new enumerator.
- if(arrayValid == 0)
- {
- enumr = dnary.GetEnumerator();
- enumrValid = true;
- }
- // Make sure we have filled the array up enough for requested element.
- while((arrayValid <= number) && enumrValid && enumr.MoveNext())
- {
- if(arrayValid >= array.Length)
- {
- Array.Resize<KeyValuePair<object, object>>(ref array, dnary.Count);
- }
- array[arrayValid++] = enumr.Current;
- }
- // If we don't have that many elements, return end-of-array status.
- return number < arrayValid;
- }
- /**
- * @brief Transmit array out in such a way that it can be reconstructed,
- * including any in-progress ForEach() enumerations.
- */
- public delegate void SendArrayObjDelegate(object graph);
- public void SendArrayObj(SendArrayObjDelegate sendObj)
- {
- // Set the count then the elements themselves.
- // UnfixKey() because sendObj doesn't handle XMRArrayListKeys.
- sendObj(dnary.Count);
- foreach(KeyValuePair<object, object> kvp in dnary)
- {
- sendObj(UnfixKey(kvp.Key));
- sendObj(kvp.Value);
- }
- }
- /**
- * @brief Receive array in. Any previous contents are erased.
- * Set up such that any enumeration in progress will resume
- * at the exact spot and in the exact same order as they
- * were in on the sending side.
- */
- public delegate object RecvArrayObjDelegate();
- public void RecvArrayObj(RecvArrayObjDelegate recvObj)
- {
- heapUse = inst.UpdateHeapUse(heapUse, EMPTYHEAP);
- // Cause any enumeration to refill the array from the sorted dictionary.
- // Since it is a sorted dictionary, any enumerations will be in the same
- // order as on the sending side.
- arrayValid = 0;
- enumrValid = false;
- // Fill dictionary.
- dnary.Clear();
- int count = (int)recvObj();
- while(--count >= 0)
- {
- object key = FixKey(recvObj());
- object val = recvObj();
- int htuse = HeapTrackerObject.Size(key) + HeapTrackerObject.Size(val);
- heapUse = inst.UpdateHeapUse(heapUse, heapUse + htuse);
- dnary.Add(key, val);
- }
- }
- /**
- * We want our index values to be of consistent type, otherwise we get things like (LSL_Integer)1 != (int)1.
- * So strip off any LSL-ness from the types.
- * We also deep-strip any given lists used as keys (multi-dimensional arrays).
- */
- public static object FixKey(object key)
- {
- if(key is LSL_Integer)
- return (int)(LSL_Integer)key;
- if(key is LSL_Float)
- return (double)(LSL_Float)key;
- if(key is LSL_Key)
- return (string)(LSL_Key)key;
- if(key is LSL_String)
- return (string)(LSL_String)key;
- if(key is LSL_List)
- {
- object[] data = ((LSL_List)key).Data;
- if(data.Length == 1)
- return FixKey(data[0]);
- return new XMRArrayListKey((LSL_List)key);
- }
- return key; // int, double, string, LSL_Vector, LSL_Rotation, etc are ok as is
- }
- /**
- * @brief When returning a key, such as for array.index(), we want to return the original
- * LSL_List, not the sanitized one, as the script compiler expects an LSL_List.
- * Any other sanitized types can remain as is (int, string, etc).
- */
- private static object UnfixKey(object key)
- {
- if(key is XMRArrayListKey)
- key = ((XMRArrayListKey)key).GetOriginal();
- return key;
- }
- }
- public class XMRArrayKeyComparer: IComparer<object>
- {
- public static XMRArrayKeyComparer singleton = new XMRArrayKeyComparer();
- /**
- * @brief Compare two keys
- */
- public int Compare(object x, object y) // IComparer<object>
- {
- // Use short type name (eg, String, Int32, XMRArrayListKey) as most significant part of key.
- string xtn = x.GetType().Name;
- string ytn = y.GetType().Name;
- int ctn = String.CompareOrdinal(xtn, ytn);
- if(ctn != 0)
- return ctn;
- ComparerDelegate cd;
- if(!comparers.TryGetValue(xtn, out cd))
- {
- throw new Exception("unsupported key type " + xtn);
- }
- return cd(x, y);
- }
- private delegate int ComparerDelegate(object a, object b);
- private static Dictionary<string, ComparerDelegate> comparers = BuildComparers();
- private static Dictionary<string, ComparerDelegate> BuildComparers()
- {
- Dictionary<string, ComparerDelegate> cmps = new Dictionary<string, ComparerDelegate>();
- cmps.Add(typeof(double).Name, MyFloatComparer);
- cmps.Add(typeof(int).Name, MyIntComparer);
- cmps.Add(typeof(XMRArrayListKey).Name, MyListKeyComparer);
- cmps.Add(typeof(LSL_Rotation).Name, MyRotationComparer);
- cmps.Add(typeof(string).Name, MyStringComparer);
- cmps.Add(typeof(LSL_Vector).Name, MyVectorComparer);
- return cmps;
- }
- private static int MyFloatComparer(object a, object b)
- {
- double af = (double)a;
- double bf = (double)b;
- if(af < bf)
- return -1;
- if(af > bf)
- return 1;
- return 0;
- }
- private static int MyIntComparer(object a, object b)
- {
- return (int)a - (int)b;
- }
- private static int MyListKeyComparer(object a, object b)
- {
- XMRArrayListKey alk = (XMRArrayListKey)a;
- XMRArrayListKey blk = (XMRArrayListKey)b;
- return XMRArrayListKey.Compare(alk, blk);
- }
- private static int MyRotationComparer(object a, object b)
- {
- LSL_Rotation ar = (LSL_Rotation)a;
- LSL_Rotation br = (LSL_Rotation)b;
- if(ar.x < br.x)
- return -1;
- if(ar.x > br.x)
- return 1;
- if(ar.y < br.y)
- return -1;
- if(ar.y > br.y)
- return 1;
- if(ar.z < br.z)
- return -1;
- if(ar.z > br.z)
- return 1;
- if(ar.s < br.s)
- return -1;
- if(ar.s > br.s)
- return 1;
- return 0;
- }
- private static int MyStringComparer(object a, object b)
- {
- return String.CompareOrdinal((string)a, (string)b);
- }
- private static int MyVectorComparer(object a, object b)
- {
- LSL_Vector av = (LSL_Vector)a;
- LSL_Vector bv = (LSL_Vector)b;
- if(av.x < bv.x)
- return -1;
- if(av.x > bv.x)
- return 1;
- if(av.y < bv.y)
- return -1;
- if(av.y > bv.y)
- return 1;
- if(av.z < bv.z)
- return -1;
- if(av.z > bv.z)
- return 1;
- return 0;
- }
- }
- /**
- * @brief Lists used as keys must be sanitized first.
- * List gets converted to an object[] and each element is converted from LSL_ types to system types where possible.
- * And we also need an equality operator that compares the values of all elements of the list, not just the lengths.
- * Note that just like LSL_Lists, we consider these objects to be immutable, so they can be directly used as keys in
- * the dictionary as they don't ever change.
- */
- public class XMRArrayListKey
- {
- private LSL_List original;
- private object[] cleaned;
- private int length;
- private int hashCode;
- /**
- * @brief Construct a sanitized object[] from a list.
- * Also save the original list in case we need it later.
- */
- public XMRArrayListKey(LSL_List key)
- {
- original = key;
- object[] given = key.Data;
- int len = given.Length;
- length = len;
- cleaned = new object[len];
- int hc = len;
- for(int i = 0; i < len; i++)
- {
- object v = XMR_Array.FixKey(given[i]);
- hc += hc + ((hc < 0) ? 1 : 0);
- hc ^= v.GetHashCode();
- cleaned[i] = v;
- }
- hashCode = hc;
- }
- /**
- * @brief Get heap tracking size.
- */
- public int Size
- {
- get
- {
- return original.Size;
- }
- }
- /**
- * @brief See if the given object is an XMRArrayListKey and every value is equal to our own.
- */
- public override bool Equals(object o)
- {
- if(!(o is XMRArrayListKey))
- return false;
- XMRArrayListKey a = (XMRArrayListKey)o;
- int len = a.length;
- if(len != length)
- return false;
- if(a.hashCode != hashCode)
- return false;
- for(int i = 0; i < len; i++)
- {
- if(!cleaned[i].Equals(a.cleaned[i]))
- return false;
- }
- return true;
- }
- /**
- * @brief Get an hash code.
- */
- public override int GetHashCode()
- {
- return hashCode;
- }
- /**
- * @brief Compare for key sorting.
- */
- public static int Compare(XMRArrayListKey x, XMRArrayListKey y)
- {
- int j = x.length - y.length;
- if(j == 0)
- {
- for(int i = 0; i < x.length; i++)
- {
- object xo = x.cleaned[i];
- object yo = y.cleaned[i];
- j = XMRArrayKeyComparer.singleton.Compare(xo, yo);
- if(j != 0)
- break;
- }
- }
- return j;
- }
- /**
- * @brief Get the original LSL_List we were built from.
- */
- public LSL_List GetOriginal()
- {
- return original;
- }
- /**
- * @brief Debugging
- */
- public override string ToString()
- {
- StringBuilder sb = new StringBuilder();
- for(int i = 0; i < length; i++)
- {
- if(i > 0)
- sb.Append(',');
- sb.Append(cleaned[i].ToString());
- }
- return sb.ToString();
- }
- }
- }
|