Advertisement

Calling a virtual function from script

Started by May 20, 2009 10:49 PM
7 comments, last by Wavesonics 15 years, 6 months ago
Hokay. So I have a script which is being passed a base reference to a application side object. In the script I call a method on that object, which is virtual. Now, the proper application side method in the child class is called, but the argument, a single int16, has an incorrect value: -6524 When the actual value should be around 10. I print it out script side and it is just fine, and if I make the function NOT virtual, and remove the instance of it in the child class, it calls to the base implementation just fine. Here is the script:

void fire( Creep@ c ) {
    int16 h = c.health();
    c.health( h - 5 );
}






The function in question being the health( int16 ) setter. The function is registered as such:

r = m_engine->RegisterObjectMethod("Creep", "void health( int16 )", asMETHODPR(Creep, health, (sf::Int16), void), asCALL_THISCALL); assert( r >= 0 );

// As well as for the child class
r = engine->RegisterObjectMethod("CreepClient", "void health( int16 )", asMETHODPR(CreepClient, health, (sf::Int16), void), asCALL_THISCALL); assert( r >= 0 );






Is there more I need to do? [Edited by - Wavesonics on May 25, 2009 1:14:14 PM]
==============================
A Developers Blog | Dark Rock Studios - My Site
You say the method expects a single float, but you've registered the method to take a int16. Is that correct?

What kind of inheritance do you use for the Creep class? AngelScript supports both single and multiple inheritance, but not virtual inheritance, i.e:

class A1 : public B {}; // supportedclass A2 : public B, C {}; // supportedclass A3 : virtual B {}; // not supported


Virtual and non-virtual methods are registered the exact same way with AngelScript. It is completely transparent to the application.

You must be careful with the this pointer when handling instances of objects with multiple inheritance, because the value of the pointer may be different depending on which base class it points to. If you use multiple inheritance for the Creep class, an incorrect this pointer seems to be the most probably cause for your problem.

What compiler and what target system are you using?

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

Advertisement
Sorry I miss typed that, it is a single int16 everywhere.

It is using non-virtual, multiple inheritance:

class GameObject;class Creep : public GameObject; // Base impl of health( int16 );class CreepClient : public Creep, public Drawable; // Child impl of health( int16 );


compiler: MinGW/GCC 4.1.2
target: Win32

I'm passing it around as a Creep reference in the script where it should be called.

But again, the correct child method is being called, it's just the argument value that is corrupt.


*edit*

Oohh... This is interesting...

Hokay so, I have two threads, one is a client, the other is a server.

Each have their own complete set of objects in the world, and the both threads execute behavior, meaning, clients and servers both execute the code to fire on creeps.

On the server, each Creep is just an instance of the base Creep class. And on the client, they are all instances of the derived, CreepClient class.

Now the CreepClient has the over ride for health( int16 ) which prints the results that I have been posting here.

Thus, when the SERVER executes the script, the NON-derived version of the function should execute, thus NOT printing anything.

And yet, both are printing:

Quote:
FIRE! Setting health to: 5 // This is from the script
FIRE! Setting health to: 5 // This is from the script
CreepClient health: 5 // This is from the application, I assume this is the ACTUAL client
CreepClient health: -6540 // This is also from the application, this must be from the server?


The Server side script must be trying to execute the over ride version of the function, even though the object isn't actually of the derived type.

But how/why? Something wrong with how I'm registering the function and it's over ride maybe?
==============================
A Developers Blog | Dark Rock Studios - My Site
If the server thread is calling the CreepClient's overloaded method, then the object it is calling it on is really a CreepClient and not a Creep as you intended it to be. There is no other way the CreepClient's method could be called.

However, the incorrect parameter value still confuses me. It could possibly be a bug in the native calling convention support for win32/mingw. Is it possible for you to produce a small code test that reproduces the problem and then send it to me so I can debug it?

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

Ya sorry, that was a silly thought. Been coding too long here.

Added in some extra code to distinguish between server and client calls to health( int16 ) and the server is calling the proper method and it's argument value is never wrong either.

I'll see about producing a test case for this when I get back, I'm gone for the next 4 days.

You know the strange thing with it is, it seems about, every other call to it is correct.

Thanks for the helps again whitchlord.
==============================
A Developers Blog | Dark Rock Studios - My Site
I think I may have narrowed it down!

It was being called twice on the client, 1 time it had the correct value, the other time it had the bad value.

