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

1923 lines
73 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 YEngine 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.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("YEngine", OpenSim.VersionInfo.VersionNumber)]
[assembly: AddinDependency("OpenSim.Region.Framework", OpenSim.VersionInfo.VersionNumber)]
namespace OpenSim.Region.ScriptEngine.Yengine
{
[Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "YEngine")]
public partial class Yengine: 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 bool m_ScriptDebugSaveSource;
public bool m_ScriptDebugSaveIL;
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;
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>();
public int m_StackSize;
private int m_HeapSize;
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 Yengine()
{
}
public string Name
{
get
{
return "YEngine";
}
}
public Type ReplaceableInterface
{
get
{
return null;
}
}
public string ScriptEnginePath
{
get
{
return m_ScriptBasePath;
}
}
public string ScriptClassName
{
get
{
return "YEngineScript";
}
}
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 Initialise(IConfigSource config)
{
TraceCalls("[YEngine]: Initialize entry");
m_ConfigSource = config;
////foreach (IConfig icfg in config.Configs) {
//// m_log.Debug("[YEngine]: Initialise: configs[" + icfg.Name + "]");
//// foreach (string key in icfg.GetKeys ()) {
//// m_log.Debug("[YEngine]: Initialise: " + key + "=" + icfg.GetExpanded (key));
//// }
////}
m_Enabled = false;
m_Config = config.Configs["YEngine"];
if(m_Config == null)
{
m_log.Info("[YEngine]: no config, assuming disabled");
return;
}
m_Enabled = m_Config.GetBoolean("Enabled", false);
m_log.InfoFormat("[YEngine]: config enabled={0}", m_Enabled);
if(!m_Enabled)
return;
numThreadScriptWorkers = m_Config.GetInt("NumThreadScriptWorkers", 1);
m_TraceCalls = m_Config.GetBoolean("TraceCalls", false);
m_Verbose = m_Config.GetBoolean("Verbose", false);
m_ScriptDebug = m_Config.GetBoolean("ScriptDebug", false);
m_ScriptDebugSaveSource = m_Config.GetBoolean("ScriptDebugSaveSource", false);
m_ScriptDebugSaveIL = m_Config.GetBoolean("ScriptDebugSaveIL", false);
m_StackSize = m_Config.GetInt("ScriptStackSize", 2048) << 10;
m_HeapSize = m_Config.GetInt("ScriptHeapSize", 1024) << 10;
// 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("[YEngine]: {0} mycode={1}, oscode={2}", i, mycode, oscode);
err = true;
}
}
if(err)
{
m_Enabled = false;
return;
}
m_SleepThread = StartMyThread(RunSleepThread, "Yengine sleep", ThreadPriority.Normal);
for(int i = 0; i < numThreadScriptWorkers; i++)
StartThreadWorker(i);
m_log.InfoFormat("[YEngine]: 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("[YEngine]: ... {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("yeng", false,
"yeng",
"yeng [...|help|...] ...",
"Run YEngine script engine commands",
RunTest);
TraceCalls("[YEngine]: Initialize successful");
}
public void AddRegion(Scene scene)
{
if(!m_Enabled)
return;
TraceCalls("[YEngine]: YEngine.AddRegion({0})", scene.RegionInfo.RegionName);
m_Scene = scene;
m_Scene.RegisterModuleInterface<IScriptModule>(this);
m_ScriptBasePath = m_Config.GetString("ScriptBasePath", "ScriptEngines");
m_ScriptBasePath = Path.Combine(m_ScriptBasePath, "Yengine");
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("[YEngine]: 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);
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("[YEngine]: added comms function " + cccg.fullName);
}
catch(Exception e)
{
m_log.Error("[YEngine]: failed to add comms function " + mi.Name);
m_log.Error("[YEngine]: - " + 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("[YEngine]: added comms constant " + sc.name);
}
catch(Exception e)
{
m_log.Error("[YEngine]: failed to add comms constant " + kvp.Key);
m_log.Error("[YEngine]: - " + e.Message);
}
}
}
else
{
Verbose("[YEngine]: 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("[YEngine]: YEngine.RemoveRegion({0})", scene.RegionInfo.RegionName);
if(m_MaintenanceTimer != null)
{
m_MaintenanceTimer.Stop();
m_MaintenanceTimer.Dispose();
}
// 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;
if(m_SleepThread != null)
{
lock(m_SleepQueue)
Monitor.PulseAll(m_SleepQueue);
if(!m_SleepThread.Join(250))
m_SleepThread.Abort();
m_SleepThread = null;
}
StopThreadWorkers();
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;
m_Enabled = false;
m_Scene = null;
}
public void RegionLoaded(Scene scene)
{
if(!m_Enabled)
return;
TraceCalls("[YEngine]: YEngine.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("[YEngine]: StartProcessing entry");
m_StartProcessing = true;
ResumeThreads();
m_log.Debug("[YEngine]: StartProcessing return");
m_Scene.EventManager.TriggerEmptyScriptCompileQueue(0, "");
}
public void Close()
{
TraceCalls("[YEngine]: YEngine.Close()");
}
private void RunTest(string module, string[] args)
{
if(args.Length < 2)
{
m_log.Info("[YEngine]: missing command, try 'yeng help'");
return;
}
m_log.Info("[YEngine]: " + m_Scene.RegionInfo.RegionName);
switch(args[1])
{
case "cvv":
m_log.InfoFormat("[YEngine]: compiled version value = {0}",
ScriptCodeGen.COMPILED_VERSION_VALUE);
break;
case "help":
case "?":
m_log.Info("[YEngine]: yeng reset [-help ...]");
m_log.Info("[YEngine]: yeng resume - resume script processing");
m_log.Info("[YEngine]: yeng suspend - suspend script processing");
m_log.Info("[YEngine]: yeng ls [-help ...]");
m_log.Info("[YEngine]: yeng cvv - show compiler version value");
m_log.Info("[YEngine]: yeng mvv [<newvalue>] - show migration version value");
m_log.Info("[YEngine]: yeng tracecalls [yes | no]");
m_log.Info("[YEngine]: yeng verbose [yes | no]");
m_log.Info("[YEngine]: yeng pev [-help ...] - post event");
break;
case "ls":
XmrTestLs(args, 2);
break;
case "mvv":
m_log.InfoFormat("[YEngine]: migration version value = {0}",
XMRInstance.migrationVersion);
break;
case "pev":
XmrTestPev(args, 2);
break;
case "reset":
XmrTestReset(args, 2);
break;
case "resume":
m_log.Info("[YEngine]: resuming scripts");
ResumeThreads();
break;
case "suspend":
m_log.Info("[YEngine]: suspending scripts");
SuspendThreads();
break;
case "tracecalls":
if(args.Length > 2)
m_TraceCalls = (args[2][0] & 1) != 0;
m_log.Info("[YEngine]: tracecalls " + (m_TraceCalls ? "yes" : "no"));
break;
case "verbose":
if(args.Length > 2)
m_Verbose = (args[2][0] & 1) != 0;
m_log.Info("[YEngine]: verbose " + (m_Verbose ? "yes" : "no"));
break;
default:
m_log.Error("[YEngine]: unknown command " + args[1] + ", try 'yeng 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("[YEngine]: YEngine.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("[YEngine]: YEngine.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("[YEngine]: YEngine.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)
{
XMRInstance instance = GetInstance(itemID);
if (instance != null)
instance.MinEventDelay = 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("[YEngine]: YEngine.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 "YEngine";
}
}
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("[YEngine]: YEngine.GetXMLState({0})", itemID.ToString());
if(!instance.m_HasRun)
return String.Empty;
XmlDocument doc = new XmlDocument();
/*
* Set up <State Engine="YEngine" 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("[YEngine]: YEngine.SetXMLState({0})", itemID.ToString());
// Make sure <State Engine="YEngine"> so we know it is in our
// format.
XmlElement stateN = (XmlElement)doc.SelectSingleNode("State");
if(stateN == null)
return false;
bool isX = false;
string sen = stateN.GetAttribute("Engine");
if (sen == null)
return false;
if (sen != ScriptEngineName)
{
if(sen != "XEngine")
return false;
isX = true;
}
// <ScriptState>...</ScriptState> contains contents of .state file.
XmlElement scriptStateN = (XmlElement)stateN.SelectSingleNode("ScriptState");
if(scriptStateN == null)
return false;
if(!isX)
{
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);
using (FileStream ss = File.Create(statePath))
{
using (StreamWriter sw = new StreamWriter(ss))
sw.Write(scriptStateN.OuterXml);
}
return true;
}
public bool PostScriptEvent(UUID itemID, string name, Object[] p)
{
if(!m_Enabled)
return false;
TraceCalls("[YEngine]: YEngine.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("[YEngine]: YEngine.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)
{
if (script.StartsWith("//MRM:"))
return;
SceneObjectPart part = m_Scene.GetSceneObjectPart(localID);
TaskInventoryItem item = part.Inventory.GetInventoryItem(itemID);
if(!m_LateInit)
{
m_LateInit = true;
OneTimeLateInitialization();
}
TraceCalls("[YEngine]: OnRezScript(...,{0},...)", itemID.ToString());
// Assume script uses the default engine
string engineName = defEngine;
// Very first line might contain // scriptengine : language
string langsrt = "";
if (script.StartsWith("//"))
{
int lineEnd = script.IndexOf('\n');
if(lineEnd > 5)
{
string firstline = script.Substring(2, lineEnd - 2).Trim();
int colon = firstline.IndexOf(':');
if(colon >= 3)
{
engineName = firstline.Substring(0, colon).TrimEnd();
if(string.IsNullOrEmpty(engineName))
engineName = defEngine;
}
if (colon > 0 && colon < firstline.Length - 2)
{
langsrt = firstline.Substring(colon + 1).Trim();
langsrt = langsrt.ToLower();
}
}
}
// 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("[YEngine]: " + itemID.ToString() + " requests undefined/disabled engine " + engineName);
// m_log.Info("[YEngine]: - " + part.GetWorldPosition());
// m_log.Info("[YEngine]: first line: " + firstline);
if(defEngine != ScriptEngineName)
{
// m_log.Info("[YEngine]: leaving it to the default script engine (" + defEngine + ") to process it");
return;
}
// m_log.Info("[YEngine]: will attempt to processing it anyway as default script engine");
}
if(!string.IsNullOrEmpty(langsrt) && langsrt !="lsl")
return;
// 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 && initerr is CVVMismatchException)
{
UUID itemID = instance.m_ItemID;
Verbose("[YEngine]: {0}/{2} first load failed ({1}), retrying after recompile",
itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString());
Verbose("[YEngine]:\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("[YEngine]: Error starting script {0}/{2}: {1}",
itemID.ToString(), initerr.Message, instance.m_Item.AssetID.ToString());
if(initerr.Message != "compilation errors")
{
Verbose("[YEngine]: - " + instance.m_Part.GetWorldPosition() + " " + instance.m_DescName);
Verbose("[YEngine]: 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("[YEngine]: {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("[YEngine]: 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("[YEngine]: YEngine.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("[YEngine]: YEngine.OnGetScriptRunning({0},{1})", objectID.ToString(), itemID.ToString());
IEventQueue eq = World.RequestModuleInterface<IEventQueue>();
if(eq == null)
{
controllingClient.SendScriptRunningReply(objectID, itemID,
instance.Running);
}
else
{
eq.ScriptRunningEvent(objectID, itemID, instance.Running, 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("[YEngine]: YEngine.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)
{
if(inst.m_IState != XMRInstState.ONSTARTQ)
throw new Exception("bad state");
lock(m_StartQueue)
m_StartQueue.InsertTail(inst);
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))
{
inst.m_IState = XMRInstState.ONSTARTQ;
Monitor.Exit(inst.m_QueueLock);
lock(m_StartQueue)
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();
}
}
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("[YEngine]: YEngine.Die({0})", itemID.ToString());
instance.Die();
}
}
/**
* @brief Get specific script instance for which OnRezScript()
* has been called for an YEngine 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("[YEngine]: YEngine.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("[YEngine]: YEngine.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("[YEngine]: starting thread " + name);
Thread thread = WorkManager.StartThread(start, name, priority, false, false);
return thread;
}
public static void UpdateMyThread()
{
Watchdog.UpdateThread();
}
public static void MyThreadExiting()
{
Watchdog.RemoveThread(true);
}
}
}