The FTS Message System Programmer Guide.

This file documents the FTS Message System.

Introduction

The FTS message system combines an object system and a message passing system. It is a new definition of Max/FTS message system already implemented in Max/Macintosh and in previous versions of Max/FTS on the ISPW.

The main concepts of FTS message system are:


Objects

A FTS object is an abstraction that store information and can receive and emit messages. To an object is allocated some memory storing its state as a C structure.
typedef struct {
  fts_object_t o;   /* MUST BE FIRST STRUCTURE MEMBER */
  long n;           /* the state of a integer object */
} integer_t;
Attention !!! A FTS object must always have a first structure member of type fts_object_t. This implements in C the fact that all FTS objects inherits from the fts_object_t type.

The only "system" member of the structure which is made mandatory by the FTS object system is the first member of type fts_object_t. The other members of the structure are "user" members and are not handled by the system.

An number of functions or macros are provided to get some of the object characteristic:

int  
fts_object_get_outlets_number(fts_object_t *obj);
Return the number of outlets the object obj have.
fts_symbol_t *fts_object_get_outlet_typethe (fts_object_t *obj, int outlet);
Return the type of the outlet outlet for obj (see Outlet Typing).
int  
fts_object_get_inlets_number(fts_object_t *obj);
Return the number of inlets the object obj have.
const char *fts_object_get_class_name(fts_object_t *obj);
Return as a C string the name of the object class; to be used only for user messages.

Future releases will include more functions and macros of this family.


Methods

A method is a C-function that will be called when an object receives a message on one of its inlets. It has a standard signature, and all the methods access their arguments the same way.

Method Signature

All FTS methods have the same signature, i.e. the same number of arguments of the same types. This signature is the following :

void method( fts_object_t *object, int winlet, const fts_symbol_t *selector, int ac, const fts_atom_t *at)

The arguments of the method have the following meaning :

object
a pointer to the object receiving the message
winlet
the number of the inlet on which message was received
selector
the message selector, a symbol. The fts_symbol_t data structure is discussed in details in FTS Kernel Reference Manual.
ac
the number of arguments
at
the arguments array

Method Arguments

The arguments of a method are of type fts_atom_t. The fts_atom_t data structure is discussed in details in FTS Kernel Reference Manual.

Attention !!! The arguments are passed by the message system as an array of constant atoms. This means that a method cannot modify its arguments. Modifying the arguments of a method in the body of the method can lead to unpredictable side effects. If this is needed for convenience, the arguments must first be copied.

Accessing Arguments

A method access its arguments via access macros, which handle both accessing the atoms and giving a default value if corresponding argument is optional.

Attention !!! It is strongly recommended to use the arguments access macros. Accessing directly the members of the fts_atom_t structure is garanteed not to work across different releases of FTS, because fts_atom_t implementation may change.

This is an example of a simple method, taking one argument of type long, with a default value of 0, which stores the value of this argument in the state of the object :

void
integer_in1( fts_object_t *object, int winlet, const fts_symbol_t *selector, 
                  int ac, const fts_atom_t *at)
{
  integer_t *this = (integer_t *)object;

  this->n = fts_get_long_arg( at, ac, 0, 0);
}
In this method, the object argument, typed as an fts_object_t * is in fact a pointer to an object of type integer_t (defined earlier), which first structure member is of type fts_object_t. This explains the cast made at beginning of method.

List of Access Macros

Below is the list of available access macros :
fts_get_long_arg( AT, AC, N, DEFAULT)
gets an argument of type long
fts_get_float_arg( AT, AC, N, DEFAULT)
gets an argument of type float
fts_get_symbol_arg( AT, AC, N, DEFAULT)
gets an argument of type fts_symbol_t *
fts_get_string_arg( AT, AC, N, DEFAULT)
gets an argument of type const char *
fts_get_obj_arg( AT, AC, N, DEFAULT)
gets an argument of type void *
The macro arguments have the following meaning :
AT
the atom array
AC
the number of atoms in array AT
N
the index of the requested argument
DEFAULT
the default value for the requested argument
All these macros have the same semantic : if N is less than AC, return the value of atom N in array AT, else return value DEFAULT.

One special macro is available:

fts_get_float_long_arg( AT, AC, N, DEFAULT)
gets an argument of type long or float.
As the above macros, but get either a long or float value, depending on the type of the passed atom; this macro is usefull to write code that work with generic numeric values; callers of this macros should always cast the result to the desired numeric type.

Message Sending

Messages can be send on the outlets of an object, using the fts_outlet_send function.

