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

Metaprogramming for AngelScript

Started by
3 comments, last by SiCrane 16 years, 9 months ago
I have a long weekend this weekend, so I decided to re-work my AngelScript bindings with (hopefully) my improved understanding of the language. Right now I've banged together a binding system for registering objects and functions with AS that should detect calling convention and invalid argument/return types based on type traits.

#include "registrar.h"

// declare that std::string will be used with AS with the name "string" and
//   allow handles as well as script creation 
ASTRAITS(std::string, "string", true, true);

int main(int, char **) {
  asIScriptEngine *engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
 
  // REGISTER_TYPE will automatically generate object behavior for construction
  //   assignment, destructor, add ref, release, alloc and free if appropriate
  //   for the combination of allowing script creation and object handles
  REGISTER_TYPE(std::string, engine);
  engine->REGISTER_OBJECT_METHOD(std::string, "length", &std::string::size);
 
  engine->Release();
  return 0;
}
As an extra special treat, I've got an explanation of how the code was developed: Article Source. Keep in mind that I started coding this a little more than 24 hours ago, so it probably contains a fair number of bugs/special cases I didn't consider. Also, the code is heavily dependent on Boost.
Advertisement
That's one amazing piece of metaprogramming. It will take me a while to understand it all, but it looks great.

It looks like you're allowing &inout references for most types. AngelScript only allow that for types that support object handles, because it has no way of guaranteeing the lifetime of the reference otherwise. You can however you can set the engine property asEP_ALLOW_UNSAFE_REFERENCES to change this behaviour.

I noticed the use of boost::has_trivial_constructor, boost::has_trivial_destructor, and boost::has_trivial_assign to determine the asOBJ_CLASS_CONSTRUCTOR/DESTRUCTOR/ASSIGNMENT flags. I'll have to investigate this and see if I can incorporate this as part of the core library, as these flags are what users normally fail to set correctly. Do you know if these templates differentiate between a declared default constructor and a compiler generated one?

Example:

class A{  int a;};class B{  B() {b=0;}  int b;};


Class A should be registered without asOBJ_CLASS_CONSTRUCTOR and class B should be registered with it. Does boost::has_trivial_constructor handle this correctly?

I would also like to be able to determine the calling convention automatically, as that's another problematic part for new-comers. Maybe I can make that detection part of the asFUNCTION and asMETHOD macros, so that the user only has to inform asCALL_OBJLAST or asCALL_OBJFIRST if a global function is registered as a class method.

I'd also like to give a heads up to the changes coming with 2.11.0. Now the RegisterObjectType call takes extra flags to tell AngelScript how the type is intended to be used. Because of that AngelScript is now able to validate if a type is missing behaviours or if behaviours are registered that are not compatible with the intention of the type. AngelScript is now also able to detect if the application registers functions that take reference counted object types by value, or return them by value.

Another change is that reference counted types should now register FACTORY behaviours instead of CONSTRUCT behaviours. A FACTORY behaviour is a global function that allocates and initializes the memory for the object.

Example:

void Constructor(void *obj){  // Initialize the pre-allocated memory  new(obj) Object();}Object *Factory(){  // Allocate and initialize the memory  return new Object();}


Beside the improved performance this also makes it easier to match the allocation with the deallocation. Previously you would have to register the ALLOC behaviour as well to make sure the memory was allocated using the same heap/method as the it will be deallocated with in the release behaviour.

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

Quote: Original post by WitchLord
That's one amazing piece of metaprogramming. It will take me a while to understand it all, but it looks great.

It's a little more complex than necessary in order to try to give meaningful error messages at each step. e.g. the AS_StdcallFunctionsCannotBeObjectMethods() calls and all that. If you want a scaled back version that would be easier to understand without all that, I can also produce that for you.
Quote:
It looks like you're allowing &inout references for most types. AngelScript only allow that for types that support object handles, because it has no way of guaranteeing the lifetime of the reference otherwise. You can however you can set the engine property asEP_ALLOW_UNSAFE_REFERENCES to change this behaviour.

I know, I tend to run AS that way. It hasn't caused any problems so far. *knock on wood*
Quote:
I noticed the use of boost::has_trivial_constructor, boost::has_trivial_destructor, and boost::has_trivial_assign to determine the asOBJ_CLASS_CONSTRUCTOR/DESTRUCTOR/ASSIGNMENT flags. I'll have to investigate this and see if I can incorporate this as part of the core library, as these flags are what users normally fail to set correctly. Do you know if these templates differentiate between a declared default constructor and a compiler generated one?

That has_trivial_* templates require special compiler support to implement correctly. AFAIK, only MSVC 2005 and later and the latest Metroworks CodeWarrior provide that support. For those compilers it would detect the A, B situation correctly. For everything else, it'll return asOBJ_CLASS_CDA, and do the behavior registrations.

Quote:
I would also like to be able to determine the calling convention automatically, as that's another problematic part for new-comers. Maybe I can make that detection part of the asFUNCTION and asMETHOD macros, so that the user only has to inform asCALL_OBJLAST or asCALL_OBJFIRST if a global function is registered as a class method.

That shouldn't be too difficult if you add an additional member variable to the asUPtr structure for the calling convention. One of the nicer aspects of working with boost::preprocessor is that you can run an abbreviated version of a header through the preprocessor (and then run that through a code beautifier) to get a version that doesn't depend on boost::preprocessor.

Quote:
I'd also like to give a heads up to the changes coming with 2.11.0. Now the RegisterObjectType call takes extra flags to tell AngelScript how the type is intended to be used. Because of that AngelScript is now able to validate if a type is missing behaviours or if behaviours are registered that are not compatible with the intention of the type. AngelScript is now also able to detect if the application registers functions that take reference counted object types by value, or return them by value.

Could you also add detecting registering two behavior functions with the same function signatures to that list? e.g. Registering two functions as asBEHAVE_INDEX both with the function signature "int f(uint)".

Quote:
Another change is that reference counted types should now register FACTORY behaviours instead of CONSTRUCT behaviours. A FACTORY behaviour is a global function that allocates and initializes the memory for the object.

That should simplify some of the weirder parts aspects of interacting with AngelScript. So would the asBEHAVE_ALLOC and asBEHAVE_FREE behaviors go away completely?
Quote:
If you want a scaled back version that would be easier to understand without all that, I can also produce that for you.


That won't be necessary, but thanks for the offer.

Quote:
I know, I tend to run AS that way. It hasn't caused any problems so far. *knock on wood*


No wonder that you've had any problems yet, you'll have to do some really bad programming to invalidate the reference. But, to provide a sandbox environment I have to make the language safe even for bad programmers. Here's an example where the unsafe references will break:

int[] array(5);void func(int &inout value){   array.resize(1);   value = 3;}void main(){  func(array[4]);}


Quote:
That has_trivial_* templates require special compiler support to implement correctly. AFAIK, only MSVC 2005 and later and the latest Metroworks CodeWarrior provide that support. For those compilers it would detect the A, B situation correctly. For everything else, it'll return asOBJ_CLASS_CDA, and do the behavior registrations.


That's too bad, because it means your code is not working correctly for the compilers that don't support this. GnuC for example won't handle passing classes without destructors correctly by value if registered through your code. The calling convention is different for classes with destructors and without them. For MSVC the calling convention is different if the class has neither of the three.

Quote:
That shouldn't be too difficult if you add an additional member variable to the asUPtr structure for the calling convention.


That's what I was thinking. I'll have to see if it works on older compilers first though, otherwise I won't be able to use it.

Quote:
Could you also add detecting registering two behavior functions with the same function signatures to that list? e.g. Registering two functions as asBEHAVE_INDEX both with the function signature "int f(uint)".


Sure, I'll do that as well.

Quote:
That should simplify some of the weirder parts aspects of interacting with AngelScript. So would the asBEHAVE_ALLOC and asBEHAVE_FREE behaviors go away completely?


Yes it does. In fact, I've already removed them in the WIP. In a future I'll change AngelScript to allow it to allocate value types on the stack, instead of on the heap, which should also improve the performance.

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

Quote: Original post by WitchLord
That's too bad, because it means your code is not working correctly for the compilers that don't support this. GnuC for example won't handle passing classes without destructors correctly by value if registered through your code. The calling convention is different for classes with destructors and without them. For MSVC the calling convention is different if the class has neither of the three.

Hmm. That's rather annoying. On the plus side, type_traits got into tr1, so more compilers should start supporting it over the next few years. IIRC, support has gotten checked into one of the gcc branches, though it doesn't look like it'll migrate to the main branch for a while.

This topic is closed to new replies.

Advertisement