OD Dynamic Updating
Volume Number: 12
Issue Number: 8
Column Tag: Opendoc
Updating Parts Dynamically With SOM
Fast, automatic inter-part communication
By Jeremy Roschelle
Note: Source code files accompanying article are located on MacTech CD-ROM or
source code disks.
As a developer of learning technologies, I am concerned with integrated tools for math
and science education. Learners need a wide variety of views, such as meters, graphs,
tables, calculators, simulations, data analyzers. Ideally the same object (an
accelerated particle, say) can be simultaneously updated in every view.
In the past, all these visualizations would have to be built as a parts of a single
monolithic application - a huge effort. With OpenDoc, my colleagues and I are building
each view as a separate OpenDoc component. Students can simply drag and drop a
particle from a simulation to a graph to a meter. For example, Figure 1 shows a
simulation of an oscillating ball, with a vector meter (written by Arni McKinley of
Minds in Motion), an adjustable equation (which I wrote), a text editor part, and a
graph (by Jim Correia).
The simulation, equation, graph, and vector meter are each separate components,
written and compiled at different times, and with different frameworks (PowerPlant,
OpenDoc Development Framework, and SamplePart). Yet, as the particle moves, all
views instantly update to show its current position, velocity, and acceleration.
Furthermore, new views can be added to the system by any developer. For example,
Steve Beardslee at Tufts University has written a data collection component that drives
a particle with real data from a cart moving on a frictionless track.
Figure 1. A component-based physics lesson
The ability to mix and match components should lead to a rapidly expanding and
improving collection of OpenDoc components for math and science education. This
article is your opportunity to help education and learn something about OpenDoc at the
same time. By the end, you’ll know everything you need to contribute a particle
viewer to the suite. In fact, you’ll build a digital meter that drops right into a window
and interoperates with the simulation. I guarantee you’ll have a warm, satisfied
feeling when you see your very own component working alongside the ones that Arni,
Steve, Jim, and I wrote.
The techniques described in this article also apply to many areas besides
education. In fact, they will be useful in any application where one kind of data object
is displayed in many different views. Examples would be a personal information
manager or a financial checkbook program. If you implement the techniques here,
OpenDoc could offer your customers some powerful solutions. They could freely mix
and match various viewers to suit their needs. You could sell specific advanced editors
to specific niche markets. Fast, object-based data sharing could let your company
offer a highly integrated solution with many option packages and upgrade potentials.
SOM: A Better Way to Share Data
Using the standard OpenDoc APIs, editors and viewers share data by writing out a
stream format either to the clipboard or to a link storage unit. This is similar to the
familiar techniques for handling the clipboard, or the System 7
publish-and-subscribe mechanism. An obvious problem with stream-based sharing
is speed. The delay is particularly painful if the object is large and the client only
needs a small portion of the data. A less obvious problem is that you need to
standardize a stream format, which can be hard for objects with complex data.
Finally, you can only change the data at the source.
A better way to share data is to share an object. Getting data is done with a
method call. For example, I can get the position vector of my particle as follows:
SDataVector position(3);
myParticle->GetPosition(ev,time,position);
The chief advantage of getting data via a method call is speed. A particle might
have thousands of data points. Using this method, I get only the data point I need. But
there are other advantages as well. Any viewer that has a particle can call its methods
to change its data. Furthermore, you can use class inheritance to manage the growing
complexity of your objects. For example, my base particle only has data access
methods. My sampled particle builds upon this by introducing methods for entering
sampled data. My constant acceleration particle adds specific methods for constant
acceleration situations. Rather than define one format that meets every need, I can
introduce classes that inherit from the core and specialize appropriately for
particular situations. Due to the magic of object-oriented programming, older
viewers will continue to work with newer, more advanced classes (whereas older
viewers would typically crash with newer file formats).
OpenDoc makes it possible to share objects among parts by using the System
Object Model (SOM). SOM packages an object binary in a special shared library,
where it can be used from many OpenDoc editors, regardless of the compiler or
language used to code the editor. Among the numerous advantages of SOM, it solves the
fragile base class problem; you can change an object’s interface methods, and older
editors will continue to work without your needing to recompile them. While in-depth
coverage of SOM is not possible here, you will get a strong feeling for its ease of use
and power. Full reference materials for SOM are on the OpenDoc Developer CD and at
the OpenDoc Web site.
Becoming a SOM User
Soma: an intoxicating plant juice referred to
in the literature of Vedic ritual.
- Webster’s New Collegiate Dictionary, 1972, p. 1356
Unlike soma, SOM is not a drug. But I’ll admit that SOM’s ability to make
component software flow is somewhat intoxicating, even if it does require a few arcane
rituals.
To use a SOM-based class, you first include its header, which has the .xh suffix.
This is just like including the .h header for a C++ class. You call the methods of the
class, including new and delete, just as you would for an ordinary C++ class. You
must also include the SOM shared library in your project.
The example below uses the particle class to calculate the position of a ball that is
thrown in the air for 10 seconds.
Calculate Position
#include "EduConstAccParticle.xh
double CalculatePosition(Environment *ev)
{
double finalPos = 0.0;
EduConstAccParticle *p = nil;
SOM_TRY
p = new EduConstAccParticle;
// SDataVector is a C++ utility class that makes it easy
// to manipulate vectors. 1 is the number of dimensions.
// SDataVector uses 1-based indexing, because I have trouble thinking
// of the x-axis as the zeroeth dimension of a 3D vector.
SDataVector pos(1), vel(1), acc(1);
pos[1] = 0.0;
vel[1] = 300.0;
acc[1] = -9.8;
// set to constant acceleration motion, with 0.0 initial position and 300.0 m/s
// and gravitational acceleration.
p->SetConstantAcceleration(ev,pos,vel,acc);
// get the value at 10 seconds
finalPos = p->Get1Position(ev,10.0,1);
delete p;
SOM_CATCH_ALL
delete p;
RERAISE;
SOM_ENDTRY
return finalPos;
}
One difference you will note in using SOM is the first argument, which is always
an environment pointer. The environment is a struct that SOM uses to return
exceptions. The environment is necessary because a SOM object may not be in the same
address space as its caller, and thus may not be able to throw an ordinary exception.
Upon returning from SOM code you should always check the environment for errors.
To make this easy, most SOM headers are produced with an option (CHECK-ENV) that
automatically converts the environment into a C++ exception if it contains an error.
Thus your code can catch SOM-based exceptions and C++-based exceptions with the
same try-catch blocks. You should always have at least one exception handler active at
the time you call a SOM method (lest you abort your process upon the first bad
environment that is returned).
Since you now know how to call SOM methods - it’s not very different from C++
- we can move to the real issue: how can particle classes be designed to allow multiple
live views?
Sharing Particles: A Class Act
My goal is to enable multiple components, like simulations, graphs, and tables, to
simultaneously display the data of a single object. To accomplish this, I use the
model-view-controller strategy (Figure 2), made famous in SmallTalk. In this
strategy, the controller (a simulation) makes changes to the model (a particle), and
the particle notifies all its views (graphs, meters, tables). The views call the
particle’s methods to fetch the appropriate data for updating the display.
Figure 2. Model-View-Controller Architecture
Three classes are needed to implement the strategy in OpenDoc:
1. EduObject: a sharable object. (EduParticle inherits from EduObject.)
2. EduObjectListener: registers for change notification with a particular
EduObject.
3. EduObjectMgr: publishes EduObjects to other components.
Each display will have a pointer to an EduObject. A meter, for example, gets a
pointer to an EduParticle by receiving a reference in a drag. The method
EduObjectListener::GetObject uses the particle reference to get access to the
EduParticle, and the meter uses this EduParticle to get data it can display.
The simulation changes a particle by calling its methods. For example,
SetCurrentTime changes the simulation time for the particle. This method will also
broadcast a message to every EduObjectListener. When a meter receives the
message in its listener, for example, it will call EduParticle methods to extract data
and update its display. Every time the simulation changes a particle, all the listeners
will be notified and will update their displays.
Finally, it is desirable for the meter to remember its particle when the document
is saved, so that the link between the simulation and the meter will persist when the
document is opened again. Since the particle is a pointer, it cannot be saved directly.
Instead, the meter’s listener writes out a reference to the particle (in exactly the
same format used to receive a drag). When it is opened, the meter can get the particle
object using the GetObject method as described above.
The listings below are an abridged version of the interface definitions which
specify the API to the classes. These are written in SOM’s Interface Definition
Language (IDL). IDL reads almost like a C++ class definition, except that each
argument is explicitly marked for direction of data flow: in, out, or inout.
EduObject Interface
interface EduObject : SOMObject
{
// abridged interface definition
// change notification methods
void RegisterListener(in EduObjectListener listener);
void UnRegisterListener(in EduObjectListener listener);
void TellListeners(in long message, inout ODByteArray param);
// support for dragging: externalizes a reference to this object.
// storage should be the drag storage unit,
// EduObjectListener has a method for internalizing this from storage
void ExternalizeReference(in ODStorageUnit storage);
};
EduParticle Interface
// inherits from EduObject
interface EduParticle : EduObject
{
// abridged interface definition
// inherits all EduObject methods
// EduConstAccParticle and EduSampledPosParticle
// add methods for setting data values
// access to data values
void GetPosition(in double time, inout EduDataVector vector);
void GetVelocity(in double time, inout EduDataVector vector);
void GetAcceleration(in double time, inout EduDataVector vector);
// time related information
double GetCurrentTime();
void SetCurrentTime(in double time);
};
EduObjectListener Interface
interface EduObjectListener : SOMObject
{
// abridged interface definition
// messages are received here
void ListenToMessage(in long message, inout ODByteArray param);
// support for receiving drags