int id = hw_RegisterUserObject(lua_State *L, STRPTR name, struct hwObjectList **list, int (*attrfunc)(lua_State *L, int attr, lua_ID *id));
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:
hw_RegisterUserObject()
.
id:
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:
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:
ptr
is NULL
, Hollywood will
automatically use the numerical identifier specified in num
.
reserved:
NULL
.
succ:
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) |
lua_State
struct hwObjectList
pointer (see above)