The fts_outlet_send Function

fts_status_t fts_outlet_send( fts_object_t *object, int woutlet, fts_symbol_t *selector, int argc, const fts_atom_t *args)

The arguments of fts_outlet_send have the following meaning :

object
the object from which outlet the message is send
woutlet
the number of the outlet on which the message is send
selector
the message selector, a symbol
argc
the count of arguments of the message
args
the arguments of the message, an array of atoms

Message Sending Example

Message sending is most of the time done in the body of a method, as in the following example, a method sending on the outlet the content of the state of the object :

void
integer_bang( fts_object_t *object, int winlet, const fts_symbol_t *selector,
                   int ac, const fts_atom_t *at)
{
  integer_t *this = (integer_t *)object;
  fts_atom_t a;

  fts_set_long( &a, this->n);
  fts_outlet_send( object, 0, fts_s_int, 1, &a);
}

Message Arguments Building

Typically, sending a message is done in 2 phases : Attention !!! It is strongly recommended to use the atoms setting macros to format the arguments. Accessing directly the members of the fts_atom_t structure is garanteed not to work across different releases of FTS, because fts_atom_t implementation may change.

Message Selectors

A message selector is a fts_symbol_t. The fts_symbol_t data structure is discussed in details in FTS Kernel Reference Manual.

There is a number of predefined selectors which are defined in the header files for the message system. Below is the list of already defined selectors for release 1.3.x together with associated symbol name :

fts_s_int
"int"
fts_s_float
"float"
fts_s_bang
"bang"
fts_s_list
"list"
fts_s_symbol
"symbol"
fts_s_set
"set"
fts_s_sig
"sig"
fts_s_put
"put"
If a selector which is not already defined is needed, the fts_new_symbol function generates a new symbol.


Messages and Methods

Method definition is done using the fts_method_define function, which is usually called when initializing the class. This function defines to which message the method will be associated, and which kind of arguments it expects.

The fts_method_define Function

fts_status_t fts_method_define( fts_class_t *class, int winlet, fts_symbol_t *selector, fts_method_t method, int argc, fts_atom_type_t *argtypes )

The arguments of fts_method_define have the following meaning :

class
the class in which the definition is valid
winlet
the number of the inlet for which the definition is valid
selector
the associated message selector : when receiving a message having this selector, the method will be called with the message arguments, after arguments type checking
method
the method : a function having the signature already defined
argc
the count of expected arguments
argtypes
the types of expected arguments : the type checking mechanism will be detailled later

Method Definition Example

Below is an example showing the installation of the method integer_in1 already defined for the integer object. This method is defined for inlet number 1 and takes one argument of type FTS_LONG.

fts_status_t
integer_instantiate( fts_class_t *class, int ac, fts_atom_t *at)
{
  fts_atom_type_t a;

  a = fts_Long;
  fts_method_define( class, 1, fts_s_int, integer_in1, 1, &a);
}

Method Arguments Types

When defining a method associated with a message, it is necessary to define the arguments types which are expected. This will be used by the system to do a type checking, at connection-time and at run-time (with some optimizations).

This type definition is done with the argtypes argument of function fts_method_define. This argument is an array of elements of type fts_atom_type_t. Each element defines the expected type of the argument having same index in the array of atoms which is the arguments of the received message.

The elements of the array argtypes can take the following values (or an bit-or of some of them) :

fts_Long
argument must be of type FTS_LONG, initialized with fts_set_long
fts_Float
argument must be of type FTS_FLOAT, initialized with fts_set_float
fts_Symbol
argument must be of type FTS_SYM, initialized with fts_set_symbol
fts_String
argument must be of type FTS_STRING, initialized with fts_set_string
fts_Object
argument must be of type FTS_OBJ, initialized with fts_set_obj
fts_Any
argument can be of any type
fts_OptArg
argument is optionnal.
fts_VarArgs
no arguments type checking is done. This value must be the first and only in array argtypes

System Methods

Some of the methods of a class can be "system" methods, handling system level functions. These functions are now : object initialisation and deletion, objects connection and disconnection.

All the system methods are associated to messages received on the system inlet, which is a symbolic constant defined by the message system. Except this, system methods are strictly identical to standard methods.

The init Method

The init method handles the initialisation of an object, but doesn't need to handle memory allocation. It is very similar to a C++ constructor. If it is defined, it is called automaticaly by the system when an object is created.

The init method arguments, are the object creation arguments, including the class name; i.e. using FTS with the standard Max editor, the arguments of the init arguments are the content of the object box. The declaration of the types of these so-called creation arguments must take into account this feature.

