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

627 lines
28 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.
*/
using System;
using System.Threading;
using System.Collections.Generic;
using OpenMetaverse;
using OpenSim.Framework;
using OpenSim.Region.ScriptEngine.Shared;
using OpenSim.Region.ScriptEngine.Shared.Api;
using log4net;
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;
namespace OpenSim.Region.ScriptEngine.Yengine
{
/****************************************************\
* This file contains routines called by scripts. *
\****************************************************/
public class XMRLSL_Api: LSL_Api
{
public AsyncCommandManager acm;
private XMRInstance inst;
public void InitXMRLSLApi(XMRInstance i)
{
acm = AsyncCommands;
inst = i;
}
protected override void ScriptSleep(int ms)
{
ms = (int)(ms * m_ScriptDelayFactor);
if (ms < 10)
return;
inst.Sleep(ms);
}
public override void llSleep(double sec)
{
inst.Sleep((int)(sec * 1000.0));
}
public override void llDie()
{
inst.Die();
}
/**
* @brief Seat avatar on prim.
* @param owner = true: owner of prim script is running in
* false: avatar that has given ANIMATION permission on the prim
* @returns 0: successful
* -1: no permission to animate
* -2: no av granted perms
* -3: av not in region
*/
/* engines should not have own API
public int xmrSeatAvatar (bool owner)
{
// Get avatar to be seated and make sure they have given us ANIMATION permission
UUID avuuid;
if (owner) {
avuuid = inst.m_Part.OwnerID;
} else {
if ((m_item.PermsMask & ScriptBaseClass.PERMISSION_TRIGGER_ANIMATION) == 0) {
return -1;
}
avuuid = m_item.PermsGranter;
}
if (avuuid == UUID.Zero) {
return -2;
}
ScenePresence presence = World.GetScenePresence (avuuid);
if (presence == null) {
return -3;
}
// remoteClient = not used by ScenePresence.HandleAgentRequestSit()
// agentID = not used by ScenePresence.HandleAgentRequestSit()
// targetID = UUID of prim to sit on
// offset = offset of sitting position
presence.HandleAgentRequestSit (null, UUID.Zero, m_host.UUID, OpenMetaverse.Vector3.Zero);
return 0;
}
*/
/**
* @brief llTeleportAgent() is broken in that if you pass it a landmark,
* it still subjects the position to spawn points, as it always
* calls RequestTeleportLocation() with TeleportFlags.ViaLocation.
* See llTeleportAgent() and CheckAndAdjustTelehub().
*
* @param agent = what agent to teleport
* @param landmark = inventory name or UUID of a landmark object
* @param lookat = looking direction after teleport
*/
/* engines should not have own API
public void xmrTeleportAgent2Landmark (string agent, string landmark, LSL_Vector lookat)
{
// find out about agent to be teleported
UUID agentId;
if (!UUID.TryParse (agent, out agentId)) throw new ApplicationException ("bad agent uuid");
ScenePresence presence = World.GetScenePresence (agentId);
if (presence == null) throw new ApplicationException ("agent not present in scene");
if (presence.IsNPC) throw new ApplicationException ("agent is an NPC");
if (presence.IsGod) throw new ApplicationException ("agent is a god");
// prim must be owned by land owner or prim must be attached to agent
if (m_host.ParentGroup.AttachmentPoint == 0) {
if (m_host.OwnerID != World.LandChannel.GetLandObject (presence.AbsolutePosition).LandData.OwnerID) {
throw new ApplicationException ("prim not owned by land's owner");
}
} else {
if (m_host.OwnerID != presence.UUID) throw new ApplicationException ("prim not attached to agent");
}
// find landmark in inventory or by UUID
UUID assetID = ScriptUtils.GetAssetIdFromKeyOrItemName (m_host, landmark);
if (assetID == UUID.Zero) throw new ApplicationException ("no such landmark");
// read it in and make sure it is a landmark
AssetBase lma = World.AssetService.Get (assetID.ToString ());
if ((lma == null) || (lma.Type != (sbyte)AssetType.Landmark)) throw new ApplicationException ("not a landmark");
// parse the record
AssetLandmark lm = new AssetLandmark (lma);
// the regionhandle (based on region's world X,Y) might be out of date
// re-read the handle so we can pass it to RequestTeleportLocation()
var region = World.GridService.GetRegionByUUID (World.RegionInfo.ScopeID, lm.RegionID);
if (region == null) throw new ApplicationException ("no such region");
// finally ready to teleport
World.RequestTeleportLocation (presence.ControllingClient,
region.RegionHandle,
lm.Position,
lookat,
(uint)TeleportFlags.ViaLandmark);
}
*/
/**
* @brief Allow any member of group given by config SetParcelMusicURLGroup to set music URL.
* Code modelled after llSetParcelMusicURL().
* @param newurl = new URL to set (or "" to leave it alone)
* @returns previous URL string
*/
/* engines should not have own API
public string xmrSetParcelMusicURLGroup (string newurl)
{
string groupname = m_ScriptEngine.Config.GetString ("SetParcelMusicURLGroup", "");
if (groupname == "") throw new ApplicationException ("no SetParcelMusicURLGroup config param set");
IGroupsModule igm = World.RequestModuleInterface<IGroupsModule> ();
if (igm == null) throw new ApplicationException ("no GroupsModule loaded");
GroupRecord grouprec = igm.GetGroupRecord (groupname);
if (grouprec == null) throw new ApplicationException ("no such group " + groupname);
GroupMembershipData gmd = igm.GetMembershipData (grouprec.GroupID, m_host.OwnerID);
if (gmd == null) throw new ApplicationException ("not a member of group " + groupname);
ILandObject land = World.LandChannel.GetLandObject (m_host.AbsolutePosition);
if (land == null) throw new ApplicationException ("no land at " + m_host.AbsolutePosition.ToString ());
string oldurl = land.GetMusicUrl ();
if (oldurl == null) oldurl = "";
if ((newurl != null) && (newurl != "")) land.SetMusicUrl (newurl);
return oldurl;
}
*/
}
public partial class XMRInstance
{
/**
* @brief The script is calling llReset().
* We throw an exception to unwind the script out to its main
* causing all the finally's to execute and it will also set
* eventCode = None to indicate event handler has completed.
*/
public void ApiReset()
{
// do not do llResetScript on entry
if(eventCode == ScriptEventCode.state_entry && stateCode == 0)
return;
ClearQueueExceptLinkMessages();
throw new ScriptResetException();
}
/**
* @brief The script is calling one of the llDetected...(int number)
* functions. Return corresponding DetectParams pointer.
*/
public DetectParams GetDetectParams(int number)
{
DetectParams dp = null;
if((number >= 0) && (m_DetectParams != null) && (number < m_DetectParams.Length))
dp = m_DetectParams[number];
return dp;
}
/**
* @brief Script is calling llDie, so flag the run loop to delete script
* once we are off the microthread stack, and throw an exception
* to unwind the stack asap.
*/
public void Die()
{
// llDie doesn't work in attachments!
if(m_Part.ParentGroup.IsAttachment || m_DetachQuantum > 0)
return;
throw new ScriptDieException();
}
/**
* @brief Called by script to sleep for the given number of milliseconds.
*/
public void Sleep(int ms)
{
lock(m_QueueLock)
{
// Say how long to sleep.
m_SleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds(ms);
// Don't wake on any events.
m_SleepEventMask1 = 0;
m_SleepEventMask2 = 0;
}
// The compiler follows all calls to llSleep() with a call to CheckRun().
// So tell CheckRun() to suspend the microthread.
suspendOnCheckRunTemp = true;
}
/**
* Block script execution until an event is queued or a timeout is reached.
* @param timeout = maximum number of seconds to wait
* @param returnMask = if event is queued that matches these mask bits,
* the script is woken, that event is dequeued and
* returned to the caller. The event handler is not
* executed.
* @param backgroundMask = if any of these events are queued while waiting,
* execute their event handlers. When any such event
* handler exits, continue waiting for events or the
* timeout.
* @returns empty list: no event was queued that matched returnMask and the timeout was reached
* or a background event handler changed state (eg, via 'state' statement)
* else: list giving parameters of the event:
* [0] = event code (integer)
* [1..n] = call parameters to the event, if any
* Notes:
* 1) Scrips should use XMREVENTMASKn_<eventname> symbols for the mask arguments,
* where n is 1 or 2 for mask1 or mask2 arguments.
* The list[0] return argument can be decoded by using XMREVENTCODE_<eventname> symbols.
* 2) If all masks are zero, the call ends up acting like llSleep.
* 3) If an event is enabled in both returnMask and backgroundMask, the returnMask bit
* action takes precedence, ie, the event is returned. This allows a simple specification
* of -1 for both backgroundMask arguments to indicate that all events not listed in
* the returnMask argumetns should be handled in the background.
* 4) Any events not listed in either returnMask or backgroundMask arguments will be
* queued for later processing (subject to normal queue limits).
* 5) Background event handlers execute as calls from within xmrEventDequeue, they do
* not execute as separate threads. Thus any background event handlers must return
* before the call to xmrEventDequeue will return.
* 6) If a background event handler changes state (eg, via 'state' statement), the state
* is immediately changed and the script-level xmrEventDequeue call does not return.
* 7) For returned events, the detect parameters are overwritten by the returned event.
* For background events, the detect parameters are saved and restored.
* 8) Scripts must contain dummy event handler definitions for any event types that may
* be returned by xmrEventDequeue, to let the runtime know that the script is capable
* of processing that event type. Otherwise, the event may not be queued to the script.
*/
private static LSL_List emptyList = new LSL_List(new object[0]);
public override LSL_List xmrEventDequeue(double timeout, int returnMask1, int returnMask2,
int backgroundMask1, int backgroundMask2)
{
DateTime sleepUntil = DateTime.UtcNow + TimeSpan.FromMilliseconds(timeout * 1000.0);
EventParams evt = null;
int callNo, evc2;
int evc1 = 0;
int mask1 = returnMask1 | backgroundMask1; // codes 00..31
int mask2 = returnMask2 | backgroundMask2; // codes 32..63
LinkedListNode<EventParams> lln = null;
object[] sv;
ScriptEventCode evc = ScriptEventCode.None;
callNo = -1;
try
{
if(callMode == CallMode_NORMAL)
goto findevent;
// Stack frame is being restored as saved via CheckRun...().
// Restore necessary values then jump to __call<n> label to resume processing.
sv = RestoreStackFrame("xmrEventDequeue", out callNo);
sleepUntil = DateTime.Parse((string)sv[0]);
returnMask1 = (int)sv[1];
returnMask2 = (int)sv[2];
mask1 = (int)sv[3];
mask2 = (int)sv[4];
switch(callNo)
{
case 0:
goto __call0;
case 1:
{
evc1 = (int)sv[5];
evc = (ScriptEventCode)(int)sv[6];
DetectParams[] detprms = ObjArrToDetPrms((object[])sv[7]);
object[] ehargs = (object[])sv[8];
evt = new EventParams(evc.ToString(), ehargs, detprms);
goto __call1;
}
}
throw new ScriptBadCallNoException(callNo);
// Find first event that matches either the return or background masks.
findevent:
Monitor.Enter(m_QueueLock);
for(lln = m_EventQueue.First; lln != null; lln = lln.Next)
{
evt = lln.Value;
evc = (ScriptEventCode)Enum.Parse(typeof(ScriptEventCode), evt.EventName);
evc1 = (int)evc;
evc2 = evc1 - 32;
if((((uint)evc1 < (uint)32) && (((mask1 >> evc1) & 1) != 0)) ||
(((uint)evc2 < (uint)32) && (((mask2 >> evc2) & 1) != 0)))
goto remfromq;
}
// Nothing found, sleep while one comes in.
m_SleepUntil = sleepUntil;
m_SleepEventMask1 = mask1;
m_SleepEventMask2 = mask2;
Monitor.Exit(m_QueueLock);
suspendOnCheckRunTemp = true;
callNo = 0;
__call0:
CheckRunQuick();
goto checktmo;
// Found one, remove it from queue.
remfromq:
m_EventQueue.Remove(lln);
if((uint)evc1 < (uint)m_EventCounts.Length)
m_EventCounts[evc1]--;
Monitor.Exit(m_QueueLock);
m_InstEHEvent++;
// See if returnable or background event.
if((((uint)evc1 < (uint)32) && (((returnMask1 >> evc1) & 1) != 0)) ||
(((uint)evc2 < (uint)32) && (((returnMask2 >> evc2) & 1) != 0)))
{
// Returnable event, return its parameters in a list.
// Also set the detect parameters to what the event has.
int plen = evt.Params.Length;
object[] plist = new object[plen + 1];
plist[0] = (LSL_Integer)evc1;
for(int i = 0; i < plen;)
{
object ob = evt.Params[i];
if(ob is int)
ob = (LSL_Integer)(int)ob;
else if(ob is double)
ob = (LSL_Float)(double)ob;
else if(ob is string)
ob = (LSL_String)(string)ob;
plist[++i] = ob;
}
m_DetectParams = evt.DetectParams;
return new LSL_List(plist);
}
// It is a background event, simply call its event handler,
// then check event queue again.
callNo = 1;
__call1:
ScriptEventHandler seh = m_ObjCode.scriptEventHandlerTable[stateCode, evc1];
if(seh == null)
goto checktmo;
DetectParams[] saveDetParams = this.m_DetectParams;
object[] saveEHArgs = this.ehArgs;
ScriptEventCode saveEventCode = this.eventCode;
m_DetectParams = evt.DetectParams;
ehArgs = evt.Params;
eventCode = evc;
try
{
seh(this);
}
finally
{
m_DetectParams = saveDetParams;
ehArgs = saveEHArgs;
eventCode = saveEventCode;
}
// Keep waiting until we find a returnable event or timeout.
checktmo:
if(DateTime.UtcNow < sleepUntil)
goto findevent;
// We timed out, return an empty list.
return emptyList;
}
finally
{
if(callMode != CallMode_NORMAL)
{
// Stack frame is being saved by CheckRun...().
// Save everything we need at the __call<n> labels so we can restore it
// when we need to.
sv = CaptureStackFrame("xmrEventDequeue", callNo, 9);
sv[0] = sleepUntil.ToString(); // needed at __call0,__call1
sv[1] = returnMask1; // needed at __call0,__call1
sv[2] = returnMask2; // needed at __call0,__call1
sv[3] = mask1; // needed at __call0,__call1
sv[4] = mask2; // needed at __call0,__call1
if(callNo == 1)
{
sv[5] = evc1; // needed at __call1
sv[6] = (int)evc; // needed at __call1
sv[7] = DetPrmsToObjArr(evt.DetectParams); // needed at __call1
sv[8] = evt.Params; // needed at __call1
}
}
}
}
/**
* @brief Enqueue an event
* @param ev = as returned by xmrEventDequeue saying which event type to queue
* and what argument list to pass to it. The llDetect...() parameters
* are as currently set for the script (use xmrEventLoadDets to set how
* you want them to be different).
*/
public override void xmrEventEnqueue(LSL_List ev)
{
object[] data = ev.Data;
ScriptEventCode evc = (ScriptEventCode)ListInt(data[0]);
int nargs = data.Length - 1;
object[] args = new object[nargs];
Array.Copy(data, 1, args, 0, nargs);
PostEvent(new EventParams(evc.ToString(), args, m_DetectParams));
}
/**
* @brief Save current detect params into a list
* @returns a list containing current detect param values
*/
private const int saveDPVer = 1;
public override LSL_List xmrEventSaveDets()
{
object[] obs = DetPrmsToObjArr(m_DetectParams);
return new LSL_List(obs);
}
private static object[] DetPrmsToObjArr(DetectParams[] dps)
{
int len = dps.Length;
object[] obs = new object[len * 16 + 1];
int j = 0;
obs[j++] = (LSL_Integer)saveDPVer;
for(int i = 0; i < len; i++)
{
DetectParams dp = dps[i];
obs[j++] = (LSL_String)dp.Key.ToString(); // UUID
obs[j++] = dp.OffsetPos; // vector
obs[j++] = (LSL_Integer)dp.LinkNum; // integer
obs[j++] = (LSL_String)dp.Group.ToString(); // UUID
obs[j++] = (LSL_String)dp.Name; // string
obs[j++] = (LSL_String)dp.Owner.ToString(); // UUID
obs[j++] = dp.Position; // vector
obs[j++] = dp.Rotation; // rotation
obs[j++] = (LSL_Integer)dp.Type; // integer
obs[j++] = dp.Velocity; // vector
obs[j++] = dp.TouchST; // vector
obs[j++] = dp.TouchNormal; // vector
obs[j++] = dp.TouchBinormal; // vector
obs[j++] = dp.TouchPos; // vector
obs[j++] = dp.TouchUV; // vector
obs[j++] = (LSL_Integer)dp.TouchFace; // integer
}
return obs;
}
/**
* @brief Load current detect params from a list
* @param dpList = as returned by xmrEventSaveDets()
*/
public override void xmrEventLoadDets(LSL_List dpList)
{
m_DetectParams = ObjArrToDetPrms(dpList.Data);
}
private static DetectParams[] ObjArrToDetPrms(object[] objs)
{
int j = 0;
if((objs.Length % 16 != 1) || (ListInt(objs[j++]) != saveDPVer))
throw new Exception("invalid detect param format");
int len = objs.Length / 16;
DetectParams[] dps = new DetectParams[len];
for(int i = 0; i < len; i++)
{
DetectParams dp = new DetectParams();
dp.Key = new UUID(ListStr(objs[j++]));
dp.OffsetPos = (LSL_Vector)objs[j++];
dp.LinkNum = ListInt(objs[j++]);
dp.Group = new UUID(ListStr(objs[j++]));
dp.Name = ListStr(objs[j++]);
dp.Owner = new UUID(ListStr(objs[j++]));
dp.Position = (LSL_Vector)objs[j++];
dp.Rotation = (LSL_Rotation)objs[j++];
dp.Type = ListInt(objs[j++]);
dp.Velocity = (LSL_Vector)objs[j++];
SurfaceTouchEventArgs stea = new SurfaceTouchEventArgs();
stea.STCoord = LSLVec2OMVec((LSL_Vector)objs[j++]);
stea.Normal = LSLVec2OMVec((LSL_Vector)objs[j++]);
stea.Binormal = LSLVec2OMVec((LSL_Vector)objs[j++]);
stea.Position = LSLVec2OMVec((LSL_Vector)objs[j++]);
stea.UVCoord = LSLVec2OMVec((LSL_Vector)objs[j++]);
stea.FaceIndex = ListInt(objs[j++]);
dp.SurfaceTouchArgs = stea;
dps[i] = dp;
}
return dps;
}
/**
* @brief The script is executing a 'state <newState>;' command.
* Tell outer layers to cancel any event triggers, like llListen(),
* then tell outer layers which events the new state has handlers for.
* We also clear the event queue as per http://wiki.secondlife.com/wiki/State
*/
public override void StateChange()
{
// Cancel any llListen()s etc.
// But llSetTimerEvent() should persist.
object[] timers = m_XMRLSLApi.acm.TimerPlugin.GetSerializationData(m_ItemID);
AsyncCommandManager.RemoveScript(m_Engine, m_LocalID, m_ItemID);
m_XMRLSLApi.acm.TimerPlugin.CreateFromData(m_LocalID, m_ItemID, UUID.Zero, timers);
// Tell whoever cares which event handlers the new state has.
m_Part.SetScriptEvents(m_ItemID, GetStateEventFlags(stateCode));
// Clear out any old events from the queue.
lock(m_QueueLock)
{
m_EventQueue.Clear();
for(int i = m_EventCounts.Length; --i >= 0;)
m_EventCounts[i] = 0;
}
}
}
/**
* @brief Thrown by things like llResetScript() to unconditionally
* unwind as script and reset it to the default state_entry
* handler. We don't want script-level try/catch to intercept
* these so scripts can't interfere with the behavior.
*/
public class ScriptResetException: Exception, IXMRUncatchable
{
}
/**
* @brief Thrown by things like llDie() to unconditionally unwind as
* script. We don't want script-level try/catch to intercept
* these so scripts can't interfere with the behavior.
*/
public class ScriptDieException: Exception, IXMRUncatchable
{
}
}