Table of Contents

A Guide for the Bewildered

The ABLE UI: A Guide for the Bewildered

A Guide for the Bewildered

1 Introduction

The ABLE UI toolkit is an extension of the Tcl/Tk toolkit that provides a "box and line" editor for manipulating design information stored in an architectural database. This toolkit is used to create the "design" user interface, or design UI. We use this term to mean the interface the user sees when creating and editing architectural designs.

The UI toolkit is structured as a collection of Fcl classes along with many utility modules for common operations. The class hierarchy parallel's the class hierarchy for the database classes, with each "visual" class specifying how the corresponding database class should look when shown on the screen. The utility modules include a simple form builder for manipulating the attributes of database objects, some high level hooks into the low level event system, and a collection of routines to handle drawing graph-structured designs on a Tk canvas.

This document serves a as a high level road map to the code.The documentation in the code gives more information about expected parameter types on procedures and other issues. Unfortunately, some of the code is not documented in any way.

Note: All of this a first generation prototype at best, so the design is neither clean nor orthogonal. It reflects the designer's inexperience in such matters and most of these bugs should be ironed out in the next generation.

2 The Visual Classes

The visual class tree, rooted at the class CVisObject defines the range of objects that the user interface knows how to manipulate. Here is a list of the classes we currently use and what they are for:

Root class for visual objects. Provides the basic interfaces for the rest of the world.
A visual component. May contain visual ports and aggregates
A visualized connector. May contain visual roles and aggregates
A visual port.
A visual role.
Visualization of a single collection of objects with an explicit abstraction boundary
Binding points at the abstraction boundary of an aggregate.
A binding between ports.
A binding between roles.
Utility class used in the component workshops. It is used to draw the shrunken pictures of components.
Utility class for the connector workshop. Used to draw the shrunken connectors in the workshop.
Roughly speaking, each visual class is responsible for all of the user-level operations that are available from the design environment. Each one explicitly manages one or more design objects. In the current design, design objects are oblivious to their visualizations. In addition, at this point, one cannot take a design that was created by anything other than the UI and expect the UI to understand it. The UI can only correctly access databases that it itself creates.

Some method names start with the prefix __prim. This is generally used to indicate that the method does the same high level operation as one without the prefix, but does so without any high level checking. For example, delPort deletes a port with lots of checks to make sure its OK, __primDelPort does the same thing, but without the checks. This is handy for performance reasons, and because sometimes you really don't want the checks done.

2.1 The Common Method Suite

Each visual class defines a set of methods that are common to all visual objects: Further down in the document, if a method isn't mentioned, it means either that the method is not overridden in the new class or the method is overridden but implemented with largely the same semantics as specified here.

Data access methods for data slots in each object. The CVisObject class defines the following slots: sRealObject, sXpos, sYpos, sFgColor, sBgColor, sVis, sScaleX, sScaleY. Most of these hold the obvious thing. The sRealObject slot holds design object that this visual object represents. The sVis slot holds a high level description of how to draw the object.
render c x y 
Draw the object on the canvas c at the position (x,y). Also render any visual objects that are logically contained within the object (i.e. ports on a component).
unRender c
Remove the object from the canvas c.
bindMouse c
Setup mouse bindings on the object in the given canvas.
delete c
Like unrender, but delete the object as well.
moveTo c x y 
Move the object to a new place.
Associated or disassociate the object with a design object.
Make a duplicate of the object.
Note: In the current system, default implementation of this interface isn't well thought out and there are many hidden dependencies between the visual database and the design database. They become a real nuisance in the higher level classes, especially if the instance of the class contains other objects.
I had in mind implementing something like the SmallTalk model/view/controller paradigm in these classes, but it clearly hasn't worked out that way. As we will see, the visual objects must be very careful to stay consistent with the design objects that are logically the `model' here. In fact, each visual class has an interface that is nearly identical to the design object class that it represents.

Maybe this means that the visual classes ought to be subclasses of the model objects?

2.2 CVisComponent

The visual component class provides visualizations of components. In addition to the normal method suite, visual components have methods for manipulating ports and representations that a component may contain. The full method suite on visual components is listed below:

Data access: getPorts {},__setPorts, getWidth {}, __setWidth { new }, 
getHeight {}, __setHeight { new }, getPlace {}, __setPlace {new}, getReps 
{}, __setReps {new}, __setScaleX {new}, __setScaleY {new}, getScaleX {}, 
getScaleY {}
__primAddPort { port } 
Add a port without checks.
addPort { port } 
Add a port
__primDelPort { port } 
Delete a port without checks.
delPort { port } 
Delete a port. Also updates bindings in aggregate reps.
placePort { c className visName tag} 
Place a new port on the body of the component. Uses the sPlace slot to figure out which side to try first. This is just an entry point.
doPlacePort { c newVPort tag } 
Low level helper routine for placePort. This routine also makes sure that all ports have corresponding bindings in any aggregates reps contained by the component.
__primAddRep { r } 
Add a rep without checks.
addRep {r} 
Add a rep.
delRep {r} 
Delete a rep.
renderBody { c x y {tag ""}} 
Draw just the box of the component. The render method calls this and does the rest of the work.
When a component is moved, unRendered or deleted, all of the objects it contains are also moved, unRendered or deleted.

Note: The implementation of this class points out some of the awful cross-dependencies that exist between the visual classes and the database classes. First, each method in the DB class CFamComponent has a brother here in CVisComponent. This is redundant and tedious. Second, there are also "back-door" methods that bypass the normal DB checks. This is needed, for example, to be able to delete all the ports of a component before deleting the component itself even when a style might prohibit such an action. This is a dangerous breach of abstraction boundaries that never-the-less seems necessary.
What we really need is two sets of interfaces, one for tools that want checks and one for tools with more knowledge that don't want checks.

Another dependency is the processing needed to keep aggregate reps consistent with the port configuration of the component that owns them.

Finally, the implementations of these methods have lots of hacks in them that should live in higher level UI code. These are mostly related to processing of cut and paste operations. There needs to be a better separation between user editing operations and low level operations on objects.

2.3 CVisConnector

This class is used for visualizing connectors. The following new methods are for data access:

getRoles {} 
__setRoles { new } 
getReps {} 
__setReps { new } 
getPlace {} 
 __setPlace {new} 
getWidth {} 
__setWidth { newWidth } 
getHeight {} 
__setHeight { newHeight } 
__primAddRole { role } 
Add a role without checks.
addRole { role } 
Add a new role.
__primDelRole { role } 
Delete a role without checks.
delRole { role } 
Delete a role.
placeRole { c className visName tag { absX -1 } { absY -1 }} 
Put a new role down on the canvas, and do other processing.
doPlaceRole { c newVRole tag { absX -1 } { absY -1 }} 
Low level helper for placeRole. Probably shouldn't be public.
showName { c } 
Show your name in the canvas or message box.
addRep {r} 
Add a rep.
delRep {r} 
Remove a rep.

Note: This class has all the same problems as CVisComponent.

2.4 CVisPort

This class visualizes a port. It has the following new data access methods:

getRoles {} 
__setRoles { new } 
getSideOf {} 
__setSideOf { newside } 
setSideOf { newside } 
getBindings {} 
__setBindings { newbindings } 
In addition, it has two new methods.

stickToComponent { c x y } 
Make sure the port sticks to its owner when it moves.
showName { c } 
Show the name of the port in the canvas or message box.

2.5 CVisRole

This class visualizes a role. It is much like CVisPort, except that moving a role is more complex than moving a port. In particular, when a role moves, it must check to see if it lands on anything that it should attach itself to, such as a binding or a port. One new slot, sLineIds keeps track of the canvas ID of the line connecting the role to its connector. The following are new data access methods:

getLineIds {} 
__setLineIds { new } 
getSideOf {} 
__setSideOf { newside } 
getPorts {} 
__setPorts { new } 
__setBindings { new } 
The following are new other methods:

getStickies {} 
See what this role is stuck (attached or bound) to.
stickTo { c thing } 
Attach this role to the object thing.
unStick { c thing } 
Detach this role from the object thing.
primMoveTo { c x y } 
Move the role without extra processing to check for new attachments.