Below is the example of the init method for the integer object already defined. This method just initialize the state of the object with the value of its argument.

void
integer_init( fts_object_t *object, int winlet, const fts_symbol_t *selector, 
                   int ac, const fts_atom_t *at)
{
  integer_t *this = (integer_t *)object;

  post( "initializing an object of class %s", fts_symbol_name(fts_get_symbol_arg( at, ac, 0, 0)));
  this->n = fts_get_long_arg( at, ac, 1, 0);
}
This method is installed by the following code :
fts_status_t
integer_instantiate( fts_class_t *class, int ac, fts_atom_t *at)
{
  fts_atom_type_t a[2];

  a[0] = fts_Symbol;
  a[1] = fts_Long;
  fts_method_define( class, fts_SystemInlet, fts_s_init, integer_init, 2, a);
}

The delete Method

The delete method handles the destruction of an object, but doesn't need to handle memory de-allocation. It is very similar to a C++ destructor. If it is defined, it is called automatically by the system when an object is deleted, without arguments.

A delete method can be installed for the integer object by the following code :

fts_status_t
integer_instantiate( fts_class_t *class, int ac, fts_atom_t *at)
{
  fts_method_define( class, fts_SystemInlet, fts_s_delete, integer_delete, 
                     0, 0);
}

The connect Method

The connect method is not documented in this release.

The disconnect Method

The disconnect method is not documented in this release.

System Messages Selectors

There is a number of predefined system messages selectors which are defined in the header files for the message system. Below is the list of already defined selectors for release 1.3.x together with associated symbol name :
fts_s_init
"$init"
fts_s_delete
"$delete"
fts_s_connect_inlet
"$connect_inlet"
fts_s_connect_outlet
"$connect_outlet"
fts_s_disconnect_inlet
"$disconnect_inlet"
fts_s_disconnect_outlet
"$disconnect_outlet"

Outlet Typing

The outlets of an object can be statically typed. If an outlet is going to send only one kind of message selector, this selector and the types of associated arguments can be declared using the fts_outlet_type_define function.

The fts_outlet_type_define Function

fts_status_t fts_outlet_type_define( fts_class_t *class, int woutlet, fts_symbol_t *selector,int argc, fts_atom_type_t *argtypes)

The arguments of fts_outlet_type_define have the following meaning :

class
the class for which the type is defined
woutlet
the number of the outlet for which the type is defined
selector
the associated message selector
argc
the count of arguments
argtypes
the types of message arguments
The types of message arguments are the same as for function fts_method_define.

The type of an outlet will be used by the system to do a type checking, at connection-time and at run-time (when needed).

Outlet typing do not affect the efficency of method calling; the method dispatching use a entry dynamic cache that optimize methods calls for all objects, not only statically type ones. The type of an outlet is unique : if an outlet is supposed to send different kinds of messages, then it must not be typed; this means that the symbol fts_s_anything is not accepted as argument to this function.

Outlet Typing Example

The following code defines the type of the outlet for the integer object. The outlet number 1 is defined to send only one type of message, with selector int and one argument of type FTS_LONG.

fts_status_t
integer_instantiate( fts_class_t *class, int ac, fts_atom_t *at)
{
  fts_atom_type_t a;

  a = fts_Long;
  fts_outlet_type_define(class, 0, fts_s_int, 1, &a);
}

Classes

The definition of a class is the definition of : It comes from this definition that all objects of a class have same number of inlets and outlets. The mechanism for having variable number of inlets and outlets is the metaclass described below.

The function that defines a class is called the instantiation function.

The Class Instantiation Function

The signature of a class instantiation function is the following :

fts_status_t my_class_instantiate( fts_class_t *class, int ac, fts_atom_t *at)

The arguments of the class instantiation function have the following meaning :

class
the class that is to be defined
ac
the number of arguments
at
the arguments array. The contents of these arguments will be discussed in the Class Instantiation Process description.
The instantiation function contains at least the 3 following steps :

The fts_class_init Function

fts_status_t fts_class_init( fts_class_t *class, unsigned int size, int ninlets, int noutlets, void *user_data)

The arguments of fts_class_init have the following meaning :

class
the class which is being initialized
size
the size of the objects of the class. It is the sizeof of the object structure
ninlets
the number of inlets of the objects of the class
noutlets
the number of outlets of the objects of the class
user_data
this argument will be simply stored in the fts_class_t structure, giving a simple way to share datas between all the objects of a class

Class Instantiation Function Example

