.\" @(#)packets	1.2	12/8/92
.VR 0.1.2 12/8/92
.TI "Appendix:  Messages in Ptolemy"
.AU
Joseph Buck
.AE
.IE "messages"
.IE "heterogenous message interface"
.IE "packet interface"
.H1 "Design Criteria for the Packet Interface
.pp
The Ptolemy heterogeneous message interface is designed to provide a mechanism that
allows stars to transmit message that are arbitrary objects to other
stars.
.(f
This interface was formerly called the packet interfaces; the classes
have been renamed.
.)f
The design was constrained by the following criteria:
.IE "message design criteria"
.bu
Existing stars (stars that were written
before the message interface was added) that handle ANYTYPE work
with message particles without change.
.bu
Message portholes should be able to send different types of messages
during the same simulation (just as a real network does).
.bu
The design should be efficient: we should not repeatedly be required
to copy large messages.  This suggests using a reference-count mechanism
as is used in many C++ classes (for example, string classes).
.bu
At the same time, it should be possible to safely modify large messages
without excessive memory allocation and deallocation.
.bu
It should be easy for users to define their own message types, and
no change to the kernel should be required to transmit the new message
types.
.lp
To accomplish this goal, three classes were designed:
.bu
The
.c  Message
.IE "class Message"
class is the base class from which all other message datatypes
are derived.  A user who wishes to define an application-specific
message type creates a new class that is derived from
.c Message .
.bu
The
.c Envelope
.IE "class Envelope"
class is an envelope; it "holds" an object derived from class
.c Message .
.c Envelope
objects may be copied or duplicated; when this
happens, the contents are not copied (several
.c Envelope
objects share the same
.c Message
object); at any time, the
.c Message
object's reference count says how many
.c Envelope
objects refer to it.  When the last reference is removed, the contents
are deleted.
.bu
The
.c MessageParticle
.IE "class MessageParticle"
class is a type of
.c Particle
(like
.c IntSample ,
.c FloatSample ,
etc); it contains a
.c Envelope .
Most users will never see this class directly;
if a
.c PortHole
is declared to be of type ``message'' it will use this type of
.c Particle .
.pp
Class
.c Particle
contains two member functions for message support:
.c getMessage ,
to receive a message, and the "<<" operator with a
.c Envelope
as the right argument, to load a message into a particle.  These functions
return errors in the base class; they are overridden in the
.c MessageParticle
class with functions that "do the right thing".
.H1 "Defining A New Message Class"
.IE "user-defined messages"
.pp
The designer of a new type of message does so by defining a new
class that is derived from class
.c Message .
Certain virtual functions
defined in that class must be overriden; others may optionally be
overriden.  Here is an example of a user-defined message type:
.IE "vector message"
.(c
// This is a simple vector message body.  It stores
// an array of integer values of arbitrary length.
// The length is specified by the constructor.

#include "Message.h"

class IntVecData : public Message {
private:
	int len;

	init(int length,int *srcData) {
		len = length;
		data = new int[len];
		for (int i = 0; i < len; i++)
			data[i] = *srcData++;
	}

public:
	// the pointer is public for simplicity
	int *data;

	int length() const { return len;}

	// functions for type-checking
	const char* dataType() const { return "IntVecData";}

	// isA responds TRUE if given the name of the class or
	// of any baseclass.
	int isA(const char* typ) const {
		if (strcmp(typ,"IntVecData") == 0) return TRUE;
		else return Message::isA(typ);
	}

	// constructor: makes an uninitialized array
	IntVecData(int length) : len(length) {
		data = new int[length];
	}

	// constructor: makes an initialized array from a int array
	IntVecData(int length,int *srcData) { init(length,srcData);}

	// copy constructor
	IntVecData(const IntVecData& src) { init(src.len,src.data);}

	// clone: make a duplicate object
	Message* clone() const { return new IntVecData(*this);}

	// destructor
	~IntVecData() {
		delete data;
	}
};
.)c
.pp
This message body can contain a vector of integers of arbitrary length.
Some functions in the class are arbitrary and the user may define them
in whatever way is most convenient; however, there are some requirements.
.pp
The class must redefine class
.c Message 's
.c dataType()
function.
This function returns a string identifying the message type.  This string
should be identical to the name of the class.  In addition, the
.c isA()
function must be defined.
The
.c isA
Function
responds with TRUE (1) if given the name of the class or of any baseclass;
it returns FALSE (0) otherwise.  This mechanism permits stars to handle
any of a whole group of message types, even for classes that are defined
after the star is written.
.pp
Because of the regular structure of
.c isA
function bodies, macros are provided to generate them.  The
.c ISA_INLINE
macro expands to an inline definition of the function; for example,
.(c
	ISA_INLINE(IntVecData,Message)
.)c
could have been written above instead of the definition of
.c isA
to generate exactly the same code.  Alternatively, to put the function
body in a .cc file, one can write
.(c
		int isA(const char*) const;
.)c
in the class body and put
.(c
ISA_FUNC(IntVecData,Message)
.)c
in the .cc file.
.pp
The class must define a copy constructor, unless the default
copy constructor generated by the compiler, which does memberwise copying,
will do the job.
.pp
The class must redefine class
.c Message 's
.c  clone()
function.  Given that the copy constructor is defined, the form shown
in the example, where a new object is created with the
.c new
operator and the copy constructor, will suffice.
.pp
In addition, the user may optionally define type conversion and printing
functions if they make sense.  If a star that produces messages is connected
to a star that expects integers (or floating values, or complex values),
the appropriate type conversion function is called.  The base class,
.c Message ,
defines the virtual conversion functions
.c asInt() ,
.c asFloat() ,
and
.c asComplex()
and the printing function
.c print()
\- see the Message.h file for their exact types.  The base class conversion
functions assert a run-time error, and the default print function returns
a
.c StringList
saying
.(l
<\fItype\fR>: no print method
.)l
where
.i type
is whatever is returned by
.c dataType() .
.pp
By redefining these functions, you can make it legal to connect a star
that generates messages to a star that expects integer,
floating, or complex particles, or you can connect to a Printer or Xgraph
star (for Xgraph to work, you must define the
.c asFloat
function; for Printer to work, you must define the
.c print
function).
.H1 "Use of the Envelope class"
.IE "class Envelope"
.pp
The
.c Envelope
class serves as an envelope to hold objects of class
.c Message
or derived classes.  Once a user's Message object is placed into
an Envelope object, the Envelope takes over responsibility for
managing its memory: maintaining reference counts, deleting the
Message when it is no longer needed.
.pp
The constructor (which takes as argument a reference to
.c Message ),
copy constructor, and assignment operator for
.c Envelope
manipulate the reference counts of the
.c Message
object inside the
.c Envelope
objects.  Assignment simply copies a pointer and increments the
reference count.  Whenever the destructor for a
.c Envelope
is called, the reference count of the contents is decremented; if
it reaches zero, the contained
.c Message
object is deleted.  Because of this deletion, a
.c Message
must never be put inside a
.c Envelope
unless it was created with the
.c new
operator.  Once a
.c Message
object is put into a
.c Envelope
it should never be deleted; it will ``live'' as long as
there is at least one
.c Envelope
that contains it and will then be deleted automatically.
.pp
It is possible for an
.c Envelope
to be ``empty''.  If it is, the
.c empty()
member function will return true, and the data field will point
to a special ``dummy message'' whose type is DUMMY and that has
no data in it.
.pp
The
.c dataType()
member function of
.c Envelope
returns the datatype of the contained
.c Message
object; the functions
.c asInt() ,
.c asFloat() ,
.c asComplex() ,
and
.c print()
are also ``passed through'' in a similar way to the contained object.
.pp
Two functions are provided for convenience to make type checking simpler:
.c typeCheck
and
.c typeError .
A simple example illustrates their use:
.(c
	if (!envelope.typeCheck("IntVecData")) {
		Error::abortRun(*this,envelope.typeError("IntVecData"));
		return;
	}
.)c
.c typeCheck
calls
.c isA
on the message contents and returns the result, so an error will be
reported if the message contents are not IntVecData and are not
derived from IntVecData.  Since the above code segment is so common in
stars; a macro is included in Message.h to generate it; the macro
.(c
	TYPE_CHECK(envelope,"IntVecData");
.)c
expands to essentially the same code as above and is shorter to type.
.c typeError
generates an appropriate error message:
.pp
Expected message type '\fIarg\fR', got '\fItype\fR'
.pp
To access the data, two functions are provided:
.c getData()
and
.c writableCopy() .
The
.c getData
function
returns a pointer to the contained
.c Message -derived
object.
.i "The data pointed to by this pointer must not be modified" ,
since other
.c Envelope
objects in the program may also contain it.  If you convert its type,
always make sure that the converted type is a pointer to const
(see the programming example for UnPackInt below).  This will
ensure that the compiler will complain if you do anything illegal.
.pp
The
.c writableCopy
function also returns a pointer to the contained object, but with a difference.
If the object's reference count is one, the envelope is emptied (set to
the dummy message) and the contents are returned.  If the reference
count is more than one, a
.i clone
of the contents is made (by calling its
.c clone()
function) and that is returned; again the envelope is zeroed (to
avoid the making of additional clones later on).
.pp
In some cases, a star writer will need to keep a received
.c Message
object around between executions.  One way to accomplish this is
for the star to have a member of type
.c Envelope ,
and to use this member object to hold the message data between
executions.  Messages should always be kept in envelopes so that
the user does not have to worry about managing their memory.
.H1 "Use of the MessageParticle class"
.IE "class MessageParticle"
.pp
If a porthole is of type ``message'', then its particles are of
the class
.c MessageParticle .
A MessageParticle is simply a particle whose data field is an
Envelope, which means that it can hold a Message in the same
way that Envelope objects do.
.pp
Many of
.c Particle 's
functions are redefined by this class
to cause a run-time error; for example, it
is illegal to send an integer, floating, or complex number to the
particle with the `<<' operator.  The conversion operators (conversion
to type int, float, or Complex) return errors by default, but can
be made legal by redefining the
.c asInt ,
.c asFloat ,
or
.c asComplex
member functions for a specific message type.
.pp
The principal operations on
.c MessageParticle
objects are `<<' with an argument of type
.c Envelope ,
to load a message into the particle, and
.c getMessage(Envelope&) ,
to transfer message contents from the particle into a user-supplied
message.
.c getMessage
removes the message contents from the particle.
.(f
The reason for this
``aggressive reclamation'' policy (both here and in other places)
is to minimize the number of no-longer-needed
messages in the system and to prevent unnecessary clones from being
generated by
.c writableCopy()
by eliminating references to
.c Message
objects as soon as possible.
.)f
For cases where the destructive behavior of
.c getMessage
cannot be tolerated, an alternate interface,
.c accessMessage(Envelope&) ,
is provided.  It does not remove the message contents from the particle.
Promiscuous use of
.c accessMessage
in systems where large-sized messages may be present can cause the
amount of virtual memory occupied to grow (though all message will
be deleted eventually).
.H1 "Use of Messages in Stars"
.pp
Here are a couple of simple examples of stars that produce and
consume messages.  For more advanced samples, look in the Ptolemy
distribution for stars that produce or consume messages.
.IE "message programming example"
.(c
defstar {
	name { PackInt }
	domain { SDF }
	desc { Accept integer inputs and produce IntVecData messages.}
	defstate {
		name { length }
		type { int }
		default { 10 }
		desc { number of values per message }
	}
	input {
		name { input }
		type { int }
	}
	output {
		name { output }
		type { message }
	}
	ccinclude { "Message.h", "IntVecData.h" }
	start {
		input.setSDFParams(int(length),int(length-1));
	}
	go {
		int l = length;
		IntVecData * pd = new IntVecData(l);
		// Fill in message.  input%0 is newest, must reverse
		for (int i = 0; i < l; i++)
			pd->data[l-i-1] = int(input%i);
		Envelope pkt(*pd);
		output%0 << pkt;
	}
}
.)c
.pp
Since this is an SDF star, it must produce and consume a constant
number of tokens on each step, so the message length must be fixed
(though it is controllable with a state).  Notice that the output
porthole is declared to be of type message.  Notice also the
.c ccinclude
statement; we must include the file Message.h in all message-manipulating
stars, and we must also include the definition of the specific
message type we wish to use.
.pp
The code itself is fairly straightforward -- an
.c IntVecData
object is created with 
.c new ,
it is filled in with data, put into a
.c Envelope
envelope, and sent.  Resist the temptation to declare the
.c IntVecData
object as a local variable; it won't work.
.pp
And here's a star to do the inverse operation:
.(c
defstar {
	name { UnPackInt }
	domain { SDF }
	desc {
Accept IntVecData messages and produce integers.  The first 'length'
values from each message are produced.
	}
	defstate {
		name { length }
		type { int }
		default { 10 }
		desc { number of values output per message }
	}
	input {
		name { input }
		type { message }
	}
	output {
		name { output }
		type { int }
	}
	ccinclude { "Message.h", "IntVecData.h" }
	start {
		output.setSDFParams(int(length),int(length-1));
	}
	go {
		Envelope pkt;
		(input%0).getMessage(pkt);
		if (!pkt.typeCheck("IntVecData")) {
			Error::abortRun(*this,pkt.typeError("IntVecData"));
			return;
		}
		const IntVecData * pd = (const IntVecData *)pkt.myData();
		if (pd.length() < int(length)) {
			Error::abortRun(*this,"Received message is too short");
			return;
		}
		for (i = 0; i < int(length); i++) {
			output%(int(length)-i-1) << pd->data[i];
		}
	}
}
.pp
Because the domain is SDF, we must always produce the same number of
outputs regardless of the size of the messages.  The simple approach
taken here is to require at least a certain amount of data or else
die.
.pp
The operations here are to declare an envelope, get the data from the
particle into the envelope with
.c getMessage ,
check the type, and then access the contents.  Notice the cast operation;
this is needed because
.c myData
returns a const pointer to class
.c Message .
It is important that we converted the pointer to
.c "const IntVecData *"
and not
.c "IntVecData *"
because we have no right to modify the message through this pointer.
Most C++ compilers will not warn by default about ``casting away const''; we
recommend turning on compiler warnings when compiling code that
uses messages to avoid getting into trouble (for
.i g++ ,
say
.c \-Wcast-qual ;
for
.i cfront -derived
compilers, say
.c \+w ).
.pp
If we wished to modify the message and then send the result as an output,
we would call
.c writableCopy
instead of
.c myData ,
modify the object, then send it on its way as in the previous star.
.)c