drawLine { c } 
Draw the line connecting the role handle to the body of its connector.

2.6 CVis[Port/Role]Binding

This class is a placeholder for possible bindings in an aggregate representation. Since binding objects aren't really visible outside of an aggregate, we play some odd games here. In particular, normal DB bindings don't allow one side of the binding to "float", while their visual counterpart has to allow this.

Port and role bindings work pretty much the same way except that port bindings have a role like handle for attaching to ports with while role bindings are just boxes to drop a port on.

The sInterface[X/Y] slots hold the position of the box part of the binding. In a port binding, the position of the binding is determined by the handle. In a role binding, the position of the binding is the same as the position of the interface.

Data access:

getInterfaceX {}

getInterfaceY {}

getInterfaceVis {}

setInterfaceX { newX }

setInterfaceY { newY }

setInterfaceVis { newVis }

setInner { newPort }

getInner {}

setOuter { newPort }

getOuter {}

New methods:

__primRender { c x y { tags "" } } 
Lower level render method. Just draw without extra processing for mouse bindings, etc.
getStickies {} 
What's the binding stuck to, if anything?
stickTo { c thing } 
Attach the binding to something.
unStick { c thing } 
Detach the binding from something.
init { thing } 
General initialization. The code here figures out a reasonable initial place to put the drawing of the binding. On port bindings, it also picks a place to put the connection handle.
positionInterface {} 
Position the big box on the outside of the aggregate's editor area.
primMoveTo { c x y } 
Move without extra processing.
Port bindings have a drawLine method like roles for drawing the line between the handle and the interface. Role bindings don't have this because they have no handle.

2.7 CVisAggregate

This class presents an editing window allowing the user to manipulate the objects stored in an aggregate representation of a component or connector.

Data access:

getBindings {} 
__setBindings { newBindings} 
getChildren {} 
__setChildren { new } 
New methods:

bind { inner outer binding} 
unbind { inner outer binding } 
addBinding { binding } 
delBinding { binding } 
_primAddChild { child } 
addChild { child } 
__primDelChild { child } 
delChild { child } 
__drawBindingsBox { c } 
__copyBindings { newVisAgg } 
__copyAttachments { newVisAgg } 
These all pretty much correspond to what CFamAggregates do.

3 The Big Picture

The rest of the design UI code is split up into a bunch of Tcl procedures that manipulate the editing canvas. Each item on the canvas has a CVisObject backing it up. The editor window itself has a CVisAggregate backing it up. The toplevel object of a design is an invisible component with one aggregate representation that is presented to the user for editing. The user cannot manipulate this magic object.

All of the editor commands create and/or manipulate visual objects directly and DB objects indirectly. DB objects are totally oblivious to the fact that they have some external visual representation. This is a proof of concept that we can build a UI without any hooks in the DB, but it makes the UI code overly crufty and complicated.

The following few sections will outline how things work so you have a shot at making sense of the code. The sections are organized by the name of the file containing the code.

3.1 ui_lib.fcl

This is the top level script that s in all the definitions making up the user interface library. It depends on the variable ui_library which should contain the path of the directory that holds all of the files in the UI library. It also sets up the variable wright_library as the place where things related to FDR live. This includes the special synthesizer generator editors and the various scripts that do protocol checking.

3.2 utilities.tcl

Various utility routines. The important ones deal with the global list of windows and things that happen at startup and shutdown of windows. This file has fairly good high level comments.

3.3 uic_cut.tcl

This code implements cut and paste. It is horribly complicated and opaque. The basic scheme is fairly simple, but this code is complicated by the fact that the undo system was not mature and the various data structures for representing designs were irregular and somewhat in flux.

Now we'll go over how each of the primitive editing operations works. Two important primitives stand out. The routine __uic__addToClipBoard adds each selected object to the clipboard. The current code keeps a set of different clipboards in order to support undo. This is more complexity than we actually need and will probably be changed later.

The other important routine is __uic__copyAttachments. This procedure walks over the set of objects that we have moved into the clipboard and records the attachments that must be created at paste-time.

