PDA

View Full Version : Scripting



pstudio
15-05-2010, 03:14 PM
Hi Jarrod,

I've started playing around with the latest version of PyroGine(v0.4.2) and despite the lack of documentation I'm liking it ;)

I was wondering if you could give me a small example on how to use the scripting system. I've seen how you can compile and run the full AstroBlaster demo but that's not quite what I want. I would like to know how I can expose specific entities to a script so the script can manipulate the entities?

For instance a script can define the logic for an enemy, so for each TEnemy inheriting from TPGEntity, I'll call the EnemyLogic script during each update.

Pyrogine
15-05-2010, 03:53 PM
@pstudio

Coolness, thanks. Sure, that can be done with PGE. Wait for my reply and I will post a small demo that will show how this is done.

pstudio
15-05-2010, 04:03 PM
Thanks

The build-in scripting system is one of the thing about Pyro that makes it stand out compared to other solutions IMO.

Pyrogine
15-05-2010, 07:40 PM
Ok, here is an example of controlling the ship in script from the host side.

host side:

program pstudio_scripting;

{$APPTYPE CONSOLE}

uses
SysUtils,
PGSystem,
PGGraphics,
PGActor,
PGScript,
PGApplication;

type
{ --- TScript --------------------------------------------------------------- }
TScript = class(TPGScript)
protected
FLastModual: string;
public
constructor Create; override;
destructor Destroy; override;
procedure OnCompilerProgress; override;
end;

{ --- TShip ----------------------------------------------------------------- }
TShip = class(TPGEntity)
public
constructor Create; override;
destructor Destroy; override;
function NextFrame: Boolean; override;
procedure RotateRel(aAngle: Single); override;
procedure Thrust(aSpeed: Single); override;
end;

{ TShipUpdate }
TShipUpdate = procedure(aShip: TShip; aElapsedTime: Single);

{ --- TScript --------------------------------------------------------------- }
constructor TScript.Create;
begin
inherited;
FLastModual := '';
end;

destructor TScript.Destroy;
begin
inherited;
end;

procedure TScript.OnCompilerProgress;
begin
if FLastModual <> Self.GetCurrModuleName then
begin
FLastModual := Self.GetCurrModuleName;
WriteLn('Compiling ',FLastModual,'...');
end;
end;


{ --- TShip ----------------------------------------------------------------- }
constructor TShip.Create;
begin
inherited;
end;

destructor TShip.Destroy;
begin
inherited;
end;

function TShip.NextFrame: Boolean;
begin
inherited;
end;

procedure TShip.RotateRel(aAngle: Single);
begin
inherited;
end;

procedure TShip.Thrust(aSpeed: Single);
begin
inherited;
end;

var
Sprite: TPGSprite;
Page : Integer;
Group : Integer;
Ship : TShip;
Script: TScript;
Ship_Update: TShipUpdate;

{ --- Demo ------------------------------------------------------------------ }
function Demo_Init: Boolean;
var
i: Integer;
s: string;
begin
Result := False;

// init display device
PG.DisplayDevice.Open('pstudio - Scripting', dm800x600, True, True);

// init render device
PG.RenderDevice.SetMode(0, seDiscard);

// init sprite object
Sprite := TPGSprite.Create;

// load ship texture
Page := Sprite.LoadPageFromFile('ship.png', PG_ColorKey);

// add ship group
Group := Sprite.AddGroup;

// add images to ship group
Sprite.AddImageFromGrid(Page, Group, 0, 1, 64, 64);
Sprite.AddImageFromGrid(Page, Group, 1, 1, 64, 64);
Sprite.AddImageFromGrid(Page, Group, 2, 1, 64, 64);

// init ship entity
Ship := TShip.Create;
Ship.Init(Sprite, Group);
Ship.FrameFPS := 14;
Ship.SetPosAbsXY(400, 300);

// init script manager
Script := TScript.Create;

// register TShip class type
i := Script.RegisterClassType(0, TShip);

// register TShip methods
Script.RegisterHeader(i, 'constructor Create; override;', @TShip.Create);
Script.RegisterHeader(i, 'destructor Destroy; override;', @TShip.Destroy);
Script.RegisterHeader(i, 'function NextFrame: Boolean; override;', @TShip.NextFrame);
Script.RegisterHeader(i, 'procedure RotateRel(aAngle: Single); override;', @TShip.RotateRel);
Script.RegisterHeader(i, 'procedure Thrust(aSpeed: Single); override;', @TShip.Thrust);

// add a new pascal module
Script.AddModule('ship_logic', lsPascal);

// add source into pascal module
Script.AddCodeFromFile('ship_logic', 'ship_logic.pas');

// compile the script
if Script.Compile then
begin
// get the address of update routine
Ship_Update := Script.GetAddress('Ship_Update');

