🎉 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!

C++ call AS class member function

Started by
14 comments, last by RsblsbShawn 16 years, 5 months ago
I'm trying to Augment my existing Engine with AngelScript. The root game object in C++ that I have is: "Entity" All self-updating objects with graphics, physics or logic inherent from it. I created a subclass called ASEntity() which overloads the update() function that gets called each internal tick. I exposed a factory function that creates ASEntitys called: createEntity(). What I'm trying to do now is simulate inheritance. Basically I want to create a class in AngelScript like so:

class Test {
    Entity @e;
    Test() {
        @e = createEntity();
    }
    void update() {
        //this needs to be called from C++;
    }
};
How can I register that update() to be called by C++? Thanks in advance! and AS is the 3rd scripting engine I've tried to embed into my engine: both Python and GameMonkey failed due to overbearing memory management, AS seems to be good so far =)
Advertisement
You'll want to look at engine calls GetTypeIdByDecl(), CreateScriptObject(), and GetMethodIDByDecl() as well as creating and initializing a script context.

Look at test_scriptclassmethod.cpp as it demonstrates calling a class method.
Thanks for the quick reply.

I can see in test3 that it calls it by a named variable. ie: myclass c in AS, then in C++ it looks it up by "c" then can call a member function on it.

This situation won't work for me since the name won't be known, and it isn't necessarily global or unique.

What's the best way to approach this?

Thanks again!
-Shawn
I suggest you register an interface from the application. Then have the script class inherit from this interface. This will allow the application to interact with all script entities the same way, without having to worry about the script class types.

The script:

class Test : IEntity{   ... the same way you did already}


The application:

// Register the interfaceengine->RegisterInterface("IEntity");engine->RegisterInterfaceMethod("IEntity", "void update()");... build the script// Get the function id of the interface method 'update' (you'll want to do this once)int interfaceId = engine->GetTypeIdByDecl(0, "IEntity");int updateFuncId = engine->GetMethodIdByName(interfaceId, "update");// Create the script class object instance (probably as part of loading the level, or something like that)asIScriptStruct *entity = (asIScriptStruct*)engine->CreateScriptObject(engine->GetTypeIdByDecl("your module name", "Test""));// Call the object's method (you can reuse the context for subsequent calls as a speed up)asIScriptContext *ctx = engine->CreateContext();ctx->Prepare(updateFuncId);ctx->SetObject(entity);;ctx->Execute();if( ctx )  ctx->Release();


If you don't use the interface, you can still call the class methods, but you'll need to discover the type id of the class first, so you can find the function id afterwards. The easiest way of discovering the class methods is to have some config file tell you the name of the class, perhaps in the level data, or something like that.

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

Awesome, I'll try this.

I did some exploring on my own while on a plane today, (no access to the net to find a reply), and I modified the Analyze() function in test_scriptclassmethod.cpp. I created a function which registers the type and address of the calling AS class in C++.

void Analyze(asIScriptGeneric *gen) {
CScriptAny *a = (CScriptAny*)gen->GetArgAddress(0);
int myclassId = a->GetTypeId();
asIScriptStruct *s = 0;
a->Retrieve(&s, myclassId);
as_object = s; //line added
s->Release();
}

that sets as_object to the AngelScript object.

I can then call functions on it like:

int typeId = engine->GetTypeIdByDecl(0, "ASClass");
int mtdId = engine->GetMethodIDByDecl(typeId, "void update()");
asIScriptContext *ctx = engine->CreateContext();
ctx->Prepare(mtdId);
ctx->SetObject(as_object);
ctx->Execute();
ctx->Release();

What are the downsides and upsides to this method vs using the interface method?
I haven't got it fully tested yet so I'm not sure if what I did is even viable with thousands of objects... but it works for one ;)

Thanks so much for your help!
-Shawn.
The only down side that I can think of is that you'll need to determine the method id for each of the script class types. You don't want to do this for each call, since it it quite slow, so you'll need to develop some way of caching the method id per script type. You'll probably need some kind of map for this.

The interface will help you abstract the script class types, so that the application doesn't have to know about all the different script class types that it may encounter. It just need to know one method id, the one for "void IEntity::update()".

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 sort of implemented the Interface approach you mentioned and I'm confused about a few things.

1. It seems like you have to know about all script derived types in advance. I assume this is what you were referring to when you said "// Create the script class object instance (probably as part of loading the level, or something like that)". This, (obviously), creates an instance of the interface... is there a way to do it without instantiating it at least once?

2. I still don't know how to keep track of each instance of the interface. I have a register function in C++ that's based off of the Analyze() I mentioned earlier, but it can only get the object once it's fully constructed in script. ie: I can't have script entities register themselves from their constructor. Am I missing something here? how is C++ supposed to know about every instance of all classes that derive from IEntity?

3. How are these reference counted and destroyed? I've never successfully been able to destroy an instance of a class derived from IEntity. I even tried manually decrefing it in C++ in the psuedo-analyze function I created. It's key for me to have control over the destruction of objects since simply going out of scope or losing all handles to an object does not mean it should be destroyed. I keep track of all entities in std::list and am able to send events to them, such as "endLevel" which would delete all objects that respond to that event, so keeping handles to them isn't necessary.

Thanks again for your help!
-Shawn
I have everything working now except the ability to register all derived instances with C++.

I want to create an init() function in C++ that all script derived classes have to call manually, (if this can be done automatically even better), so that C++ can know about each instance so it can call update() on them.

How can this be done?
I'm glad you've got it working so far.

I can think of a couple of different ways that the application can know about the object instances.

1) Have the application create the instances. This could be done either through engine->CreateScriptObject(), or have the script implement a factory function that the application calls. When the instance is created, the application automatically stores the handle in the collection of live objects.

2) If you're using a common interface for all entities, like I described above, you can easily register an application function that takes a handle to that interface, and then have all script classes call it as they are created.

3) If you're not using the interface, then you can register an application function that takes a variable argument type. You can take a look at the CScriptAny add-on for more information on how that can be implemented. There is also information on this in the manual.

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

I am using an interface, and I tried option 2, but I don't think I have the correct syntax. Could you post an example of exactly how to take a class derived from an interface as a parameter in C++ and call a member function on it?

Thanks a lot!

This topic is closed to new replies.

Advertisement