Dies ist eine alte Version des Dokuments!
The scripting system is initialised by creating one or more instances of the ScriptManager class. Each instance represents a scripting environment where scripts can be loaded and executed. Each instance also provides a global variable scope that the scripts can use to share data.
Manager manager = new Manager();
Once a script manager is available, scripts can be loaded by creating instances of the Script class. The script object's constructor requires a reference to the script manager and a name to identify the script. By default, this name corresponds to a disk filename.
Script script = new Script(manager, "script.txt");
The script object's constructor automatically compiles the script source into Conscript byte code. By default, the generated byte code is optimised using a „peephole“ assembly optimiser. Debug instructions are added to facilitate mapping of the generated code with the original source. These settings may be controlled by setting the ScriptManager's properties DebugMode and OptimiseCode.
A script object represents only the programming instructions contained within and not its execution state. To execute the script, an instance of the ScriptContext class must be created. The class' constructor requires a reference to the script to be executed or a reference to one of the script's functions. A script reference implies that the main function will be executed. The script context provides execution control, as well as access to the execution state in terms of the variables defined during execution, the next statement to be executed and so on. The ScriptContext class represents a running instance of a script. Hence, multiple instances of the same script object can be executed within the same script manager by creating multiple script contexts referencing the same script.
// create a context for the script's main function Interpreter interpreter = new Interpreter(script); // also creates a context for the script's main function Interpreter interpreter = new Interpreter(script.MainFunction); // create a context for one of the script's named functions Function scriptFunction = script.Functions["WanderAround"]; Interpreter interpreter = new Interpreter(scriptFunction);
The script context object allows execution of the referenced script via the three variants of its Execute method. These are: execution of scripts for an indefinite amount of time; execution of scripts for a given time interval or execution of scripts for up to a maximum number of executed statements. The first method variant allows the referenced script function to execute indefinitely or until the end of the function is reached. If the script contains an infinite loop, this method will block indefinitely unless an interrupt is generated. The Execute method returns the total number of statements executed since its invocation.
// execute indefinitely, or until termination, or a script interrupt is generated interpreter.Interpret();
The second variant of the Execute method allows the script context to execute up to a given maximum number of statements. The script context may break out of execution before the maximum is reached if there are no more statements to process or if an interrupt is generated.
// execute up to a maximumum of 10 statements interpreter.Interpret(10);
The third variant of the Execute method accepts a TimeSpan defining the maximum time interval allowed for script execution. The method may break out of execution earlier than the given interval if there are no more statements to process or an interrupt is generated. Given a script with a good balance of different statements, a possible use of this method is to determine the speed of the scripting system on the target environment in terms of statements executed per second.
// execute for up to 10 milliseconds TimeSpan tsInterval = new TimeSpan(0, 0, 0, 0, 10); interpreter.Interpret(tsInterval);
The second and third variants of Execute may be used to implement a virtual multi-threaded scripting environment. Global variables may be used as semaphores to synchronise concurrently running scripts.
A script context will normally execute its referenced script function indefinitely, for a given time interval, until a given maximum number of statements are executed or until there are no more statements to process. In some cases it is desirable to break execution prematurely, such as to return control to the host when specific statements are executed, or because a script is too computationally intensive to execute in one go. Conscript provides two ways for generating script interrupts:
To allow for loading of scripts from other sources – such as an archive file, network or database – a custom script loader class can be developed and bound to the script manager to be used for loading the script. The script loader class may be any class that implements the ScriptLoader interface. The loader is used to retrieve the script specified in the Script class constructor and also any additional include scripts defined within the original script.
// custom script loader class
public class MyScanner : Scanner
{
public List<string /> Scan(String strResourceName)
{
// loader implementation here...
}
}
// in initialisation code...
MyScanner scanner = new MyScanner();
interpreter.scanner = scanner;
One approach for allowing a script to communicate with or control the host application entails the application polling the local variables of the associated script context and global variables of the associated script manager. It does this by querying the LocalDictionary property of the ScriptContext object, the ScriptDictionary property of the Script object or the GlobalDicitonary property of the ScriptManager object.
// get value of local variable int i = (int)interpreter.LocalMemory["variableName"]; // get value of script variable int i = (int)script.ScriptMemory["variableName"]; // get value of global variable bool bNewQuest = (bool)manager.SharedMemory["variableName"];
A more powerful alternative to allow a script to interface with the host application is to register host functions with the script manager and assign a script handler at script context or manager level. The script handler in turn provides an implementation for the host functions. These functions are first defined by creating an instance of the HostFunctionPrototype class to define the functions' names, parameters and return values. Unlike script-defined functions, host functions can enforce type-checking on parameters and return values that are performed at compile time. Thus, scripts that use host functions must be loaded within a script manager that has the required host functions registered beforehand.
// define host function to move player HostFunctionPrototype hostFunctionPrototype = new HostFunctionPrototype( null, "Player_MoveTo", typeof(int), typeof(int));
A number of constructors are provided for quick construction of function prototypes with up to three parameters. For functions requiring more parameters, a constructor that accepts a type list, List<Type>, is also provided:
// prepare parameter type list List<Type> listParameterTypes = new List<Type>(); listParameterTypes.Add(typeof(String)); listParameterTypes.Add(typeof(int)); listParameterTypes.Add(typeof(int)); listParameterTypes.Add(typeof(bool)); listParameterTypes.Add(typeof(AssociativeArray)); // define function prototype with many parameters HostFunctionPrototype hostFunctionPrototype = new HostFunctionPrototype( typeof(float), "FuncWithManyParams", listParameterTypes);
Once the host function prototype is defined, it can be registered with the script manager. The function prototype ensures that a corresponding host function is recognised during compilation and runtime and that the parameters passed alongside correspond in number, type and order to those defined in the function prototype. The following statement registers a host function prototype without a handler:
// register host function m_scriptManager.RegisterHostFunction(hostFunctionPrototype);
The registration method illustrated above requires handler objects to be attached to every ScriptContext created from a script that uses the host function. A handler may be any class that implements the HostFunctionHandler interface consisting of the method OnHostFunctionCall(…). A good approach is to extend the class that owns the relevant script context. This is illustrated by the following example, which implements host functions for enemy A.I. scripts in a game:
public class Enemy
: HostFunctionHandler
{
// class implementation
private ScriptContext m_scriptContext
public Enemy(..., Script script, ...)
{
// create script context
m_scriptContext = new ScriptContext(script);
// assign self as host function handler
m_scriptContext.Handler = this;
}
// HostFunctionHandler implementation
public object OnHostFunctionCall(String strFunctionName,
List<object> listParameters)
{
// handle random number generator
if (strFunctionName == "GetRandom")
{
int iMin = (int)listParameters[0];
int iMax = (int)listParameters[1];
return s_random.Next(iMin, iMax);
}
// handle enemy movement
else if (strFunctionName == "Move")
{
int iDeltaX = (int)listParameters[0];
int iDeltaY = (int)listParameters[1];
Move(iDeltaX, iDeltaY);
}
// handle enemy direction
else if (strFunctionName == "SetDirection")
{
Direction = (EntityDirection)(int)listParameters[0];
}
// handle enemy direction flag
else if (strFunctionName == "SetAutomaticDirection")
{
AutomaticDirection = (bool)listParameters[0];
}
// handle enemy position query
else if (strFunctionName == "GetPosition")
{
AssociativeArray associativeArrayPosition =
new AssociativeArray();
associativeArrayPosition["X"] = this.X;
associativeArrayPosition["Y"] = this.Y;
return associativeArrayPosition;
}
// handle player position query
else if (strFunctionName == "GetPlayerPosition")
{
AssociativeArray associativeArrayPosition =
new AssociativeArray();
associativeArrayPosition["X"] = m_player.X;
associativeArrayPosition["Y"] = m_player.Y;
return associativeArrayPosition;
}
// handle enemy fire
else if (strFunctionName == "CastSpell")
{
Image imageSpell = Properties.Resources.EnemySpell;
int iDamage = m_enemyType == EnemyType.Wizard ? 40 : 20;
Spell spell =
new Spell(m_enemies.Room.Spells,
imageSpell, false, iDamage, Direction);
spell.X = X;
spell.Y = Y - 30;
m_enemies.Room.Spells.Add(spell);
return null;
}
return null;
}
Worth noting in the above example is that no parameter validation is performed, nor is it needed because the validation is performed at compile time. The ability to define a different handler for every script context allows a host function to have different implementations and / or contexts depending on the script contexts to which respective script handlers are bound. For example, the implementation for a Move(x, y) function within a script might affect the movement of a player character, a non-player character or a projectile. Alternatively, a host function prototype may be registered with an associated handler directly:
// register global Sine function HostFunctionPrototype hostFunctionPrototype = new HostFunctionPrototype( typeof(float), "Sin", typeof(float)); m_scriptManager.RegisterHostFunction(hostFunctionPrototype, trigHandler); // register global Cosine function HostFunctionPrototype hostFunctionPrototype = new HostFunctionPrototype( typeof(float), "Cos", typeof(float)); m_scriptManager.RegisterHostFunction(hostFunctionPrototype, trigHandler); // register global Tangent function HostFunctionPrototype hostFunctionPrototype = new HostFunctionPrototype( typeof(float), "Tan", typeof(float)); m_scriptManager.RegisterHostFunction(hostFunctionPrototype, trigHandler);
This approach allows the association of a common handler that is shared amongst all script contexts created within the same script manager. A typical example is the registration of trigonometric and other common math functions required in many scripts.
Registering shared host functions individually required a substantial amount of setup code. To alleviate this problem, a HostModule interface is provided to allow the registration of multiple host functions in bulk. The implementation of a host module entails defining a method that returns a list of function prototypes implemented by the module, together with a handler method as per the HostFunctionHandler interface. This provides an implementation for the functions. The following illustrates an alternative implementation of the earlier trigonometry functions example:
public class TrigonometryModule
: HostModule
{
// list of functions stored statically for one-off creation
private static ReadOnlyCollection<hostfunctionprototype />
s_listHostFunctionPrototypes;
// module constructor
public TrigonometryModule()
{
// if list created, don't do anything else
if (s_listHostFunctionPrototypes != null) return;
// create list of function prototypes
List<hostfunctionprototype /> listHostFunctionPrototypes =
new List<hostfunctionprototype />();
HostFunctionPrototype hostFunctionPrototype = null;
// add Sine prototype to list
hostFunctionPrototype =
new HostFunctionPrototype(typeof(float), "Sin", typeof(float));
listHostFunctionPrototypes.Add(hostFunctionPrototype);
// add Cosine prototype to list
hostFunctionPrototype =
new HostFunctionPrototype(typeof(float), "Cos", typeof(float));
listHostFunctionPrototypes.Add(hostFunctionPrototype);
// add Tangent prototype to list
hostFunctionPrototype =
new HostFunctionPrototype(typeof(float), "Tan", typeof(float));
listHostFunctionPrototypes.Add(hostFunctionPrototype);
s_listHostFunctionPrototypes =
listHostFunctionPrototypes.AsReadOnly();
}
// returns list of available functions
public ReadOnlyCollection<hostfunctionprototype /> HostFunctionPrototypes
{
get { return s_listHostFunctionPrototypes; }
}
// implements functions
public object OnHostFunctionCall(String strFunctionName,
List<object> listParameters)
{
if (strFunctionName == "Sin")
// implement Sine
return (float)Math.Sin((float)listParameters[0]);
else if (strFunctionName == "Cos")
// implement Cosine
return (float)Math.Cos((float)listParameters[0]);
else if (strFunctionName == "Tan")
// implement Tangent
return (float)Math.Tan((float)listParameters[0]);
// unknown function (should not happen)
throw new ExecutionException(
"Unimplemented function '" + strFunctionName + "'.");
}
}
Host module registration is very similar to individual function registration:
// create module instance TrigonometryModule trigonometryModule = new TrigonometryModule(); // register module m_scriptManager.RegisterHostModule(trigonometryModule)