index

Registering a C++ class

The are two principal paths to take when registering a new type, either the type is a reference type that is located in dynamic memory, or the type is a value type that is located on the stack. Complex types are usually registered as reference types, while simple types that are meant to be used as primitives are registered as value types. A reference type support object handles, but cannot be passed by value to application registered functions, a value type doesn't support handles and can be passed by value to application registered functions.

Registering a basic reference type

The basic reference type should be registered with the following behaviours: asBEHAVE_FACTORY, asBEHAVE_ADDREF, and asBEHAVE_RELEASE. If it is desired that assignments should be allowed for the type the asBEHAVE_ASSIGNMENT behaviour must be registered as well. Other behaviours, such as math operators, comparisons, etc may be registered as needed.

// Registering the reference type
r = engine->RegisterObjectType("ref", 0, asOBJ_REF); assert( r >= 0 );

Factory function

The factory function is the one that AngelScript will use to instanciate objects of this type when a variable is declared. It is responsible for allocating and initializing the object memory.

The default factory function doesn't take any parameters and should return an object handle for the new object. Make sure the object's reference counter is accounting for the reference being returned by the factory function, so that the object is properly released when all references to it are removed.

CRef::CRef()
{
    // Let the constructor initialize the reference counter to 1
    refCount = 1;
}

CRef *Ref_Factory()
{
    // The class constructor is initializing the reference counter to 1
    return new CRef();
}

// Registering the factory behaviour
r = engine->RegisterObjectBehaviour("ref", asBEHAVE_FACTORY, "ref@ f()", asFUNCTION(Ref_Factory), asCALL_CDECL); assert( r >= 0 );

You may also register factory functions that take parameters, which may then be used when initializing the object.

The factory function must be registered as a global function, but can be implemented as a static class method, common global function, or a global function following the generic calling convention.

Addref and release behaviours

void CRef::Addref()
{
    // Increase the reference counter
    refCount++;
}

void CRef::Release()
{
    // Decrease ref count and delete if it reaches 0
    if( --refCount == 0 )
        delete this;
}

// Registering the addref/release behaviours
r = engine->RegisterObjectBehaviour("ref", asBEHAVE_ADDREF, "void f()", asMETHOD(CRef,AddRef), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("ref", asBEHAVE_RELEASE, "void f()", asMETHOD(CRef,Release), asCALL_THISCALL); assert( r >= 0 );

Assignment behaviour

CRef &CRef::operator =(const CRef &other)
{
    // Copy everything from the other class, except the reference counter
}

// Registering the assignment behaviour
r = engine->RegisterObjectBehaviour("ref", asBEHAVE_ASSIGNMENT, "ref &f(const &in)", asMETHOD(CRef,operator=), asCALL_THISCALL); assert( r >= 0 );

The assignment behaviour can be overloaded with other types if that is desired, that way the script writer doesn't have to manually convert the expressions before assigning the values to the type.

Registering a garbage collected reference type

Reference counting as memory management has a drawback in that it is difficult to detect circular references when determining dead objects. AngelScript allows the application to register types with special behaviours to support the garbage collection for detecting circular references. These behaviours make the class a bit more complex, but you should only have to register them for a few types, e.g. generic container classes.

// Registering the garbage collected reference type
r = engine->RegisterObjectType("ref", 0, asOBJ_REF | asOBJ_GC); assert( r >= 0 );

The difference between the garbage collected and non-garbage collected types is in the addref and release behaviours, the class constructor, and the extra support behaviours.

GC support behaviours

The GC determines when objects should be destroyed by counting the references it can follow for each object. If the GC can see all references that points to an object, it knows that the object is part of a circular reference. If all the objects involved in that circular reference have no outside references it means that they should be destroyed.

The process of determining the dead objects uses the first for of the behaviours below, while the destruction of the objects is done by forcing the release of the object's references.

void CGCRef::SetGCFlag()
{
    // Set the gc flag as the high bit in the reference counter
    refCount |= 0x80000000;
}

bool CGCRef::GetGCFlag()
{
    // Return the gc flag
    return (refCount & 0x80000000) ? true : false;
}

int CGCRef::GetRefCount()
{
    // Return the reference count, without the gc flag
    return (refCount & 0x7FFFFFFF);
}

void CGCRef::EnumReferences()
{
    // Call the engine::GCEnumCallback for all references to other objects held
    engine->GCEnumCallback(myref);
}

void CGCRef::ReleaseAllReferences()
{
    // When we receive this call, we are as good as dead, but
    // the garbage collector will still hold a references to us, so we
    // cannot just delete ourself yet. Just free all references to other
    // objects that we hold
    if( myref )
    {
        myref->Release();
        myref = 0;
    }
}

// Register the GC support behaviours
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_SETGCFLAG, "void f()", asMETHOD(CGCRef,SetGCFlag), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_GETGCFLAG, "bool f()", asMETHOD(CGCRef,GetGCFlag), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_GETREFCOUNT, "int f()", asMETHOD(CGCRef,GetRefCount), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_ENUMREFS, "void f(int&in)", asMETHOD(CGCRef,EnumReferences), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_RELEASEREFS, "void f(int&in)", asMETHOD(CGCRef,ReleaseAllReferences), asCALL_THISCALL); assert( r >= 0 );

