🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

AngelScript 1.8.0 WIP #2 (2004/06/26)

Started by
46 comments, last by WitchLord 19 years, 12 months ago
very cool. I also found something called ac_string.

How would I register char* object properties using bstr?

class foo { public: char *bar; };

how would I register bar? Would I just make it a bstr and register like this:

engine->RegisterObjectProperty("foo", "bstr bar", offsetof(foo, bar));

Or, would I just make bar a bstr in both my game and the scripting engine?
Advertisement
Since bstr relies on some extra information than a normal C string has, you'll have to make your "char *bar" a bstr in the game as well.

It is ok to convert a bstr to char * with a static cast for reading purposes, and even updates within the string. But it is not ok to convert a char * to bstr with a static cast, to do that conversion you should allocate memory for the bstr and then copy the string to it.

A bstr has a hidden property that gives the length of the string. The functions registered for bstr uses this hidden property to copy string data, etc.

I recognize that strings are somewhat complicated matter to deal with, but I have yet to find a better solution. The real problem lies in that the script writer shouldn't have to worry about memory leaks when using strings. This unfortunately requires a little more work by the application writer. At least with version 1.8.0 users are free to change the way strings are handled if they don't like the way bstr works.

[edit: I forgot to login]

[Edited by - WitchLord on June 29, 2004 12:52:36 PM]
The last AP was me ;)

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Nice, I got that working, but have some questions...

When I want to make a string in my host app that will hold script filenames, but using BSTR's, would I use asBSTR *file or
asBSTR file? Could I just say file = new(BSTR)?

Also, let's say I have an object "game" from the class CGame. Do I register everything from the class "CGame" that I want the engine to be able to use, and then register a pointer to the object "game" so that the engine can actually change info in the object? Hmm..That was confusing.

btw:
I think angelcode is a very cool scripting language. I tried out Lua, but it is pretty useless unless you can bind stuff to the scripting engine. Then I tried to compile LuaBind forever, but never had any luck. I'm glad you developed angelscript so you could just open a project and build it.
Though I posted right before here, I came up with some more questions:

I am also confused about how to write scripts.

Do I make a "main" function in the script, and that will be the entry point?

I also am wondering how fast the engine compiles scripts. Should I pre-compile all the scripts when the games starts, and then only execute scripts when the time comes, or is that impossible? In larger games with a lot of scripts, I would assume that to be too demanding on memory. But, let's say I want to run the same script several times per frame in my game, I shouldn't recomplile each time, correct? So how can I load a script, compile it, and store it in a buffer or something, and then execute the buffer?

how do scripts execute...Why would I ever suspend the execution of a scripts?

I found this code in one of the example programs. Should I use these functions to load and execute scripts?

int LoadScript(const char *filename)
{
FILE *f = fopen(filename, "rb");
if( f == 0 )
{
hge->System_Log("Failed to open the script file.");
return -1;
}
int len = _filelength(_fileno(f));
acCString code;
code.Allocate(len, false);
int c = fread(code.AddressOf(), len, 1, f);
fclose(f);
if( c == 0 )
{
hge->System_Log("Failed to load script file.");
return -1;
}
int r = engine->AddScriptSection(filename, code, len);
if( r System_Log("An error occured while adding the script section.");
return r;
}
return 0;
}

void AS_CALL COutStream::Write(const char *text) { printf(text); }

int CompileScript()
{
COutStream out;
int r = engine->Build(&out);
if( r System_Log("Failed to compile script."); return -1; }
// If we wish to build again, the script sections has to be added again.
// Now we will verify the interface of the script functions we wish to call
acCString str;
str.Allocate(256, false);
r = engine->GetFunctionDeclaration(engine->GetFunctionIDByName("main"), str.AddressOf(), 256);
if( r System_Log("Failed to retrieve declaration of function 'main'"); return -1; }
if( str != "float main(float, float, float)" ) { hge->System_Log("Function main should be declared as follows 'float main(float, float, float)"); return -1; }
return 0;
}

