breve Documentation: version 1.9 | ||
---|---|---|
<< previous | next >> |
breve's plugin architecture allows you to incorporate arbitrary code into a breve simulation. By loading external code into breve, you can add customized types of computation, bridges to other languages, connections to other input and output methods and much more.
![]() | Programming Experience Required |
---|---|
Building plugins for breve does require some programming experience in C, plus access to and familiarity with the GCC compiler. |
![]() | The breve Plugin API |
---|---|
In order to build plugins for breve, you'll need to download the plugin API from the breve website. You'll also need a C compiler—the instructions here assume you're using GCC. In addition to the documentation listed here, you should also look at the sample plugin files included with the breve distribution. These samples show how to build simple plugins for Mac OS X, Linux and Windows. |
In order to write plugins for breve, you'll need to follow a few simple steps.
compose C wrapper functions around your external code (C or C++), Section 11.1.1
create an "entry point function" in C which will load your functions into the breve engine Section 11.1.2
write a class (or classes) to interface with your newly created functions
The first step in composing a breve plugin is to write wrapper functions around your existing code. The wrapper functions simply act as a bridge between the internal breve function calling code, and standard C function calls. When a function is called from within steve, the wrapper function is called. The wrapper function, in turn, calls the necessary C code and coordinates input and output between the C code and the breve call.
The wrapper function passes input and output data between breve and C using a
structure called stEval
.
The stEval
struct is a C data structure which is used internally
to hold the values of expressions in steve. The structure is used to hold any and all
types of steve expressions. So ints
, lists
,
objects
and the rest of the steve types are all held in
stEval
structs. The type
field of the
struct specifies the type of the expression. The values
union
of the struct contains the actual value of the expression. Information on how
to use these fields is listed below.
Wrapper functions have the following prototype:
int function(stEval arguments[], stEval *result, void *instance); |
stEval
structs. The function output is returned by
setting the contents of the stEval
structure pointed to by
result. instance is an
internal pointer to the calling instance—this can be ignored.
To access native C types stored in the stEval
struct, you'll
need to use the following macros, which are defined in the header file distributed
with the API.
STINT(&eval)
, returns the int (int) contained in eval
STDOUBLE(&eval)
, returns the float (double) contained in eval
STVECTOR(&eval)
, returns the vector (slVector) contained in eval
STMATRIX(&eval)
, returns the matrix (double [3][3]) contained in eval
STSTRING(&eval)
, returns the string (char*) contained in eval
STOBJECT(&eval)
, returns the object (stInstance*) contained in eval
STPOINTER(&eval)
, returns the pointer (void*) contained in eval
STDATA(&eval)
, returns the data (stData*) contained in eval
STHASH(&eval)
, returns the hash (stEvalHash*) contained in eval
STLIST(&eval)
, returns the list (stEvalList*) contained in eval
These macros correspond to steve constants representing types. You'll need these constants in the next section when defining the inputs and outputs your functions will take.
ST_INT
ST_DOUBLE
ST_STRING
ST_VECTOR
ST_MATRIX
ST_DATA
ST_HASH
ST_LIST
ST_OBJECT
ST_POINTER
Your wrapper function should use these macros to extract data from the arguments
array, and to store the result. The return value of your wrapper function
should be EC_OK
in the event of sucessful execution, or EC_ERROR
in the event of
a fatal error. Returning EC_ERROR
will cause the simulation to stop, so you
should generally not return this value. In many cases it is better to indicate
the error using a special return value of the internal function (that is to say,
buy putting a special value in the "result" struct, not actually returning from
your C code with a special value). You can then handle the error from within
steve.
As an example of a breve function wrapper around an existing function, imagine a function with the following prototype:
char *downloadURL(char *url, int timeout); |
int breveDownloadURL(stEval *arguments, stEval *result, void *instance) { char *url; int timeout; url = STSTRING(&arguments[0]); timeout = STINT(&arguments[1]); STSTRING(result) = downloadURL(url, timeout); return EC_OK; } |
Your entry point function will be called when the plugin is loaded. Its job is to tell the breve engine what new steve functions to add, their names, and the arguments they will take.
The prototype for an entry-point function is:
void entryPointFunctionName(void *data); |
This entry-point function will be filled with one or more calls to the
function stNewSteveCall
. The calling convention
for this function is:
stNewSteveCall(data, "functionName", cFunctionPointer, returnType, arg1, arg2, ..., 0); |
The first argument, data, is the "data" pointer which gets passed in to the entry-point function.
The second argument, functionName, is the quoted function name as it will appear in steve.
The third argument, cFunctionPointer, is the unquoted name of the C function.
The fourth argument, returnType, is the return type (as a steve constant, listed in the previous section).
Subsequent arguments are the types of input arguments (as steve constants, listed in the previous section) that your steve function will expect, with the value 0 afterwards indicating the end of the parameter list.
The final argument, to follow all of the input types, must be 0.
For example, if you have a function which takes two vector
inputs and produces an int
output, your stNewSteveCall
might look like this:
stNewSteveCall(data, "mySteveFunctionName", myCFunctionName, AT_INT, AT_VECTOR, AT_VECTOR, 0); |
In order to write plugins for breve, you'll first need to familiarize yourself with a feature of steve which is generally hidden from users—the C-style function call.
C-style function calls in breve work just as they do in C: they take a number of arguments and may return a value. In breve, a C-style function call is used to access code which is built in to the breve engine (as opposed to code written in steve). In fact, the built-in class hierarchy provided with breve uses C-style function calls extensively to interface with the breve engine.
From the user's perspective, all computation in breve happens within objects.
So when we write a plugin, we'll also give it an object interface. Here's a
simple example in which the plugin simply provides some data (like a
float
or an int
) back to the caller.
Object : mySimplePluginObject { + to get-input-from-plugin: return getPluginInput(). } |
The more important reason to use objects, however, is so that the plugin can be used by more than one agent simultaneously. Imagine, for example, a plugin which simulates neural networks. It's easy to imagine that a breve simulation might want to use several of these neural networks at the same time. Because the neural networking code requires a "persistent state", we would need a way to store many distinct states simultaneously.
Inside our breve object, we'll hold a pointer to C-memory representing these distinct states. Whenever a neural network function is needed, we'll pass that pointer back to the plugin so that it can operate on the correct state. Here's an example:
Object : myNeuralNetwork { + variables: networkPointer (pointer). + to init: networkPointer = newNeuralNetwork(). + to iterate: neuralNetworkIterate(networkPointer). + to get-output: return neuralNetworkOutput(networkPointer). + to set-input to value (double): neuralNetworkSet(networkPointer, value). } |
<< previous | breve Documentation table of contents | next >> |
Using a Web-interface to a breve Simulation | Building Plugins |