Skip to main content
GameDev.net gamedev.net
🔒 Locked

A few questions about fundamentals

Started by Diran Jan 14, 2011 at 1:10 PM 25 replies 6.5k views
Original Post
Diran
Diran
Hi. I am totally noob in scripting languages, so many aspects of this technology are like a divine revelation for me. Please be merciful.
I finally decided to stick with AS for my scripting purposes and just an hour ago did run through the docs.
Surpsrisingly most of it is quite comprehendable, thanks. But of course a few things are still vague for me.
Explanation needed for:
1 - What is a Module thing? What is the purpose behind this entity? I understand that it's some kind of arbitrary collection of script-functions. But it seems this point of view is superficial.
2 - The same about code-section. Can I say that code section == content of script.as?
3 - Let's assume that I have three threads(Environment, Tank1, Tank2). I want AS to provide external event handlers for tanks, and some weather-generation scripts for Environment. So, what is the best strategy for AS application? Obviously I need three contexts for every thread. I guess I have to know which script-functions belong to tank 1, and tank 2. Probably It can be done via function naming convention like plasmaAtackHandler1, plasmaAtackHandler2. Maybe this is what are modules for? ... the flow of mind my be continued, but I'd like to see the optimal solution from AS's design point of view.
WitchLord
WitchLord
1. You can think of the module as a program, or perhaps a library. The module holds the functions, class implementations, and global variables. Multiple contexts can execute functions from the same module, however all of them will share the same set of global variables.

2. You can think of the code sections as the script files. Multiple code sections can be built together as a single script, resulting in the module.

3. Assuming the tanks share the same logic, my suggestion is that you design your scripts to be in built in two modules. One module would hold the logic for the environment, and the other module would hold the logic for the tanks. If you other types, you would add more modules, one for each type, e.g. plane, car, motorcycle, etc. You don't want to use the global variables to store the state of the individual entities though, store this in an instance of a class instead. My recommendation is that you design your script to be event based, i.e. each function that is called executes and returns within the same frame. In my opinion this is the easiest way to work. With this you only need one script context that you will re-use for each of the calls to the scripts.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran
1 - So a module may be considered as a user-defined collection representing a logic for a target entity or a group of entities with exactly the same behavior?
If I want my tanks to handle events differently I should create a module per tank?
PlasmaAtackHandler() //for tank 1
{
shutdown_and_cool();
}
PlasmaAtackHandler() //for tank 2
{
use_fire_exstinguisher();
}

2 - And if my tanks do exist in a separate threads than I have to do something like this to handle PlasmaAtackEvent from C++:
int funcId = engine->GetModule(tankId)->GetFunctionIdByName("[color=#1C2837][size=2]plasmaAtackHandler");
thisTankThread.mContext.Prepare(funcId);

Am I correct?
WitchLord
WitchLord
1.

Yes, you can design it that way, though you do not have to. I use it this way in my own game engine.

2.

Well, your tankId should probably be called tankType, to avoid confusing it with the id of the actual instance of the tank entity. Also, you will want to cache the funcId so you do not have to call GetFunctionIdByName (or better GetFunctionIdByDecl) everytime. Otherwise you're correct.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
_orm_
_orm_
I second the caching. Do it. You have no excuse not to. Expect your programs speed to bite the dust if you don't.
Diran
Diran
Thanks. Understood.
Here's another wave.
I want to make uninstantiable reference type so I do this.


r = mEngine->RegisterObjectType("IEntity", 0, asOBJ_REF ); assert( r >= 0 );
r = mEngine->RegisterObjectMethod("IEntity", "int getId()", asMETHODPR(IEntity, getId, (void), int), asCALL_THISCALL); assert( r >= 0 );

And I have 2 problems:
1 - It forces me to remove constness from int getId() const. Otherwise compilation fails with some twisted static cast error. But getId is a standard const getter, I do not want to change it. What I did wrong?
2 - Later I get a set of errors "IEntity missing behaviors" and blah-blah. But aren't they needed for [color=#1C2837][size=2]asOBJ_VALUE?
mandrav
mandrav

1 - It forces me to remove constness from int getId() const. Otherwise compilation fails with some twisted static cast error. But getId is a standard const getter, I do not want to change it. What I did wrong?




r = mEngine->RegisterObjectMethod("IEntity", "int getId() const", asMETHODPR(IEntity, getId, (void) const, int), asCALL_THISCALL); assert( r >= 0 );