Factory for garbage collection

Whenever a garbage collected class is created, the garbage collector must be notified of it's existence. The easiest way of doing that is to have the factory behaviour, or the class constructor call the NotifyGarbageCollectorOfNewObject() method on the engine when initializing the class.

CGCRef *GCRef_Factory()
{
    // Create the object and then notify the GC of its existence
    CGCRef *obj = new CGCRef();
    int typeId = engine->GetTypeIdByDecl("gc");
    engine->NotifyGarbageCollectorOfNewObject(obj, typeId);
    return obj;
}

You may want to consider caching the typeId, so that it doesn't have to be looked up through the relatively expensive call to GetTypeIdByDecl every time an object of this type is created.

Note, if you create objects of this type from the application side, you must also notify the garbage collector of its existence, so it's a good idea to make sure all code use the same way of creating objects of this type.

Addref and release for garbage collection

For garbage collected objects it is important to make sure the AddRef and Release behaviours clear the GC flag. Otherwise it is possible that the GC incorrectly determine that the object should be destroyed.

void CGCRef::AddRef()
{
    // Clear the gc flag and increase the reference counter
    refCount = (refCount&0x7FFFFFFF) + 1;
}

void CGCRef::Release()
{
    // Clear the gc flag, decrease ref count and delete if it reaches 0
    refCount &= 0x7FFFFFFF;
    if( --refCount == 0 )
        delete this;
}

// Registering the addref/release behaviours
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_ADDREF, "void f()", asMETHOD(CGCRef,AddRef), asCALL_THISCALL); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("gc", asBEHAVE_RELEASE, "void f()", asMETHOD(CGCRef,Release), asCALL_THISCALL); assert( r >= 0 );

Registering an uninstanciable reference type

Sometimes it may be useful to register types that cannot be instanciated by the scripts, yet can be interacted with. You can do this by registering the type as a normal reference type, but omit the registration of the factory behaviour. You can later register global properties, or functions that allow the scripts to access objects created by the application via object handles.

This would be used when the application has a limited number of objects available and doesn't want to create new ones. For example singletons, or pooled objects.

Registering a single-reference type

A variant of the uninstanciable reference types is the single-reference type. This is a type that have only 1 reference accessing it, i.e. the script cannot store any extra references to the object during execution. The script is forced to use the reference it receives from the application at the moment the application passes it on to the script.

The reference can be passed to the script through a property, either global or a class member, or it can be returned from an application registered function or class method.

// Registering the type so that it cannot be instanciated
// by the script, nor allow scripts to store references to the type
r = engine->RegisterObjectType("single", 0, asOBJ_REF | asOBJ_NOHANDLE); assert( r >= 0 );

This sort of type is most useful when you want to have complete control over references to an object, for example so that the application can destroy and recreate objects of the type without having to worry about potential references held by scripts. This allows the application to control when a script has access to an object and it's members.

Registering a value type

When registering a value type, the size of the type must be given so that AngelScript knows how much space is needed for it. If the type doesn't require any special treatment, i.e. doesn't contain any pointers or other resource references that must be maintained, then the type can be registered with the flag asOBJ_POD. In this case AngelScript doesn't require the default constructor, assignment behaviour, or destructor as it will be able to automatically handle these cases the same way it handles built-in primitives.