With these two routines, explaining "cut" is fairly simple. You copy the selection into the clipboard, then copy any needed attachments, then you have each visual object remove itself from the canvas using the unRender method. In the current implementation, the unRender method has some special hooks to record undo information related to attachments that must be re-established to undo a cut. In addition, objects are never removed from the aggregate in a cut operation until the window is closed. Instead, they are placed in a special data structure called the "trashcan" so that we can get them back if the cut is undone. Right now, none of this mechanism is in-use since undo/redo still don't work. It will be replaced in the next version.

Copy does basically the same thing as cut, but without the unRender step. Copy also doesn't record any undo info because its not undoable.

Paste is more complicated. The main complication is dealing with re-attaching things and dealing with ports and roles that live in the clipboard without being attached to a parent. When this happens in the current system, the code looks through the current selection and tags the port/role onto anything that is available. At some point, we might want to give the user more control over how this works.

3.4 uic_init.tcl

These are routines that create and initialize new windows in the design UI. Combined with the code in uic_menu.tcl and, this code creates the editing canvas, the tool palette and the toplevel menubar. Note that global startup processing is handled in main.tcl.

3.5 uic_menu.tcl

Code that initializes and manipulates the various menus in the design UI. In addition, the code for handling the canvas popup menu is here.

3.6 uic_obj.tcl

This file collects generic operations on database objects that the box-line editor uses as primitives. Generally, things that operate on objects but don't want to be methods for one reason or another go here. Many of these things could be factored out into common superclasses or something, maybe. The file has short comments on what each procedure does. Note that this file implements no object methods, only Tcl procedures.

Some important routines include:

This is the code that roles/bindings use to figure out how to stick to or unstick from nearby ports
Check to make sure something that is about to be deleted isn't bound to in some lower level of the hierarchy.
__uic__putPorts, __uic__putRoles
Add ports/roles to things in the current selection, if possible.
There are also various routines that create representations and so forth.

3.7 uic_select.tcl

This file collects routines related to manipulating the selection in the drawing editor. This includes the logic for adding/removing items from the selection, the drag rectangle code, the code for dragging objects in the current selection around and moving them afterwards and the code for resizing objects. Also, the important routine __uic__shuffle lives here. This shuffles the priority of things on the canvas so that so things stay visible in a useful way. For example, roles on drawn on top of ports, This routine is called a lot.

3.8 ui_dlog.tcl

A generic dialog utility. Shouldn't be used any more. The forms stuff does more. I think there are still some instances of code using the procedures in this library. We have two or three different implementations of this functionality which I don't have the time to consolidate.

3.9 generic_palette.fcl

A generic example of how to create items on the canvas using commands dispatched from the button palette. This works as follows:

  1. The file creates mappings from DB classes to visual classes. Each such mapping corresponds to an available palette button.
  2. Each button in the palette dispatches to a callback defined in this file. It sends along the classes of the various objects to make and the canvas to draw them on.
  3. Each callback creates the appropriate pair of visual and DB objects, and draw the visual objects on the appropriate canvas.
New styles would either replace these callbacks with new ones that have different functionality, or they just change the table in to create objects of different classes. Right now, all our of styles do the latter.

3.10 vis_draw.tcl

Defines the routines that provide a somewhat higher level language for telling the UI how to draw things. In practice, this hasn't really been much better than before. The useful thing about this code is the coordinate hacking that it does for us.

3.11 uiShelfStuff.fcl

Hooks for interfacing the design UI with the shelf.

3.12 ui.fcl

Top level design UI code. This files contains the routines that create the control box and all the top level dialogs for opening and manipulating designs.

3.13 visDB.fcl

This file defines the routines used in manipulating the "palette table." This table maps DB object classes to visual object classes and also attaches callbacks to buttons appearing in the design UI palette. The procedure defClass stores all this information in the appropriate table based on the parameters given. Look at the file for an example, its pretty straightforward.

The global database is called __visDB__DB and is indexed by a class name and one of several parameter names. The class name is a symbolic text name like "Role" or "Component". The two parameters dbclass and visclass map the symbolic name to a Fcl DB object class and a Fcl visual object class. The two other standard parameters are the bitmap name, specifying where to find the bitmap with which to make a palette button and the name of the callback procedure that will create an instance in the editing canvas.

