607 lines
20 KiB
C#
607 lines
20 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 OpenSim Project nor the
|
|
* names of its contributors may be used to endorse or promote products
|
|
* derived from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
|
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
|
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
using System;
|
|
using System.Reflection;
|
|
using log4net;
|
|
using OpenMetaverse;
|
|
using OpenSim.Framework;
|
|
using OpenSim.Region.Environment.Scenes;
|
|
using OpenSim.Region.ScriptEngine.Interfaces;
|
|
using OpenSim.Region.ScriptEngine.Shared;
|
|
using OpenSim.Region.ScriptEngine.Shared.Api;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Runtime.Serialization.Formatters.Binary;
|
|
using System.Threading;
|
|
using OpenSim.Region.ScriptEngine.Shared.ScriptBase;
|
|
using OpenSim.Region.ScriptEngine.Shared.CodeTools;
|
|
|
|
namespace OpenSim.Region.ScriptEngine.DotNetEngine
|
|
{
|
|
public class InstanceData
|
|
{
|
|
public IScript Script;
|
|
public string State;
|
|
public bool Running;
|
|
public bool Disabled;
|
|
public string Source;
|
|
public int StartParam;
|
|
public AppDomain AppDomain;
|
|
public Dictionary<string, IScriptApi> Apis;
|
|
}
|
|
|
|
public class ScriptManager
|
|
{
|
|
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
|
|
|
#region Declares
|
|
|
|
private Thread scriptLoadUnloadThread;
|
|
private static Thread staticScriptLoadUnloadThread;
|
|
private Queue<LUStruct> LUQueue = new Queue<LUStruct>();
|
|
private static bool PrivateThread;
|
|
private int LoadUnloadMaxQueueSize;
|
|
private Object scriptLock = new Object();
|
|
private bool m_started = false;
|
|
private Dictionary<InstanceData, DetectParams[]> detparms =
|
|
new Dictionary<InstanceData, DetectParams[]>();
|
|
|
|
// Load/Unload structure
|
|
private struct LUStruct
|
|
{
|
|
public uint localID;
|
|
public UUID itemID;
|
|
public string script;
|
|
public LUType Action;
|
|
public int startParam;
|
|
public bool postOnRez;
|
|
}
|
|
|
|
private enum LUType
|
|
{
|
|
Unknown = 0,
|
|
Load = 1,
|
|
Unload = 2
|
|
}
|
|
|
|
public Dictionary<uint, Dictionary<UUID, InstanceData>> Scripts =
|
|
new Dictionary<uint, Dictionary<UUID, InstanceData>>();
|
|
|
|
private Compiler LSLCompiler;
|
|
|
|
public Scene World
|
|
{
|
|
get { return m_scriptEngine.World; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void Initialize()
|
|
{
|
|
// Create our compiler
|
|
LSLCompiler = new Compiler(m_scriptEngine);
|
|
}
|
|
|
|
public void _StartScript(uint localID, UUID itemID, string Script,
|
|
int startParam, bool postOnRez)
|
|
{
|
|
m_log.DebugFormat(
|
|
"[{0}]: ScriptManager StartScript: localID: {1}, itemID: {2}",
|
|
m_scriptEngine.ScriptEngineName, localID, itemID);
|
|
|
|
// We will initialize and start the script.
|
|
// It will be up to the script itself to hook up the correct events.
|
|
string CompiledScriptFile = String.Empty;
|
|
|
|
SceneObjectPart m_host = World.GetSceneObjectPart(localID);
|
|
|
|
if (null == m_host)
|
|
{
|
|
m_log.ErrorFormat(
|
|
"[{0}]: Could not find scene object part corresponding "+
|
|
"to localID {1} to start script",
|
|
m_scriptEngine.ScriptEngineName, localID);
|
|
|
|
return;
|
|
}
|
|
|
|
UUID assetID = UUID.Zero;
|
|
TaskInventoryItem taskInventoryItem = new TaskInventoryItem();
|
|
if (m_host.TaskInventory.TryGetValue(itemID, out taskInventoryItem))
|
|
assetID = taskInventoryItem.AssetID;
|
|
|
|
ScenePresence presence =
|
|
World.GetScenePresence(taskInventoryItem.OwnerID);
|
|
|
|
try
|
|
{
|
|
// Compile (We assume LSL)
|
|
CompiledScriptFile =
|
|
LSLCompiler.PerformScriptCompile(Script,
|
|
assetID.ToString());
|
|
|
|
if (presence != null && (!postOnRez))
|
|
presence.ControllingClient.SendAgentAlertMessage(
|
|
"Compile successful", false);
|
|
|
|
m_log.InfoFormat("[SCRIPT]: Compiled assetID {0}: {1}",
|
|
assetID, CompiledScriptFile);
|
|
|
|
InstanceData id = new InstanceData();
|
|
|
|
IScript CompiledScript;
|
|
CompiledScript =
|
|
m_scriptEngine.m_AppDomainManager.LoadScript(
|
|
CompiledScriptFile, out id.AppDomain);
|
|
|
|
id.Script = CompiledScript;
|
|
id.Source = Script;
|
|
id.StartParam = startParam;
|
|
id.State = "default";
|
|
id.Running = true;
|
|
id.Disabled = false;
|
|
|
|
// Add it to our script memstruct
|
|
m_scriptEngine.m_ScriptManager.SetScript(localID, itemID, id);
|
|
|
|
id.Apis = new Dictionary<string, IScriptApi>();
|
|
|
|
ApiManager am = new ApiManager();
|
|
|
|
foreach (string api in am.GetApis())
|
|
{
|
|
id.Apis[api] = am.CreateApi(api);
|
|
id.Apis[api].Initialize(m_scriptEngine, m_host,
|
|
localID, itemID);
|
|
}
|
|
|
|
foreach (KeyValuePair<string,IScriptApi> kv in id.Apis)
|
|
{
|
|
CompiledScript.InitApi(kv.Key, kv.Value);
|
|
}
|
|
|
|
// Fire the first start-event
|
|
int eventFlags =
|
|
m_scriptEngine.m_ScriptManager.GetStateEventFlags(
|
|
localID, itemID);
|
|
|
|
m_host.SetScriptEvents(itemID, eventFlags);
|
|
|
|
m_scriptEngine.m_EventQueueManager.AddToScriptQueue(
|
|
localID, itemID, "state_entry", new DetectParams[0],
|
|
new object[] { });
|
|
|
|
if (postOnRez)
|
|
{
|
|
m_scriptEngine.m_EventQueueManager.AddToScriptQueue(
|
|
localID, itemID, "on_rez", new DetectParams[0],
|
|
new object[] { new LSL_Types.LSLInteger(startParam) });
|
|
}
|
|
}
|
|
catch (Exception e) // LEGIT: User Scripting
|
|
{
|
|
if (presence != null && (!postOnRez))
|
|
presence.ControllingClient.SendAgentAlertMessage(
|
|
"Script saved with errors, check debug window!",
|
|
false);
|
|
try
|
|
{
|
|
// DISPLAY ERROR INWORLD
|
|
string text = "Error compiling script:\r\n" +
|
|
e.Message.ToString();
|
|
if (text.Length > 1100)
|
|
text = text.Substring(0, 1099);
|
|
|
|
World.SimChat(Utils.StringToBytes(text),
|
|
ChatTypeEnum.DebugChannel, 2147483647,
|
|
m_host.AbsolutePosition, m_host.Name, m_host.UUID,
|
|
false);
|
|
}
|
|
catch (Exception e2) // LEGIT: User Scripting
|
|
{
|
|
m_scriptEngine.Log.Error("[" +
|
|
m_scriptEngine.ScriptEngineName +
|
|
"]: Error displaying error in-world: " +
|
|
e2.ToString());
|
|
m_scriptEngine.Log.Error("[" +
|
|
m_scriptEngine.ScriptEngineName + "]: " +
|
|
"Errormessage: Error compiling script:\r\n" +
|
|
e2.Message.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
public void _StopScript(uint localID, UUID itemID)
|
|
{
|
|
InstanceData id = GetScript(localID, itemID);
|
|
if (id == null)
|
|
return;
|
|
|
|
// Stop long command on script
|
|
AsyncCommandManager.RemoveScript(m_scriptEngine, localID, itemID);
|
|
|
|
try
|
|
{
|
|
// Get AppDomain
|
|
// Tell script not to accept new requests
|
|
id.Running = false;
|
|
id.Disabled = true;
|
|
AppDomain ad = id.AppDomain;
|
|
|
|
// Remove from internal structure
|
|
RemoveScript(localID, itemID);
|
|
|
|
// Tell AppDomain that we have stopped script
|
|
m_scriptEngine.m_AppDomainManager.StopScript(ad);
|
|
}
|
|
catch (Exception e) // LEGIT: User Scripting
|
|
{
|
|
m_scriptEngine.Log.Error("[" +
|
|
m_scriptEngine.ScriptEngineName +
|
|
"]: Exception stopping script localID: " +
|
|
localID + " LLUID: " + itemID.ToString() +
|
|
": " + e.ToString());
|
|
}
|
|
}
|
|
|
|
public void ReadConfig()
|
|
{
|
|
// TODO: Requires sharing of all ScriptManagers to single thread
|
|
PrivateThread = true;
|
|
LoadUnloadMaxQueueSize = m_scriptEngine.ScriptConfigSource.GetInt(
|
|
"LoadUnloadMaxQueueSize", 100);
|
|
}
|
|
|
|
#region Object init/shutdown
|
|
|
|
public ScriptEngine m_scriptEngine;
|
|
|
|
public ScriptManager(ScriptEngine scriptEngine)
|
|
{
|
|
m_scriptEngine = scriptEngine;
|
|
}
|
|
|
|
public void Setup()
|
|
{
|
|
ReadConfig();
|
|
Initialize();
|
|
}
|
|
|
|
public void Start()
|
|
{
|
|
m_started = true;
|
|
|
|
|
|
AppDomain.CurrentDomain.AssemblyResolve +=
|
|
new ResolveEventHandler(CurrentDomain_AssemblyResolve);
|
|
|
|
//
|
|
// CREATE THREAD
|
|
// Private or shared
|
|
//
|
|
if (PrivateThread)
|
|
{
|
|
// Assign one thread per region
|
|
//scriptLoadUnloadThread = StartScriptLoadUnloadThread();
|
|
}
|
|
else
|
|
{
|
|
// Shared thread - make sure one exist, then assign it to the private
|
|
if (staticScriptLoadUnloadThread == null)
|
|
{
|
|
//staticScriptLoadUnloadThread =
|
|
// StartScriptLoadUnloadThread();
|
|
}
|
|
scriptLoadUnloadThread = staticScriptLoadUnloadThread;
|
|
}
|
|
}
|
|
|
|
~ScriptManager()
|
|
{
|
|
// Abort load/unload thread
|
|
try
|
|
{
|
|
if (scriptLoadUnloadThread != null &&
|
|
scriptLoadUnloadThread.IsAlive == true)
|
|
{
|
|
scriptLoadUnloadThread.Abort();
|
|
//scriptLoadUnloadThread.Join();
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Load / Unload scripts (Thread loop)
|
|
|
|
public void DoScriptLoadUnload()
|
|
{
|
|
if (!m_started)
|
|
return;
|
|
|
|
lock (LUQueue)
|
|
{
|
|
if (LUQueue.Count > 0)
|
|
{
|
|
m_scriptEngine.Log.InfoFormat("[{0}]: Loading script",
|
|
m_scriptEngine.ScriptEngineName);
|
|
LUStruct item = LUQueue.Dequeue();
|
|
|
|
if (item.Action == LUType.Unload)
|
|
{
|
|
_StopScript(item.localID, item.itemID);
|
|
RemoveScript(item.localID, item.itemID);
|
|
}
|
|
else if (item.Action == LUType.Load)
|
|
{
|
|
_StartScript(item.localID, item.itemID, item.script,
|
|
item.startParam, item.postOnRez);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Helper functions
|
|
|
|
private static Assembly CurrentDomain_AssemblyResolve(
|
|
object sender, ResolveEventArgs args)
|
|
{
|
|
return Assembly.GetExecutingAssembly().FullName == args.Name ?
|
|
Assembly.GetExecutingAssembly() : null;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Start/Stop/Reset script
|
|
|
|
/// <summary>
|
|
/// Fetches, loads and hooks up a script to an objects events
|
|
/// </summary>
|
|
/// <param name="itemID"></param>
|
|
/// <param name="localID"></param>
|
|
public void StartScript(uint localID, UUID itemID, string Script, int startParam, bool postOnRez)
|
|
{
|
|
lock (LUQueue)
|
|
{
|
|
if ((LUQueue.Count >= LoadUnloadMaxQueueSize) && m_started)
|
|
{
|
|
m_scriptEngine.Log.Error("[" +
|
|
m_scriptEngine.ScriptEngineName +
|
|
"]: ERROR: Load/unload queue item count is at " +
|
|
LUQueue.Count +
|
|
". Config variable \"LoadUnloadMaxQueueSize\" "+
|
|
"is set to " + LoadUnloadMaxQueueSize +
|
|
", so ignoring new script.");
|
|
return;
|
|
}
|
|
|
|
LUStruct ls = new LUStruct();
|
|
ls.localID = localID;
|
|
ls.itemID = itemID;
|
|
ls.script = Script;
|
|
ls.Action = LUType.Load;
|
|
ls.startParam = startParam;
|
|
ls.postOnRez = postOnRez;
|
|
LUQueue.Enqueue(ls);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disables and unloads a script
|
|
/// </summary>
|
|
/// <param name="localID"></param>
|
|
/// <param name="itemID"></param>
|
|
public void StopScript(uint localID, UUID itemID)
|
|
{
|
|
LUStruct ls = new LUStruct();
|
|
ls.localID = localID;
|
|
ls.itemID = itemID;
|
|
ls.Action = LUType.Unload;
|
|
ls.startParam = 0;
|
|
ls.postOnRez = false;
|
|
lock (LUQueue)
|
|
{
|
|
LUQueue.Enqueue(ls);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Perform event execution in script
|
|
|
|
// Execute a LL-event-function in Script
|
|
internal void ExecuteEvent(uint localID, UUID itemID,
|
|
string FunctionName, DetectParams[] qParams, object[] args)
|
|
{
|
|
InstanceData id = GetScript(localID, itemID);
|
|
if (id == null)
|
|
return;
|
|
|
|
detparms[id] = qParams;
|
|
|
|
if (id.Running)
|
|
id.Script.ExecuteEvent(id.State, FunctionName, args);
|
|
|
|
detparms.Remove(id);
|
|
}
|
|
|
|
public uint GetLocalID(UUID itemID)
|
|
{
|
|
foreach (KeyValuePair<uint, Dictionary<UUID, InstanceData> > k
|
|
in Scripts)
|
|
{
|
|
if (k.Value.ContainsKey(itemID))
|
|
return k.Key;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public int GetStateEventFlags(uint localID, UUID itemID)
|
|
{
|
|
try
|
|
{
|
|
InstanceData id = GetScript(localID, itemID);
|
|
if (id == null)
|
|
{
|
|
return 0;
|
|
}
|
|
int evflags = id.Script.GetStateEventFlags(id.State);
|
|
|
|
return (int)evflags;
|
|
}
|
|
catch (Exception)
|
|
{
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
#endregion
|
|
|
|
#region Internal functions to keep track of script
|
|
|
|
public List<UUID> GetScriptKeys(uint localID)
|
|
{
|
|
if (Scripts.ContainsKey(localID) == false)
|
|
return new List<UUID>();
|
|
|
|
Dictionary<UUID, InstanceData> Obj;
|
|
Scripts.TryGetValue(localID, out Obj);
|
|
|
|
return new List<UUID>(Obj.Keys);
|
|
}
|
|
|
|
public InstanceData GetScript(uint localID, UUID itemID)
|
|
{
|
|
lock (scriptLock)
|
|
{
|
|
InstanceData id = null;
|
|
|
|
if (Scripts.ContainsKey(localID) == false)
|
|
return null;
|
|
|
|
Dictionary<UUID, InstanceData> Obj;
|
|
Scripts.TryGetValue(localID, out Obj);
|
|
if (Obj.ContainsKey(itemID) == false)
|
|
return null;
|
|
|
|
// Get script
|
|
Obj.TryGetValue(itemID, out id);
|
|
return id;
|
|
}
|
|
}
|
|
|
|
public void SetScript(uint localID, UUID itemID, InstanceData id)
|
|
{
|
|
lock (scriptLock)
|
|
{
|
|
// Create object if it doesn't exist
|
|
if (Scripts.ContainsKey(localID) == false)
|
|
{
|
|
Scripts.Add(localID, new Dictionary<UUID, InstanceData>());
|
|
}
|
|
|
|
// Delete script if it exists
|
|
Dictionary<UUID, InstanceData> Obj;
|
|
Scripts.TryGetValue(localID, out Obj);
|
|
if (Obj.ContainsKey(itemID) == true)
|
|
Obj.Remove(itemID);
|
|
|
|
// Add to object
|
|
Obj.Add(itemID, id);
|
|
}
|
|
}
|
|
|
|
public void RemoveScript(uint localID, UUID itemID)
|
|
{
|
|
if (localID == 0)
|
|
localID = GetLocalID(itemID);
|
|
|
|
// Don't have that object?
|
|
if (Scripts.ContainsKey(localID) == false)
|
|
return;
|
|
|
|
// Delete script if it exists
|
|
Dictionary<UUID, InstanceData> Obj;
|
|
Scripts.TryGetValue(localID, out Obj);
|
|
if (Obj.ContainsKey(itemID) == true)
|
|
Obj.Remove(itemID);
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
public void ResetScript(uint localID, UUID itemID)
|
|
{
|
|
InstanceData id = GetScript(localID, itemID);
|
|
string script = id.Source;
|
|
StopScript(localID, itemID);
|
|
SceneObjectPart part = World.GetSceneObjectPart(localID);
|
|
part.GetInventoryItem(itemID).PermsMask = 0;
|
|
part.GetInventoryItem(itemID).PermsGranter = UUID.Zero;
|
|
StartScript(localID, itemID, script, id.StartParam, false);
|
|
}
|
|
|
|
|
|
#region Script serialization/deserialization
|
|
|
|
public void GetSerializedScript(uint localID, UUID itemID)
|
|
{
|
|
// Serialize the script and return it
|
|
// Should not be a problem
|
|
FileStream fs = File.Create("SERIALIZED_SCRIPT_" + itemID);
|
|
BinaryFormatter b = new BinaryFormatter();
|
|
b.Serialize(fs, GetScript(localID, itemID));
|
|
fs.Close();
|
|
}
|
|
|
|
public void PutSerializedScript(uint localID, UUID itemID)
|
|
{
|
|
// Deserialize the script and inject it into an AppDomain
|
|
|
|
// How to inject into an AppDomain?
|
|
}
|
|
|
|
#endregion
|
|
|
|
public DetectParams[] GetDetectParams(InstanceData id)
|
|
{
|
|
if (detparms.ContainsKey(id))
|
|
return detparms[id];
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|