Notice the 'const' keyword I added in "int getId() const" as well as in "asMETHODPR(IEntity, getId, (void) const, int)"
Diran
Diran
Thanks, that's it. What about 2?
WitchLord
WitchLord

2 - Later I get a set of errors "IEntity missing behaviors" and blah-blah. But aren't they needed for [color="#1c2837"]asOBJ_VALUE?


For question 2, the problem is that you didn't register the ADDREF and RELEASE behaviours. An uninstanciable reference type just means the scripts cannot create new instances of the type, AS still needs to know how to handle the references it may receive from the application.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran
For question 2, the problem is that you didn't register the ADDREF and RELEASE behaviours. An uninstanciable reference type just means the scripts cannot create new instances of the type, AS still needs to know how to handle the references it may receive from the application.


Well, what should I do if I do not wish to do any refernce counting? The lifecycle of the reference passed to AS-function is controlled by shared_ptr and I guarantee that it is, and will stay valid while it's in use.
So, as I understand AS expects me to register some function any way, so it's kinda stupid but may I just register empty AddRef() {} and what's the pitfall?
WitchLord
WitchLord
You can register the ADDREF and RELEASE behaviours with an empty function. As long as you guarantee the object will stay alive for as long as anyone might reference it, you will not have any problem. The risk is just if you do not have full control of where the scripts may or may not keep a reference, e.g. in global variables, or as members of another object, and so on.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran
Thanks. Clear.
The next one.
I coudn't figure out the proper way to create an object inside script to return it out.
I'd like to see correct script-side example for something like this:


PhysicalParamsHolder& test4()
{
int id = GetTypeIdByDecl("PhysicalParamsHolder");
PhysicalParamsHolder& pph = CreateScriptObject(id);
pph.setVisual(100);
return pph;
}

(PhysicalParamsHolder is refcounted with factory_func as in manual)
WitchLord
WitchLord

PhysicalParamsHolder@ test4() // Return a handle to PPH
{
PhysicalParamsHolder pph;
pph.setVisual(100);
return pph;
}


You may think of object handles as smart pointers. This article explains how they work on the application side, and this article explains how they work on the script side.

Regards,
Andreas
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran


PhysicalParamsHolder@ test4() // Return a handle to PPH
{
PhysicalParamsHolder pph;
pph.setVisual(100);
return pph;
}


You may think of object handles as smart pointers. This article explains how they work on the application side, and this article explains how they work on the script side.

Regards,
Andreas



[font="arial, verdana, tahoma, sans-serif"]Ok. Works fine.
Uhmmm, some more qs about handles. I want to make clear all the cases when I should explicitly call addRef/release. Just made a test[/font]


asWrapper.execute(*ch->context);
PhysicalParamsHolder* ph = *(PhysicalParamsHolder**) (ch->context->GetAddressOfReturnValue());
ph->addRef();
fid = asWrapper.getFucnId("test5", "modA");
asWrapper.setupScriptContext(*ch->context, fid);
ch->context->SetArgAddress(0, ph);
asWrapper.execute(*ch->context);

Without ph->addRef(); it crashes.
I suppose because of on the next call to setupScriptContext an implicit release() is called for PhysicalParamsHolder;
So, I personally have to intervene in reference handling if I want to keep holders alive out of their scope. Other cases?
Is there a case where I have to do it from scripts?
WitchLord
WitchLord
Your assumption is correct. If you do not call addRef on the object returned by the script function, the pointer may become invalid when the context is reused for the next call if the returned handle is the last reference to the object.

The articles I linked to in the previous post should explain most of the situations where you will need to manually call addref and release from the application side.

From the script side, there is no way to call the addref and release behaviours manually. It is all taken care of by the script engine.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran
Thanks, WitchLord. It's getting better.
The next ones.
1 - What do you think about shared_ptr+proxy / intrusive_ptr strategies? Currently in my project I use shared_ptr+proxy.
But after being writing another ProxyEntity to pass it to AS I became kinda annoyed. Maybe there's a sense to switch to intrusive,
while it's not too late, but I am hesitant, because it demands a lot of reworking and I'm afraid I will have to do addRef/release through
mutexes for it properly to work and MT-environment.
2 - I am not sure I have a right to complain, but is it possible to extend error handling a little further?
To be more concrete - I often get "Failed to prepare the context" and it literally may be anything. The last one happend cause I did a mistake registering enums(registered the same value twice). I spent a lot of time on commenting/uncommenting code to catch that mistake,
when I'd like to get messagelike "Enum X duplicate value attempt". I'd find it more valuable than even new features. So...?
3 - Another issue I'd like to ask. I just tried to create ObjectHolder on stack and pass it to AS, ObjHolder oh; setArg(as &oh); The script itself worked well, but in the end of execution it crashed severly. So I switched to heap-allocated holder and everything's fine. Can you explain what's going on there?
WitchLord
WitchLord
shared_ptr+proxy works great in C++ as the shared_ptr object can overload the -> operator to give the actual pointer to the class allowing you to call the class methods directly without awkward syntax. Unfortunately this doesn't let you take an address of the function that AngelScript can call directly.