// check if valied
if not Assigned(Ship_Update) then
begin
PG.ShowMessage('debug', 'Failed to get routine address', &#91;]);
Exit;
end;
end
else
begin
// display errors
for i := 0 to Script.GetErrorCount - 1 do
begin
s := Format('%s(%d:%d):%s', [Script.GetErrorModuleName(i),
Script.GetErrorLineNumber(i) + 1, Script.GetErrorLinePos(i) + 1,
Script.GetErrorMessage(i)]);
WriteLn(s);
end;
PG.ConPause('Press ENTER to continue...');
Exit;
end;

Result := True;
end;

procedure Demo_Done;
begin
// destroy script
PG_FreeAndNil(Script);

// destroy sprite
PG_FreeAndNil(Sprite);

// restore desktop mode
PG.RenderDevice.RestoreMode;

// close display device
PG.DisplayDevice.Close;
end;

procedure Demo_Update(aElapedTime: Single);
begin
// update ship via script
Ship_Update(Ship, aElapedTime);
end;

procedure Demo_Render;
begin
// render ship
Ship.Render(0, 0);
end;

procedure Demo_Run;
begin
// game loop
while not PG.Terminated do
begin
// process windows messages
PG.ProcessMessages;

// check if the render device is ready, loop if not
if not PG.RenderDevice.Ready then
begin
continue;
end;

// update timing
PG.Timer.Update;

// update demo
Demo_Update(PG.Timer.ElapsedTime);

// clear frame buffer
PG.RenderDevice.ClearFrame(cfDefault, PG_SkyBlue);

// start frame
if PG.RenderDevice.StartFrame then
begin
// render demo
Demo_Render;

// end frame
PG.RenderDevice.EndFrame;
end;

// show frame buffer
PG.RenderDevice.ShowFrame;
end;
end;

{ --- Main ------------------------------------------------------------------ }
var
ok: Boolean;
begin
// init demo
ok := Demo_Init;
try

// if all ok run demo
if ok then
begin
Demo_Run;
end;
finally

// shut down demo
Demo_Done;
end;
end.

script side:


// update native TShip from compiled script
procedure Ship_Update(aShip: TShip; aElapedTime: Single);
begin
// update next frame
aShip.NextFrame;

// rotate to new angle
aShip.RotateRel(3.0*aElapedTime);

// thrust in the direction of rotate
aShip.Thrust(7.0*aElapedTime);
end;

begin
end.

You can download the Delphi project here (http://pyrogine.com/support/pstudio/pstudio.zip).

Comments:
Even though TPGEntity is already registered by the scripting system, because TObject is crossing the DLL boundary you will still need to registered all methods and routines that you want "seen" by the scripting system from the host side.
Once the script is compiled, you can use GetAddress to get the address of a script object (variable, routine, method)
If you wanted to access a class on the script side you would simply call CreateObject, GetAddress to get it's address, and finally call DestroyObject to destroy the class.
Note it really does not matter which direction you take because the compiled script is x86 native code. On average it will be about 3 times slower than optimized Delphi code.
All the examples from the pascal folder are standalone compile versions which shows just how fast the scripting system really is.
Scripts can be compiled and save out to a DLM (Dynamic Loadable Module). You can then load this in at run-time to access your routines and classes.
The whole SDK is already registered so from script you have access to PGE and can do almost anything in script, including loading in other DLMs and having the running script access that.

pstudio
15-05-2010, 08:02 PM
Wow Great Thanks:)

And it is impressive that it all compiles to native x86 code. PyroGine seems to be an impressive engine.

I suppose StellarDefense is ment to show just how awesome PyroGine is? :D

Pyrogine
15-05-2010, 08:18 PM
No worries. I'm here to help. In fact during the weekends I will try to be on IM if anyone needs me.

You can find me via IM on one of these:

Windows IM GoogleTalk Yahoo IM
------------------------------------------------------
pyroginedev [ at ] hotmail.com | gmail.com | yahoo.com

Astra (Trillian)
------------------------------------------------------
pyrogine [ at ] pyrogine.com


Yea, I'm currently working on SD. I made PGE to make games. My vision was to get the engine to a point where I can do maybe 1-2 games per year and about once a year or so then do a major overhaul on the engine. The next major version will focus on cross platform support. SD will show case the great features of PGE, yes.

On one thing... I need to add the PG DLL in the distro because I had to fix a few bugs making this demo. This is why it took a bit to finish. Wait for a few min and then re download from the same link. Sorry about that.

Pyrogine
15-05-2010, 08:22 PM
Ok, I've re uploaded a new demo that include the PyroGine.dll. When working on this for you, I discovered and fixed a few errors relating to scripting. So, thanks for the request.

To compile and run this demo and anything relating to scripting you need to include this version or higher of the DLL. The next release will have this fix included.

pstudio
15-05-2010, 10:54 PM
Thanks again.
I noticed it wasn't working with the old dll but all is working fine now.
EDIT: Apparently I can't compile the project myself. I get the following error:

[Pascal Error] pstudio_scripting.dpr(72): E2008 Incompatible types

at

function TShip.NextFrame: Boolean;
begin
inherited; // Here
end;


I do have one question though. What exactly is the Integer argument 'aLevelId' in TPGScript.RegisterClassType and TPGScript.RegisterHeader and what does the return value mean?

Pyrogine
15-05-2010, 11:07 PM
Hmm....

Try this:

function TShip.NextFrame: Boolean;
begin
Result := inherited NextFrame;
end;


aLevel is the current level for the object your registering. For example, if you are registering a name space, then for all the objects that should go in this name space you pass in that value. Such as this:


// register name and return an id
n := Script.RegisterNamespace('MyStuff');

// register TMyClassStuff in the 'MyStuff' name space
c := Script.RegisterClassType(n, TMyClassStuff);

// the return value of c can be used to finish register methods and type related to the class


Oh, what version of Delphi are you using? If the above does not work I will zip up the distro. I may have made some changes to the interfaces the last few days.

Pyrogine
15-05-2010, 11:16 PM
Here is the current PGE v0.4.3 build (http://pyrogine.com/support/pstudio/PGE_v0.4.3.zip), try it.

pstudio
16-05-2010, 11:53 AM
I'm using Turbo Delphi.

I'm using v0.4.3 now but that doesn't change anything.

If I write
function TShip.NextFrame: Boolean;
begin
Result := inherited NextFrame;
end;

like you suggested it will compile, but then I get the following error written in the console:

Exception EAccessViolation in module PyroGine.dll at 0000C047.
Access violation at address 01B6C047 in module 'PyroGine.dll'. Read of address 0
0000000.

I've done some tracing and the error appears to occur at the following line:

i := Script.RegisterClassType(0, TShip);

I'll try and do some more experimenting later on.

Pyrogine
16-05-2010, 01:53 PM
Working on it.... I'll update you soon.

Pyrogine
16-05-2010, 08:17 PM
Phew.... ok I think I finally got it resolved now. Turned out to be an issue with Ansi vs Unicode. Everything will be transparent to the developer as far as usage goes, but I had to modify the code base that deals with scripting to handle this particular case. I've tested PGE in Delphi 5 (the lowest version I officially support) through D2010 so it should work for everyone that has been having issues recently. I will post an update shortly.

Thanks.

pstudio
16-05-2010, 08:59 PM
Ok cool,

sorry to bring up bugs in your code :D

Pyrogine
16-05-2010, 10:27 PM
Ok, I think I got all the latest reported issues resolved now. Please download (http://pyrogine.com/support/PGE_v0.4.3.zip) this build which if there are no reported issues will become the new official v0.4.3 release. I recommend everyone using PGE to upgrade to this build ASAP.

Some comments:
When you update to a new build, make sure the DLLs have been updated and it's best to update any lingering .DCUs in the sdk\delphi folder.
Ansi TObject and unicode TObject are different enough that I had to separate out the scripting system. If your using Ansi Delphi you will need to distribute PGScript.dll along with your application. The ansi version of the scripting system is in there. I had to do it this way in order for things to continue working transparently for the developer. PGE is written in D2010 so TObject in the PyroGine.dll is Unicode. When you register a class in D7 for example, the scripting engine will try and register an ansi version which will eventually crash the system. The way I have it now, all should be ok.
It is now mandatory to add PGShareMem at the first line in your project uses section. There will be error message displayed if not. This assures that shared memory exist between your app and the PGE DLL so strings and objects are managed properly.


Ok cool,

sorry to bring up bugs in your code Laugh
No worries, it's a good thing really. This helps me to get these problems identified and resolved so thanks to everyone for the feedback.

pstudio
16-05-2010, 11:42 PM
I've downloaded it and it seems to work fine now.
At least I could compile your example and run it (after remembering to add PGShareMem) :)

Pyrogine
17-05-2010, 01:40 AM
Wonderful. Good to know.

Thanks.

pstudio
17-05-2010, 01:43 PM
Btw I did get a few warnings about PGShareMem using some deprecated memory classes. Don't know if you are aware of them already (or if you even care), but I thought I would mention them just to make sure you've seen them.


[Pascal Warning] PGShareMem.pas(23): W1000 Symbol 'THeapStatus' is deprecated
[Pascal Warning] PGShareMem.pas(39): W1000 Symbol 'TMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(40): W1000 Symbol 'TMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(41): W1000 Symbol 'TMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(53): W1000 Symbol 'TMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(55): W1000 Symbol 'GetMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(82): W1000 Symbol 'SetMemoryManager' is deprecated
[Pascal Warning] PGShareMem.pas(94): W1000 Symbol 'SetMemoryManager' is deprecated

Pyrogine
17-05-2010, 05:05 PM
Hi,

Yea I know about them. I will add some conditionals later to prevent this. It's because I have to support older versions of Delphi where in later versions those routines have been replaced by different/enhanced ones.

Thanks.