OpenSimMirror/OpenSim/Region/ScriptEngine/XMREngine/XMREngine.cs

2103 lines
81 KiB
C#

/*
* 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.
*/
// based on XMREngine from Mike Rieker (Dreamnation) and Melanie Thielker
// but with several changes to be more cross platform.
using log4net;
using Mono.Addins;
using Nini.Config;
using OpenSim.Framework;
using OpenSim.Framework.Console;
using OpenSim.Framework.Monitoring;
using OpenSim.Region.ClientStack.Linden;
using OpenSim.Region.Framework.Interfaces;
using OpenSim.Region.Framework.Scenes;
using OpenSim.Region.ScriptEngine.Interfaces;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using OpenMetaverse;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading;
using System.Timers;
using System.Xml;
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;
[assembly: Addin("XMREngine", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
namespace OpenSim.Region.ScriptEngine.XMREngine
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "XMREngine")]
public partial class XMREngine : INonSharedRegionModule, IScriptEngine,
IScriptModule
{
public static readonly DetectParams[] zeroDetectParams = new DetectParams[0];
private static ArrayList noScriptErrors = new ArrayList();
public static readonly ILog m_log =
LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
private static readonly string[] scriptReferencedAssemblies = new string[0];
private bool m_LateInit;
private bool m_TraceCalls;
public bool m_Verbose;
public bool m_ScriptDebug;
public Scene m_Scene;
private IConfigSource m_ConfigSource;
private IConfig m_Config;
private string m_ScriptBasePath;
private bool m_Enabled = false;
public bool m_StartProcessing = false;
public bool m_UseSourceHashCode = false;
public ConstructorInfo uThreadCtor;
private Dictionary<UUID, ArrayList> m_ScriptErrors =
new Dictionary<UUID, ArrayList>();
private Dictionary<UUID, List<UUID>> m_ObjectItemList =
new Dictionary<UUID, List<UUID>>();
private Dictionary<UUID, XMRInstance[]> m_ObjectInstArray =
new Dictionary<UUID, XMRInstance[]>();
public Dictionary<string,FieldInfo> m_XMRInstanceApiCtxFieldInfos =
new Dictionary<string,FieldInfo> ();
private int m_StackSize;
private int m_HeapSize;
private XMRScriptThread[] m_ScriptThreads;
private int m_WakeUpOne = 0;
public object m_WakeUpLock = new object();
private Dictionary<Thread,XMRScriptThread> m_AllThreads = new Dictionary<Thread,XMRScriptThread> ();
private bool m_SuspendScriptThreadFlag = false;
/**
* @brief Something was just added to the Start or Yield queue so
* wake one of the XMRScriptThread instances to run it.
*/
private Thread m_SleepThread = null;
private bool m_Exiting = false;
private int m_MaintenanceInterval = 10;
private System.Timers.Timer m_MaintenanceTimer;
public int numThreadScriptWorkers;
private object m_FrameUpdateLock = new object ();
private event ThreadStart m_FrameUpdateList = null;
// Various instance lists:
// m_InstancesDict = all known instances
// find an instance given its itemID
// m_StartQueue = instances that have just had event queued to them
// m_YieldQueue = instances that are ready to run right now
// m_SleepQueue = instances that have m_SleepUntil valid
// sorted by ascending m_SleepUntil
private Dictionary<UUID, XMRInstance> m_InstancesDict =
new Dictionary<UUID, XMRInstance>();
public Queue<ThreadStart> m_ThunkQueue = new Queue<ThreadStart> ();
public XMRInstQueue m_StartQueue = new XMRInstQueue();
public XMRInstQueue m_YieldQueue = new XMRInstQueue();
public XMRInstQueue m_SleepQueue = new XMRInstQueue();
private string m_LockedDict = "nobody";
public XMREngine()
{
}
public string Name
{
get { return "XMREngine"; }
}
public Type ReplaceableInterface
{
get { return null; }
}
public string ScriptEnginePath
{
get { return m_ScriptBasePath; }
}
public string ScriptClassName
{
get { return "XMREngineScript"; }
}
public string ScriptBaseClassName
{
get { return typeof (XMRInstance).FullName; }
}
public ParameterInfo[] ScriptBaseClassParameters
{
get { return typeof(XMRInstance).GetConstructor (new Type[] { typeof (WaitHandle) }).GetParameters (); }
}
public string[] ScriptReferencedAssemblies
{
get { return scriptReferencedAssemblies; }
}
public void WakeUpOne()
{
lock (m_WakeUpLock)
{
m_WakeUpOne++;
Monitor.Pulse(m_WakeUpLock);
}
}
public void AddThread(Thread thd, XMRScriptThread xthd)
{
lock(m_AllThreads)
m_AllThreads.Add(thd, xthd);
}
public void RemoveThread(Thread thd)
{
lock(m_AllThreads)
m_AllThreads.Remove(thd);
}
public XMRScriptThread CurrentScriptThread ()
{
XMRScriptThread st;
lock (m_AllThreads)
m_AllThreads.TryGetValue (Thread.CurrentThread, out st);
return st;
}
public void Initialise(IConfigSource config)
{
TraceCalls("[XMREngine]: Initialize entry");
m_ConfigSource = config;
////foreach (IConfig icfg in config.Configs) {
//// m_log.Debug("[XMREngine]: Initialise: configs[" + icfg.Name + "]");
//// foreach (string key in icfg.GetKeys ()) {
//// m_log.Debug("[XMREngine]: Initialise: " + key + "=" + icfg.GetExpanded (key));
//// }
////}
m_Enabled = false;
m_Config = config.Configs["XMREngine"];
if (m_Config == null)
{
m_log.Info("[XMREngine]: no config, assuming disabled");
return;
}
m_Enabled = m_Config.GetBoolean("Enabled", false);
m_log.InfoFormat("[XMREngine]: config enabled={0}", m_Enabled);
if (!m_Enabled)
return;
Type uThreadType = null;
uThreadType = typeof (ScriptUThread_Nul);
uThreadCtor = uThreadType.GetConstructor (new Type[] { typeof (XMRInstance) });
m_UseSourceHashCode = m_Config.GetBoolean("UseSourceHashCode", false);
numThreadScriptWorkers = m_Config.GetInt("NumThreadScriptWorkers", 3);
m_ScriptThreads = new XMRScriptThread[numThreadScriptWorkers];
m_TraceCalls = m_Config.GetBoolean("TraceCalls", false);
m_Verbose = m_Config.GetBoolean("Verbose", false);
m_ScriptDebug = m_Config.GetBoolean("ScriptDebug", false);
// Verify that our ScriptEventCode's match OpenSim's scriptEvent's.
bool err = false;
for (int i = 0; i < 32; i ++)
{
string mycode = "undefined";
string oscode = "undefined";
try
{
mycode = ((ScriptEventCode)i).ToString();
Convert.ToInt32(mycode);
mycode = "undefined";
}
catch { }
try
{
oscode = ((OpenSim.Region.Framework.Scenes.scriptEvents)(1 << i)).ToString();
Convert.ToInt32(oscode);
oscode = "undefined";
}
catch { }
if (mycode != oscode)
{
m_log.ErrorFormat("[XMREngine]: {0} mycode={1}, oscode={2}", i, mycode, oscode);
err = true;
}
}
if (err)
{
m_Enabled = false;
return;
}
for (int i = 0; i < numThreadScriptWorkers; i ++)
{
m_ScriptThreads[i] = new XMRScriptThread(this, i);;
}
m_SleepThread = StartMyThread(RunSleepThread, "xmrengine sleep", ThreadPriority.Normal);
m_StackSize = m_Config.GetInt("ScriptStackSize", 2048) << 10;
m_HeapSize = m_Config.GetInt("ScriptHeapSize", 1024) << 10;
m_log.InfoFormat("[XMREngine]: Enabled, {0}.{1} Meg (0x{2}) stacks",
(m_StackSize >> 20).ToString (),
(((m_StackSize % 0x100000) * 1000)
>> 20).ToString ("D3"),
m_StackSize.ToString ("X"));
m_log.InfoFormat("[XMREngine]: ... {0}.{1} Meg (0x{2}) heaps",
(m_HeapSize >> 20).ToString (),
(((m_HeapSize % 0x100000) * 1000)
>> 20).ToString ("D3"),
m_HeapSize.ToString ("X"));
m_MaintenanceInterval = m_Config.GetInt("MaintenanceInterval", 10);
if (m_MaintenanceInterval > 0)
{
m_MaintenanceTimer = new System.Timers.Timer(m_MaintenanceInterval * 60000);
m_MaintenanceTimer.Elapsed += DoMaintenance;
m_MaintenanceTimer.Start();
}
MainConsole.Instance.Commands.AddCommand("xmr", false,
"xmr",
"xmr [...|help|...] ...",
"Run xmr script engine commands",
RunTest);
TraceCalls("[XMREngine]: Initialize successful");
}
public void AddRegion(Scene scene)
{
if (!m_Enabled)
return;
TraceCalls("[XMREngine]: XMREngine.AddRegion({0})", scene.RegionInfo.RegionName);
m_Scene = scene;
m_Scene.RegisterModuleInterface<IScriptModule>(this);
m_ScriptBasePath = m_Config.GetString ("ScriptBasePath", "ScriptData");
m_ScriptBasePath = Path.Combine (m_ScriptBasePath, scene.RegionInfo.RegionID.ToString());
Directory.CreateDirectory(m_ScriptBasePath);
m_Scene.EventManager.OnRezScript += OnRezScript;
m_Scene.StackModuleInterface<IScriptModule>(this);
}
private void OneTimeLateInitialization ()
{
// Build list of defined APIs and their 'this' types and define a field in XMRInstanceSuperType.
ApiManager am = new ApiManager ();
Dictionary<string,Type> apiCtxTypes = new Dictionary<string,Type> ();
foreach (string api in am.GetApis ())
{
m_log.Debug ("[XMREngine]: adding api " + api);
IScriptApi scriptApi = am.CreateApi (api);
Type apiCtxType = scriptApi.GetType ();
if (api == "LSL") apiCtxType = typeof (XMRLSL_Api);
apiCtxTypes[api] = apiCtxType;
}
if (ScriptCodeGen.xmrInstSuperType == null) // Only create type once!
{
// Start creating type XMRInstanceSuperType that contains a field
// m_ApiManager_<APINAME> that points to the per-instance context
// struct for that API, ie, the 'this' value passed to all methods
// in that API. It is in essence:
// public class XMRInstanceSuperType : XMRInstance {
// public XMRLSL_Api m_ApiManager_LSL; // 'this' value for all ll...() functions
// public MOD_Api m_ApiManager_MOD; // 'this' value for all mod...() functions
// public OSSL_Api m_ApiManager_OSSL; // 'this' value for all os...() functions
// ....
// }
AssemblyName assemblyName = new AssemblyName ();
assemblyName.Name = "XMRInstanceSuperAssembly";
AssemblyBuilder assemblyBuilder = Thread.GetDomain().DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
#if DEBUG
Type daType = typeof(DebuggableAttribute);
ConstructorInfo daCtor = daType.GetConstructor(new Type[] { typeof(DebuggableAttribute.DebuggingModes) });
CustomAttributeBuilder daBuilder = new CustomAttributeBuilder(daCtor, new object[] {
DebuggableAttribute.DebuggingModes.DisableOptimizations |
DebuggableAttribute.DebuggingModes.EnableEditAndContinue |
DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints |
DebuggableAttribute.DebuggingModes.Default });
assemblyBuilder.SetCustomAttribute(daBuilder);
#endif
ModuleBuilder moduleBuilder = assemblyBuilder.DefineDynamicModule("XMRInstanceSuperModule");
TypeBuilder typeBuilder = moduleBuilder.DefineType("XMRInstanceSuperType", TypeAttributes.Public | TypeAttributes.Class);
typeBuilder.SetParent(typeof (XMRInstance));
foreach (string apiname in apiCtxTypes.Keys)
{
string fieldName = "m_ApiManager_" + apiname;
typeBuilder.DefineField (fieldName, apiCtxTypes[apiname], FieldAttributes.Public);
}
// Finalize definition of XMRInstanceSuperType.
// Give the compiler a short name to reference it by,
// otherwise it will try to use the AssemblyQualifiedName
// and fail miserably.
ScriptCodeGen.xmrInstSuperType = typeBuilder.CreateType ();
ScriptObjWriter.DefineInternalType ("xmrsuper", ScriptCodeGen.xmrInstSuperType);
}
// Tell the compiler about all the constants and methods for each API.
// We also tell the compiler how to get the per-instance context for each API
// by reading the corresponding m_ApiManager_<APINAME> field of XMRInstanceSuperType.
foreach (KeyValuePair<string,Type> kvp in apiCtxTypes)
{
// get API name and the corresponding per-instance context type
string api = kvp.Key;
Type apiCtxType = kvp.Value;
// give script compiler an abbreviated name for the API context type
ScriptObjWriter.DefineInternalType ("apimanager_" + api, apiCtxType);
// this field tells the compiled code where the per-instance API context object is
// eg, for the OSSL API, it is in ((XMRInstanceSuperType)inst).m_ApiManager_OSSL
string fieldName = "m_ApiManager_" + api;
FieldInfo fieldInfo = ScriptCodeGen.xmrInstSuperType.GetField (fieldName);
m_XMRInstanceApiCtxFieldInfos[api] = fieldInfo;
// now tell the compiler about the constants and methods for the API
ScriptConst.AddInterfaceConstants (null, apiCtxType.GetFields ());
TokenDeclInline.AddInterfaceMethods (null, apiCtxType.GetMethods (), fieldInfo);
}
// Add sim-specific APIs to the compiler.
IScriptModuleComms comms = m_Scene.RequestModuleInterface<IScriptModuleComms> ();
if (comms != null)
{
// Add methods to list of built-in functions.
Delegate[] methods = comms.GetScriptInvocationList ();
foreach (Delegate m in methods)
{
MethodInfo mi = m.Method;
try
{
CommsCallCodeGen cccg = new CommsCallCodeGen (mi, comms, m_XMRInstanceApiCtxFieldInfos["MOD"]);
Verbose ("[XMREngine]: added comms function " + cccg.fullName);
}
catch (Exception e)
{
m_log.Error ("[XMREngine]: failed to add comms function " + mi.Name);
m_log.Error ("[XMREngine]: - " + e.ToString ());
}
}
// Add constants to list of built-in constants.
Dictionary<string,object> consts = comms.GetConstants ();
foreach (KeyValuePair<string,object> kvp in consts)
{
try
{
ScriptConst sc = ScriptConst.AddConstant (kvp.Key, kvp.Value);
Verbose ("[XMREngine]: added comms constant " + sc.name);
}
catch (Exception e)
{
m_log.Error ("[XMREngine]: failed to add comms constant " + kvp.Key);
m_log.Error ("[XMREngine]: - " + e.Message);
}
}
}
else
{
Verbose ("[XMREngine]: comms not enabled");
}
}
/**
* @brief Generate code for the calls to the comms functions.
* It is a tRUlY EvIL interface.
* To call the function we must call an XMRInstanceSuperType.m_ApiManager_MOD.modInvoker?()
* method passing it the name of the function as a string and the script
* argument list wrapped up in an object[] array. The modInvoker?() methods
* do some sick type conversions (with corresponding mallocs) so we can't
* call the methods directly.
*/
private class CommsCallCodeGen : TokenDeclInline
{
private static Type[] modInvokerArgTypes = new Type[] { typeof (string), typeof (object[]) };
public static FieldInfo xmrInstModApiCtxField;
private MethodInfo modInvokerMeth;
private string methName;
/**
* @brief Constructor
* @param mi = method to make available to scripts
* mi.Name = name that is used by scripts
* mi.GetParameters() = parameter list as defined by module
* includes the 'UUID host','UUID script' parameters that script does not see
* allowed types for script-visible parameters are as follows:
* Single -> float
* Int32 -> integer
* OpenMetaverse.UUID -> key
* Object[] -> list
* OpenMetaverse.Quaternion -> rotation
* String -> string
* OpenMetaverse.Vector3 -> vector
* mi.ReturnType = return type as defined by module
* types are same as allowed for parameters
* @param comms = comms module the method came from
* @param apictxfi = what field in XMRInstanceSuperType the 'this' value is for this method
*/
public CommsCallCodeGen (MethodInfo mi, IScriptModuleComms comms, FieldInfo apictxfi)
: base (null, false, NameArgSig (mi), RetType (mi))
{
methName = mi.Name;
string modInvokerName = comms.LookupModInvocation (methName);
if (modInvokerName == null)
throw new Exception("cannot find comms method " + methName);
modInvokerMeth = typeof(MOD_Api).GetMethod(modInvokerName, modInvokerArgTypes);
xmrInstModApiCtxField = apictxfi;
}
// script-visible name(argtype,...) signature string
private static string NameArgSig (MethodInfo mi)
{
StringBuilder sb = new StringBuilder ();
sb.Append (mi.Name);
sb.Append ('(');
ParameterInfo[] mps = mi.GetParameters ();
for (int i = 2; i < mps.Length; i ++)
{
ParameterInfo pi = mps[i];
if (i > 2) sb.Append (',');
sb.Append (ParamType (pi.ParameterType));
}
sb.Append (')');
return sb.ToString ();
}
// script-visible return type
// note that although we support void, the comms stuff does not
private static TokenType RetType (MethodInfo mi)
{
Type rt = mi.ReturnType;
if (rt == typeof (float)) return new TokenTypeFloat (null);
if (rt == typeof (int)) return new TokenTypeInt (null);
if (rt == typeof (object[])) return new TokenTypeList (null);
if (rt == typeof (OpenMetaverse.UUID)) return new TokenTypeKey (null);
if (rt == typeof (OpenMetaverse.Quaternion)) return new TokenTypeRot (null);
if (rt == typeof (string)) return new TokenTypeStr (null);
if (rt == typeof (OpenMetaverse.Vector3)) return new TokenTypeVec (null);
if (rt == null || rt == typeof (void)) return new TokenTypeVoid (null);
throw new Exception ("unsupported return type " + rt.Name);
}
// script-visible parameter type
private static string ParamType (Type t)
{
if (t == typeof (float)) return "float";
if (t == typeof (int)) return "integer";
if (t == typeof (OpenMetaverse.UUID)) return "key";
if (t == typeof (object[])) return "list";
if (t == typeof (OpenMetaverse.Quaternion)) return "rotation";
if (t == typeof (string)) return "string";
if (t == typeof (OpenMetaverse.Vector3)) return "vector";
throw new Exception ("unsupported parameter type " + t.Name);
}
/**
* @brief Called by the compiler to generate a call to the comms function.
* @param scg = which script is being compiled
* @param errorAt = where in the source code the call is being made (for error messages)
* @param result = a temp location to put the return value in if any
* @param args = array of script-visible arguments being passed to the function
*/
public override void CodeGen (ScriptCodeGen scg, Token errorAt, CompValuTemp result, CompValu[] args)
{
// Set up 'this' pointer for modInvoker?() = value from ApiManager.CreateApi("MOD").
scg.PushXMRInst ();
scg.ilGen.Emit (errorAt, OpCodes.Castclass, xmrInstModApiCtxField.DeclaringType);
scg.ilGen.Emit (errorAt, OpCodes.Ldfld, xmrInstModApiCtxField);
// Set up 'fname' argument to modInvoker?() = name of the function to be called.
scg.ilGen.Emit (errorAt, OpCodes.Ldstr, methName);
// Set up 'parms' argument to modInvoker?() = object[] of the script-visible parameters,
// in their LSL-wrapped form. Of course, the modInvoker?() method will malloc yet another
// object[] and type-convert these parameters one-by-one with another round of unwrapping
// and wrapping.
// Types allowed in this object[]:
// LSL_Float, LSL_Integer, LSL_Key, LSL_List, LSL_Rotation, LSL_String, LSL_Vector
int nargs = args.Length;
scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, nargs);
scg.ilGen.Emit (errorAt, OpCodes.Newarr, typeof (object));
for (int i = 0; i < nargs; i ++)
{
scg.ilGen.Emit (errorAt, OpCodes.Dup);
scg.ilGen.Emit (errorAt, OpCodes.Ldc_I4, i);
// get location and type of argument
CompValu arg = args[i];
TokenType argtype = arg.type;
// if already in a form acceptable to modInvoker?(),
// just push it to the stack and convert to object
// by boxing it if necessary
// but if something like a double, int, string, etc
// push to stack converting to the LSL-wrapped type
// then convert to object by boxing if necessary
Type boxit = null;
if (argtype is TokenTypeLSLFloat)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_Float);
}
else if (argtype is TokenTypeLSLInt)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_Integer);
}
else if (argtype is TokenTypeLSLKey)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_Key);
}
else if (argtype is TokenTypeList)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_List);
}
else if (argtype is TokenTypeRot)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_Rotation);
}
else if (argtype is TokenTypeLSLString)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_String);
}
else if (argtype is TokenTypeVec)
{
args[i].PushVal (scg, errorAt);
boxit = typeof (LSL_Vector);
}
else if (argtype is TokenTypeFloat)
{
args[i].PushVal (scg, errorAt, new TokenTypeLSLFloat (argtype));
boxit = typeof (LSL_Float);
}
else if (argtype is TokenTypeInt)
{
args[i].PushVal (scg, errorAt, new TokenTypeLSLInt (argtype));
boxit = typeof (LSL_Integer);
}
else if (argtype is TokenTypeKey)
{
args[i].PushVal (scg, errorAt, new TokenTypeLSLKey (argtype));
boxit = typeof (LSL_Key);
}
else if (argtype is TokenTypeStr)
{
args[i].PushVal (scg, errorAt, new TokenTypeLSLString (argtype));
boxit = typeof (LSL_String);
}
else
throw new Exception ("unsupported arg type " + argtype.GetType ().Name);
if (boxit.IsValueType)
scg.ilGen.Emit (errorAt, OpCodes.Box, boxit);
// pop the object into the object[]
scg.ilGen.Emit (errorAt, OpCodes.Stelem, typeof (object));
}
// Call the modInvoker?() method.
// It leaves an LSL-wrapped type on the stack.
if (modInvokerMeth.IsVirtual)
scg.ilGen.Emit (errorAt, OpCodes.Callvirt, modInvokerMeth);
else
scg.ilGen.Emit (errorAt, OpCodes.Call, modInvokerMeth);
// The 3rd arg to Pop() is the type on the stack,
// ie, what modInvoker?() actually returns.
// The Pop() method will wrap/unwrap as needed.
Type retSysType = modInvokerMeth.ReturnType;
if (retSysType == null)
retSysType = typeof (void);
TokenType retTokType = TokenType.FromSysType (errorAt, retSysType);
result.Pop (scg, errorAt, retTokType);
}
}
/**
* @brief Called late in shutdown procedure,
* after the 'Shutting down..." message.
*/
public void RemoveRegion(Scene scene)
{
if (!m_Enabled)
return;
TraceCalls("[XMREngine]: XMREngine.RemoveRegion({0})", scene.RegionInfo.RegionName);
// Write script states out to .state files so it will be
// available when the region is restarted.
DoMaintenance(null, null);
// Stop executing script threads and wait for final
// one to finish (ie, script gets to CheckRun() call).
m_Exiting = true;
m_Scene.EventManager.OnFrame -= OnFrame;
m_Scene.EventManager.OnRezScript -= OnRezScript;
m_Scene.EventManager.OnRemoveScript -= OnRemoveScript;
m_Scene.EventManager.OnScriptReset -= OnScriptReset;
m_Scene.EventManager.OnStartScript -= OnStartScript;
m_Scene.EventManager.OnStopScript -= OnStopScript;
m_Scene.EventManager.OnGetScriptRunning -= OnGetScriptRunning;
m_Scene.EventManager.OnShutdown -= OnShutdown;
for (int i = 0; i < numThreadScriptWorkers; i ++)
{
XMRScriptThread scriptThread = m_ScriptThreads[i];
if (scriptThread != null)
{
scriptThread.WakeUpScriptThread();
Monitor.PulseAll (m_WakeUpLock);
scriptThread.Terminate();
m_ScriptThreads[i] = null;
}
}
if (m_SleepThread != null)
{
lock (m_SleepQueue)
{
Monitor.PulseAll (m_SleepQueue);
}
if(!m_SleepThread.Join(250))
m_SleepThread.Abort();
m_SleepThread = null;
}
m_Enabled = false;
m_Scene = null;
}
public void RegionLoaded(Scene scene)
{
if (!m_Enabled)
return;
TraceCalls("[XMREngine]: XMREngine.RegionLoaded({0})", scene.RegionInfo.RegionName);
m_Scene.EventManager.OnFrame += OnFrame;
m_Scene.EventManager.OnRemoveScript += OnRemoveScript;
m_Scene.EventManager.OnScriptReset += OnScriptReset;
m_Scene.EventManager.OnStartScript += OnStartScript;
m_Scene.EventManager.OnStopScript += OnStopScript;
m_Scene.EventManager.OnGetScriptRunning += OnGetScriptRunning;
m_Scene.EventManager.OnShutdown += OnShutdown;
InitEvents();
}
public void StartProcessing()
{
m_log.Debug ("[XMREngine]: StartProcessing entry");
m_Scene.EventManager.TriggerEmptyScriptCompileQueue (0, "");
m_StartProcessing = true;
for (int i = 0; i < numThreadScriptWorkers; i ++) {
WakeUpOne();
}
m_log.Debug ("[XMREngine]: StartProcessing return");
}
public void Close()
{
TraceCalls("[XMREngine]: XMREngine.Close()");
}
private void RunTest (string module, string[] args)
{
if (args.Length < 2)
{
m_log.Info ("[XMREngine]: missing command, try 'xmr help'");
return;
}
switch (args[1])
{
case "cvv":
switch (args.Length)
{
case 2:
m_log.InfoFormat ("[XMREngine]: compiled version value = {0}",
ScriptCodeGen.COMPILED_VERSION_VALUE);
break;
case 3:
try
{
ScriptCodeGen.COMPILED_VERSION_VALUE = Convert.ToInt32 (args[2]);
}
catch
{
m_log.Error ("[XMREngine]: bad/missing version number");
}
break;
default:
m_log.Error ("[XMREngine]: xmr cvv [<new_compiled_version_value>]");
break;
}
break;
case "echo":
for (int i = 0; i < args.Length; i ++)
m_log.Info ("[XMREngine]: echo[" + i + "]=<" + args[i] + ">");
break;
case "gc":
GC.Collect();
break;
case "help":
case "?":
m_log.Info ("[XMREngine]: xmr cvv [<newvalue>] - show/set compiled version value");
m_log.Info ("[XMREngine]: xmr gc");
m_log.Info ("[XMREngine]: xmr ls [-help ...]");
m_log.Info ("[XMREngine]: xmr mvv [<newvalue>] - show/set migration version value");
m_log.Info ("[XMREngine]: xmr pev [-help ...] - post event");
m_log.Info ("[XMREngine]: xmr reset [-help ...]");
m_log.Info ("[XMREngine]: xmr resume - resume script processing");
m_log.Info ("[XMREngine]: xmr suspend - suspend script processing");
m_log.Info ("[XMREngine]: xmr tracecalls [yes | no]");
m_log.Info ("[XMREngine]: xmr verbose [yes | no]");
break;
case "ls":
XmrTestLs (args, 2);
break;
case "mvv":
switch (args.Length)
{
case 2:
m_log.InfoFormat ("[XMREngine]: migration version value = {0}",
XMRInstance.migrationVersion);
break;
case 3:
try
{
int mvv = Convert.ToInt32 (args[2]);
if ((mvv < 0) || (mvv > 255)) throw new Exception ("out of range");
XMRInstance.migrationVersion = (byte) mvv;
}
catch (Exception e)
{
m_log.Error ("[XMREngine]: bad/missing version number (" + e.Message + ")");
}
break;
default:
m_log.Error ("[XMREngine]: xmr mvv [<new_migration_version_value>]");
break;
}
break;
case "pev":
XmrTestPev (args, 2);
break;
case "reset":
XmrTestReset (args, 2);
break;
case "resume":
m_log.Info ("[XMREngine]: resuming scripts");
m_SuspendScriptThreadFlag = false;
for (int i = 0; i < numThreadScriptWorkers; i ++)
m_ScriptThreads[i].WakeUpScriptThread();
Monitor.PulseAll(m_WakeUpLock);
break;
case "suspend":
m_log.Info ("[XMREngine]: suspending scripts");
m_SuspendScriptThreadFlag = true;
for (int i = 0; i < numThreadScriptWorkers; i ++)
m_ScriptThreads[i].WakeUpScriptThread();
Monitor.PulseAll(m_WakeUpLock);
break;
case "tracecalls":
if (args.Length > 2)
m_TraceCalls = (args[2][0] & 1) != 0;
m_log.Info ("[XMREngine]: tracecalls " + (m_TraceCalls ? "yes" : "no"));
break;
case "verbose":
if (args.Length > 2)
m_Verbose = (args[2][0] & 1) != 0;
m_log.Info ("[XMREngine]: verbose " + (m_Verbose ? "yes" : "no"));
break;
default:
m_log.Error ("[XMREngine]: unknown command " + args[1] + ", try 'xmr help'");
break;
}
}
// Not required when not using IScriptInstance
//
public IScriptWorkItem QueueEventHandler(object parms)
{
return null;
}
public Scene World
{
get { return m_Scene; }
}
public IScriptModule ScriptModule
{
get { return this; }
}
public void SaveAllState()
{
m_log.Error("[XMREngine]: XMREngine.SaveAllState() called!!");
}
#pragma warning disable 0067
public event ScriptRemoved OnScriptRemoved;
public event ObjectRemoved OnObjectRemoved;
#pragma warning restore 0067
// Events targeted at a specific script
// ... like listen() for an llListen() call
//
public bool PostScriptEvent(UUID itemID, EventParams parms)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null) return false;
TraceCalls("[XMREngine]: XMREngine.PostScriptEvent({0},{1})", itemID.ToString(), parms.EventName);
instance.PostEvent(parms);
return true;
}
// Events targeted at all scripts in the given prim.
// localID = which prim
// parms = event to post
//
public bool PostObjectEvent (uint localID, EventParams parms)
{
SceneObjectPart part = m_Scene.GetSceneObjectPart(localID);
if (part == null)
return false;
TraceCalls("[XMREngine]: XMREngine.PostObjectEvent({0},{1})", localID.ToString(), parms.EventName);
// In SecondLife, attach events go to all scripts of all prims
// in a linked object. So here we duplicate that functionality,
// as all we ever get is a single attach event for the whole
// object.
if (parms.EventName == "attach")
{
bool posted = false;
foreach (SceneObjectPart primpart in part.ParentGroup.Parts)
{
posted |= PostPrimEvent (primpart, parms);
}
return posted;
}
// Other events go to just the scripts in that prim.
return PostPrimEvent (part, parms);
}
private bool PostPrimEvent (SceneObjectPart part, EventParams parms)
{
UUID partUUID = part.UUID;
// Get list of script instances running in the object.
XMRInstance[] objInstArray;
lock (m_InstancesDict)
{
if (!m_ObjectInstArray.TryGetValue (partUUID, out objInstArray))
return false;
if (objInstArray == null)
{
objInstArray = RebuildObjectInstArray (partUUID);
m_ObjectInstArray[partUUID] = objInstArray;
}
}
// Post event to all script instances in the object.
if (objInstArray.Length <= 0) return false;
foreach (XMRInstance inst in objInstArray)
inst.PostEvent (parms);
return true;
}
public DetectParams GetDetectParams(UUID itemID, int number)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null)
return null;
return instance.GetDetectParams(number);
}
public void SetMinEventDelay(UUID itemID, double delay)
{
}
public int GetStartParameter(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null)
return 0;
return instance.StartParam;
}
// This is the "set running" method
//
public void SetScriptState(UUID itemID, bool state, bool self)
{
SetScriptState (itemID, state);
}
public void SetScriptState(UUID itemID, bool state)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.Running = state;
}
// Control display of the "running" checkbox
//
public bool GetScriptState(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null)
return false;
return instance.Running;
}
public void SetState(UUID itemID, string newState)
{
TraceCalls("[XMREngine]: XMREngine.SetState({0},{1})", itemID.ToString(), newState);
}
public void ApiResetScript(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.ApiReset();
}
public void ResetScript(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
{
IUrlModule urlModule = m_Scene.RequestModuleInterface<IUrlModule>();
if (urlModule != null)
urlModule.ScriptRemoved(itemID);
instance.Reset();
}
}
public IConfig Config
{
get { return m_Config; }
}
public IConfigSource ConfigSource
{
get { return m_ConfigSource; }
}
public string ScriptEngineName
{
get { return "XMREngine"; }
}
public IScriptApi GetApi(UUID itemID, string name)
{
FieldInfo fi;
if (!m_XMRInstanceApiCtxFieldInfos.TryGetValue (name, out fi))
return null;
XMRInstance inst = GetInstance (itemID);
if (inst == null) return null;
return (IScriptApi)fi.GetValue (inst);
}
/**
* @brief Get script's current state as an XML string
* - called by "Take", "Take Copy" and when object deleted (ie, moved to Trash)
* This includes the .state file
*/
public string GetXMLState(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null)
return String.Empty;
TraceCalls("[XMREngine]: XMREngine.GetXMLState({0})", itemID.ToString());
if (!instance.m_HasRun)
return String.Empty;
XmlDocument doc = new XmlDocument();
/*
* Set up <State Engine="XMREngine" UUID="itemID" Asset="assetID"> tag.
*/
XmlElement stateN = doc.CreateElement("", "State", "");
doc.AppendChild(stateN);
XmlAttribute engineA = doc.CreateAttribute("", "Engine", "");
engineA.Value = ScriptEngineName;
stateN.Attributes.Append(engineA);
XmlAttribute uuidA = doc.CreateAttribute("", "UUID", "");
uuidA.Value = itemID.ToString();
stateN.Attributes.Append(uuidA);
XmlAttribute assetA = doc.CreateAttribute("", "Asset", "");
string assetID = instance.AssetID.ToString();
assetA.Value = assetID;
stateN.Attributes.Append(assetA);
// Get <ScriptState>...</ScriptState> item that hold's script's state.
// This suspends the script if necessary then takes a snapshot.
XmlElement scriptStateN = instance.GetExecutionState(doc);
stateN.AppendChild(scriptStateN);
return doc.OuterXml;
}
// Set script's current state from an XML string
// - called just before a script is instantiated
// So we write the .state file so the .state file will be seen when
// the script is instantiated.
public bool SetXMLState(UUID itemID, string xml)
{
XmlDocument doc = new XmlDocument();
try
{
doc.LoadXml(xml);
}
catch
{
return false;
}
TraceCalls("[XMREngine]: XMREngine.SetXMLState({0})", itemID.ToString());
// Make sure <State Engine="XMREngine"> so we know it is in our
// format.
XmlElement stateN = (XmlElement)doc.SelectSingleNode("State");
if (stateN == null)
return false;
if (stateN.GetAttribute("Engine") != ScriptEngineName)
return false;
// <ScriptState>...</ScriptState> contains contents of .state file.
XmlElement scriptStateN = (XmlElement)stateN.SelectSingleNode("ScriptState");
if (scriptStateN == null)
return false;
string sen = stateN.GetAttribute("Engine");
if ((sen == null) || (sen != ScriptEngineName))
return false;
XmlAttribute assetA = doc.CreateAttribute("", "Asset", "");
assetA.Value = stateN.GetAttribute("Asset");
scriptStateN.Attributes.Append(assetA);
// Write out the .state file with the <ScriptState ...>...</ScriptState> XML text
string statePath = XMRInstance.GetStateFileName(m_ScriptBasePath, itemID);
FileStream ss = File.Create(statePath);
StreamWriter sw = new StreamWriter(ss);
sw.Write(scriptStateN.OuterXml);
sw.Close();
ss.Close();
return true;
}
public bool PostScriptEvent(UUID itemID, string name, Object[] p)
{
if (!m_Enabled)
return false;
TraceCalls("[XMREngine]: XMREngine.PostScriptEvent({0},{1})", itemID.ToString(), name);
return PostScriptEvent(itemID, new EventParams(name, p, zeroDetectParams));
}
public bool PostObjectEvent(UUID itemID, string name, Object[] p)
{
if (!m_Enabled)
return false;
TraceCalls("[XMREngine]: XMREngine.PostObjectEvent({0},{1})", itemID.ToString(), name);
SceneObjectPart part = m_Scene.GetSceneObjectPart(itemID);
if (part == null)
return false;
return PostObjectEvent(part.LocalId, new EventParams(name, p, zeroDetectParams));
}
// about the 3523rd entrypoint for a script to put itself to sleep
public void SleepScript(UUID itemID, int delay)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.Sleep (delay);
}
// Get a script instance loaded, compiling it if necessary
//
// localID = the object as a whole, may contain many scripts
// itemID = this instance of the script in this object
// script = script source code
// startParam = value passed to 'on_rez' event handler
// postOnRez = true to post an 'on_rez' event to script on load
// defEngine = default script engine
// stateSource = post this event to script on load
public void OnRezScript(uint localID, UUID itemID, string script,
int startParam, bool postOnRez, string defEngine, int stateSource)
{
SceneObjectPart part = m_Scene.GetSceneObjectPart(localID);
TaskInventoryItem item = part.Inventory.GetInventoryItem(itemID);
if (!m_LateInit)
{
m_LateInit = true;
OneTimeLateInitialization ();
}
TraceCalls("[XMREngine]: OnRezScript(...,{0},...)", itemID.ToString());
// Assume script uses the default engine, whatever that is.
string engineName = defEngine;
// Very first line might contain "//" scriptengine ":".
string firstline = "";
if (script.StartsWith("//")) {
int lineEnd = script.IndexOf('\n');
if (lineEnd > 1) firstline = script.Substring(0, lineEnd).Trim();
int colon = firstline.IndexOf(':');
if (colon >= 2) {
engineName = firstline.Substring(2, colon - 2).Trim();
if (engineName == "") engineName = defEngine;
}
}
// Make sure the default or requested engine is us.
if (engineName != ScriptEngineName) {
// Not us, if requested engine exists, silently ignore script and let
// requested engine handle it.
IScriptModule[] engines = m_Scene.RequestModuleInterfaces<IScriptModule> ();
foreach (IScriptModule eng in engines)
{
if (eng.ScriptEngineName == engineName)
return;
}
// Requested engine not defined, warn on console.
// Then we try to handle it if we're the default engine, else we ignore it.
m_log.Warn ("[XMREngine]: " + itemID.ToString() + " requests undefined/disabled engine " + engineName);
m_log.Info ("[XMREngine]: - " + part.GetWorldPosition ());
m_log.Info ("[XMREngine]: first line: " + firstline);
if (defEngine != ScriptEngineName)
{
m_log.Info ("[XMREngine]: leaving it to the default script engine (" + defEngine + ") to process it");
return;
}
m_log.Info ("[XMREngine]: will attempt to processing it anyway as default script engine");
}
// Put on object/instance lists.
XMRInstance instance = (XMRInstance)Activator.CreateInstance (ScriptCodeGen.xmrInstSuperType);
instance.m_LocalID = localID;
instance.m_ItemID = itemID;
instance.m_SourceCode = script;
instance.m_StartParam = startParam;
instance.m_PostOnRez = postOnRez;
instance.m_StateSource = (StateSource)stateSource;
instance.m_Part = part;
instance.m_PartUUID = part.UUID;
instance.m_Item = item;
instance.m_DescName = part.Name + ":" + item.Name;
instance.m_IState = XMRInstState.CONSTRUCT;
lock (m_InstancesDict)
{
m_LockedDict = "RegisterInstance";
// Insert on internal list of all scripts being handled by this engine instance.
m_InstancesDict[instance.m_ItemID] = instance;
// Insert on internal list of all scripts being handled by this engine instance
// that are part of the object.
List<UUID> itemIDList;
if (!m_ObjectItemList.TryGetValue(instance.m_PartUUID, out itemIDList))
{
itemIDList = new List<UUID>();
m_ObjectItemList[instance.m_PartUUID] = itemIDList;
}
if (!itemIDList.Contains(instance.m_ItemID))
{
itemIDList.Add(instance.m_ItemID);
m_ObjectInstArray[instance.m_PartUUID] = null;
}
m_LockedDict = "~RegisterInstance";
}
// Compile and load it.
lock (m_ScriptErrors)
m_ScriptErrors.Remove (instance.m_ItemID);
LoadThreadWork (instance);
}
/**
* @brief This routine instantiates one script.
*/
private void LoadThreadWork (XMRInstance instance)
{
// Compile and load the script in memory.
ArrayList errors = new ArrayList();
Exception initerr = null;
try
{
instance.Initialize(this, m_ScriptBasePath, m_StackSize, m_HeapSize, errors);
}
catch (Exception e1)
{
initerr = e1;
}
if ((initerr != null) && !instance.m_ForceRecomp)
{
UUID itemID = instance.m_ItemID;
Verbose ("[XMREngine]: {0}/{2} first load failed ({1}), retrying after recompile",
itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString());
Verbose ("[XMREngine]:\n{0}", initerr.ToString ());
initerr = null;
errors = new ArrayList();
instance.m_ForceRecomp = true;
try
{
instance.Initialize(this, m_ScriptBasePath, m_StackSize, m_HeapSize, errors);
}
catch (Exception e2)
{
initerr = e2;
}
}
if (initerr != null)
{
UUID itemID = instance.m_ItemID;
Verbose ("[XMREngine]: Error starting script {0}/{2}: {1}",
itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString());
if (initerr.Message != "compilation errors")
{
Verbose ("[XMREngine]: - " + instance.m_Part.GetWorldPosition () + " " + instance.m_DescName);
Verbose ("[XMREngine]: exception:\n{0}", initerr.ToString());
}
OnRemoveScript (0, itemID);
// Post errors where GetScriptErrors() can see them.
if (errors.Count == 0)
errors.Add(initerr.Message);
else
{
foreach (Object err in errors)
{
if (m_ScriptDebug)
m_log.DebugFormat ("[XMREngine]: {0}", err.ToString());
}
}
lock (m_ScriptErrors)
m_ScriptErrors[instance.m_ItemID] = errors;
return;
}
// Tell GetScriptErrors() that we have finished compiling/loading
// successfully (by posting a 0 element array).
lock (m_ScriptErrors)
{
if (instance.m_IState != XMRInstState.CONSTRUCT) throw new Exception("bad state");
m_ScriptErrors[instance.m_ItemID] = noScriptErrors;
}
// Transition from CONSTRUCT->ONSTARTQ and give to RunScriptThread().
// Put it on the start queue so it will run any queued event handlers,
// such as state_entry() or on_rez(). If there aren't any queued, it
// will just go to idle state when RunOne() tries to dequeue an event.
lock (instance.m_QueueLock)
{
if (instance.m_IState != XMRInstState.CONSTRUCT)
throw new Exception("bad state");
instance.m_IState = XMRInstState.ONSTARTQ;
if (!instance.m_Running)
instance.EmptyEventQueues ();
}
QueueToStart(instance);
}
public void OnRemoveScript(uint localID, UUID itemID)
{
TraceCalls("[XMREngine]: OnRemoveScript(...,{0})", itemID.ToString());
// Remove from our list of known scripts.
// After this, no more events can queue because we won't be
// able to translate the itemID to an XMRInstance pointer.
XMRInstance instance = null;
lock (m_InstancesDict)
{
m_LockedDict = "OnRemoveScript:" + itemID.ToString();
// Tell the instance to free off everything it can.
if (!m_InstancesDict.TryGetValue(itemID, out instance))
{
m_LockedDict = "~OnRemoveScript";
return;
}
// Tell it to stop executing anything.
instance.suspendOnCheckRunHold = true;
// Remove it from our list of known script instances
// mostly so no more events can queue to it.
m_InstancesDict.Remove(itemID);
List<UUID> itemIDList;
if (m_ObjectItemList.TryGetValue (instance.m_PartUUID, out itemIDList))
{
itemIDList.Remove(itemID);
if (itemIDList.Count == 0)
{
m_ObjectItemList.Remove(instance.m_PartUUID);
m_ObjectInstArray.Remove(instance.m_PartUUID);
}
else
m_ObjectInstArray[instance.m_PartUUID] = null;
}
// Delete the .state file as any needed contents were fetched with GetXMLState()
// and stored on the database server.
string stateFileName = XMRInstance.GetStateFileName(m_ScriptBasePath, itemID);
File.Delete(stateFileName);
ScriptRemoved handlerScriptRemoved = OnScriptRemoved;
if (handlerScriptRemoved != null)
handlerScriptRemoved(itemID);
m_LockedDict = "~~OnRemoveScript";
}
// Free off its stack and fun things like that.
// If it is running, abort it.
instance.Dispose ();
}
public void OnScriptReset(uint localID, UUID itemID)
{
TraceCalls("[XMREngine]: XMREngine.OnScriptReset({0},{1})", localID.ToString(), itemID.ToString());
ResetScript(itemID);
}
public void OnStartScript(uint localID, UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.Running = true;
}
public void OnStopScript(uint localID, UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.Running = false;
}
public void OnGetScriptRunning(IClientAPI controllingClient,
UUID objectID, UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
{
TraceCalls("[XMREngine]: XMREngine.OnGetScriptRunning({0},{1})", objectID.ToString(), itemID.ToString());
IEventQueue eq = World.RequestModuleInterface<IEventQueue>();
if (eq == null)
{
controllingClient.SendScriptRunningReply(objectID, itemID,
instance.Running);
}
else
{
eq.Enqueue(EventQueueHelper.ScriptRunningReplyEvent(objectID,
itemID, instance.Running, true),
controllingClient.AgentId);
}
}
}
public bool HasScript(UUID itemID, out bool running)
{
XMRInstance instance = GetInstance (itemID);
if (instance == null)
{
running = true;
return false;
}
running = instance.Running;
return true;
}
/**
* @brief Called once per frame update to see if scripts have
* any such work to do.
*/
private void OnFrame ()
{
if (m_FrameUpdateList != null)
{
ThreadStart frameupdates;
lock (m_FrameUpdateLock)
{
frameupdates = m_FrameUpdateList;
m_FrameUpdateList = null;
}
frameupdates ();
}
}
/**
* @brief Add a one-shot delegate to list of things to do
* synchronized with frame updates.
*/
public void AddOnFrameUpdate (ThreadStart thunk)
{
lock (m_FrameUpdateLock)
m_FrameUpdateList += thunk;
}
/**
* @brief Gets called early as part of shutdown,
* right after "Persisting changed objects" message.
*/
public void OnShutdown()
{
TraceCalls("[XMREngine]: XMREngine.OnShutdown()");
}
/**
* @brief Queue an instance to the StartQueue so it will run.
* This queue is used for instances that have just had
* an event queued to them when they were previously
* idle. It must only be called by the thread that
* transitioned the thread to XMRInstState.ONSTARTQ so
* we don't get two threads trying to queue the same
* instance to the m_StartQueue at the same time.
*/
public void QueueToStart(XMRInstance inst)
{
lock (m_StartQueue)
{
if (inst.m_IState != XMRInstState.ONSTARTQ) throw new Exception("bad state");
m_StartQueue.InsertTail(inst);
}
WakeUpOne();
}
public void QueueToTrunk(ThreadStart thds)
{
lock (m_WakeUpLock)
m_ThunkQueue.Enqueue (thds);
WakeUpOne();
}
/**
* @brief A script may be sleeping, in which case we wake it.
*/
public void WakeFromSleep(XMRInstance inst)
{
// Remove from sleep queue unless someone else already woke it.
lock (m_SleepQueue)
{
if (inst.m_IState != XMRInstState.ONSLEEPQ)
return;
m_SleepQueue.Remove(inst);
inst.m_IState = XMRInstState.REMDFROMSLPQ;
}
// Put on end of list of scripts that are ready to run.
lock (m_YieldQueue)
{
inst.m_IState = XMRInstState.ONYIELDQ;
m_YieldQueue.InsertTail(inst);
}
// Make sure the OS thread is running so it will see the script.
WakeUpOne();
}
/**
* @brief An instance has just finished running for now,
* figure out what to do with it next.
* @param inst = instance in question, not on any queue at the moment
* @param newIState = its new state
* @returns with instance inserted onto proper queue (if any)
*/
public void HandleNewIState(XMRInstance inst, XMRInstState newIState)
{
// RunOne() should have left the instance in RUNNING state.
if (inst.m_IState != XMRInstState.RUNNING)
throw new Exception("bad state");
// Now see what RunOne() wants us to do with the instance next.
switch (newIState)
{
// Instance has set m_SleepUntil to when it wants to sleep until.
// So insert instance in sleep queue by ascending wake time.
// Then wake the timer thread if this is the new first entry
// so it will reset its timer.
case XMRInstState.ONSLEEPQ:
lock (m_SleepQueue)
{
XMRInstance after;
inst.m_IState = XMRInstState.ONSLEEPQ;
for (after = m_SleepQueue.PeekHead(); after != null; after = after.m_NextInst)
{
if (after.m_SleepUntil > inst.m_SleepUntil)
break;
}
m_SleepQueue.InsertBefore(inst, after);
if (m_SleepQueue.PeekHead() == inst)
{
Monitor.Pulse (m_SleepQueue);
}
}
break;
// Instance just took a long time to run and got wacked by the
// slicer. So put on end of yield queue to let someone else
// run. If there is no one else, it will run again right away.
case XMRInstState.ONYIELDQ:
lock (m_YieldQueue)
{
inst.m_IState = XMRInstState.ONYIELDQ;
m_YieldQueue.InsertTail(inst);
}
break;
// Instance finished executing an event handler. So if there is
// another event queued for it, put it on the start queue so it
// will process the new event. Otherwise, mark it idle and the
// next event to queue to it will start it up.
case XMRInstState.FINISHED:
Monitor.Enter(inst.m_QueueLock);
if (!inst.m_Suspended && (inst.m_EventQueue.Count > 0))
{
Monitor.Exit(inst.m_QueueLock);
lock (m_StartQueue)
{
inst.m_IState = XMRInstState.ONSTARTQ;
m_StartQueue.InsertTail (inst);
}
}
else
{
inst.m_IState = XMRInstState.IDLE;
Monitor.Exit(inst.m_QueueLock);
}
break;
// Its m_SuspendCount > 0.
// Don't put it on any queue and it won't run.
// Since it's not IDLE, even queuing an event won't start it.
case XMRInstState.SUSPENDED:
inst.m_IState = XMRInstState.SUSPENDED;
break;
// It has been disposed of.
// Just set the new state and all refs should theoretically drop off
// as the instance is no longer in any list.
case XMRInstState.DISPOSED:
inst.m_IState = XMRInstState.DISPOSED;
break;
// RunOne returned something bad.
default:
throw new Exception("bad new state");
}
}
/**
* @brief Thread that moves instances from the Sleep queue to the Yield queue.
*/
private void RunSleepThread()
{
double deltaTS;
int deltaMS;
XMRInstance inst;
while (true)
{
lock (m_SleepQueue)
{
// Wait here until there is a script on the timer queue that has expired.
while (true)
{
UpdateMyThread ();
if (m_Exiting)
{
MyThreadExiting ();
return;
}
inst = m_SleepQueue.PeekHead();
if (inst == null)
{
Monitor.Wait (m_SleepQueue, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2);
continue;
}
if (inst.m_IState != XMRInstState.ONSLEEPQ) throw new Exception("bad state");
deltaTS = (inst.m_SleepUntil - DateTime.UtcNow).TotalMilliseconds;
if (deltaTS <= 0.0)
break;
deltaMS = Int32.MaxValue;
if (deltaTS < Int32.MaxValue)
deltaMS = (int)deltaTS;
if (deltaMS > Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2)
{
deltaMS = Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2;
}
Monitor.Wait (m_SleepQueue, deltaMS);
}
// Remove the expired entry from the timer queue.
m_SleepQueue.RemoveHead();
inst.m_IState = XMRInstState.REMDFROMSLPQ;
}
// Post the script to the yield queue so it will run and wake a script thread to run it.
lock (m_YieldQueue)
{
inst.m_IState = XMRInstState.ONYIELDQ;
m_YieldQueue.InsertTail(inst);
}
WakeUpOne ();
}
}
/**
* @brief Thread that runs a time slicer.
*/
public void Suspend(UUID itemID, int ms)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
instance.Sleep(ms);
}
public void Die(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
{
TraceCalls("[XMREngine]: XMREngine.Die({0})", itemID.ToString());
instance.Die();
}
}
/**
* @brief Get specific script instance for which OnRezScript()
* has been called for an XMREngine script, and that
* OnRemoveScript() has not been called since.
* @param itemID = as passed to OnRezScript() identifying a specific script instance
* @returns null: not one of our scripts (maybe XEngine etc)
* else: points to the script instance
*/
public XMRInstance GetInstance(UUID itemID)
{
XMRInstance instance;
lock (m_InstancesDict)
{
if (!m_InstancesDict.TryGetValue(itemID, out instance))
instance = null;
}
return instance;
}
// Called occasionally to write script state to .state file so the
// script will restart from its last known state if the region crashes
// and gets restarted.
private void DoMaintenance(object source, ElapsedEventArgs e)
{
XMRInstance[] instanceArray;
lock (m_InstancesDict)
instanceArray = System.Linq.Enumerable.ToArray(m_InstancesDict.Values);
foreach (XMRInstance ins in instanceArray)
{
// Don't save attachments
if (ins.m_Part.ParentGroup.IsAttachment)
continue;
ins.GetExecutionState(new XmlDocument());
}
}
/**
* @brief Retrieve errors generated by a previous call to OnRezScript().
* We are guaranteed this routine will not be called before the
* corresponding OnRezScript() has returned. It blocks until the
* compile has completed.
*/
public ArrayList GetScriptErrors(UUID itemID)
{
ArrayList errors;
lock (m_ScriptErrors)
{
while (!m_ScriptErrors.TryGetValue (itemID, out errors))
{
Monitor.Wait (m_ScriptErrors);
}
m_ScriptErrors.Remove (itemID);
}
return errors;
}
/**
* @brief Return a list of all script execution times.
*/
public Dictionary<uint, float> GetObjectScriptsExecutionTimes ()
{
Dictionary<uint, float> topScripts = new Dictionary<uint, float> ();
lock (m_InstancesDict)
{
foreach (XMRInstance instance in m_InstancesDict.Values)
{
uint rootLocalID = instance.m_Part.ParentGroup.LocalId;
float oldTotal;
if (!topScripts.TryGetValue (rootLocalID, out oldTotal))
oldTotal = 0;
topScripts[rootLocalID] = (float)instance.m_CPUTime + oldTotal;
}
}
return topScripts;
}
/**
* @brief A float the value is a representative execution time in
* milliseconds of all scripts in the link set.
* @param itemIDs = list of scripts in the link set
* @returns milliseconds for all those scripts
*/
public float GetScriptExecutionTime (List<UUID> itemIDs)
{
if ((itemIDs == null) || (itemIDs.Count == 0))
return 0;
float time = 0;
foreach (UUID itemID in itemIDs)
{
XMRInstance instance = GetInstance (itemID);
if ((instance != null) && instance.Running)
time += (float) instance.m_CPUTime;
}
return time;
}
/**
* @brief Block script from dequeuing events.
*/
public void SuspendScript(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
{
TraceCalls("[XMREngine]: XMREngine.SuspendScript({0})", itemID.ToString());
instance.SuspendIt();
}
}
/**
* @brief Allow script to dequeue events.
*/
public void ResumeScript(UUID itemID)
{
XMRInstance instance = GetInstance (itemID);
if (instance != null)
{
TraceCalls("[XMREngine]: XMREngine.ResumeScript({0})", itemID.ToString());
instance.ResumeIt();
}
else
{
// probably an XEngine script
}
}
/**
* @brief Rebuild m_ObjectInstArray[partUUID] from m_ObjectItemList[partUUID]
* @param partUUID = which object in scene to rebuild for
*/
private XMRInstance[] RebuildObjectInstArray (UUID partUUID)
{
List<UUID> itemIDList = m_ObjectItemList[partUUID];
int n = 0;
foreach (UUID itemID in itemIDList)
{
if (m_InstancesDict.ContainsKey(itemID))
n ++;
}
XMRInstance[] a = new XMRInstance[n];
n = 0;
foreach (UUID itemID in itemIDList)
{
if (m_InstancesDict.TryGetValue (itemID, out a[n]))
n ++;
}
m_ObjectInstArray[partUUID] = a;
return a;
}
public void TraceCalls (string format, params object[] args)
{
if (m_TraceCalls)
m_log.DebugFormat (format, args);
}
public void Verbose (string format, params object[] args)
{
if (m_Verbose)
m_log.DebugFormat (format, args);
}
/**
* @brief Manage our threads.
*/
public static Thread StartMyThread (ThreadStart start, string name, ThreadPriority priority)
{
m_log.Debug ("[XMREngine]: starting thread " + name);
Thread thread = new Thread (start);
thread.Name = name;
thread.Priority = priority;
thread.IsBackground = true;
thread.Start ();
Watchdog.ThreadWatchdogInfo info = new Watchdog.ThreadWatchdogInfo (thread, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS, name);
Watchdog.AddThread (info, name, true);
return thread;
}
public static void UpdateMyThread ()
{
Watchdog.UpdateThread();
}
public static void MyThreadExiting ()
{
Watchdog.RemoveThread(true);
}
public void RunScriptThread(XMRScriptThread xthd)
{
XMRInstance inst;
while (!m_Exiting)
{
Watchdog.UpdateThread();
/*
* Handle 'xmr resume/suspend' commands.
*/
if (m_SuspendScriptThreadFlag)
{
lock (m_WakeUpLock)
{
while (m_SuspendScriptThreadFlag &&
!m_Exiting &&
(m_ThunkQueue.Count == 0))
{
Monitor.Wait (m_WakeUpLock, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2);
XMREngine.UpdateMyThread ();
}
}
}
/*
* Maybe there are some scripts waiting to be migrated in or out.
*/
ThreadStart thunk = null;
lock (m_WakeUpLock)
{
if (m_ThunkQueue.Count > 0)
thunk = m_ThunkQueue.Dequeue ();
}
if (thunk != null)
{
inst = (XMRInstance)thunk.Target;
thunk ();
if (m_Exiting || m_SuspendScriptThreadFlag)
continue;
}
if (m_StartProcessing)
{
// If event just queued to any idle scripts
// start them right away. But only start so
// many so we can make some progress on yield
// queue.
int numStarts;
for (numStarts = 5; -- numStarts >= 0;)
{
lock (m_StartQueue)
{
inst = m_StartQueue.RemoveHead();
}
if (inst == null) break;
if (inst.m_IState != XMRInstState.ONSTARTQ) throw new Exception("bad state");
xthd.RunInstance (inst);
if (m_Exiting || m_SuspendScriptThreadFlag)
continue;
}
// If there is something to run, run it
// then rescan from the beginning in case
// a lot of things have changed meanwhile.
//
// These are considered lower priority than
// m_StartQueue as they have been taking at
// least one quantum of CPU time and event
// handlers are supposed to be quick.
lock (m_YieldQueue)
{
inst = m_YieldQueue.RemoveHead();
}
if (inst != null)
{
if (inst.m_IState != XMRInstState.ONYIELDQ) throw new Exception("bad state");
xthd.RunInstance(inst);
numStarts = -1;
}
// If we left something dangling in the m_StartQueue or m_YieldQueue, go back to check it.
if (m_Exiting || numStarts < 0)
continue;
}
// Nothing to do, sleep.
lock (m_WakeUpLock)
{
if (!xthd.m_WakeUpThis && (m_WakeUpOne <= 0) && !m_Exiting)
Monitor.Wait(m_WakeUpLock, Watchdog.DEFAULT_WATCHDOG_TIMEOUT_MS / 2);
xthd.m_WakeUpThis = false;
if ((m_WakeUpOne > 0) && (--m_WakeUpOne > 0))
Monitor.Pulse (m_WakeUpLock);
}
}
Watchdog.RemoveThread(true);
}
}
}