I have 1 Script side function that is running just after the one you have seen already, and in it, it does this:
( the idea is, the function we were looking at is actually running correctly, and this function was the unexplained x-factor )
void fireEffects( TurretClient@ t, CreepClient@ c, WorldClient@ w ) {    if( c.health() != 0 ) {        // Create an explosion        OverlayExplosion@ o = OverlayExplosion( 1.0 );        // Add the explosion to the creep we fired on        c.addOverlay( o );    }    ...}


And it appears that the call to "c.health()" is actually calling: "c.health( int16 )", and of course, with no value supplied, it is getting an invalid value!

If it does think that is the other function, how is it even compiling? This is what the registration looks like:

    // Register our Creep    r = m_engine->RegisterObjectType("Creep", sizeof(Creep), asOBJ_REF ); assert( r >= 0 );    r = m_engine->RegisterObjectBehaviour("Creep", asBEHAVE_ADDREF, "void f()", asFUNCTION(DumbyAddRef), asCALL_CDECL_OBJFIRST); assert( r >= 0 );    r = m_engine->RegisterObjectBehaviour("Creep", asBEHAVE_RELEASE, "void f()", asFUNCTION(DumbyReleaseRef), asCALL_CDECL_OBJFIRST); assert( r >= 0 );    r = m_engine->RegisterObjectMethod("Creep", "int16 health() const", asMETHODPR(Creep, health, (void) const, sf::Int16), asCALL_THISCALL); assert( r >= 0 );    r = m_engine->RegisterObjectMethod("Creep", "void health( int16 )", asMETHODPR(Creep, health, (sf::Int16), void), asCALL_THISCALL); assert( r >= 0 );    // CreepClient    r = engine->RegisterObjectType("CreepClient", sizeof(CreepClient), asOBJ_REF ); assert( r >= 0 );    r = engine->RegisterObjectBehaviour("CreepClient", asBEHAVE_ADDREF, "void f()", asFUNCTION(DumbyAddRef), asCALL_CDECL_OBJFIRST); assert( r >= 0 );    r = engine->RegisterObjectBehaviour("CreepClient", asBEHAVE_RELEASE, "void f()", asFUNCTION(DumbyReleaseRef), asCALL_CDECL_OBJFIRST); assert( r >= 0 );    r = engine->RegisterObjectMethod("CreepClient", "int16 health() const", asMETHODPR(CreepClient, health, (void) const, sf::Int16), asCALL_THISCALL); assert( r >= 0 );    r = engine->RegisterObjectMethod("CreepClient", "void health( int16 )", asMETHODPR(CreepClient, health, (sf::Int16), void), asCALL_THISCALL); assert( r >= 0 );


Maybe the fact that "CreepClient@ c" is not const, and "int16 health() const" is declared const is confusing it?


*edit*

Found a temporary fix:
class CreepClient ... {...    virtual sf::Int16 health() const;    virtual void health( sf::Int16 newHealth );...};


Declaring over rides for BOTH similar functions fixes the problem, but of course, this is not ideal, since there is no reason for the "sf::Int16 health() const" over ride.

[Edited by - Wavesonics on May 22, 2009 12:52:14 AM]
==============================
A Developers Blog | Dark Rock Studios - My Site
Advertisement
You have indeed made great progress on this problem. You seem to have been able to isolate the bug, and I should be able to reproduce it now.

I have to wonder if this is a bug in MinGW or AngelScript though. MinGW 4.x.x is still in beta, isn't it? Have you tried compiling your application with another compiler? MSVC++ Express 2008, for example.

I'll try to investigate this further during the weekend.

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

I found the problem. You fell into a trap of C++ obscure language specifications. :)

When a class that inherits from another implements a method with the same name as that of any of the base class' methods, the derived class actually hides all the base class' methods of that name. So when your CreepClient implemented only 'void health(int16)' you told the C++ compiler that the CreepClient didn't have the 'int16 health() const' method.

Unfortunately I had implemented the asMETHODPR macro to use the unsafe C-style cast, which illegally converted this to the method pointer that you specified without any compiler warnings. I changed the macro to use the safe static_cast<> operator instead and now the C++ compiler gives a proper error instead. The error goes away when you implement the 'int16 health() const' method as you found in your 'temporary' fix.

I recommend updating the angelscript.h file from the SVN to pick up the improved asMETHODPR and asFUNCTIONPR macros.

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, glad it's figured out now anyway, I actually encountered this sort of problem once before, but never found out why it was, good to know about the hiding of the base methods.

Suppose it might be more flexible to name them: "getHealth()" "setHealth()"

I'm building against the SVN anyway so it'll be easy for me to grab the fix, thanks again! :)

*edit*
I just noticed this was my 600th post here on GD :)
==============================
A Developers Blog | Dark Rock Studios - My Site

This topic is closed to new replies.

Advertisement