You can look stuff up in the symbolic class database using the visdb_classInfo routine. Just give it the two keywords to search on. The workshop code does this to create reps and ports/roles from its popup menus. The popup menus contain symbolic names, rather than Fcl class names.

3.14 form_util.tcl

This is a set of utilities for creating complicated dialog boxes for displaying and editing object data interactively. The routine mkForm creates such a dialog using a description that is just a list of entries. Each entry is defined by a bunch of switches that specify the type of data in the entry, packing options, text names, and so on. Below is the list of available field types and their configuration options. In the next implementation, we'll probably keep the code the same but make each field type a separate command and remove the list processing. You would just build a form using a sequence of field commands.

A labelled text editing box. Usually tied to a particular slot of a particular object.
A check button.
A group of radio buttons.
A popup menu button to run commands with.
A popup menu button to set values with. Like the "combo-box" in windows. This isn't used at all in the current code, might not work.
A sub-frame with more stuff in it. Used to define a hierarchy of frames if you need to pack things in a special way.
A text label
A list box.
A scale slider widget.
A normal push-button.
Each of the widgets can take various configuration options, listed below:

The type of widget that follows.
Tk widget pathname. Useful when defining groups that you will later pack stuff into. Note, the "group" widget uses "name" for this instead of "win_name."
Text name of the window. Used for labels, or for things that have a always label in addition to their main body, like text boxes.
In an editing box, the a script to call to read the initial value out of the object. Text boxes and list boxes use this for initialization. In the script, several Tk style substitutions are done: %c is replaced with the canvas name, %o is replaced with the object handle of the effected DB object, %v is replaced with the object handle of the effected visual object, %f is replaced with the window path of the dialog, %X/Y is replaced with the x/y-coordinate of a mouse click, %t is replaced with the a text value when applicable (text entries, menu buttons, and so on).
Script that is run to reset some database value based on the new value in the form. The same substitutions as above are done.
Configuration options for the widgets created by the forms code. See the Tk documentation for how this works.
Options to tell the packer how to pack the widget or group of widgets created by the forms code. See the documentation on the packer for more info.
Special hack so we can set the pack options of a text entry separately from its label.
Widget options for the label in a text entry. Special hack so we can set these separately. Generally you need this for special formatting.
list of Tcl values. Used by the group widget in an ad-hoc way that is never exploited. This will go away. Also used by the radio button group to specify the individual buttons. This should be regularized.
Default value for a radio button group.
Script to run when something in a list box or menu is selected. Standard substitutions are applied before the code is evaluated.
In enum_buttons, code to run when the user picks a new value out of the popup menu. You would generally use this to side effect some object in the database in response to the user's action. Standard text substitutions are applied to the script.
In an enum_button, code to run to fetch values to display in the label next to the popup menu. Usually, you would fetch this stuff out of some database object. Standard text substitutions are applied to the script.
In enum_button widgets, this code is evaluated to build the items list for the menu.
In menu_button widgets, this code is evaluated to build the items list for the menu. This should be more consistent with enum_button widgets, oh well.
Command proc. for a standard push-button.
For thumbnails, code to run for initial drawing.
For thumbnails, code to run to update drawing.

3.15 workshop_common.fcl

Routines that are common to all of the workshops. This includes manipulations on representations, opening external editors, and manipulation of the thumbnail sketches. Some basic object editing is also included here.

3.16 comp_workshop.fcl

Code for the component workshop. Most of the code just sets up the form dialog and its various callbacks. There is also a smaller form that is used to edit port names and such. The callbacks provide the code needed to add/remove ports from a component. Representations are dealt with using the code in workshop_common.fcl.

3.17 conn_workshop.fcl

Connector workshop. Sets up a form just like in the component workshop, and provides callbacks for adding, removing and editing roles.

3.18 port_workshop.fcl

The port workshop. Sets up a form like int he other workshops, plus callbacks for adding and removing attachments.

3.19 role_workshop.fcl

