Adding a frontend language to the breve engine: the breve object API

This section describes how to incorporate another frontend language with breve. Using this information, you'll be able to use the breve engine from any language that can interface a C library.

The following steps are required to set up a language frontend in breve.

Structures used by the breve Object API

The following structures are used by the breve Object API. These structures are passed to the frontend language callback functions. See the file kernel/breveObjectAPI.h and its documentation for a full description of these structs.

  • brObjectType: information and callbacks for a language frontend

  • brObject: a class in any frontend language

  • brInstance: an instance in any frontend language

  • brMethod: information about a method in an object

You should also familiarize yourself with the brEval structure and types which are described in the section called “Writing C Wrapper Functions Around Existing Code”.

Callbacks required to add a new frontend language

In order to provide a language frontend to breve, a set of callbacks must be defined.

  • findObject: locate a class by name.

    void *(*findObject)( void *inObjectTypeUserData, char *name );
    

    When breve encounters a class name that it does not recognize, it will use this function to attempt to lookup the object. The pointer that is returned will be placed in the pointer field of a brObject. This pointer will likely be used later to instantiate objects or locate instance methods by your instantiate or findMethod callbacks.

  • findMethod: locate a method for a class by name and argument count.

    void *(*findMethod)( void *inObjectUserData, const char *inName, unsigned char *inTypes, int inTypeCount );
    

    When breve needs to call a method in an object, it will be looked up using this callback. The inObjectUserData argument refers to the object in which the method can be found—it is the pointer returned by your findObject callback. The inName argument is the name of the method to look for.

    The inTypeCount argument gives the number of arguments that will be passed to the method, and the inTypes argument contains the argument types. The types are described in the section called “Writing C Wrapper Functions Around Existing Code”

  • instantiate: create an instance of an object.

    brInstance *(*instantiate)( brEngine *inEngine, brObject *inObject, const brEval **inArguments, int inArgCount )
    

    breve calls this function to create a new instance of a class. The inObject argument specifes the object to be created and contains the user data found with findObject in the userData field.

    The function is expected to add the newly created instance to the breve engine by calling brEngineAddInstance. brEngineAddInstance returns a brInstance, which should in turn be returned by instantiate. brEngineAddInstance is called as follows:

    brInstance *brEngineAddInstance( brEngine *inEngine, brObject *inObject, void *inInstanceUserData );
    

    The constructor arguments inArguments and count inArgCount are currently unused.

  • callMethod: trigger a method call in the frontend language.

    int (*callMethod)(brInstance *instancePointer, brMethod *method, brEval **arguments, brEval *result);
    

    The breve engine will need to trigger method calls in the frontend language for a number of events such as iteration and collision handling. This callback is used to trigger such events.

    The callback is given the instance to be used, a brInstance instance structure (which contains a native language instance pointer in the "pointer" field); the method to be called, a brMethod structure (which contains a native language method pointer in the "pointer" field); an array of brEval argument pointers, and a pointer to an output brEval structure.

    The callback should trigger the method call method for the instance instancePointer. It expects that the arguments array contains the number of items specified by the brMethod structure's argumentCount field.

  • isSubclass: determine whether a class is a subclass of another.

    int (*isSubclass)(brObject *class1, brObject *class2);
    

    In order to correctly handle certain interactions like collisions, the breve engine needs to know whether one class is a subclass of another.

    This callback is given two breve object pointers, and must return 1 if class1 is a subclass of class2, and 0 otherwise.

  • destroyObject: release memory allocated by your findObject callback.

    void (*destoryObject)(void *objectData);
    

    If your findObject callback allocates memory, this callback should release that memory.

  • destroyInstance: release memory allocated by your instantiate callback.

    void (*destoryInstance)(void *instanceData);
    

    If your instantiate callback allocates memory, this callback should release that memory.

  • destroyMethod: release memory allocated by your findMethod callback.

    void (*destoryMethod)(void *methodData);
    

    If your findMetho callback allocates memory, this callback should release that memory.

  • destroyObjectType: release the memory associated with your brObjectType.

    void (*destroyData)(void *objectTypeData);
    

    If you have allocate memory to be placed in the data field of the brObjectType, this callback should release that memory.

Specifying a new breve object type, and

Every object in a frontend language that will have instances in the breve engine must be registered with the breve engine. Moreover, when an object is added to the breve engine, it must also tell the engine what "type" of object it is. Each object "type" corresponds to a different language frontend and a different set of callbacks, so for each language frontend one creates, one must also create a brObjectType structure which contains the proper callbacks. The structure is shown below.

struct brObjectType {
        /**
         * Finds a method in a given class
         */
        void                    *(*findMethod)( void *inObject, const char *inName, unsigned char *inTypes, int inTypeCount );

        /**
         * Finds an object class in the given language frontend
         */
        void                    *(*findObject)( void *inObjectType, const char *inName );

        /**
         * Creates a new instance of the given class.  The constructor arguments are currently unused.
         */
        brInstance              *(*instantiate)( brEngine *inEngine, brObject *inObject, const brEval **inArguments, int inArgCount );

        /**
         * Calls a method in the language frontend
         */
        int                     (*callMethod)( void *inInstance, void *, const brEval **, brEval * );

        /**
         * Returns 1 if parent is a subclass of parent.
         */
        int                     (*isSubclass)( void *inChild, void *inParent );

        /**
         * Destroys an instance of a language object previously created with instantiate.
         */
        void                    (*destroyObject)( void *inObject );

        /**
         * Destroys an instance of a language method previously created with findMethod.
         */
        void                    (*destroyMethod)( void *inMethod );

        /**
         * Destroys an instance of a language instance previously created with instantiate.
         */
        void                    (*destroyInstance)( void *inInstance );

        /**
         * Frees any leftover memory associated with the frontend, typically _userData.
         */
        void                    (*destroyObjectType)( void *inObjectType );
        
        /**
         * A function to execute code in this frontend language.
         */                     
        int                     (*evaluate)( brEngine *inEngine, char *inFilename, char *inFiletext );

        /**
         * A user-data callback pointer.
         */
        void                    *_userData;
        
        /**
         * A unique identifier which will be set for all objects of this object type.  This
         * identifier can be used to determine whether an instance or object is native to
         * a certain object type.
         */
        long                    _typeSignature;

};

Only one brObjectType is required for each language frontend.

Once the language frontend is defined, you can add it to the engine with the following function:

void brEngineRegisterObjectType( brEngine *engine, brObjectType *type );

Testing and using the object frontend

Once you have written code to setup and register your new breve object type, you'll want to test that breve can locate objects, instantiate them and call their methods. To do so, you can add your frontend object type to the breve engine, and then instantiate an object from steve code.

To add your frontend object type to the breve engine, edit the file kernel/frontendAPI.c, and look at the function breveFrontendInit. The existing breve applications call this function to create a breve engine. At the bottom of this function, you can create and register your custom language frontend.

brEngineRegisterObjectType(frontend->engine, functionToCreateLanguageFrontend());

After recompiling breve with this change, it should be possible to instantiate an object from your new language frontend from directly within a steve simulation. See the Java example java/JavaTest.tz for more information.