From 83e2fee71be695b78438e0c9dc50b649a539d0e3 Mon Sep 17 00:00:00 2001 From: UbitUmarov Date: Fri, 2 Feb 2018 12:49:40 +0000 Subject: 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. --- OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs | 534 ++++++++++++++++++++++ 1 file changed, 534 insertions(+) create mode 100644 OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs (limited to 'OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs') diff --git a/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs new file mode 100644 index 0000000..36d95d3 --- /dev/null +++ b/OpenSim/Region/ScriptEngine/XMREngine/XMRArray.cs @@ -0,0 +1,534 @@ +/* + * 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.XMREngine +{ + /** + * @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 dnary; + private SortedDictionary.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[] 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 (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> (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[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> (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 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 { + + public static XMRArrayKeyComparer singleton = new XMRArrayKeyComparer (); + + /** + * @brief Compare two keys + */ + public int Compare (object x, object y) // IComparer + { + /* + * 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 comparers = BuildComparers (); + + private static Dictionary BuildComparers () + { + Dictionary cmps = new Dictionary (); + 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 (); + } + } +} -- cgit v1.1