int ExecuteScript()
{
// Create a context in which the script will be executed.
// Several contexts may exist in parallel, holding the execution
// of various scripts in the same engine. At the moment contexts are not
// thread safe though so you should make sure that only one executes
// at a time. An execution can be suspended to allow another
// context to execute.
asIScriptContext *ctx;
int r = engine->CreateContext(&ctx);
if( r System_Log("Failed to create a context"); return -1; }
// Prepare the context for execution
// When a context has finished executing the context can be reused by calling
// PrepareContext on it again. If the same stack size is used as the last time
// there will not be any new allocation thus saving some time.
r = ctx->Prepare(engine->GetFunctionIDByName("main"), 1000);
if( r System_Log("Failed to prepare context"); return -1; }
// If the script function takes any parameters we need to
// copy them to the context's stack by using SetArguments()
float a = 1.0f, b = 2.0f, c = 3.0f;
r = ctx->SetArguments(0, (asDWORD*)&a, 1);
r = ctx->SetArguments(1, (asDWORD*)&b, 1);
r = ctx->SetArguments(2, (asDWORD*)&c, 1);
// Execute script
hge->System_Log("Starting script execution");
r = ctx->Execute();
if( r System_Log("Unexpected error during script execution"); return -1; }
if( r == asEXECUTION_FINISHED )
{
hge->System_Log("Execution finished successfully\n");
// If the script function is returning any
// data we can get it with GetReturnValue()
float retVal;
r = ctx->GetReturnValue((asDWORD*)&retVal, 1);
hge->System_Log("Returned: %f", retVal);
}
else if( r == asEXECUTION_SUSPENDED )
{
hge->System_Log("Execution was suspended.");
// In this case we can call Execute again to continue
// execution where it last stopped.
acCString str;
str.Allocate(256, false);
int funcID = ctx->GetCurrentFunction();
engine->GetFunctionName(funcID, str.AddressOf(), 256);
hge->System_Log("func : %s", str.AddressOf());
hge->System_Log("line : %d", ctx->GetCurrentLineNumber());
}
else if( r == asEXECUTION_ABORTED )
{
hge->System_Log("Execution was aborted.");
}
else if( r == asEXECUTION_EXCEPTION )
{
hge->System_Log("An exception occured during execution");
// Print exception description
acCString str;
str.Allocate(256, false);
int funcID = ctx->GetExceptionFunction();
engine->GetFunctionName(funcID, str.AddressOf(), 256);
hge->System_Log("func : %s", str.AddressOf());
hge->System_Log("line : %d", ctx->GetExceptionLineNumber());
ctx->GetExceptionString(str.AddressOf(), 256);
hge->System_Log("desc : %s\n", str.AddressOf());
}
// Don't forget to release the context when you are finished with it
ctx->Release();
return 0;
}
That's quite a lot of questions. Let's see if I can satisfy your curiousity. By the way, have you read the overview that I recently wrote? http://www.angelcode.com/angelscript/articles/overview.html

asBSTR is perhaps not the most convenient way of working with strings when it comes to C++. You're better of using std::string or perhaps even acCString (that you'll find in the AngelScript library source). Here's an example on how you would use asBSTR in C++:

#include "bstr.h"void TestBStr(){  // Allocate a string with 10 characters  asBSTR aString = asBStrAlloc(10);   // Copy data to the string buffer  memcpy(aString, "Hello there", asBStrLength(aString));   // asBStr guarantees a null character after the allocated length  printf("%s\n", aString);  // Free the allocated memory  asBStrFree(aString);}


This code would output

Hello ther


Exactly 10 characters as the bstr puts a null character after the allocated length.

-----

You should register all the methods of CGame object you wish to give the scripts access to. You may also register member variables for direct manipulation. The member variables could be registered as const so that the script cannot alter the value.

Once you've registered the interface to CGame you could register a global property with either a reference to the CGame object or a pointer. Or you could register a function that returns the reference or pointer to the object.

Whatever you choose you can be sure that the script will not be able to do anything with the object that you haven't allowed it to do. That is, the script cannot change any internal states of the CGame except through the registered functions/methods or properties.

----

I'm happy you like AngelScript. I started developing AngelScript for the same reason you started using it. I took a look at Lua and Python but didn't like what I saw, and thought that I could do it better myself. Perhaps not better, but more to my liking. As it turns out a lot of other people also think it is easier to use, which makes me happy with my decision. :)

----

