Advertisement

RegisterGlobalFunction() with method?

Started by February 15, 2009 03:10 PM
3 comments, last by WitchLord 15 years, 9 months ago
Hi Witchlord, first thing: respect for writing and maintaining AngelScript, as well as releasing it as open source and providing free support to developers. I've been able to integrate it with my upcoming game in relatively short time - which is nice. Overall the AngelScript design is pretty good, allows for quite some flexibility when it comes to working with the lib. So far there's only one thing that I've been banging my head against, and it is inability to RegisterGlobalFunction() with method to some class. Most of my interesting behaviours are contained within classess, which makes it harder to expose them to scripts, as I have to create proxy function, define global variable, initialize it, and lastly call variable->behaviour() from within proxy function. You can't call RegisterGlobalFunction() with asMETHOD since who's going to provide 'this' then? Would it be possible to extend it with a object parameter, just like SetMessageHandler, which supports functions and methods? Am I missing something here? ... Nevermind, while writing this post I figured how to workaround it rather nicely. Just register some kind of 'Game' class which will be the provider of 'global services', register said services as its methods, then instantiate it and register as a global property. Not ideal (ie. what when multiple classess contain needed behaviours - proxy classes?) but definitely workable. Pheew. Leaving the topic for others to learn on my example.
Quote: Original post by Koshmaar
Hi Witchlord, first thing: respect for writing and maintaining AngelScript, as well as releasing it as open source and providing free support to developers. I've been able to integrate it with my upcoming game in relatively short time - which is nice.

Overall the AngelScript design is pretty good, allows for quite some flexibility when it comes to working with the lib.


I'm glad you like it. :)

Quote: Original post by Koshmaar
So far there's only one thing that I've been banging my head against, and it is inability to RegisterGlobalFunction() with method to some class. Most of my interesting behaviours are contained within classess, which makes it harder to expose them to scripts, as I have to create proxy function, define global variable, initialize it, and lastly call variable->behaviour() from within proxy function.

You can't call RegisterGlobalFunction() with asMETHOD since who's going to provide 'this' then? Would it be possible to extend it with a object parameter, just like SetMessageHandler, which supports functions and methods? Am I missing something here?


I've been thinking about adding this support, but it is rather low priority at the moment with so many other things that needs to be added.

Quote: Original post by Koshmaar
Nevermind, while writing this post I figured how to workaround it rather nicely. Just register some kind of 'Game' class which will be the provider of 'global services', register said services as its methods, then instantiate it and register as a global property. Not ideal (ie. what when multiple classess contain needed behaviours - proxy classes?) but definitely workable. Pheew. Leaving the topic for others to learn on my example.


This is how I currently do it in my own engine. Very few of the functions in the application interface are exposed as global functions to the scripts. Only the most primitive functions, such as math functions, string manipulations, etc. All others are exposed through an object that provides the namespace for the methods. The object itself is registered as a singleton so that the scripts can't create new instances of the object, but only use the one instance that the application provides through a registered global property.

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
Cool :-) now I see that your approach is probably the best one. Still, sometimes you might want to have global functions in AS, that internally call methods in C++ (ie. Log() wins hands down with engine.Log() when it comes to brevity, and in my case logging is implemented by class) but as you said, it's very low priority... as long as user knows how to do what he wants in other way.

So IMHO it's good idea to explicitely state what are the disadvantages of certain functions and ways of working-around them (in this case it would be information that RegisterGlobalFunction doesn't support methods and won't do it for a long time, and you might consider doing this or that - might appear trivial, yet for some people will be timesaver). I've been doing such things in my own library, SDL_Config, and to my surprise, regulary I've found myself utilizing those little hints, tips and tricks.

Anyway, I think it would be great if you'd add to documentation a section with guidelines, best practices and ways on how to use AngelScript to get the most from it, perhaps also FAQ. Collecting there all those tiny (and not tiny) bits of precious informations that are scattered on this forum into one place and polishing them a little - that would be very valuable for novices. I understand it's hard dividing attention across writing docs (which might get outdated after next version) and writing code, so no pressure on you. Last thing, you might consider using www.naturaldocs.org instead of Doxygen, IMHO it's better.

Cheers!

