The DejaVu project aims may be characterized
by a number of buzz-words, familiar no doubt to all those
who have had the privilege to write project proposals themselves:
The DejaVu framework is intended to be
open (in the sense of extendible by a variety
of other software),
heterogeneous
(that is allowing for heterogeneous components,
possibly developed within different software paradigms),
distributed
(which means operating on both LAN and WAN computer
configurations),
object-oriented
(which is intended to characterize our approach at
software development, including modeling and programming),
and intelligent
(which may, lacking a better definition, be characterized
as allowing for a declarative approach to the knowledge-level
aspects of the application).
Wide in scope, and certainly ambitious,
but on the other hand (as argued in
The DejaVu project has, in a period of about three years, resulted in a collection of software components that allow for building quite complex systems in a relatively easy way. To substantiate this claim, we may remark that our software has succesfully been used over the last years in CS2 project assignments for Software Engineering, ranging over the realization of routeplanners, product/components control systems, business simulation models and (simple) geographical information systems. It has also been used, at a more advanced level, in OOP assignments, including the development of gambling machines (mimicking existing 'one armed' bandits), musical score editors (with soundsynthesis facilities) and interactive games (such as those you may find in a video game hall). Ease of use, that is a natural and simple class interface, is enforced (to the extent possible) as an explicit design goal for all components that constitute the DejaVu framework, now and in the future.
In this paper we will discuss the organization and modeling principles underlying the hush library and its extensions. The acronym hush stands for hyper utility shell and reflects our intention to provide the means for incorporating 'hypertechnology' in a variety of applications. The acronym hush is also intended to reflect our wish to allow each program developed with hush to act as a shell that interprets scripts written in a general purpose script language that is extended with functionality defined by the application itself.
A central theme in the DejaVu project
has been to support a multi-lingual
software development platform,
allowing in particular for a close coupling
between C++, the distributed logic programming
language DLP
The extensions of hush include support for discrete event simulation (sms), facilities for realtime soundsynthesis and music (hymne), support for digital video (xanim) and facilities to connect to the World Wide Web (web), also allowing to extend the web browsing facilities with client-side computing.
With respect to the area of hypermedia systems, we may remark that our results compete with developments undertaken by the big players in the field (such as the Java project by Sun Systems), yet our approach is fundamentally different to the extent that we are not interested in one particular application but rather in an approach to software development that allows for employing a variety of components, in a multiparadigm fashion.
Application development generally encompasses a variety of programming tasks, including system-level software development (for example for networking or multimedia functionality), programming the user interface (including the definition of screen layout and the responsivity of the interface widgets to user actions), and the definition of (high-level) application-specific functionality. Each of these kinds of tasks may require a different approach and possibly a different application programming language. For example, the development of the user interface is often more conveniently done using a scripting language, to avoid the waiting times involved in compiling and linking. Similarly, defining knowledge-level application-specific functionality may benefit from the use of a declarative or logic programming language.
In our project, we decided from the start to support a multiparadigm approach to software development and consequently we had to define the mutual interaction between the various language paradigms, as for example the interaction between C++ and a scripting language, such as Tcl. Current scripting languages, including Python and Tcl, provide facilities for being embedded in C and C++, but extending these languages with functionality defined in C or C++ and employing the language from within C/C++ is rather cumbersome. What we need is a simple and generic mechanism to associate script commands with actions defined by the application and (inversely) a generic way to employ scripting facilities in the application.
interface kit { void eval(char* cmd); char* result(); void action(char* name, handler* h); };The function eval is used for evaluating (script) commands, and result may be used to communicate data back. The limitation of this approach, obviously, is that it is purely string based. In practice, however, this proves to be flexible and sufficiently powerful. The action function may be used to define new commands and associate it with functionality defined in handler objects, that will be introduced soforth.
interface handler : client { int dispatch( event* e );The dispatch function is called by the underlying system. (In effect, a standard callback function is used with the handler object as client data, which explains why the handler object inherits from the class client.) The dispatch function receives a pointer to a sytsem-defined event which encodes the information relevant for that particular callback. In its turn dispatch calls the application operator. Classes derived from handler need only redefine the operator() function. Information needed when activating a handler object must be provided when creating the object, or obtained from the event for which the handler is activated.to dispatch event
int operator()(); };
The use of handler objects is closely connected to the paradigm of event-driven computation. An event, conceptually speaking is an entity that is characterized by two significant moments, the moment of its creation and the moment of its activation, its occurrence Naturally, an event may be activated multiple times and even record a history of its activation, but the basic principle underlying the use of events is that all the information that is needed is stored at creation time and, subsequently, activation may proceed blindly.
interface widget : handler { ... void bind( handler* h ); void bind( char* user, handler* h ); ... };The first member function bind may be used for installing a handler for the default bindings of the widget, whereas the second bind function is to be used for overriding any specific bindings. (Notice that the class widget is derived from handler class to allow the widget to be its own handler. In this way inheritance or the delegation to a separate handler object may be used to define the functionality of a widget.)
In addition to the widget class, the hush library also provides the class item, representing graphical items. Graphical items, however, are to be placed within a canvas widget, and may be tagged to allow for the groupwise manipulation of a collection of items, as for example moving them in response to dragging the mouse pointer.
interface event : handler { operator()(); };Actual event classes are derived from the generic class event, and a scheduler is provided to activate events at the appropriate time. (In effect, we provide a fully functional discrete event simulation library, including facilities for generating random distributions and analysing the outcome of experiments.) Note that there is an important difference between user-defined events and system-defined events. System-defined events are delivered to the user by activating a handler callback. In contrast, user-defined events are (directly) activated by a scheduler. They contain, so to speak, their own handler.
The hush library contains the classes kit, event, handler, widget and item. In addition to these, hush provides the class session. Application programmers are minimally required to derive a class, say application, from session and redefine the function main. When the program is to be used as a script interpreter, the function prelude may be redefined to allow for initializing external packages or other components of the DejaVu framework.
As a comment, the hush library originated as a collection of classes giving the C++ programmer convenient access to the Tcl/Tk window programming toolkit. Its design has been inspired by the Interviews library. However, although less powerful wit respect to device-independent graphics, its class structure is less complex and, as experience shows, much easier to employ by undergraduate students. In addition, it offers a rich collection of user interface widgets.
button* b = new button(".b"); handler* h = new help_handler(b); b->text("Help"); b->bind(h);We assume that help_handler does something meaningful, like displaying a hypertext help file.
Alternatively, a widget may declare itself to be its own handler. In that case we derive a new class from button and define the functionality (previously defined externally in the help_handler) in the new class itself, as illustrated below:
class help_button : public button { public: help_button( char* path ) : button( path ) { this->text("help"); this->bind(this); } int operator()(); // does what help_handler did };Dependent on circumstances, either one of the approaches may be the most convenient. Allowing for delegating to an external handler helps to avoid cluttering the class name space. On the other hand, employing inheritance may result in a significant reuse of initialization code.
The DejaVu framework supports both MIDI and the sound synthesis facilities offered by Csound [XX]. When creating an instance of the icsound class a process is created which is capable of receiving sound events, as defined by the Csound numerical score language, and converting these events to data for the audio device. (The icsound class inherits from a wrapper class providing the functionality for spawning of a new process that communicates with the original process via pipes.) Care is taken that the audio device is connected only once. However, multiple instances of the icsound class may exist.
In addition to the icsound class, which accepts only low level sound events, instances of player may be used which accept musical events defined in the high level music description language Scot, that comes with Csound. The player class is also an event. This allows instances of player to be embedded in scripts, as well as being activated as user-defined events.
Our video extension is based on the public domain xanim package and includes support for MPEG, AVI and Quicktime.
<h1>Digital video for HushWWW</h1> <hush tcl=source [urlfile www.cs.vu.nl/~se/mpeg.tcl> You need the hush browser to play this MPEG demo </hush>The actual script code itself may be obtained from some site by using the special command urlfile. The text inbetween the hush begin and end tag will be displayed by 'ordinary' browsers, such as Mosaic and Netscape. Evaluating the script code may, similar as for the hypertext widget, result in packing additional widgets to the browser (widget), as for example a video widget showing an mpeg movie.
Figure Dining shows the corresponding HTML markup for the first page. We use two <app> tags to include inline applets written in Tcl/Tk. Note that text inside the open and close tag will be ignored by our browser and may be used to display warnings if the page is browsed by non-hush browsers as Mosaic or Netscape.
<title>The Dining Philosophers<title> <h1>The Dining Philosophers<h1> <h2>The problem<h2> Five philosophers sit around a table, with five chopsticks in between. ... We are interested in <a href="results.html">the percentage of the time<a> a philosopher actually thinks. <app class="sim-setup"> Error: Dining table deleted: use our hush browser! </app> <hr> Press this button: <app class="sim-button"> Error: Run button deleted: use our hush browser! </app>
The Tcl/Tk applets used in the example above are in fact only responsible for the graphical interface, the simulation itself is modeled in C++, by refining the entity class (modeling a philosopher) and the resource class (modeling a chopstick) of the simulation library.
The simulation is initialized by scheduling five philosopher entity objects in a "waiting" phase at time t=0.0. The "simulation init" command will be executed by first applet. The simulation can be started by executing the "simulation run" command, which is bound on a push button by the sim-button applet.
When a philosopher is due to be activated, the scheduler will call its application operator which will perform an explicit dispatch on the current phase:
int philosopher::operator()() { switch (phase()){ case EATING : eat(); return OK; case THINKING : think(); return OK; case WAITING : await(); return OK; default: return FALSE; // undefined phase } }
Suppose the philosopher is still in a waiting phase. The await() member will be called and the philosopher will see whether his chopsticks are available. If so, she will acquire them and starts eating. If not, the philosopher will remain in a waiting state, but is put on the scheduler's list of conditional events.
void philosopher::await() { if (chopsticks_available()) { acquirechopsticks(); phase(EATING); } else if (!conditional()) { waitonchopsticks(); sim->hold(this); } }
However, if the philosopher is invoked while she is in a eating phase, she will continue with eating for some (exponentially distributed) random time interval after which she will move to a "thinking" phase.
void philosopher::eat() { double t = gen->exponential(EAT_TIME); phase(THINKING); sim->wait(t); }
In addition, we extended the simulation package with a soft-real time option, that allows us to schedule clock-synchronized events. For example, we implemented a simple slide show, by the repetitive scheduling of HTML pages with a fixed time interval.
However, general hypermedia applications require support for far more complex synchronization relations and temporal dependencies between the components of a hypermedia document. Such high-level temporal alignment cannot be (easily) expressed using the basic simulation primitives, nor can the corresponding documents be expressed by (text oriented) markup languages as HTML.
Therefor, we are currently developing components supporting more complex scheduling primitives. One of our goals is to add a (subset of) the event scheduling mechanisms of HyTime [REFXXX] to our new, SGML-based Web widget.
To illustrate our approach, look at the definition of the class kit:
class kit { public: kit( char* kind = "tcl" ); virtual int eval(char* cmd) { return _kit?_kit->eval(cmd):0; } ... protected: kit( kit* ); kit* _kit; };The constructor of kit indicates as a default a Tcl interpreter which is encapsulated by the class:
class tcl_kit : public kit { public: tcl_kit() : kit(0) { ... } ... };The constructor of kit assigns the tcl_kit instance to its _kit variable. In contrast, the tcl_kit initializes its (parent) _kit variable to zero. For the kit instance created by the user, calls are delegated to the tcl_kit instance. However, the user may still inherit from kit and selectively override its functions.
class event : public handler { public: event( event* x ); // expects x event event( ); // creates sim event // X event interface virtual int type() { return _event?_event->type():0; } virtual int x() { return _event?_event->x():0; } virtual int y() { return _event?_event->y():0; } // simulation interface virtual void timestamp(); virtual double timespent() { return _event?_event->timespent():0; } protected: event* -event; };The disadvantage of this approach, clearly, is that we need to offer a fat interface (which could in principle be factored using multiple inheritance, at the expense of additional classes).
The (public) constructor event::event(event*) is used to encapsulate events generated by the (X) window system. An advantage of this approach is instance may be accessed via both pointer and object references, without sacrificing dynamic binding. (To our taste, it is more natural to write e.x() than e->x() when accessing the x coordinate of the mouse pointer stored in an event instance.)
For events that are to be scheduled under programmer's control, a class must be specified inheriting from event (overriding the operator() activation function). In that case a simulation event is created containing the additional data structures needed for bookkeeping and scheduling.
class drawtool : public canvas { public: drawtool() : canvas() { } // to declare drawtool drawtool(char* path) : canvas() { initialize( path ); // constructs the components redirect( _canvas ); // delegate to _canvas component } int operator()() { // interface to Tcl if ( !strcmp("drawtool",argv[1]) return create(argc, argv); else return self()->eval( flatten(argc, argv) ); } private: void initialize( char* path ); int create( int argc, char* argv[] ); canvas* _canvas; };For completeness, the first constructor is only used to define the script command drawtool as in
tk->action("drawtool", new drawtool() );which may occur in main or prelude of the application class.
The call redirect(_canvas) in the regular constructor results in delegating both member function calls and script commands addressed at the drawtool widget to the canvas widget pointed to by _canvas, which is assumed to be created in initialize. The _canvas widget acts, so to speak, as the primary component of drawtool in that it receives all bindings and (configuration) commands. Actual redirection occurs by calling self(). The definition of widget::self() is simply:
widget* self() { return _self?_self->self():this; }In other words, it checks the private _self variable of the widget, which may be given a value by widget::_redirect, until _self is undefined (zero), in which case the this pointer is returned. In this way primary widget components can be embedded at arbitrary depth within a composite widget, without any need to adapt the interface of the embedding (composite) widget.
Those well-versed in design patterns will recognize the Decorator
patterns (as applied in the Interviews MonoGlyph class
class handler { ... } class widget : public handler { public: ... void handler( class handler* h ); // to install a default handler ... };Consequently, we had to rename the widget::handler function into widget::installhandler. However, we decided to overload the widget::bind function to allow for installing a default handler.
Much time was spent in finding ways to satisfy both the GNU compiler and the AT&T compiler (and later the Sun CC 4.0 compiler). A situation which is unfortunately still characteristic for working with C++. Much time was also spent in adapting to differences in include files and linking conventions when moving to Solaris. We deal with different platform and compilers now by providing configuration set up files, trying to avoid as much as possible to clutter our code with conditional compilation macro instructions.
Another question that may arise is how the notions of handlers and events are related to a hypermedia document model. The answer must be, only in a very loose fashion, that is to the extent that we need (and support) embeddable anchors and (in the future) a linkbase facility to store and retrieve associative links.
For developing actual hypermedia systems we need to provide support in the form of actual widgets (including a hypertext widget) and, equally important, guidelines and patterns for developing applications. In addition, we have to provide adequate documentation support.