Below is the example of the instantiation function of the integer object already defined. This instantiation function first initialize the class, then defines all the methods of the class, then defines the type of the outlet. The objects of this class have 2 inlets and 1 outlet :
fts_status_t
integer_instantiate( fts_class_t *class, int ac, fts_atom_t *at)
{
  fts_atom_type_t a;

  fts_class_init( class, sizeof( integer_t), 2, 1, 0);

  a = fts_Long;
  /* init method */
  fts_method_define( class, fts_SystemInlet, fts_s_init, integer_init, 1, &a);

  /* message "int" on inlet 0 method */
  fts_method_define( class, 0, fts_s_int, integer_int, 1, &a);

  /* message "int" on inlet 1 method */
  fts_method_define( class, 1, fts_s_int, integer_in1, 1, &a);

  /* message "bang" on inlet 0 method */
  fts_method_define( class, 0, fts_s_bang, integer_bang, 0, 0);

  /* outlet type is int */
  fts_outlet_type_define(class, 0, fts_s_int, 1, &a);

  return fts_Success;
}

Metaclasses and Class Instantiation

A metaclass is a definition of a set of classes having common behaviour and sharing the same instantiation function. To a metaclass is associated a base of already instantiated classes. A class is instantiated when an object is created, following the class instantiation process which is described bellow.

Class Instantiation Process

The class instantiation process is based on the following assumption : the decision that 2 objects are in the same class can be made by comparing the objects creation arguments only.

A class is instanciated on demand, when an object is created, through the following steps :

Equivalence Functions

The equivalence function is used by the message system at object creation time. It is used to decide if 2 objects are in the same class or not. int a_equivalence_function( int ac0, const fts_atom_t *at0, int ac1, const fts_atom_t *at1)

The arguments of a equivalence_function have the following meaning :

ac0, ac1
the number of atoms in array at0, at1
at0, at1
the arrays of arguments, one being the creation arguments of object being created, the other being the creation arguments of the class in the class base
The equivalence function should return true if the two set of arguments can correspond to the same instance class, i.e. if they are equivalent with respect to the class system.

Existing Equivalence Functions

The message system already provides a number of widely used equivalence functions. These functions are described in the table below.
fts_arg_equiv
return true (i.e. not zero) if the arguments are identical (same number, same types, same values).
fts_arg_type_equiv
return true (i.e. not zero) if the numbers and the types of the arguments are the same.
fts_first_arg_equiv
return true (i.e. not zero) if the first arguments are identical.
fts_narg_equiv
return true (i.e. not zero) if the numbers of arguments are the same.
fts_always_equiv
Always return true (i.e. not zero). Using this equivalence function means that there will be only one class instanciated. This is the case for standard objects with fixed numbers of inlets and oulets.
fts_never_equiv
Always return false (i.e.zero). Using this equivalence function means that there will be a new class instanciated for each object instantiated. This is usefull as a first "coarse" implementation of a metaclass.

Example of Equivalence Function

Below is the example of the bangbang class : the bangbang object has 1 inlet, receiving a bang, and a certain number of outlets. The number of outlets is given by the creation argument, which is of type FTS_LONG. When receiving a bang message on its inlet, the bangbang object outputs a bang message on all its outlets, starting from the last.

The bangbang class is a metaclass, with a simple equivalence function : it simply compares the identity of the arguments, which must be of type FTS_LONG.

static int
bangbang_equiv( int ac0, const fts_atom_t *at0,
                  int ac1, const fts_atom_t *at1)
{
  if (ac0 == 1 && ac1 == 1 
      && fts_is_long(at0) && fts_is_long(at1)
      && fts_get_long(at0) == fts_get_long(at1))
    return 1;
  else
    return 0;
}
typedef struct {
  fts_object_t o;
  int noutlets;
} bangbang_t;

static void
bangbang_bang( fts_object_t *object, int winlet, fts_symbol_t *selector,
               int ac, const fts_atom_t *at)
{
  bangbang_t *this;
  int i;

  this = (bangbang_t *)object;
  for (i = this->noutlets-1; i >= 0; i--)
    fts_outlet_send(object, i, fts_s_bang, 0, 0);
}

static void
bangbang_init( fts_object_t *object, int winlet, fts_symbol_t *selector,
               int ac, const fts_atom_t *at)
{
  bangbang_t *this;

  this->noutlets = fts_get_long_arg( at, ac, 0, 2);
}