I tend to use template meta-programming for this kind of task. Using the return value of RegisterGlobalFunction to retrieve function ids, and GetUserData()/SetUserData() you can register a global function that indexes the function id into a map of boost::function<void (asIScriptGeneric *)> and create thunk objects that translate the asIScriptGeneric arguments into the actual function arguments. This kind of solution wouldn't rely on global state. The code for 0 and 1 argument member functions would look something like:
#include "angelscript.h"#include "scriptstring.h"#include <boost/function.hpp>#include <boost/bind.hpp>#include <boost/shared_ptr.hpp>#include <iostream>#include <map>#include <string>#include <cassert>class Binder {  public:    Binder(asIScriptEngine * engine) : engine_(engine) {      engine_->AddRef();      engine_->SetUserData(this);    }        ~Binder() {      engine_->Release();    }    template <typename T, typename R>    void bind(R (T::*mem_fn)(void), boost::shared_ptr<T> object, const char * sig) {      GlobalThunk<boost::function<R (void)> > thunk(boost::bind(mem_fn, object));      int func_id = engine_->RegisterGlobalFunction(sig, asFUNCTION(global_helper), asCALL_GENERIC);      assert(func_id >= 0);      global_map_[func_id] = thunk;    }    template <typename T, typename R, typename A0>    void bind(R (T::*mem_fn)(A0), boost::shared_ptr<T> object, const char * sig) {      GlobalThunk<boost::function<R (A0)> > thunk(boost::bind(mem_fn, object, _1));      int func_id = engine_->RegisterGlobalFunction(sig, asFUNCTION(global_helper), asCALL_GENERIC);      assert(func_id >= 0);      global_map_[func_id] = thunk;    }  private:    typedef boost::function<void (asIScriptGeneric *)> GenericFn;    typedef std::map<int, GenericFn> GlobalMap;    asIScriptEngine * engine_;    GlobalMap global_map_;    static void __cdecl global_helper(asIScriptGeneric * gen) {      Binder * self = static_cast<Binder *>(gen->GetEngine()->GetUserData());          int func_id = gen->GetFunctionId();      GlobalMap::iterator it = self->global_map_.find(func_id);      assert(it != self->global_map_.end());      (it->second)(gen);    }};template <typename T>struct ArgPtrT {  typedef T * result;};template <typename T>struct ArgPtrT<T &> {  typedef T * result;};template <typename T>struct ArgPtrT<const T &> {  typedef T * result;};template <typename FunctionType>class GlobalThunk {};template <>class GlobalThunk<boost::function<void (void)> > {  public:    GlobalThunk(boost::function<void (void)> fn) : fn_(fn) {}    void operator()(asIScriptGeneric *) {      fn_();    }  private:    boost::function<void (void)> fn_;};template <typename R>class GlobalThunk<boost::function<R (void)> > {  public:    GlobalThunk(boost::function<R (void)> fn) : fn_(fn) {}    void operator()(asIScriptGeneric * gen) {      R * ptr = static_cast<R *>(gen->GetReturnPointer());      *ptr = fn_();    }  private:    boost::function<void (void)> fn_;};template <typename A0>class GlobalThunk<boost::function<void (A0)> > {  public:    GlobalThunk(boost::function<void (A0)> fn) : fn_(fn) {}    void operator()(asIScriptGeneric * gen) {      typename ArgPtrT<A0>::result arg0 = static_cast<typename ArgPtrT<A0>::result>(gen->GetAddressOfArg(0));      fn_(*arg0);    }  private:    boost::function<void (A0)> fn_;};template <typename R, typename A0>class GlobalThunk<boost::function<R (A0)> > {  public:    GlobalThunk(boost::function<R (A0)> fn) : fn_(fn) {}    void operator()(asIScriptGeneric * gen) {      typename ArgPtrT<A0>::result arg0 = static_cast<typename ArgPtrT<A0>::result>(gen->GetAddressOfArg(0));      R * ptr = static_cast<R *>(gen->GetReturnPointer());      *ptr = fn_(*arg0);    }  private:    boost::function<R (A0)> fn_;};void PrintString(std::string &str) {	std::cout << str;}struct Tester {  int data;  int foo(int arg) {    return data * arg;  }};int main(int, char **) {  boost::shared_ptr<Tester> ptr(new Tester);  ptr->data = 10;    asIScriptEngine * engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);  Binder binder(engine);  RegisterScriptString(engine);  int r = engine->RegisterGlobalFunction("void Print(string &in)", asFUNCTION(PrintString), asCALL_CDECL); assert( r >= 0 );   binder.bind(&Tester::foo, ptr, "int test(int)");    const char script[] =    "void main() {\n"    "  Print(\"result = \" + test(5) + \"\\n\");\n"    "}";  asIScriptModule * mod = engine->GetModule(0, asGM_ALWAYS_CREATE);  r = mod->AddScriptSection("script", script, sizeof(script) - 1); assert(r >= 0);  r = mod->Build(); assert(r >= 0);    int func_id = mod->GetFunctionIdByDecl("void main()"); assert(r >= 0);  asIScriptContext * ctx = engine->CreateContext();  ctx->Prepare(func_id); assert(r >= 0);  ctx->Execute(); assert(r >= 0);  ptr->data = 5;  ctx->Prepare(func_id); assert(r >= 0);  ctx->Execute(); assert(r >= 0);  ctx->Release();  engine->Release();}
I added a link to this thread on the wiki. SiCrane's code snippet is especially interesting.

I'll see if I can add a word or two about this in the documentation as well. I know the documentation is still quite shallow, and things like this would definitely bring up the quality of the docs.

Thanks for the tip about Natural Docs. I'll definitely take a closer look at it, even though I am quite satisfied with Doxygen.

Thanks,
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

This topic is closed to new replies.

Advertisement