If you use shared_ptr you will be forced to implement wrappers for all methods you want to expose to the script. This is not necessarily a bad thing, I do this in my own game engine. In the wrappers I also add logic to shield the script from objects that have been destroyed from the game engine itself. This makes it a lot easier for me to manage the memory in the game engine, as I don't have to worry about release all references to the object before I destroy it.

On the error handling side, it looks like you're not checking the return codes while registering the actual interface. RegisterEnumValue for example returns an error if you attempt to register the same name twice. Always check the return codes. At the very least add an assert so you can validate the registration in debug mode.

Please elaborate on the ObjectHolder problem. How did you implement this class? And how was it used when the application crashed? You may have encountered a bug in the script library, but I need more detail to determine what's going on.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran

shared_ptr+proxy works great in C++ as the shared_ptr object can overload the -> operator to give the actual pointer to the class allowing you to call the class methods directly without awkward syntax.

Wow, may be a code example?



On the error handling side, it looks like you're not checking the return codes while registering the actual interface. RegisterEnumValue for example returns an error if you attempt to register the same name twice. Always check the return codes. At the very least add an assert so you can validate the registration in debug mode.

Damn, I feel ashamed. I forgot to to assign value for assertion checker... Where can I read about return codes in detail?


Please elaborate on the ObjectHolder problem. How did you implement this class? And how was it used when the application crashed? You may have encountered a bug in the script library, but I need more detail to determine what's going on.

ObjectHolders are mostly non-instansiable references. AddRef/Release only

The calling code looks like this

method(boost::shared_ptr e, boost::shared_ptr reciever)
{
EventHolder eventH(e);
EntityHolder entH(reciever);
asIScriptContext* ctx = reciever->getScriptContext()->context;
asWrapper::Instance()->setupScriptContext(*ctx, mFuncId);
ctx->SetArgAddress(0, &eventH);
ctx->SetArgAddress(1, &entH);
asWrapper::Instance()->execute(*ctx);
}
It crashes after script execution goes to the end, unless you change it to a form of EventHolder* eventH = new EventHolder(e);
I did Print( event.getRefCount() ) at the end of script and it's == 1
WitchLord
WitchLord
The boost::shared_ptr implements the operator-> overload, so in your case you can access the members of the Event of IEntity objects directly with:

e->property;
receiver->Method();

I'm sure you already knew that.


The manual is a good place to look for details about return values.


What is the signature of the script function called with this code? The SetArgAddress() method doesn't increase the ref count so if the script function takes a handle the reference will be decreased at the end of the function thus deleting the object. If the script function takes a reference, then the reference will not be decreased and all is fine. The SetArgObject() is probably a better choice for you, as it has the intelligence to increase the ref count for object handles, or copy the object if the function takes the object by value.
AngelCode.com - game development and more - Reference DB - game developer references
AngelScript - free scripting library - BMFont - free bitmap font generator - <a href="http://www.angelcode.com/tower" rel
Diran
Diran

The boost::shared_ptr implements the operator-> overload, so in your case you can access the members of the Event of IEntity objects directly with:

e->property;
receiver->Method();

I'm sure you already knew that.
[size=2]

[size="1"]I mean... Ahh, I guess I'm drunk. it's Ok, I'll try that.


[size=2]

What is the signature of the script function called with this code? The SetArgAddress() method doesn't increase the ref count so if the script function takes a handle the reference will be decreased at the end of the function thus deleting the object. If the script function takes a reference, then the reference will not be decreased and all is fine. The SetArgObject() is probably a better choice for you, as it has the intelligence to increase the ref count for object handles, or copy the object if the function takes the object by value.



void func(EventHolder@ eh, IEntityHolder@ receiver) so the trouble is with the method... Yes I confirm. It works via [size=2]SetArgObject. Thank you.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.