Mixin Interfaces

Object Based Mixin Interfaces

A variant of the FPInterface, known as a Mixin interface, is provided and can be multiply-inherited by a plug-in's class and returned via its implementation of the above new GetInterface(Interface_ID) method, in the way that most existing object-based interfaces are now implemented. Here's an example setup. First, the public header that would be used by an SDK-level client of the interface:

// interface ID
#define FOO_INTERFACE Interface_ID(0x342323, 0x55664)
#define GetFooInterface(obj) \
   ((FooInterface*)obj->GetInterface(FOO_INTERFACE))

// function IDs
enum { foo_move, foo_setRadius, };

// mixin interface
class FooInterface : public  FPMixinInterface
{
   BEGIN_FUNCTION_MAP
     VFN_1(foo_move, Move, TYPE_POINT3);
     VFN_1(foo_setRadius, SetRadius, TYPE_FLOAT);
   END_FUNCTION_MAP

    FPInterfaceDesc* GetDesc();
   virtualvoid SetRadius(float radius)=0;
   virtualvoid Move(Point3 p)=0;
};

This is much the same as existing stand-alone FnPub interfaces, except that the function map is put into the virtual interface class, rather than the implementing interface class. Then, in the plug-in class, you inherit the mixin interface (you are "mixing" it into the class). You provide implementations for the interface's virtual methods in the main class and an implementation of GetInterface() that returns the object cast to the interface, exactly as you would have done for old-style object-based interfaces:

class MyObject : public SimpleObject2, publicFooInterface
{
   public:
     //...
     void SetRadius(float radius);
     void Move(Point3 p);
     //...

     FPInterface* GetInterface(Interface_ID id) {
     if (id == FOO_INTERFACE)
      return (FooInterface*)this;
     else
       return SimpleObject2::GetInterface(id);
     }
     //...
}

Note that the GetInterface() method needs to cast the 'this' to the mixin interface class so the correct vtable is used (this was the case with old-style object interfaces, as well). It also calls SimpleObject2::GetInterface() if the id doesn't match so that other base class interfaces and stand-alone interfaces, if any, are made available to the caller. Finally, you provide a descriptor for the interface, as with stand-alone interfaces, but in this case, you must make it an instance of FPInterface, not FooInterface or MyObject, and must specify the FN_MIXIN flag to denote the interface as mixin:

static  FPInterfaceDesc foo_mixininterface(FOO_INTERFACE,
   _T("foo"), 0,
   &myObjDesc, FP_MIXIN,

   foo_move, _T("move"), 0, TYPE_VOID, 0, 1,
     _T("vector"), 0, TYPE_POINT3,
   foo_setRadius, _T("setRadius"), 0, TYPE_VOID, 0, 1,
     _T("radius"), 0, TYPE_FLOAT,
   end
);

FPInterfaceDesc* FooInterface::GetDesc()
{
   return &foo_mixininterface;
}

This static instance provides the interface metadata and is recorded in the class descriptor. To access the interface metadata, you must get this instance directly from the ClassDesc via ClassDesc2::GetInterface(), and not via Animatable::GetInterface() on an object.

All this is pretty-much the same work you would do to provide an Animatable::GetInterface() interface currently in 3ds Max. The extra stuff is the FUNCTION_MAP and the interface descriptor. The SDK-level clients use the interface in exactly the same way, but now the scripter and other external systems can find and use these interfaces automatically at runtime.

Mixin object-based interfaces are accessible in the scripter in similar way to stand-alone published interfaces. In particular, the functions are available in a struct function package which is itself accessed as a property on the plug-in class object. For example, if the above example plug-in class is named 'MyObject' in the scripter, the functions in its 'foo' mixin interface would be accessed as:

MyObject.foo.move
MyObject.foo.setRadius

The functions are effectively 'generic' functions that require an instance of the plug-in as the first argument, resulting in:

MyObject.foo.move $baz [10,10,10]
MyObject.foo.setRadius $bar 123.4