Below is an overview document that briefly explains how to integrate Evil script to your program.
Declares an evil script instance:
var
SE: TScriptEngine;
...
SE := TScriptEngine.Create;
Resets the script engine to default state:
SE.Reset;
Pass the source code to the script engine. Note that TScriptEngine.Reset will automatically be called:
SE.Source := 'writeln("hello, world!")';
Executes the script:
SE.Exec;
Forces script recompilation, without reset the state:
SE.Lex;
SE.Parse;
Given the following script:
a = 5
fn add(b) {
result = a + b
}
Executes the script once to initialize global variables:
SE.Exec;
Executes a named function inside the script, then shows the result on screen:
Writeln(SE.ExecFuncOnly('add', [7]).VarNumber); // Should be 12
Declares a new function. The function below will accept 2 parameters, add them together and return the result:
type
TCustomFunctions = class
public
class function Add(const VM: TSEVM; const Args: PSEValue; const ArgCount: Cardinal): TSEValue;
end;
...
class function TCustomFunctions.Add(const VM: TSEVM; const Args: PSEValue; const ArgCount: Cardinal): TSEValue;
begin
Result := Args[0] + Args[1];
end;
Register the function to the script engine:
SE.RegisterFunc('add', @TCustomFunctions(nil).Add, 2);
SE.Source := 'a = add(2, 3)';
Declares a new function. The function below will accept 2 parameters, add them together and assign the result to self.value:
type
TCustomFunctions = class
public
class function Add(const VM: TSEVM; const Args: PSEValue; const ArgCount: Cardinal; const This: TSEValue): TSEValue;
end;
...
class function TCustomFunctions.Add(const VM: TSEVM; const Args: PSEValue; const ArgCount: Cardinal; const This: TSEValue): TSEValue;
begin
This.SetValue('value', Args[0] + Args[1]);
end;
Register the function to the script engine:
SE.RegisterFuncWithSelf('add', @TCustomFunctions(nil).Add, 2);
SE.Source :=
'obj = [ value: 0, add: add ]' + #10 +
'obj.add(2, 3)';
yield is useful when you need to temporarily exit the script, perform tasks on the Pascal side, and then resume the script where it left off.
Consider the following script:
i = 0
while i < 3 {
// Exit the script. The next time it will start from where it left off
yield
// Do something
i += 1
}
On Pascal side, we call TScriptEngine.Exec in a loop until IsDone flag is set to true:
while not SE.IsDone do
begin
SE.Exec;
// Do something else
end;
Execis used when you want to execute the script until it's done (check theTScriptEngine.IsDoneflag).yieldcan be used to quit the script and return later.ExecFuncexecutes a named function. If you want to initialize global variables, callExecbeforeExecFunc.yieldcan be used to quit the script and return later.ExecFuncOnlysimilar toExecFuncexcept it's one time only and because of thatyieldCANNOT be used.
Useful if we want to modify a global variable after intialized them via TScriptEngine.Exec()
SE.VM.SetGlobalVariable('a', NewValue);
By default the compiler will optimizes constant values. If we want to change constant values without having to recompile the script, it is necessary to disable OptimizeConstants:
SE.OptimizeConstants := False;
SE.SetConst('my_const', 5);
SE.Source := 'writeln(my_const)';
SE.Exec; // Print 5
SE.SetConst('my_const', 10);
SE.Exec; // Print 10
A 16-byte data structure. TSEValue.Kind stores the type of variable, which can be one of the following values: sevkNumber, sevkBoolean, sevkString, sevkMap, sevkBuffer, sevkFunction, sevkPascalObject, sevkNull.
- Declares a new TSEValue:
var V: TSEValue; - Assigns null value:
V := SENull; - Assigns number:
V := 5; // Equivalent to: // V.Kind := sevkNumber; // V.VarNumber := 5; - Assigns boolean value:
V := True; // Equivalent to: // V.Kind := sevkBoolean; // V.VarNumber := 1; - Assigns string:
V := 'This is a string'; // Equivalent to GC.AllocString(@V, 'This is a string'); // You can access the string directly via V.VarString^ - Creates a new map:
GC.AllocMap(@V); // You can read / write the map via V.GetValue() / V.SetValue() helpers. // You can access map instance directly via V.VarMap - Creates a new buffer:
// 1024 bytes GC.AllocBuffer(@V, 1024); // Access to buffer via V.VarBuffer^.Ptr pointer. DO NOT touch V.VarBuffer^.Base pointer. - Assigns a Pascal object:
// If IsManaged is true, then the script engine's garbage collector will automatically free AnObjectInstance when the variable is unreachable. GC.AllocPascalObject(@V, AnObjectInstance, IsManaged); // Access the object Obj := V.VarPascalObject^.Value; - Assigns a function:
V.Kind := sevkFunction; // VarFuncKind stores the type of function: // - sefkNative = Pascal function registered via TScriptEngine.RegisterFunc() / TScriptEngine.RegisterFuncWithSelf(). // - sefkScript = Evil script function. V.VarFuncKind := sefkScript; // VarFuncIndx is function index. // We can look for function index via TScriptEngine.FindFuncScript() or TScriptEngine.FindFuncNative() V.VarFuncIndx := 2;
- Install
https://github.com/avk959/LGenericsand enableSE_MAP_AVK959flag for a significant map-related performance boost (approximately 200% more for scripts heavily reliant on maps). - Set
GC.EnableParalleltoTrueto reduce stuttering on tight loops. This is especially useful for games. Note that so far I only test this feature withSE_MAP_AVK959on.