There is no fixed entry point for the scripts. It is the application that decides what it needs. You could for example want to write an application that runs one script function and lets the script control the rest of the program flow. In that case it makes sense to call the script function "main". But other situations may need an initialization function and a termination function and nothing between. In those cases the functions might be called "init" and "uninit". It's all up to you. There is no "fits-all" solution.

The engine compiles the script pretty fast. The exact time depends on size of the script of course. But you should try to compile the scripts once, and then call the compiled functions.

Should you so many scripts that you are having trouble with memory, you might need to rethink the design of your game. Perhaps it would be possible divide the game in sections each with a smaller amount of scripts. When the player moves from one section to another the respective scripts are loaded and compiled.

-----

Scripts are compiled into bytecode and executed sequentially in separate contexts. When you call Execute() the function doesn't return until the script execution finishes or is suspended. If you have a controlling script that runs in an endless loop, you might need to suspend the script in order to allow other tasks to run once in a while. A suspended script can be resumed by calling Execute() again.

You may also want to allow AI to execute a certain number of instructions per frame. You could do that by calling ExecuteStep() the number of times desired per object and per frame. ExecuteStep() executes one script statement then returns. This way you could have several script contexts running in parallel, effectively making a multitasking scripting engine.

----

That code is a good example on how to load a script, compile it and execute it. You do well to learn from it. But again, it might not suit your purpose so don't follow it blindly.

----

Regards,
Andreas

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

Great answer. I just want to tell you the basic idea of what I want to do, and maybe you could point me in the right direction.

My game will be an arcade game. It will contain lots of enemies that each have scripts that are executed when they die. I would like to lump all the enemy scripts together in one script (seperated by functions maybe). Then each enemy would have a string containing the name of the function to call when it dies ("sharkDie()"). Then I would call something to execute "sharkDie()" using the compiled script. I would also use the scripting for loading/configuration, and maybe a menu system.

Read the overview. The first part about compiling scripts: should I just compile all my scripts together using AddScriptSection? Then call each function by using the method on the overview? So I gather there is no way to have different compiled scripts that work independantly from one another?
My recommendation is that you have one engine for each type of task, for example: One for AI routines, one for GUI management, one for loading/configuration.

The AI routines should be divided in groups depending on type of object. You could for example have a soldier type and a shark type, both of them have the same interface to interact with the game engine so they belong in the same script engine. Both types have the same script interface so your application needs a way to differentiate between the die function for the soldier and the shark. In version 1.7.1a the recommended way was to append the name of the type to each of the functions giving soldierDie() and shartDie(). But with 1.8.0 I recommend that you use the modules instead which gives soldier::Die() and shark::Die(). In 1.7.1a you would compile all the AI scripts together by first adding all the script sections then calling Build(), but in 1.8.0 you compile each module separately.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

with 1.8.0 I recommend that you use the modules instead which gives soldier::Die() and shark::Die().

How do I use the modules in the scripting language? like
soldier::Die()
{
}

just like that?

And, in the host program, what do you mean compile each module seperately? I wouldn't compile a whole script (by script I mean a complete textfile containing various functions), but just parts of a script? Why not just compile the whole script and call individual functions from the script? But, if I have to or should use modules, the enemies die script function would be something like: "soldier::die()"?
Modules is a new feature in version 1.8.0 WIP #3 (released yesterday).


Say that you have two script files. One for the soldier AI and
one for the shark AI.



// Code for soldier AIvoid Die(){  // Do the soldier death animation}



// Code for shark AIvoid Die(){  // Do the shark death animation}




Now load these scripts into the engine and compile them.

soldierScript = LoadScript("soldier.as");engine->AddScriptSection("soldier", soldierScript, strlen(soldierScript);engine->Build("soldier");sharkScript = LoadScript("shark.as");engine->AddScriptSection("shark", sharkScript, strlen(sharkScript);


Once you've loaded and compiled the scripts. You can get the id's of the functions like so:

soldierDieFunc = engine->GetFunctionIDByName("soldier", "Die");sharkDieFunc = engine->GetFunctionIDByName("shark", "Die");


The modules just makes it easier to manage all your AI types. And you could also load script modules dynamically as the player encounters new AI types.

Of course, you are not prohibited from compiling everything into one large script, should it suit you better.

AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - Tower - free puzzle game

This topic is closed to new replies.

Advertisement