static fts_status_t
bangbang_instantiate(fts_class_t *class, int ac, const fts_atom_t *at)
{
  int i, noutlets;
  fts_atom_type_t a;

  if ((ac >= 1)  && fts_is_long( at))
    noutlets = fts_get_long(at);
  else
    noutlets = 1;

  fts_class_init(class, sizeof(bangbang_t), 1, noutlets, 0);

  fts_method_define(class, 0, fts_s_bang, bangbang_bang, 0, 0);

  a = fts_Long;
  fts_method_define( class, fts_SystemInlet, fts_s_init, bangbang_init,
                     1, &a);

  for (i = 0; i < noutlets; i++)
    fts_outlet_type_define( class, i, fts_s_bang, 0, 0);

  return fts_Success;
}

Metaclasses Creation and Installation

A metaclass is created with the following function:
fts_metaclass_t *
fts_metaclass_create(fts_symbol_t *name,
		     fts_method_instantiate_t instantiate_function,
		     fts_method_equiv_t equiv_function);
The metaclass is named name; instantiate_function will be used to instantiate new classes, and equiv_function will be its equivalence function. Different metaclasses can have the same name; see Generic Metaclasses. For simplicity, it can be convenient to have multiple names (usually abbreviation) for the same kind of object; the standard way to do this in FTS is to declare an alias to a name, with the following function:
void
fts_metaclass_alias(fts_symbol_t *new_name, fts_symbol_t *old_name)
After the call to fts_metaclass_alias, the name new_name, when used as a metaclass name, will be always automatically substituted by old_name. Since more metaclasses can have the same name, this is not equivalent to say that a meta-class can have multiple names; i.e. all the metaclass with the same name will share the same set of aliases.
void
integer_config(void)
{
  /* create the class 'integer' */

  fts_metaclass_create(fts_new_symbol("int"),integer_instantiate, fts_always_equiv);

  /* ... and register 2 aliases for the "int" name: "i" and "integer" */

  fts_metaclass_alias(fts_new_symbol("integer"), fts_new_symbol("int"));
  fts_metaclass_alias(fts_new_symbol("i"), fts_new_symbol("int"));
}

Complete Examples

Source of the integer object

Source of the bangbang object


Generic Metaclasses

In some cases, objects with the same name correspond to behaviour that are so different to sugger a completely distinct implementation; in other case, a programmer would like to add a new behaviour to the same object name, without modifing the existing implementation.

The FTS message system provide a way to view different metaclasses as the implementation of a single metaclass, the generic metaclass.

The term "generic" derive from the generic programming technique; if we see FTS objects as the operator and statements of a program represented by the FTS patches, then a generic operator (in the object oriented programming sense) can be implemented with a generic metaclass; for example, the "+" operator on control message means correspond to two actual operations depending on the argument being an int or a float; so "+ 1" and "+ 1.0" correspond to two different operations, but to the same, generic, operator.

Generic operators can be (and are, for object like "+") implemented using generic metclasses.

The principle is very simple: if more than one class with the same name exists, the class instatiation algoritm explained in Metaclasses and Class Instantiation is followed with the first class found; in case of failure of the class instation function, i.e. in the case the class instantiation function do not return fts_Success, the next metaclass with the same name is tryed, and so on.

If you design a metaclass so to be extensible or modularly integrable in a generic metaclass, you should be carefull in testing the argument in the instation function and to return an error in case the metaclass cannot implement the requested class; FTS do not provide a declarative method to discriminate between different metaclasses in order to leave the most generic flexibility in generic metaclasses design; for example, a metaclass can overide the behaviour of an existing metaclass for a specific value of its argument, for example to provide an optimized implementation of a specific special case.

The fts_metaclass_create function always add the new metaclass at the end of the class list; in order to add a new behaviour on top of an existing class, the fts_metaclass_create_override function should be used; this function behave exactly like fts_metaclass_create, but add the new metaclass at the beginning of the list, so that it is allowed to override the behaviour of existing classes.


Object And Class Properties

In FTS each object and class can have properties associated with it; a property is simply a pair name value, where the name is an FTS symbol, and the value is any FTS value; a property is a kind of dynamic storage tied to the object; a class can provide default values for properties, that are valid for all the object instance of the class.

Also, the property system is extended with standard AI like constraint progagation and daemon techniques: putting a property or getting a property from an object can transparently activate a number of functions that propagate the properties in the object network.

This subsystem is currently used in FTS to implement some experimental optimization and features in the DSP compiler.

Its API is not currently documented.


The FTS Message System User's Guide.

Authors : Maurizio De Cecco, François Déchelle

Copyright © 1995 IRCAM.