Name
hw_RegisterUserObject -- register a new object type (V5.3)
Synopsis
int id = hw_RegisterUserObject(lua_State *L, STRPTR name,
                struct hwObjectList **list,
                int (*attrfunc)(lua_State *L, int attr, lua_ID *id));
Function
This function allows you to register a new object type with Hollywood. The user will then be able to use all of Hollywood's object functions with your new type. For example, it is possible to associate custom data with Hollywood objects by using the SetObjectData() function and it is possible to query object attributes by calling the GetAttribute() Hollywood function. Additionally, all registered object types will also appear in Hollywood's resource monitor.

hw_RegisterUserObject() will return an identifier for the new object type or 0 in case of an error.

The name you pass to hw_RegisterUserObject() may contain spaces and non-ASCII characters as it is only used by Hollywood's resource monitor.

You have to pass a pointer to a pointer that marks the start of an object list to this function. Each list node must start with a struct hwObjectList item so that Hollywood can iterate the list and find objects in it on its own. struct hwObjectList looks like this:

 
struct hwObjectListHeader
{
    int type;
    lua_ID id;
    APTR reserved;
};

struct hwObjectList
{
    struct hwObjectListHeader hdr;
    struct hwObjectList *succ;
    // ... your private data must follow here ...
};

Whenever you add a new Hollywood object, you need to initialize the following members:

type:
This member must be set to the object's identifier returned by hw_RegisterUserObject().

id:
This member must be set to the lua_ID of this object. Hollywood objects can use two different kinds of identifiers: They can either use a numerical identifier or an automatically chosen identifier that uses the LUA_TLIGHTUSERDATA object type. The user can request an automatically chosen identifier by passing Nil as the desired identifier when creating the object. In that case, the plugin should automatically choose an identifier for the object and return it. This is usually done by using the raw memory pointer to the newly allocated object as an identifier because this guarantees its uniqueness. Internally, the two different kinds of identifiers are managed using the lua_ID structure which looks like this:

 
typedef struct _lua_ID
{
    int num;
    void *ptr;
} lua_ID;

When adding a new object, the two structure members must be initialized like this:

num:
If the object is to use a numerical identifier, you need to write this identifier to num and set the ptr member to NULL. If the ptr member is not NULL, Hollywood will ignore whatever is in num so don't forget to set ptr to NULL.

ptr:
If the object has been created using automatic ID selection, you need to set this member to the identifier that this object should use. This is typically set to the raw memory pointer of the newly allocated object. If ptr is NULL, Hollywood will automatically use the numerical identifier specified in num.

reserved:
Reserved for future use. Must be NULL.

succ:
This must point to the next object in the list or it must be NULL in case the object is the last one. Whenever you create a new object, make sure to chain it into the list of objects that you passed to hw_RegisterUserObject().

You also have to pass a pointer to a function that is called whenever the user calls GetAttribute() on your object type. Hollywood will handle the #ATTRCOUNT attribute automatically for your object type but for all other attributes, Hollywood will simply run the callback you specified when registering the new object type. The callback then has to push the return value(s) for this attribute on the stack and return the number of values actually pushed or an error code, just like a standard Lua function would do. See below for an example implementation.

Note that all of Hollywood's inbuilt objects use constant identifiers defined by inbuilt object constants like #BRUSH, #ANIM, or #VIDEO. User objects, however, use dynamic object identifiers that are determined at runtime by hw_RegisterUserObject(). They can be different every time Hollywood is run. That is why you should never create constants to refer to your user objects because constant values will be hard-coded in applets when scripts are compiled so there can be conflicts if hw_RegisterUserObject() returns an identifier that is different from the constant definition. The recommended way of dealing with user object identifiers is to implement a function named GetObjectType() which returns the dynamic object identifier to the script.

An example implementation could look like this:

 
struct myobj
{
    struct hwObjectList list;

    // store your object data here
    ...
};

// the actual identifier will be determined at runtime by
// hw_RegisterUserObject()
static int MY_OBJECT_TYPE = 0;

// our list of objects
static struct myobj *firstobj = NULL;

// this function is called whenever the user calls GetAttribute()
// on our user object
static SAVEDS int attrfunc(lua_State *L, int attr, lua_ID *id)
{
    struct myobj *o;

    // first find the object in our list
    for(o = firstobj; o; o = o->list.succ) {
        if(id->num == o->list.hdr.id.num &&
           id->ptr == o->list.hdr.id.ptr) break;
    }

    // not found? --> error out!
    if(!o) return ERR_FINDOBJECT;

    // check attribute that should be queried and push return values
    switch(attr) {
    case MYATTRONE:
        lua_pushnumber(L, ...);
        return 1;
    }

    // unknown attribute
    return ERR_UNKNOWNATTR;
}

