| The
Mash programming model is based on an architecture where multimedia
data is generated by one or more source objects, piped through
one or more filter objects, and eventually, consumed by one
or more sink objects. A source, for example, might be a video
capture device, a filter might be a color space converter,
a compressor, a packetizer, or the like, while a sink might
be a network transmission protocol. We call this scheme the
''VuSystem
Architecture'' because it was first cleanly articulated by
the authors of that system.
The
VuSystem toolkit was built on top of an object-oriented
Tcl extension that later evolved into Object
Tcl (OTcl). C++ classes implement multimedia objects
that produce, consume, or filter real-time media streams
and a Tcl shadow object mirrors each C++ object. Methods
invoked on the Tcl shadow object are dispatched to the C++
object. Objects are created, composed, and configured from
Tcl and the interconnections between objects can be rearranged
while running. In addition to its elegant, object-oriented
architecture, the VuSystem is built around a consistent
and uniform fine-grained object model with inheritance.
Our
approach elaborates the VuSystem Architecture by bridging
the gap between C++ and OTcl. We view an object as an abstract
entity whose methods can be implemented on either side of
the OTcl/C++ boundary. C++ code can invoke methods defined
in OTcl, while OTcl code can invoke methods defined in C++.
The common idiom whereby Tcl callbacks are installed in
C data structures is replaced by a simple, clean object
method API. A callback from C++ into Tcl is simply a self-referential
method invocation (where the target method happens to be
implemented in Tcl).
In
our OTcl/C++ framework, called "TclCL'',
(for Tcl with CLasses), fine-grained objects are implemented
in C++ and complex abstractions are built by coalescing
these fine-grained objects into ``macro-objects'' using
scripts to glue together components. To support a clean
and manageable programming model, the details of a macro-object
can be encapsulated in an OTcl object with a uniform method
API. In turn, macro-objects can be arranged into larger
and richer macro-objects thereby giving the toolkit programmer
multiple levels of abstraction and allowing her to
choose the appropriate level of detail for a given implementation.
More
details on the overall mash system architecture and the
design rationale for the underlying split-object programming
model can be found in our NOSSDAV
'97 paper, also available from our publications
page.
The
Scripting API
The
Mash scripting API is defined by the ensemble of APIs to
a large collection of split OTcl/C++ objects as well as
a number of pure OTcl objects (that internally contain split
OTcl/C++ objects). This section of our documentation describes
the scripting API to these objects. We classify the objects
into two categories:
- Agents
that are often active, implement a rich set of functions,
and provide high-level building blocks for programming
multimedia apps; and
- Core
Objects
that tend to be passive and implement fairly low-level
functionality.
The
links above point to a detailed index of the APIs that define
the objects. In the rest of this page, we describe the overall
structure of the mash interpreter and scripting model.
Tcl
Resources
If you are new to Tcl and would like to learn it to help
you program mash scripts, the best resource is probably
John
Ousterhout's original book, Tcl
and the Tk Toolkit. Several
other good books have been published as well. The Tcl/Tk
project is led by SunScript.
One
problem with Tcl is that it was not originally designed
to serve as the primary language in very large software
systems. Because of this, the language does not have features
that support large projects and modularization. Since much
of mash is implemented in Tcl, we needed a solution to the
software management problem and thus adopted one of the
object-oriented extensions to Tcl. We chose OTcl primarily
because it requires no change to the Tcl core.
The
Basics
In addition to the object APIs defined above, you will want
to know the basic operational framework of the mash shell.
You can think of mash simply as a extended Tcl interpreter,
just like ''wish'' is a Tcl interepreter with the Tk extension
for building graphical user-interfaces. Mash extends Tcl/Tk
with an TclCL programming model and the set of multimedia
networking objects defined above.
As
with wish, you can run mash interactively. To do so, simply
run mash without any arguments:
% mash
You
will then get a new prompt at which you will be able to
type commands in the Mash shell.
Object
Manipulation
Before you can invoke methods on an object according to
the APIs defined above, you must first create the object.
As with C++, a new object instance is created with the new
command. The syntax is
% new < class > < args >
or
where class is the name of the OTcl/C++ class that
defines the new object and args is an arbitrary number
of arguments that are passed to the object constructor (i.e.,
the OTcl init method). The new command actually returns
the name of a new Tcl procedure, which can then be called
to invoke methods. That is, the creation of objects and
their method invocations are naturally expressed as follows:
% set object [new < class >]
% $object invoke1
% $object invoke2 arg1 arg2
If
the object is a split Tcl/C++ object, the TclCL library
automatically creates the C++ shadow object instance and
binds it to the OTcl object instance.
Our
convention for constructor arguments follows the OTcl convention,
where a set of dashed arguments are mapped into a sequence
of method calls. For example, if the arguments string is
"-m1 a -m2 b c", then the method calls "m1
a" and "m2 b c" are invoked on the object
upon its creation. That is, the following code
% set object [new Foo -m1 a -m2 b c]
is
equivalent to
% set object [new Foo]
% $object m1 a
% $object m2 b c
Although
this convention for object constructor argument processing
is our currently accepted practice, quite a bit of old code
uses an alternative practice where the arguments are actually
caught and interpreted by one or more of the mash object
constructors. Instead, we recommend that all the arguments
be passed up to the default OTcl constructor (via "$self
next $args" called from in the subclass' init method).
The default constructor handles the interpretation and the
dispatching of the dashed arguments. Another common practice
that we now discourage is the exposure of instance variables.
Instead, all instance variables should be queried and modified
through a procedural interface.
As
with other object-oriented programming languages, objects
can be destroyed, in this case, with the delete command.
The syntax is simply
% delete < object >
where
object is the name of the object to be deleted. For
example, to delete the Foo object created above, you would
simply say
% delete $object
When
the object is deleted the OTcl descructor method or methods
are invoked as well as the C++ destructor in the case of
OTcl/C++ split objects. The C++ destructor is called after
all of the OTcl destructors have been called.
An Example
To illustrate object creation and manipulation, let's walk
through the process of creating a simple IP network object.
You create the core object as follows:
% set net [new Network/IP]
As
described above, the new command creates and returns
the name of a command which can then be invoked to access
the internals of the object. Each object has its own API,
as documented above, To set the address, port and ttl (time-to-live)
of the network object to 224.41.53.124, 23562, and 31 respectively,
you would invoke:
% $net addr 224.2.53.124
% $net port 23562
% $net ttl 31
Equvilantly,
the object could have been created as follows:
% set net [new Network/IP -addr 224.2.53.124 -port 23562 -ttl 31]
You
could also request information from the object. For instance,
if you'd like to know what the address is of the network
object, you would type:
% set a [$net addr]
Then
the variable a then contains the string "224.2.53.124".
Assembling the Scripts
One way to build a Mash tool is to write a monolithic script
that is stored in a single file, which is then executed
by the Mash interpreter. But this leads to a large and difficult
to manage script and further precludes easy sharing of scripting
code across tools. An alternative approach, which we've
adopted, is to modularized the script into packages that
can be mixed and matched with tool-dependent glue.
The
question then is how do we manage the packages? One approach
is to use the Tcl package concept, where modules with well-known
names are loaded on demand from an installed repository
(e.g., from /usr/local/lib/tcl). But with this model, the
user is burdened with installing and maintaining auxiliary
packages, and evolving versions can create subtle failures.
Even with explicit version number checks, the user is forced
to re-install packages, and potentially maintain multiple
versions simultaneously, on an ad hoc basis. Instead, our
approach is to simply string together packages using the
Tcl "source" command to load each module explicitly
from a master "main.tcl''. When a stable version of
the tool is ready, we simply run a filter over main.tcl
to collapse all of constituent source files into a monolithic
script (which is never directly modified by the programmer).
Then, we can simply distribute the mash interpreter and
the each individual tool script and they will run all by
themselves.
Of
course, this is just our convention. You can easily diverge
from this practice when you develop your own scripts since
mash is a complete superset of Tcl.
All
of the scripts for our tools reside in the tcl
directory under the subdirectory named after the tool. There
is a common subdirectory, and a few other utility
directories, which contain Tcl scripts that are are shared
amond the tools.
In
each tool subdirectory there exists a file "main.tcl''.
This file contains all the Tcl "source'' commands,
which include the various Tcl files that comprise the tool's
script. Note that the pathnames are relative to the top-level
directory rather than the current one. Our Makefile includes
rules for building each individual tool. To build vic,
for example, simply run:
% make vic
This
command runs script in "tcl-expand.tcl'' on the file
tcl/vic/main.tcl. This script recursively expands
all the "source'' commands in that file to create a
single, monolithic tcl script. Finally, the header file
head.tcl is prepended to this output and the resulting
script is the vic binary. Under Unix, the resulting
script can be invoked directly from the command line due
to a subtle trick borrowed from the Tcl distribution. At
the top of each tool script, we include the following lines:
#!/bin/sh
# the exec restarts using tclsh which in turn ignores
# the command because of this backslash: \
exec mash "$0" -name TOOLNAME "$@"
The
comment above explains the trick: The script is first invoked
using the sh shell which then restarts the script
using the mash interpreter. Tcl considers any line
ending with a backslash to continue on the next line, thus
the Tcl shell associates the exec command with the
comment on the previous line, and proceeds to execute the
body of the Tcl script. Finally, note that the Makefile
rules substitute the actual tool's name with the string
TOOLNAME as part of the script building process. If the
``-name'' argument is omitted, the mash interpreter still
runs but the Tk option data base doesn't work and a number
of other problems surface, often causing subtle and/or cryptic
error messages.
|