Class Implementation File

[Note] Note

Note that Realsoft 3D class is not a C++ class. However, 'C' and 'C++' can be used for implementing Realsoft 3D classes. Actually, any language that supports 'C' function calling convention can be used for implementing Realsoft 3D classes.

Instance Data

    typedef struct myidata {
        R3BOOL invisible;
    }MYIDATA;

The instance data may also contain any private data. The instance data should be defined in the implementation file, or a private header file. Realsoft 3D objects support data hinding, they are black boxes and their internal structure should not be visible to outside.

Class Dispatcher

    void *dispatcher(R3CLASS *cl, R3OBJ *obj, R3INT method, void *p1, void *p2, void *p3);

Whenever a message is sent to an object, this function is called. The function dispatches the call to appropriate code based on the method identifier.

Various R3SendMsg() functions are simply shields calling this dispatcher function:

    R3DoA(obj, mth, p3)             dispatcher(cl, obj, NULL, NULL, p3);
    R3DoA2(obj, mth, p1, p3)        dispatcher(cl, obj, p1, NULL, p3);
    R3DoA3(obj, mth, p1, p2, p3)    dispatcher(cl, obj, p1, p2, p3);

If the dispatcher function doesn't recognize certain method, it passes the method to its super class by calling appropriate R3DoSuper() function.

Registration Function

In the registration function you initialize your class so that the caller will be able to instance your class. You also have to call the register functions of all other classes your class needs so that the user doesn't have to worry about the internal structure of your class.

A class can be created:

    R3CLASS class = R3ClassCreate(superclid, myclid, dispatcher, isize, ...);

where

[Note] Note
If the class is abstract and does not implement any methods, then the dispatcher parameter should be NULL, in which case all events get dispatched directly to the super class.

Below is an example implementation file. It handles four methods: create, set, get and dosomething.

    #include "myclass.h" /* our class header */

    typedef struct myinstancedata {
        R3BOOL invisible;
	R3VECTOR position;
    }MYIDATA;

    static void *r3rm_get(R3CLASS *cl, R3OBJ *obj, R3TAG *tl)
    {
        R3TAG *tag;
        MYIDATA *self = R3CL_IADDR(cl, obj);
        void *undef_attr = NULL;

        while(tag = R3TagNext(&tl)) {
            switch(tag->ident) {
                case MYA_Invisible:
                    *(R3BOOL *)tag->value = self->invisible;
                    break;

                case MYA_Position:
                    *(R3VECTOR *)tag->value = self->position;
                    break;

                default: 
                    if(R3DoSuper(cl, obj, R3RM_GET, tag->ident, tag->value, R3TAG_END))
                        undef_attr = tag;
                    break;
            }
        }
        return(undef_attr);
    }

    static void *r3rm_set(R3CLASS *cl, R3OBJ *obj, R3TAG *tl, int create)
    {
        R3TAG *tag;
        MYIDATA *self = R3CL_IADDR(cl, obj);
        void *undef_attr = NULL;

        while(tag = R3TagNext(&tl)) {
            switch(tag->ident) {
                case MYA_Invisible:
                    self->invisible = (R3BOOL)tag->value;
                    break;
                case MYA_Position:
                    self->position = *(R3VECTOR*)tag->value;
                    break;

                default: /* and pass unregognized attributes to our super class */
                    if(!create && R3DoSuper(cl, obj, R3RM_SET, tag->ident, tag->value, R3TAG_END))
                        undef_attr = tag;
                    break;
            }
        }
        return(undef_attr);
    }

    static void *mym_dosomething(R3CLASS *cl, R3OBJ *obj, void *p1, void *p2, void *p3)
    {
        MYIDATA *self = R3CL_IADDR(cl, obj);

        ...
        return NULL;    /* return something */
    }

    static void *r3rm_create(R3CLASS *cl, R3OBJ *obj, R3TAG *tags)
    {
        MYIDATA *self = R3CL_IADDR(cl, obj);

	/* set defaults */
	self->invisible = TRUE;
	self->position = ..;

	/* set attributes passed to constructor */
        r3rm_set(cl, obj, tags, TRUE);

        return obj;
    }
    
    static void *mydispatcher(R3CLASS *cl, R3OBJ *obj, int mth, void *p1, void *p2, void *p3)
    {
        switch(mth)
        {
            case R3RM_CREATE:
                if(obj = R3DoSuperA3(cl, obj, R3RM_CREATE, p1, p2, p3))
                    return r3rm_create(cl, obj, p1, p2, p3);
                return NULL;

            case R3RM_SET:
                return r3rm_set(cl, obj, p3, FALSE);

            case R3RM_GET:
                return r3rm_get(cl, obj, p3);

            case MYM_DOSOMETHING:
                return mym_dosomething(cl, obj, p1, p2, p3);

            default:
                return((void *)R3DoSuper(cl, obj, mth, p1, p2, p3));
        }
    }

    int RegisterMyClass(R3APP *apphnd)
    {
        static R3APP *app;

        if(!app) {
            if(!R3RegisterMySuperClass(app))
                return FALSE;

            if(R3ClassCreate(R3CLID_MYSUPERCLASS, R3CLID_MYCLASS, mydispatcher, sizeof(MYIDATA),
                             R3TAG_END) == NULL)
                return(FALSE);
            app = apphnd;
        }
        return TRUE;
    }