If the type will be passed to and from the application by value using native calling conventions, it is important to inform AngelScript of its real type in C++, otherwise AngelScript won't be able to determine exactly how C++ is treating the type in a parameter or return value. There are a few different flags for this:

asOBJ_APP_CLASS   The C++ type is a class, struct, or union
asOBJ_APP_CLASS_CONSTRUCTOR   The C++ type has a defined constructor
asOBJ_APP_CLASS_DESTRUCTOR   The C++ type has a defined destructor
asOBJ_APP_CLASS_ASSIGNMENT   The C++ type has a defined assignment operator
asOBJ_APP_PRIMITIVE   The C++ type is a C++ primitive, but not a float or double
asOBJ_APP_FLOAT   The C++ type is a float or double

// Register a primitive type, that doesn't need any special management of the content
r = engine->RegisterObjectType("pod", sizeof(pod), asOBJ_VALUE | asOBJ_POD | asOBJ_APP_PRIMITIVE); assert( r >= 0 );

// Register a class that must be properly initialized and uninitialized
r = engine->RegisterObjectType("val", sizeof(val), asOBJ_VALUE | asOBJ_APP_CLASS_CDA); assert( r >= 0 );

Constructor and destructor

If a constructor or destructor is needed they shall be registered the following way:

void Constructor(void *memory)
{
  // Initialize the pre-allocated memory by calling the
  // object constructor with the placement-new operator
  new(memory) Object();
}

void Destructor(void *memory)
{
  // Uninitialize the memory by calling the object destructor
  ((Object*)memory)->~Object();
}

// Register the behaviours
r = engine->RegisterObjectBehaviour("val", asBEHAVE_CONSTRUCT, "void f()", asFUNCTION(Constructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );
r = engine->RegisterObjectBehaviour("val", asBEHAVE_DESTRUCT, "void f()", asFUNCTION(Destructor), asCALL_CDECL_OBJLAST); assert( r >= 0 );

The assignment behaviour is registered the same way as for reference types.

Registering operator behaviours

You can register operator behaviours for your types as well. By doing this you'll allow the script to work with the types in expressions, just like the built-in types.

There two forms of operator behaviours, either object behaviours or global behaviours. An object behaviour is implemented as a class method, and a global behaviour is implemented as a global function.

// Registering an object behaviour
int &MyClass::operator[] (int index)
{
  return internal_array[index];
}

r = engine->RegisterObjectBehaviour("mytype", asBEHAVE_INDEX, "int &f(int)", asMETHOD(MyClass,operator[]), asCALL_THISCALL); assert( r >= 0 );

// Registering a global behaviour
MyClass operator+(const MyClass &a, const MyClass &b)
{
  MyClass res = a + b;
  return res;
}

r = engine->RegisterGlobalBehaviour(asBEHAVE_ADD, "mytype f(const mytype &in, const mytype &in)", asFUNCTIONPR(operator+, (const MyClass &, const MyClass &), MyClass), asCALL_CDECL); assert( r >= 0 );

You can find a complete list of behaviours here.

Registering object methods

Class methods are registered with the RegisterObjectMethod call.

// Register a class method
void MyClass::ClassMethod()
{
  // Do something
}

r = engine->RegisterObjectMethod("mytype", "void ClassMethod()", asMETHOD(MyClass,ClassMethod), asCALL_THISCALL); assert( r >= 0 );

It is also possible to register a global function that takes a pointer to the object as a class method. This can be used to extend the functionality of a class when accessed via AngelScript, without actually changing the C++ implementation of the class.

// Register a global function as an object method
void MyClass_MethodWrapper(MyClass *obj)
{
   // Access the object
   obj->DoSomething();
}

r = engine->RegisterObjectMethod("mytype", "void MethodWrapper()", asFUNCTION(MyClass_MethodWrapper), asCALL_CDECL_OBJLAST); assert( r >= 0 );

Registering object properties

Class member variables can be registered so that they can be directly accessed by the script without the need for any method calls.

struct MyStruct
{
  int a;
};

r = engine->RegisterObjectProperty("mytype", "int a", offsetof(MyStruct,a)); assert( r >= 0 );

offsetof() is a macro declared in stddef.h header file.