The role workshop. Sets up a form like in the other workshops, plus callbacks for adding and removing attachments.

3.20 getopt.tcl

Implementation of getopt-like switch parsing. Getopt allows you to specify lists of possible switches to look for, and also to parse out values for any switches that speficy such extra data. See the UNIX man page on the C library routine for details. This isn't a complete implementation, has a somewhat different syntax for specifying switches.

3.21 status.tcl

Implements the Dogbert animated status box. Stolen from the Kosher-dill demo program. Needs the BLT toolkit. This toolkit is included in the Fcl source code. Eventually, we should do an implementation that is not dependent on BLT.

3.22 main.tcl

This is where the generic style gets off the ground. All the other styles use this file as a template for getting started as well. The first part of the file sets up some variables that we use in loading other modules of the library. In a final system, the default values for these should be fetched from the environment to make installation more flexible.

Next, the two globals uiStyleMakeToplevel and uiStyleTopLevelTag are defined. The first names a procedure to run when creating a new toplevel object for a design. The toplevel object contains an aggregate representation that holds all the other objects in a design. The second variable is used in the "Open a Design" dialog to find designs which are probably safe to open in the current style. A "safe" design is one whose toplevel object has a class that matches the value of uiStyleTopLevelTag.

We then source in the basic libraries, the UI library and the shelf library. Finally, we set up the palette database from the file, start up the tool server and initialize the background FDR process for port/role checks.

3.23 start.tcl

This is where special startup code in the UI lives. This is code that must be run whenever the UI lib is loaded and initialized. Right now, the only code in this file is the procedure for starting up the tool server. Each tool server maintains control over one session named by the X display that the current UI is running under. Therefore, to make sure we only run one tool server, the startup processes leaves a session marker in the /tmp directory of the current machine. If this marker exists, no new tool servers will start up. Therefore, if for some reason you can't get a server going, its probably because there is a stray marker file somewhere.

This file should probably be renamed something like "toolserver_start.tcl" or something.


See the document Programming a Style in Aesop for information on what this file does.

3.25 toolserv.fcl

This starts up the "tool server." The tool server is a separate process (no real reason to do this) that uses the event dispatch server to implement a tool dispatch service for the rest user interface. Thus, the tool server is the one event service client in the system.

It reads a configuration file that specifies what tools are available and what event tags need to be used to start them. An example configuration file is listed below:

emacs /usr/misc/.lemacs/bin/lemacs
fedit /usr/local/lib/fcl/tools/fedit
port.syn /usr/local/lib/fcl/tools/port.syn
role.syn /usr/local/lib/fcl/tools/role.syn
conn.syn /usr/local/lib/fcl/tools/glue.syn
Each entry here is just an <event-tag, path-to-tool> pair.

3.26 The ABLE Code Repository

Info here on the code rep.

4 Future Development

Below is a list of a few major problems with the current implementation of the user interface.

Lack of coordination between the Visual classes and their Database classes.
Lack of a good undo mechanism.
I'll go over these one by one.

4.1 Lack of Visual/DB Class Coordination

The design of the UI classes was meant to mirror Smalltalk's Model-View-Controller system. The visual classes would be the views, the DB classes would be the model, and the controller would be Tk through its events and canvas widgets. However, when I originally wrote the code, I tried to avoid putting any special purpose hooks into the DB classes to support the user interface. This left the implementation of our MVC incomplete, since DB objects had no way to inform their views if their data changed due to external forces.

The result of this oversight is that all changes to the database that effect the visualization of an object must be done through the visual object, or must be implemented in such a way as to inform the visual object of the change after the fact. This makes it difficult to extend the user interface, and makes our libraries somewhat less flexible than we would like. In addition, it resulted in method suites for the visual objects that exactly mirrored the methods of the DB classes they represented. This is wasteful and makes code maintenance harder.

Part of the solution to this problem is to add a notification mechanism to the generic object class. So, each DB object would keep track of a list of other objects needing notification of changes to the database. When these changes occur, the DB object can scan this list and call an agreed upon method on each of these objects. For example, we might keep the list in a slot called "sViews" and the update operation would scan this list and call update on each object. An example implementation of this is in /usr0/psu/able/fcl/new/fam_basics.fcl. This directory contains a preliminary re-implementation of the DB classes to better support MVC.

