OpenSim.Region.ScriptEngine.Common
From OpenSimulator
NOTE! THIS PAGE IS OBSOLETE! ScriptEngine has been replaced by XEngine!
OpenSim.Region.ScriptEngine.Common
How to implement your own script engine
Creating a new script engine for OpenSim is simple (haha).
What I mean by simple is that if you already have a VM (Virtual Machine) or script engine of some sort then integrating it as a OpenSim ScriptEngine plugin is simple.
What do I get for free?
1. Loading/unloading of scripts are queued and executed in sequence. Only one load/unload at the time.
2. What script belongs to what object is automatically taken care of. You just need to provide the scripts classes - nothing else.
3. OpenSim events are translated and given to you as LSL events. Your script functions are executed automatically, no need to hook up to events or anything.
If you want to implement your own event handlers, feel free to do so. Look in "OpenSim.Region.ScriptEngine.Common.ScriptEngineBase.EventManager.cs" how its done. Your own events can run together with LSL events without any problem.
4. Whenever an event is executed, the execution is queued. Only one event per script is ever executed simultaneous. But multithreading is used to run multiple scripts in parallel.
5. Errors during script execution is automatically handled and relayed to users in-world.
6. You get all LSL commands in an easy to use object, no need to keep track of scene or objects to execute functions. Running an LSL command from your script engine is as easy as running it from inside any LSL-script.
7. Long LSL-commands that returns with an event is also handled.
Getting started
1. Create a new project named "OpenSim.Region.MySuperScriptEngine" (replace "MySuperScriptEngine" with the name of your scriptengine).
2. Create a new class (file) named "ScriptEngine". It must be named exactly ScriptEngine for OpenSim to use it. ScriptEngine must inherit from "OpenSim.Region.ScriptEngine.Common.ScriptEngineBase.ScriptEngine".
3. You need to override a couple of functions. Most probably you don't want to do any special work here, so look below for a sample default file.
4. Create a new class (file) named "ScriptManager" that inherits from "OpenSim.Region.ScriptEngine.Common.ScriptEngineBase.ScriptManager".
5. Your ScriptManager class needs to override two functions. One to start a script and one to stop a script.
ScriptEngine class
The main purpose of ScriptEngine class is to be able to intercept scene, for example starting with a "fake" scene instead of OpenSim's scene. You probably don't want this, so just go for the standard initialization.
A standard class would look like this:
using System; using Nini.Config; using OpenSim.Framework.Console; using OpenSim.Region.Environment.Scenes; namespace OpenSim.Region.ScriptEngine.MySuperScriptEngine { [Serializable] public class ScriptEngine : OpenSim.Region.ScriptEngine.Common.ScriptEngineBase.ScriptEngine { public override void Initialise(Scene scene, IConfigSource config) { InitializeEngine(scene, MainLog.Instance, true, GetScriptManager()); } public override OpenSim.Region.ScriptEngine.Common.ScriptEngineBase.ScriptManager _GetScriptManager() { return new ScriptManager(this); } } }
ScriptManager class
A script is refecenced by object id "localID" (the object/prim it is inside) and script id "itemID" (the particular (unique) instance of that script). These parameters along with a string "Script" containing the script text (source code) are passed to your (overriden) start script function in your ScriptManager class.
Keeping track of active scripts is done automatically, so your job is simply to create a new script and hand it over. After that your work is done until the script is asked to stop, then you have to do a few simple tasks to remove it again.
Here is a sample on a minimal script startup:
public override void _StartScript(uint localID, LLUUID itemID, string Script) { SceneObjectPart m_host = World.GetSceneObjectPart(localID); try { IScript CompiledScript = MySpecialCompiler.DoCompile(Script); // Compile and return an IScript object that is the actual script CompiledScript.Source = ScriptSource; // Store source for recompile on script reset events SetScript(localID, itemID, CompiledScript); // All is ok. Add script to OpenSim's script manager. From here on its on autopilot. // Create a private instance of LSL commands (note that m_scriptEngine, m_host are from baseclass, so you don't need to change them) LSL_BuiltIn_Commands LSLB = new LSL_BuiltIn_Commands(m_scriptEngine, m_host, localID, itemID); CompiledScript.Start(LSLB); // Start the script - giving it LSL commands // Fire the first start-event, this is the event run on LSL scripts when they first start m_scriptEngine.m_EventQueueManager.AddToScriptQueue(localID, itemID, "state_entry", EventQueueManager.llDetectNull, new object[] { }); } catch (Exception e) { // To see how you can send the compile error in-world, have a look at OpenSim.Region.ScriptEngine.DotNetEngine.ScriptManager } }
And your minimal script shutdown would be:
public override void _StopScript(uint localID, LLUUID itemID) { m_scriptEngine.m_LSLLongCmdHandler.RemoveScript(localID, itemID); // Stop long command on script like timers, http requests, etc. IScript LSLBC = GetScript(localID, itemID); // Get the script we are stopping if (LSLBC == null) // Nothing to do? Exit. return; try { LSLBC.Exec.StopScript(); // Tell script to stop RemoveScript(localID, itemID); // Remove script from internal memory structure } catch (Exception e) { // You probably want to log this to console or something } }
IScript interface
So you have implemented the two classes described above. Now how does this connect to your JavaScript or Perl or YourCustomSuperLanguage script engine or VM?
You have to create a class. This class must inherit "OpenSim.Region.ScriptEngine.Common.IScript". Inside IScript your class will have two important things:
1. During script start (in ScriptManager), you call yourscript.Start() and give it a LSL command module. So insite your script class where you override this Start() function you need to remember this LSL command module.
How you bind this LSL command module to your custom engine is totally up to you. The LSL command module contains all LSL functions, and any function you use there will affect the correct object/region/whatever. You don't need to do more than myLSLCommandModule.llSay(0, "Hello world!"); to make it work.
2. Any time a LSL event is generated either by an in-world event or by for example a timer, a function in your script class needs to be executed. For this you have the "Exec" override. I suggest you have a look at "OpenSim.Region.ScriptEngine.Common.Executor" to see how this work. Basically you receive a string containing function name and the parameters for that event.
If you use the default Executor you need to initialize it by giving it a reference to yourself (the IScript script class). Then you need to have functions in your class matching LSL functions when it comes to both name and parameters. Example:
public touch_start(int number_of_touches) { // whatever code for this event... probably calling your own VM's function with the same name? ;) }
AppDomain security
If you are writing a .Net based script engine and want AppDomain security and loading/unloading then:
// Load script: IScript CompiledScript = m_scriptEngine.m_AppDomainManager.LoadScript(filename);
and
// Unload script: m_scriptEngine.m_AppDomainManager.StopScript(LSLBC.Exec.GetAppDomain());