The First Plugin

The main way of extending TEMU with additional models is to write plug-ins. In-fact, virtually all TEMU models are defined in their own plug-in that needs to be imported by the TEMU plug-in system. Plug-ins are shared libraries on Linux (and Mach-O bundles on macOS). On Linux, you can compile the simple plug-in as:

$ cc -fPIC -shared first.c -I/opt/temu/2.2.0/include -o libfirst.so

The hello world plug-in is a very small:

#include <stdio.h>
#include "temu-c/Support/Objsys.h"
TEMU_PLUGIN_INIT
{
  printf("Hello TEMU plug-in\n");
}

After the plug-in has been compiled, you can load it in the command line interface as follows:

temu> import first
Hello TEMU plug-in
Subsequent imports will not re-run the plug-in initialisation function. Use of the TEMU_PLUGIN_INIT macro to declare the name and signature for the plug-in initialisation function. The advantage of using this (in contrast to e.g. the compilers library constructor / init attributes) is that TEMU_PLUGIN_INIT is guaranteed to work on all platform that TEMU supports.

In addition, the TEMU_PLUGIN_INIT macro will ensure the correct name-mangling is done if the plug-in is compiled as a C++ plug-in.

The key use of the plug-in initialisation function is to register classes with the TEMU object system. We will modify the plug-in file as follows:

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "temu-c/Support/Objsys.h"

typedef struct {
  temu_Object super;
  uint32_t a;
} MyModel;

static void*
create(const char *name, int argc, const temu_CreateArg argv[])
{
  MyModel *model = malloc(sizeof(MyModel));
  memset(model, 0, sizeof(MyModel));
  return model;
}

static void
dispose(void *obj)
{
  MyModel *model = (MyModel*)obj;
  free(model);
}

TEMU_PLUGIN_INIT
{
  temu_Class *c = temu_registerClass("MyModel", create, dispose);
  temu_addProperty(c, "a", offsetof(MyModel, a), teTY_U32, 1, NULL, NULL,
                   "property a");
}

Restart TEMU, and re-load your plug-in with the "import" command. Now, if you type class-list, you will see your new model. If you type class-info class=MyModel, you will see the meta info you registered with the class (interfaces, properties and ports). The temu_registerClass() function takes as parameters, the name of the class (which must be unique), a constructor that is responsible for allocating and intialising objects of the class, and a destructor that is responsible for freeing the object and cleaning up any resources it may have acquired (closing open files etc). The next step is to create an instance of the class:

temu> create-object class=MyModel name=foo

Typing "object-list" will let you see that foo is created with the type MyModel. Go ahead and inspect your now created object:

temu> object-info obj=foo
foo : MyModel <>
a : uint32_t = 0 = 0x0
object.timeSource : <<object>> = 0

As you can see, the property you registered has the value 0. This is expected as the first thing done in the constructor was to clear the object returned by malloc.

If you look at the signature of the create function, you see that it takes the object name, an argument count and an argument array as parameters. The name will be the name you pass to the name parameter of the create-object function. The argc/argv values are set using the optional "args" parameter. The args parameter is a string, with key-value pairs, but as the command interpreter also use key-value pairs, the syntax is slightly different. Using the args can be done as follows:

temu> create-object class=MyModel name=bar \
      args="baz:42,foobar:42.0,blah:fourtytwo"

At present, your model class does not handle the arguments, so lets add some code to handle this in the constructor function, arguments for the create function have their type inferred by the way the argument is formatted, e.g. 123 is an integer, while 123.0 is a floating point value, anything that isn’t a number will be passed as a string:

for (int i = 0; i < argc ; ++i) {
  if (!strcmp(argv[i].Key, "baz") && temu_isDiscrete(argv[i].Val)) {
    printf("create argument baz = %" PRIu64 "\n",
           temu_asUnsigned(argv[i].Val));
  } else if (!strcmp(argv[i].Key, "foobar") && temu_isReal(argv[i].Val)) {
    printf("create argument foobar = %f\n", temu_asDouble(argv[i].Val));
  } else if (!strcmp(argv[i].Key, "blah") && temu_isString(argv[i].Val)) {
    printf("create argument blah = %s\n", argv[i].Val.String);
  } else {
    printf("unknown argument '%s'\n", argv[i].Key);
  }
}

Also add #include <inttypes.h>, to get the PRIu64 macro defined. Now recompile the plug-in, restart TEMU and reload the plug-in. Try the "object-create" command above with the same arguments again. Note that, although arguments are a useful way to simplify model creation scripts, arguments suffer from the lack of built-in documentation. You therefore need to document all arguments carefully for any users of your model. There is another way of configuring your models that is more self-documenting and that is to use the property mechanism in the object system. To write a property in an object, use the object-prop-write command. In the example given, you can say:

temu> object-prop-write prop=foo.a val=42
temu> object-info obj=foo
foo : MyModel <>
a : uint32_t = 42 = 0x2a
object.timeSource : <<object>> = 0

As can be seen, executing the prop write command updated the property value that was registered with the MyModel class.