The other part of this design problem is whether or not visual objects should be stored in the database at all. Very little of the information stored in a visual object needs to be persistent, and could easily be stored in a special attribute of the DB object. This would cleanly split those visual attributes that need to be shared between UI editing sessions and those that are logically local to each session. This also has implications on the performance of the system, because its easier to tune the performance of non-persistent objects than objects that need to be stored in the database.

Of course, a problem with the above scheme is that the current object system has no way to manipulate non-persistent objects. Re-doing the world in an existing object system for Tcl, like itcl or the old BOS might be a way to fix this, but then you run into the problem of interfacing that system with the database engine. It's a messy world.

4.2 Undo

Right now, the undo system lets you log single action/inverse pairs as calls to Tcl procedures. This makes dealing with actions that do many low level operations difficult. The undo system should be rewritten to log a fixed set of low level DB operations and their inverses with explicit save-points. Then we can back out of changes one save point at a time.

There are still tricky problems with integrating undo to the Exodus transaction mechanism, the problem being that Exodus transactions don't allow external clients to participate in the two-phase commit protocol (I think, check this) so coordinating aborts with undo could be hard.

To go along with this, there needs to be a well defined sequence of actions that happen when user level actions are executed. This is motivated by the Emacs model, where keystrokes are read and translated through a dispatch table into Lisp "commands" which in turn may run arbitrary Lisp code. It is handy to separate code that is a "command" from code that isn't. I'm not exactly sure how to design this, but having a coherent design makes writing new commands simpler and encourages a clean separation between the implementation of a mechanism and its user interface.

4.3 Performance

The system is slow. It is slow for two main reasons:

Tcl is a hideously inefficient interpreter.
Our representation for objects is hideously inefficient (objects are strings in the database).
This came about because I saw the original fcl system as a quick prototype that we would throw away. Unfortunately, its too big to throw away now.

In theory, we can get around the Tcl interpreter by implementing the object system as C code. But, storing C objects in the Exodus store is non-trivial, and keeping the string based representation would make the C code nearly as slow as normal Tcl, in addition to breaking abstraction. My feeling is that as long as the system is implemented in Tcl, and uses Exodus, it will be slow.

Some longer term options are:

  1. We might see if moving the our objects into Postgres makes things better. Its possible to write C code in Postgres to manipulate objects more efficiently than in Tcl. But, Postgres also has limits, since the main interaction between clients and the server is through a string-based TCP protocol. There is a bit of flexibility here, though.
  2. Use ObjectStore. Here we would build a dynamic C or C++ based representation for objects and method dispatch. That is, objects are not defined as C++ classes, instead are represented as some data structure stored within an ObjectStore schema. For example, the itcl object system represents its objects as hash tables that map slot names to slot values. This makes slot access much faster than fcl, since you only have to scan the slot name, not the entire object string, to extract the value you want.
Such a representation for objects would be easy to build in ObjectStore, and could possibly be built in such a way as to provide support for both persistent and non-persistent objects.

This scheme has the advantage that with an appropriate library it is now easy to write methods in C or C++ without knowing the representation of the objects.

ObjectStore also has lots of cool features like dynamic loading of code, schema evolution, two-way constraint maintenance, object versioning (check-in, check-out style transactions with merging) and both C and C++ based runtimes. The constraint maintenance is particularly handy. It lets you, for example, have the ObjectStore engine keep track of the fact that if you delete an object whose parent is foo, then that object is automatically removed from the list of children for foo. No more dangling object pointers.

  1. Use ObjectStore and implement the system as C++ classes like before. The ObjectStore meta-object-protocol might be good enough to implement styles and hooks and whatnot, and its schema compiler lacks all the brain-damage in stone. Turnaround time isn't bad on a fast workstation.
  2. Look into Smalltalk based DB solutions.
  3. Look into PC based tools. Key words are things like POET, Powerbuilder, and so on.
I don't think there are free database tools to do what you want. The best we can do is for-money tools with good binary distribution agreements.