HW_EXPORT int InitLibrary(lua_State *L)
{
    // register our new object type
    MY_OBJECT_TYPE = hw_RegisterUserObject(L, "MyObject",
                        (struct hwObjectList **) &firstobj, attrfunc);

    return 0;
}

HW_EXPORT void FreeLibrary(lua_State *L)
{
    struct myobj *o, *succ;

    // do not forget to see if there are any objects that
    // the user hasn't freed yet on exit --> otherwise you
    // will leak memory

    for(o = firstobj; o; o = succ) {
        o = o->list.succ;
        freeobject(L, o);
        free(o);
    }
}

// this function is important because the actual object identifier
// can be different each time Hollywood is run
static SAVEDS int my_GetObjectType(lua_State *L)
{
    lua_pushnumber(L, MY_OBJECT_TYPE);
    return 1;
}

static SAVEDS int my_CreateObject(lua_State *L)
{
    struct myobj *o, *prev = NULL;
    lua_ID id;

    // this will check whether the user passed a number in
    // parameter 1 or nil if he passed nil, luaL_checknewid()
    // will set id.ptr to ((void *) 1)
    luaL_checknewid(L, 1, &id);

    if(!id.ptr) {
        // must check if there already is an object with this
        // id and free it
        ...
    }

    for(o = firstobj; o; o = o->list.succ) prev = o;

    // allocate new object
    if(!(o = calloc(sizeof(struct myobj), 1))) return ERR_MEM;

    // additional initialization to be done here
    ...

    // make sure to chain our object into the list
    if(!prev) {
        firstobj = o;
    } else {
        prev->list.succ = o;
    }

    // if the user wants automatic id selection, we need to set id.ptr
    // to our object and push it as light user data on the stack
    if(id.ptr) {
        id.ptr = o;
        lua_pushlightuserdata(L, id.ptr);
    }

    // don't forget to initialize the hwObjectList header
    pdf->list.hdr.type = MY_OBJECT_TYPE;
    pdf->list.hdr.id = id;

    // returns 1 if the user wants automatic id selection because in
    // that case there will be one return value; otherwise there won't
    // be any return values
    return (id.ptr != NULL);
}

static SAVEDS int my_FreeObject(lua_State *L)
{
    struct myobj *o, *prev = NULL;
    lua_ID id;

    // check whether the user passed a number or a light userdata
    // parameter
    luaL_checkid(L, 1, &id);

    // find the object in our list
    for(o = firstobj; o; o = o->list.succ) {
        if(id.num == o->list.hdr.id.num &&
           id.ptr == o->list.hdr.id.ptr) break;
        prev = o;
    }

    // not found? exit!
    if(!o) return ERR_FINDOBJECT;

    // do your clean up here
    ...

    // important! ask Hollywood to free all data associated with this
    // object!
    hw_FreeObjectData(L, (struct hwObjectList *) o);

    // unchain object from our list
    if(prev) {
        prev->list.succ = o->list.succ;
    } else {
        firstobj = o->list.succ;
    }

    // and free it
    free(o);

    return 0;
}

static const struct hwCmdStruct plug_commands[] = {
    {"CreateObject", my_CreateObject},
    {"FreeObject", my_FreeObject},
    {"GetObjectType", my_GetObjectType},
    ...
    {NULL, NULL}
};

HW_EXPORT struct hwCmdStruct *GetCommands(void)
{
    return (struct hwCmdStruct *) plug_commands;
}

Note that you have to iterate through your object list in FreeLibrary() and free all objects that the user didn't free explicitly. Hollywood won't do this automatically. If you do not iterate through your object list in FreeLibrary(), you will leak memory. It's also very important that you call hw_FreeObjectData() on every object that you have allocated. See hw_FreeObjectData for details.

If your plugin implements support for additional object types like above, the user will be able to do the following from the Hollywood script to work with these new object types:

 
MY_OBJECT_TYPE = myplug.GetObjectType()

obj1 = myplug.CreateObject(Nil, ...)

DebugPrint(GetAttribute(MY_OBJECT_TYPE, obj1, #ATTRYOURATTR))

SetObjectData(MY_OBJECT_TYPE, obj1, "test", "Hello")
DebugPrint(GetObjectData(MY_OBJECT_TYPE, obj1, "test"))

myplug.FreeObject(obj1)

Designer compatibility
Supported since Designer 5.0

Inputs
L
pointer to the lua_State
name
user object name to be displayed in the resource monitor
list
pointer to a struct hwObjectList pointer (see above)
attrfunc
function to be called when the user queries attributes for this object
Results
id
identifier of the new object or 0 on error

Show TOC