ANYTYPE
work with message particles without change.
Message
class is the base class from which all other message data types are derived. A user who wishes to define an application-specific message type derives a new class from Message
.
Envelope
class contains a pointer to an derived from Message
. When an Envelope
objects is copied or duplicated, the new envelope simply sets its own pointer to the pointer contained in the original. Several envelopes can thus reference the same Message
object. Each Message
object contains a reference count, which tracks how many Envelope
objects reference it; when the last reference is removed, the Message
is deleted.
MessageParticle
class is a type of Particle
(like IntParticle
, FloatParticle
, etc.); it contains a Envelope
. Ports of type "message" transmit and receive objects of this type.
Particle
contains two member functions for message support: getMessage
, to receive a message, and the <<
operator with an 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 MessageParticle
class with functions that perform the expected operation.
Message
. Certain virtual functions defined in that class must be overridden; others may optionally be overridden. Here is an example of a user-defined message type:
The class must redefine the
dataType
method from class Message
. This function returns a string identifying the message type. This string should be identical to the name of the class. In addition, the isA
method must be defined. The isA
method responds with TRUE
(1) if given the name of the class or of any base class; 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. Because of the regular structure of
isA
function bodies, macros are provided to generate them. The ISA_INLINE
macro expands to an inline definition of the function; for example,
isA
to generate exactly the same code. Alternatively, to put the function body in a .cc
file, one can write
.cc
file (or wherever the methods are defined). The class must define a copy constructor, unless the default copy constructor generated by the compiler, which does memberwise copying, will do the job.
The class must redefine the
clone
method of class Message
. Given that the copy constructor is defined, the form shown in the example, where a new object is created with the new
operator and the copy constructor, will suffice. 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,
Message
, defines the virtual conversion functions asInt()
, asFloat()
, and asComplex()
and the printing method print()
- see the file $PTOLEMY/src/kernel/Message.h
for their exact types. The base class conversion functions assert a run-time error, and the default print function returns a StringList
saying<type>: no print method
where type is whatever is returned by
dataType()
. By redefining these methods, 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 XMgraph
star (for XMgraph
to work, you must define the asFloat
function; for Printer
to work, you must define the print
method).
Envelope
class references objects of class Message
or derived classes. Once a message object is placed into an envelope object, the envelope takes over responsibility for managing its memory: maintaining reference counts, and deleting the message when it is no longer needed. The constructor (which takes as its argument a reference to a
Message
), copy constructor, assignment operator, and destructor of Envelope
manipulate the reference counts of the references Message
object. Assignment simply copies a pointer and increments the reference count. When the destructor of a Envelope
is called, the reference count of the Message
object is decremented; if it becomes zero, the Message
object is deleted. Because of this deletion, a Message
must never be put inside a Envelope
unless it was created with the new
operator. Once a Message
object is put into an Envelope
it must never be explicitly deleted; it will "live" as long as there is at least one Envelope
that contains it, and it will then be deleted automatically.It is possible for an
Envelope
to be "empty". If it is, the empty
method will return TRUE
, and the data field will point to a special "dummy message" with type DUMMY
that has no data in it.The
dataType
method of Envelope
returns the datatype of the contained Message
object; the methods asInt()
, asFloat()
, asComplex()
, and print()
are also "passed through" in a similar way to the contained object. Two
Envelope
methods are provided for convenience to make type checking simpler: typeCheck
and typeError
. A simple example illustrates their use:
typeCheck
calls 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
typeError
method generates an appropriate error message:
myData()
and writableCopy()
. The myData
function returns a pointer to the contained Message
-derived object. The data pointed to by this pointer must not be modified, since other 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 ensures that the compiler will complain if you do anything illegal. The
writableCopy
function also returns a pointer to the contained object, but with a difference. If the reference count is one, the envelope is emptied (set to the dummy message) and the contents are returned. If the reference count is greater than one, a clone of the contents is made (by calling its clone()
function) and returned; again the envelope is zeroed (to prevent the making of additional clones later on). In some cases, a star writer will need to keep a received
Message
object around between executions. The best way to do this is to have the star contain a member of type 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.
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. Many methods of the
Particle
class are redefined in the MessageParticle
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
, double
, or Complex
) return errors by default, but can be made legal by redefining the asInt
, asFloat
, or asComplex
methods for a specific message type.The principal operations on
MessageParticle
objects are <<
with an argument of type Envelope
, to load a message into the particle, and getMessage(Envelope&)
, to transfer message contents from the particle into a user-supplied message. The getMessage
method removes the message contents from the particle1. In cases where the destructive behavior of getMessage
cannot be tolerated, an alternative interface, accessMessage(Envelope&)
, is provided. It does not remove the message contents from the particle. Promiscuous use of 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).
4.3.4 Use of messages in stars
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. The image processing classes and stars, which are briefly described below in
"Image particles" on page 4-40, provide a particularly rich set of examples. The matrix classes described on
page 4-21 are also good examples. The matrix classes are recognized in the Ptolemy kernel, and supported by pigi
and ptlang
.
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;
}
}setSDFParams
method. Notice that the output porthole is declared to be of type message
. Notice also the 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.
The code itself is fairly straightforward-an
IntVecData
object is created with new
, is filled in with data, and is put into an Envelope
and sent. Resist the temptation to declare the IntVecData
object as a local variable: it will not work. It must reside on the heap. Here is a star to do the inverse operation:
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];
}
}
} getMessage
, check the type, and then access the contents. Notice the cast operation; this is needed because myData
returns a const pointer to class Message
. It is important that we converted the pointer to const IntVecData *
and not IntVecData*
because we have no right to modify the message through this pointer. Many 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 g++, say -Wcast-qual
; for cfront-derived compilers, say +w
). writableCopy
instead of myData
, modify the object, then send it on its way as in the previous star.
Copyright © 1990-1997, University of California. All rights reserved.