More about Instance Data

When implementing methods, such as 'mym_dosomething', the address of our instance data can be fetched using the R3CL_IADDR() macro.

static void mym_dosomething(R3CLASS *cl, R3OBJ *obj, R3FLOAT *time)
{
    MYIDATA *self = R3CL_IADDR(cl, obj);

    /* refer to our instance data */
    self->invisible = ...;

Handling R3RM_SET and R3RM_GET methods

The R3RM_GET and R3RM_SET methods take R3TAG list defining attributes to be get/set. In these methods, you should loop through the passed attribute identifiers and see if the user attempts to set/get attributes defined by our class.

In case we don't recognize a certain attribute, it must be passed to the super class using R3DoSuper() function:

Handling the R3RM_SET method is very similar to handling of R3RM_GET method. The difference is that R3RM_SET method is often called internally by the R3RM_CREATE method to handle attributes passed to R3RM_CREATE method.

[Note] Note
Don't pass attributes to the super class when the set method is called from the create method. The obvious reason for this is that the creation method has just returned from its super class and there is no need to dispatch them to the super class again

Handling R3RM_CREATE method

you must first pass the R3RM_CREATE method to the root class so that the root class can allocate instance data for the object. The parameters for the R3RM_CREATE method are the same as those defined by the R3RM_SET and R3RM_GET methods.

Sometimes one needs to allocate some extra data where size is not fixed but depends on whatever attributes. Because the size of the instance data is fixed when the class is registered, space for these 'varying attributes' must be allocated separately in the CREATE method.

This extra memory must be freed in the R3RM_DELETE method and duplicated in the R3RM_COPY method.

For example:

    static void *r3rm_create(R3CLASS *cl, R3OBJ *obj, R3TAG *tags)
    {
        MYIDATA *self;

        /* let the super class to allocate instance data */
        if(!(obj = R3DoSuperA(cl, obj, tags)))
            return NULL;
        self = R3CL_IADDR(cl, obj);

        /* defaults */
        self->count = 10;
        self->geometry = R3Alloc(sizeof(MYGEOMETRY) * self-count, R3MEMF_CLEAR);

        /* call set method internally to handle creation parameters */
        r3rm_set(cl, obj, tags, TRUE);

        return obj;
    }

Handling R3RM_DELETE method

If you do allocate resources in classes R3RM_CREATE method, you will also have to free the resources when the R3RM_DELETE method is called:

static void r3rm_delete(R3CLASS *cl, R3OBJ *obj)
{
    MYIDATA *self = R3CL_IADDR(cl, obj);

    if(self->geometry)
        R3Free(self->geometry, sizeof(MYGEOMETRY) * self->count);
    return R3DoSuperA(cl, obj, R3RM_CREATE, NULL);
}

Handling R3RM_COPY

It is often a good idea to implement copy method for your classes. In fact, many Realsoft 3D classes require this. If you do allocate resources in classes R3RM_CREATE method, then remember to copy them in your R3RM_COPY method too. The following code demonstrates how classes should handle R3RM_COPY method.

    static void *r3rm_copy(R3CLASS *cl, R3OBJ *obj)
    {
        MYIDATA *self = R3CL_IADDR(cl, obj);
        MYIDATA *newself;
        R3OBJ *newobj;

        if(!(newobj = R3DoSuperA(cl, obj, R3RM_COPY, NULL)))
            return NULL;
        newself = R3CL_IADDR(cl, newobj);

        if(self->geometry)
            newself->geometry = R3Alloc(sizeof(MYGEOMETRY) * self->count, R3MEMF_CLEAR);
        return newobj;
    }

Handling R3RM_WRITE and R3RM_READ

Many objects need to save their attributes to a file and read the data back later to reconstruct themselfs to match the state. Functions like 'File/Save As' and 'File/Load' are based on this feature and the two methods a class need to implement for this are: R3RM_WRITE and R3RM_READ. Typically all model objects (objects derived from the oops/r3model.h base class) must imlement these methods.

    static void *r3rm_write(R3CLASS *cl, R3OBJ *obj, R3TAG *tags)
    {   
        R3IDATA *self = R3CL_IADDR(cl, obj);
        R3OBJ *file = NULL;
        int *error = 0;

        R3TagGet(tags,
                 R3RA_FileObject, &file,
                 R3RA_Error,      &error,
                 R3TAG_END);

        if (!file)
            return (void*)FALSE;

        if (!R3DoSuperA(cl, obj, R3RM_WRITE, tags))
            return (void*)FALSE;

        if(!R3DoA(file, R3FM_WRITELONG, (void*)self->numpoints))
            return (void*)FALSE;

        return (void*)TRUE;
    }

    static void *r3rm_read(R3CLASS *cl, R3OBJ *obj, R3TAG *tags)
    {   
        R3IDATA *self = R3CL_IADDR(cl, obj);
        R3OBJ *file = NULL;
        int *error = 0;
        R3INT version = R3VersionForClid(R3CLID_MYCLASS);
        
        R3TagGet(tags,
                 R3RA_FileObject, &file,
                 R3RA_Error,      &error,
                 R3TAG_END);
        if (!file)
            return (void*)FALSE;
        if (!R3DoSuperA(cl, obj, R3RM_READ, tags))
            return (void*)FALSE;
        if(!R3DoA(file, R3FM_READLONG, (void*)&self->numpoints))
            return (void*)FALSE;
        return (void*)TRUE;
    }

The 'file' parameter passed to these methods is either oops/r3file.h or its derivative oops/r3iff.h.

Class Version

Class implementors may add new attributes to their classes and this should not break the backward compatibility with R3RM_READ method. For this classes define 'R3RCA_Version' attribute. Whenever you change the interface to your class, you should increase the version number of the class. This way other classes that depend on your class can make sure they don't try to use a feature which doesn't exist in that particular version.

The version number can be defined as a parameter for the R3ClassCreate() function:

    R3ClassCreate(R3CLID_MYSUPERCLASS, R3CLID_MYCLASS, mydispatcher, sizeof(MYIDATA),
                  R3RCA_Version, 2,
		  ...

By default the version number is '0'.

One can then find out the version number of a class by calling:

    R3INT version;
    R3GetClassAttrs(R3CLID_ACLASSINEED,
                    R3RCA_Version, &version,
                    R3TAG_END);

Most often the version checking is needed in the R3RM_READ method to take care of backward compatibility. Let's say you have introduce a new attribute in class version 5. If you load a file written with the older class version, then you should not try to read this attribute from the file. Call R3VersionForClid() function to determine the version of the file:

    static void *r3rm_read(R3CLASS *cl, R3OBJ *obj, R3TAG *tags)
    {
        R3IDATA *self = R3CL_IADDR(cl, obj);
        R3INT version;
        R3INT *error = NULL;
        R3OBJ *file;
        
        R3TagGet(tags,
                R3RA_FileObject, &file,
                R3RA_Error,      &error,
                R3TAG_END);

        // let the super class to read first, as usual
        if (!R3DoSuperA(cl, obj, R3RM_READ, tags))
            return (void*)FALSE;

        // read attributes common to all versions
        if(!R3DoA(file, R3FM_READLONG, &self->foo))
            return FALSE;

	// fetch the version the file was created with 
        version = R3VersionForClid(R3CLID_MYCLASS, tags);

        // read 'age' attributes only if the version is 5 or newer
        if(version >= 5) { // new object, read the attribute 
            if(!R3DoA(file, R3FM_READLONG, &self->age))
                return FALSE;
        } 
        return (void*)TRUE;
    }

You can find an example class from 'samples/kernel/class' folder.

Implementing Public Attributes

A class may expose number of attributes so that anyone can study and use them without actually knowing the class. For example, the animation system, is based on this interface.

In order to define public attributes, you have to specify properties of the public attributes in R3TAGNAME array and implement four methods which deal with the tagname array.

Public attributes are typically defined only by model classes, such as geometric sphere, post effects etc.

Define the attributes, as follows:

    static R3TAGNAME public_attrs[] = {
        /* name attribute id, type, animateable, min max /* sub type */
        {"Rotation",  MYA_Rotation,  R3TID_VECTOR,  R3TN_ANIMATEABLE, FALSE, 0, 0, R3TNS_ANGLE},
        {"Scale",     MYA_Scale,     R3TID_FLOAT,   R3TN_ANIMATEABLE, FALSE, 0, 0, 0},
        {"Invisible", MYA_Invisible, R3TID_BOOLEAN, R3TN_ANIMATEABLE, FALSE, 0, 0, 0},
        {NULL,} /* terminate */
    };

and add the following methods to your class dispatcher

    case R3RM_MAKETAGLIST:
        R3TAGLISTNODE *newnode = R3Alloc(sizeof(R3TAGLISTNODE), R3MEMF_CLEAR);
        if (newnode) {
            newnode->tagsarray = public_attrs;
            R3AddTail((R3LIST *)p3, (R3NODE*)newnode);
        }
        return R3DoSuperA3(cl, obj, mth, p1, p2, p3);

    case R3RM_FREETAGLIST:
    {
        R3TAGLISTNODE *node = (R3TAGLISTNODE *)R3RemHead((R3LIST *)p3);
        if (node)
            R3Free(node, sizeof(R3TAGLISTNODE));
        return R3DoSuperA3(cl, obj, mth, p1, p2, p3);
    }

    case R3RM_FINDTAGDESCRBYNAME: 
    {   int i;
        for (i = 0; public_attrs[i].name; i++) {
            if (!strcmp(public_attrs[i].name, (char *)p1)) {
                *(R3TAGNAME *)p3 = public_attrs[i];
                return (void *)TRUE;
            }
        }
        return R3DoSuperA(cl, obj, R3RM_FINDTAGDESCRBYNAME, (void*)p1, NULL, p3);
    }

    case R3RM_NAMEFORTAG: 
    {   int i;
        for (i = 0; public_attrs[i].name; i++) {
            if (public_attrs[i].tag == (R3INT)p1) {
                strcpy((char *)p3, public_attrs[i].name);
                return p3;
            }
        }
        return R3DoSuperA3(cl, obj, R3RM_NAMEFORTAG, (void*)p1, 0, p3);
    }