TweetFollow Us on Twitter

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
    // call HasValidObjectReference to see if you should accept this drag
    // call InternalizeReference to receive the drag
 ODBoolean HasValidObjectReference(
 in ODStorageUnit dragStorage);
 void InternalizeReference(in ODStorageUnit dragStorage);

    // utility methods for storing and retrieving a particle reference
    // in this components storage unit (the OpenDoc generalization of a file)
 void Externalize(
 in ODStorageUnit su, 
 in ODDraftKey key, 
 in ODFrame scopeFrame);

 void Internalize(in ODStorageUnit su);
  
 
    // get the object associated with this listener
    // may return nil if no object is connected.
 EduObject GetObject();
};

EduObjectMgr Interface
interface EduObjectMgr : ODExtension
{
    // abridged interface definition
 
    // called from another component to get an object
 EduObject GetObject(in long id);
 
    // called from within this component to publish objects
 void AddObject(in EduObject object);
 void RemoveObject(in EduObject object);
};

This article takes advantage of the encapsulation offered in these classes by separating the details of use from implementation. I’ll start with how to use the classes, since that is really easy. At the end, I’ll draw back the curtain and reveal how it works.

Building a DigitalMeter Component

The simplest way to make an OpenDoc component is with PartMaker (available at http://www.opendoc.apple.com). PartMaker will generate a complete CodeWarrior project, ready to compile and run. We’ll start with SamplePart, a very simple “Hello, World!” type component, and modify it to be a digital meter for a particle. To begin, drop SamplePart C++ onto PartMaker. Name your project “DigitalMeter”. You can use whatever company name you like. When PartMaker finishes grinding away, you will have a new folder named DigitalMeter.

Open the project DigitalMeter.µ, and perform a make. This will produce a new OpenDoc component. Make an alias to this component and put the alias in your Editors folder. Then drop the component onto the OpenDoc icon (found in System Folder:Extensions:OpenDoc Libraries:) to make stationery. The stationery will appear in your Stationery Folder. You should be able to double-click this stationery in order to open your brand new component. Yeah!

Now you should download EduObject and EMSim from my Web site, using the EMDemo link (http://www.slip.net/~jeremy/). Drop the EduObject library, and EMSim and EMMeter parts, into your Editors folder. You will also need ODFDraw or KickStart Draw, as I use these as a generic container. You can get them from the OpenDoc CD or Web site. To see if you have everything installed correctly, try opening the Example document. You should see the simulation and my existing meter. Select the simulation, and choose Go from its menu. The simulation will run and the meter will update.

Drag your Digital Meter stationery into the Example document. Your component will be added to the window. Now all we need to do is to make your DigitalMeter act like it really is a meter. There are five steps involved:

1. Creating a particle listener

2. Displaying particle data

3. Receiving a particle drag

4. Listening to particle changes

5. Saving a particle reference

1. Creating a particle listener

To begin, add the EduObject library to your project (it is a shared library so it won’t add its code into your component). Then add the SDataVector.cp file to your project.

The code below creates an EduObjectListener in your DigitalMeter. You need a listener because it is both your handle to the EduParticle (which is created by the simulation part) and your way to receive messages when the particle changes. As you can see, creating a SOM object is no big deal.

DigitalMeter::Initialize

void DigitalMeter::Initialize( Environment* ev )
{
 SOM_Trace("DigitalMeter","Initialize");
  
 fListener = new EduObjectListener;
 fListener->InitEduObjectListener(
 ev,fSelf,ListenToMessage,(unsigned long)this);
 CopyPascalString(fCurrentValue,"\pDrag a particle here");
    // unchanged code below here, omitted for clarity
}

DigitalMeter::ReleaseAll 

void DigitalMeter::ReleaseAll( Environment* ev )
{
 SOM_Trace("DigitalMeter","ReleaseAll");
 delete fListener;
    // unchanged code below here, omitted for clarity
}

Below I’ve listed the additions to the DigitalMeter header for all the code in this article:

// original top of header omitted for clarity
// my changes below
#include <EduObjectListener.xh> /
#include "ListenerHelper.h"
#include <EduParticle.xh> //+
#include "SDataVector.h"
#include <DgItmIt.xh>
#include <FacetItr.xh>
#include <stdio.h>

class DigitalMeter {
    // new methods
 public:
 static void ListenToMessage(
 Environment *ev, unsigned long inRefCon, 
 long inMessage, ODByteArray *ioParam);
 
 ODBooleanIsAcceptable(
 Environment *ev, ODDragItemIterator *dragInfo);
 void   ReceiveDrop(
 Environment *ev, ODDragItemIterator *dragInfo);
 
 void   UpdateDisplay(Environment *ev);
 void   DrawAll(Environment *ev);
 
    // new members
 private:
 EduObjectListener *fListener;
 Str255 fCurrentValue;
    // unchanged class definition below here, omitted for clarity
};

2. Digital display

The digital display just draws a string containing the position of the particle. It gets the particle from the listener. (You will see how the listener gets the particle in the next section.) The string is stored in the fCurrentValue member, which will be set by the UpdateDisplay method. Once the string is built, the UpdateDisplay method calls DrawAll, which redraws every facet of every frame with the new string.

DigitalMeter::UpdateDisplay

void DigitalMeter::UpdateDisplay(Environment *ev)
{
 EduParticle *particle = (EduParticle *)
 fListener->GetObject(ev);
 if (! particle) return;
 
    // get current position
 double time = particle->GetCurrentTime(ev);
 SDataVectorpos(2);
 particle->GetPosition(ev,time,pos);
 
    // use ANSI routines to write numbers into Pascal string
 char *output = (char *)&fCurrentValue[1];
 sprintf(output, "%.1f,%.1f",pos[1],pos[2]);
    // set length of Pascal string
 fCurrentValue[0] = strlen(output);
 
 DrawAll(ev);
}

DigitalMeter::DrawFrameView
void DigitalMeter::DrawFrameView(
 Environment* ev,ODFacet* facet )
{
    // setup text characteristics
 TextSize(12);
 TextFont(1);
 TextFace(bold);
 PenNormal();    
 FontInfo finfo;
 GetFontInfo(&finfo);
 MoveTo(0,finfo.ascent + 2);
 
 EraseRect(&port->portRect);
 DrawString(fCurrentValue);
}

DigitalMeter::DrawAll 
void DigitalMeter::DrawAll(Environment *ev)
{
    // iterate through frames, then facets
 CListIterator fiter(fDisplayFrames);
 for ( CFrameProxy* proxy = (CFrameProxy*) fiter.First();
 fiter.IsNotComplete(); 
 proxy = (CFrameProxy*) fiter.Next() )
 {
    // draw only frames in RAM, not any still on disk
 if (proxy->FrameIsLoaded(ev)) {
 ODFrame*frame = proxy->GetFrame(ev);
    // iterate through facets
 ODFrameFacetIterator *iter =
 frame->CreateFacetIterator(ev);
 
 for (ODFacet *facet = iter->First(ev);
  iter->IsNotComplete(ev);
  facet = iter->Next(ev)) {
 
 Draw(ev,facet,kODNULL);   
  
 }
 delete iter;
 }
 }
}

3. Its a drag!

The DigitalMeter’s listener initially learns about a particle by receiving a dragged particle reference. To receive any drag, each frame must have its flag set correctly. We set the flag when your frame is added or connected:

DigitalMeter::DisplayFrameAdded

void DigitalMeter::DisplayFrameAdded(
 Environment* ev, ODFrame* frame )
{
 SOM_Trace("DigitalMeter","DisplayFrameAdded");
 frame->SetDroppable(ev,kODTrue);
    // unchanged code below here, omitted for clarity
}

DigitalMeter::DisplayFrameConnected

void DigitalMeter:: DisplayFrameConnected (
  Environment* ev, ODFrame* frame )
{
 SOM_Trace("DigitalMeter","DisplayFrameConnected");
 frame->SetDroppable(ev,kODTrue);
    // unchanged code below here, omitted for clarity
}

The basic principle of receiving a dragged particle is simple. When something is dragged into your frame, OpenDoc will call DragEnter and pass in a storage unit that contains the dragged content. If your part decides the drag is acceptable, then when the user releases the drag, your receive drop method will be called. EduObjectListener has two methods for receiving drags. HasValidObjectReference returns true if the listener can accept the drag, and InternalizeObjectReference actually handles the drop.

DigitalMeter::IsAcceptable
ODBoolean DigitalMeter::IsAcceptable(
 Environment *ev, ODDragItemIterator *dragInfo)
{
 ODStorageUnit *sue = dragInfo->First(ev);
 return fListener->HasValidObjectReference(ev,sue);
}

DigitalMeter::ReceiveDrop
void    DigitalMeter::ReceiveDrop(
 Environment *ev, ODDragItemIterator *dragInfo)
{
 ODStorageUnit *sue = dragInfo->First(ev);
 fListener->InternalizeReference(ev,sue);
 SetDirty(ev);
 UpdateDisplay(ev);
}

Unfortunately, SamplePart has only stub methods for receiving drops, so we have to delve into the SOM class implementation of the part. The code below looks funny, but is basically just a C++ function implementation with a little extra work to interface with SOM. As with all methods in the SOM class implementation, this code just sets up exception handling and then delegates the work to the DigitalMeter class. For simplicity’s sake, drag hiliting is not implemented here.

som_DigitalMeter__DragEnter

SOM_Scope ODDragResult SOMLINK som_DigitalMeter__DragEnter(
 PartCo_som_DigitalMeter *somSelf, Environment *ev,
 ODDragItemIterator* dragInfo, ODFacet* facet,
 ODPoint* where)
{
 PartCo_som_DigitalMeterData *somThis = 
 PartCo_som_DigitalMeterGetData(somSelf);
 
 ODBooleanresult = kODFalse;
 SOM_TRY
 result = _fPart->IsAcceptable(ev,dragInfo);
 SOM_CATCH_ALL
 SOM_ENDTRY
 return result;
}

som_DigitalMeter__DragWithin
SOM_Scope ODDragResult  SOMLINK som_DigitalMeter__DragWithin(
 PartCo_som_DigitalMeter *somSelf, Environment *ev,
 ODDragItemIterator* dragInfo, ODFacet* facet,
 ODPoint* where)
{
    // same as DragEnter
 return somSelf->DragEnter(ev,dragInfo,facet,where);
}

som_DigitalMeter__Drop
SOM_Scope ODDropResult  SOMLINK som_DigitalMeter__Drop(
 PartCo_som_DigitalMeter *somSelf, Environment *ev,
 ODDragItemIterator* dropInfo, ODFacet* facet,
 ODPoint* where)
{
 PartCo_som_DigitalMeterData *somThis = 
 PartCo_som_DigitalMeterGetData(somSelf);
 
 SOM_TRY
 _fPart->ReceiveDrop(ev,dropInfo);
 SOM_CATCH_ALL
 SOM_ENDTRY
 return kODDropCopy;
}

4. Listening in

Now we get to the easy part. Every time the particle changes, your DigitalMeter will get a message. The ListenToMessage method simply calls UpdateDisplay to display the new values

DigitalMeter::ListenToMessage 
void
DigitalMeter::ListenToMessage(
 Environment *ev, unsigned long refCon,
 long inMessage, ODByteArray *ioParam)
{
 DigitalMeter  *me = (DigitalMeter *)refCon;
 switch (inMessage) {
 case EduParticle_msgDataChanged:
 case EduParticle_msgTimeChanged:
 me->UpdateDisplay(ev);
 break;
 default:
 break; 
 } 
}

5. Being persistent

As a final touch, it’s nice to store the particle reference in your part’s content, so that the same particle can be displayed in the meter when the document is opened the next time. EduObjectListener handles the work of doing this for you.

DigitalMeter::InternalizeContent 
void 
DigitalMeter::InternalizeContent(
 Environment* ev, ODStorageUnit* storageUnit )
{
 if (ODSUExistsThenFocus(ev,storageUnit, 
 kODPropContents,kDigitalMeterKind )) {
 fListener->Internalize(ev,storageUnit);     
 UpdateDisplay(ev);
 }      
}

DigitalMeter::ExternalizeContent 
void
DigitalMeter::ExternalizeContent( 
 Environment* ev, ODStorageUnit*   storageUnit,
 ODDraftKey key, ODFrame* scopeFrame)
{
 ODSUForceFocus(ev, storageUnit,
 kODPropContents,kDigitalMeterKind);
 fListener->Externalize(ev,storageUnit,key, scopeFrame);
}

After entering these changes, make your part and reopen the Example document. Try to drag a particle into your digital meter. You should begin seeing live updates!

How It Works: A Look Behind the Scenes

You might by now be wondering how EduObject and its related classes do their work. The key magic is really provided by SOM. Each EduObject class implementation is compiled into the EduObject library. Each component dynamically links to the library at run time. This enables each component to share the same EduObject code for particles and listeners.

To implement the classes, I first wrote the interface definitions (IDL files). I then submitted these to the SOM compiler, which emits .xh, .xih, and .cpp files. The .cpp file has method stubs, which I filled in with C++ implementations of each method. I compiled these into the shared library and exported each SOM class data structure.

If you want to explore the class implementations in depth, you can read the full sources, which you received when you downloaded EduObject from my Web page. Here I can only provide a quick tour of some of the key concepts behind dragging, persistence, and change notification.

To perform a drag in OpenDoc, you write data into a storage unit that represents the content of the drag. The storage unit can contain multiple flavors of data, each in a separate value. EduObject has an ExternalizeReference method which writes down two pieces of information, the ID of its containing part and the ID of the particle within the part.

EduObjectExternalizeReference

SOM_Scope void  SOMLINK EduObjectExternalizeReference(
 EduObject *somSelf, Environment *ev,
 ODStorageUnit* storage)
{
 EduObjectData *somThis = EduObjectGetData(somSelf);
SOM_TRY
 ODSUForceFocus(ev,storage,
 kODPropContents,EduObject_ReferenceKind);
    // write partID
 ODPart *part = (ODPart *)_fMgr->GetBase(ev);
 long partID = part->GetID(ev);
 StorageUnitSetValue(storage,ev,sizeof(long), &partID);
    // write objectID
 long objectID = somSelf->GetID(ev);
 StorageUnitSetValue(storage,ev,sizeof(long), &objectID);
SOM_CATCH_ALL
SOM_ENDTRY
}

To receive the drag, EduObjectListener:: HasValidObjectReference just checks to see that the correct value type (flavor) is available in the drag. EduObjectListener::InternalizeObjectReference retrieves the part ID and the object ID. (Persistence basically works the same way: Externalize writes down a part ID and object ID, and Internalize reads these numbers back in.)

EduObjectListenerHasValidObjectReference
SOM_Scope ODBoolean  SOMLINK EduObjectListenerHasValidObjectReference(
 EduObjectListener *somSelf, Environment *ev,
 ODStorageUnit* dragStorage)
{
 EduObjectListenerData *somThis =  
 EduObjectListenerGetData(somSelf);
  
 ODBooleanresult = kODFalse;

SOM_TRY
 result = dragStorage->Exists(ev,
 kODPropContents,EduObject_ReferenceKind,0);
SOM_CATCH_ALL
SOM_ENDTRY
 
 return result;
}

EduObjectListenerInternalizeReference
SOM_Scope void  SOMLINK EduObjectListenerInternalizeReference(
 EduObjectListener *somSelf, Environment *ev, 
 ODStorageUnit* dragStorage)
{
 EduObjectListenerData *somThis = 
 EduObjectListenerGetData(somSelf);

SOM_TRY
 if (ODSUExistsThenFocus(ev,dragStorage,
 kODPropContents,EduObject_ReferenceKind)) {
 StorageUnitGetValue(dragStorage,ev,sizeof(long),&_fPartID);
 StorageUnitGetValue(dragStorage,ev,sizeof(long),&_fObjectID);
SOM_CATCH_ALL
SOM_ENDTRY
}

Now how do we actually get a particle from the simulation to the meter? There is no way to directly get a particle (or any other object) out of an OpenDoc part; the ODPart interface simply does not have a method for returning particles! Therefore it is necessary to extend ODPart with an ODExtension. EduObjectMgr is an extension that adds a specific method for returning the object with a specific ID. This extension effectively publishes a set of particles so that other components can see them.

Within your meter, EduObjectListener::GetObject works as follows: First it uses the part ID to retrieve the ODPart that contains the particle. Then it gets the EduObjectMgr extension from that part. Finally, it asks the extension to return the particle with the specified object ID.

EduObjectListenerGetObject

SOM_Scope EduObject*  SOMLINK EduObjectListenerGetObject(
 EduObjectListener *somSelf, Environment *ev)
{
 EduObjectListenerData *somThis = 
 EduObjectListenerGetData(somSelf);
    // return immediately if it is in the cache
 if (_fObject) return _fObject;

 ODPart *part = kODNULL;
 EduObjectMgr *ex = kODNULL;
SOM_TRY
    // To get the particle, we first get its owner part
 part = _fThisPart->GetStorageUnit(ev)->
 GetDraft(ev)->AcquirePart(ev,_fPartID);
 
 ODISOStr extName = EduObjectMgr_ExtensionName;
 if (part!= kODNULL && part->HasExtension(ev,extName)) {
    // now get the extension of the part, and use it to get the object
 ex = (EduObjectMgr*)part->AcquireExtension(ev,extName);
 _fObject = ex->GetObject(ev,_fObjectID);
 ex->Release(ev);
 ex = kODNULL;
 }
 part->Release(ev);
 part = kODNULL; 
 
    // register to receive change notification
 if (_fObject)
 _fObject->RegisterListener(ev,somSelf);

SOM_CATCH_ALL
 if (part) part->Release(ev);
 if (ex) ex->Release(ev);
SOM_ENDTRY   
 return _fObject;
}

When a EduObjectListener has an EduObject, it calls EduObject::RegisterListener. The EduObject simply stores all registered listeners in a list. EduObject:: TellListeners will transmit a message to every listener in the list. For example, when SetCurrentTime is called for a particle, it in turn calls TellListeners with msg_TimeChanged.

EduParticleSetCurrentTime
SOM_Scope void  SOMLINK EduParticleSetCurrentTime(
 EduParticle *somSelf, Environment *ev, double time)
{
 EduParticleData *somThis = EduParticleGetData(somSelf);
 _fTime = time;
 
 SOM_TRY
 ODByteArrayba;
 ba._length = ba._maximum  = 0;
 ba._buffer = kODNULL;
 somSelf->TellListeners(ev,msgTimeChanged,&ba);
 SOM_CATCH_ALL
 SOM_ENDTRY
}

EduObjectTellListeners

SOM_Scope void  SOMLINK EduObjectTellListeners(
 EduObject *somSelf, Environment *ev,
 long message, ODByteArray* param)
{
 EduObjectData *somThis = EduObjectGetData(somSelf);
 if (! _fIsTelling) return;
SOM_TRY
 ListenerIter  iter(_fListenerList);
 for(   EduObjectListener *thisListener = iter.First(); 
 iter.IsNotComplete();
 thisListener = iter.Next()) {
 thisListener->ListenToMessage(ev,message,param);
 }
SOM_CATCH_ALL
SOM_ENDTRY
}

Conclusion: Towards Fast, Flexible Suites

The model-view-controller technique used here to implement fast, object-based data sharing is a common technique within many monolithic applications. It is powerful because it separates the data model (particle) from its interface (viewer components), while providing change notification techniques (listeners) to keep everything nicely synchronized. It allows a suite of editors and viewers all to operate and display the same data.

Before OpenDoc, there was no way to use this technique outside the bounds of a monolithic application. SOM, in particular, makes it possible to share objects across separately authored and compiled components. SOM makes it possible to distribute an object library and the object interfaces for a specific data type. Then anybody can write a component to view and edit that type of data. (And in the future, with DSOM, objects can even be distributed across the Internet).

OpenDoc and SOM provide a powerful way to enable a group of small developers to collectively produce a software suite. For example, four projects have already contributed educational components based on EduObject. Together we are quickly building a powerful suite for physics and calculus, each contributing based on our particular strengths. The components in this suite integrate smoothly, and will provide teachers much flexibility in customizing their presentation to their students.

(You can contribute an educational part too! How about a nice analog clock, or a strip chart, or a speedometer? If you feel inspired, I’d love to see a copy of the result.)

Notice, however, that the specifics of particles are completely separated from the general interfaces that enable data sharing. You could use EduObject, EduObjectListener, and EduObjectMgr with any kind of object - a financial transaction, a personal contact record, or a calendar entry. To do so, just define a new class that inherits from EduObject.

Moreover, there is no reason to limit this power to groups of small developers who are building a suite. A larger developer that produces a deep and rich collection of editors can benefit as well. If I were building a Personal Information Manager today, I would build each contact entry as an object, and make each possible view (e.g. calendar, address book, to-do list) be a separate component. The obvious benefit is that then I could provide customers with custom solutions by substituting components. Some might like a more powerful calendar, whereas others have specific address-book requirements. Moreover, the larger developer could use components to manage the upgrade process; each component can be separately upgraded without forcing a recompile of the project. The developer could even contract some components out, while keeping the core object and components in-house. The flexibility of components allows some powerful marketing opportunities, and the architecture of OpenDoc and SOM supports much quicker time-to-market with custom solutions.

Apple, IBM and the other OpenDoc partners have delivered a powerful architecture for developers to finally realize the promise of object technology - reuse, plug-and-play integration, and smooth evolution. Now we need tool developers to seize the day and make this architecture as accessible as the stand-alone application. Then perhaps, some day soon, reusable, high-quality educational components will be as familiar to every child as their first Lego kit, model train set, or hi-fi stereo system.

 

Community Search:
MacTech Search:

Software Updates via MacUpdate

Latest Forum Discussions

See All

The Legend of Heroes: Trails of Cold Ste...
I adore game series that have connecting lore and stories, which of course means the Legend of Heroes is very dear to me, Trails lore has been building for two decades. Excitedly, the next stage is upon us as Userjoy has announced the upcoming... | Read more »
Go from lowly lizard to wicked Wyvern in...
Do you like questing, and do you like dragons? If not then boy is this not the announcement for you, as Loongcheer Game has unveiled Quest Dragon: Idle Mobile Game. Yes, it is amazing Square Enix hasn’t sued them for copyright infringement, but... | Read more »
Aether Gazer unveils Chapter 16 of its m...
After a bit of maintenance, Aether Gazer has released Chapter 16 of its main storyline, titled Night Parade of the Beasts. This big update brings a new character, a special outfit, some special limited-time events, and, of course, an engaging... | Read more »
Challenge those pesky wyverns to a dance...
After recently having you do battle against your foes by wildly flailing Hello Kitty and friends at them, GungHo Online has whipped out another surprising collaboration for Puzzle & Dragons. It is now time to beat your opponents by cha-cha... | Read more »
Pack a magnifying glass and practice you...
Somehow it has already been a year since Torchlight: Infinite launched, and XD Games is celebrating by blending in what sounds like a truly fantastic new update. Fans of Cthulhu rejoice, as Whispering Mist brings some horror elements, and tests... | Read more »
Summon your guild and prepare for war in...
Netmarble is making some pretty big moves with their latest update for Seven Knights Idle Adventure, with a bunch of interesting additions. Two new heroes enter the battle, there are events and bosses abound, and perhaps most interesting, a huge... | Read more »
Make the passage of time your plaything...
While some of us are still waiting for a chance to get our hands on Ash Prime - yes, don’t remind me I could currently buy him this month I’m barely hanging on - Digital Extremes has announced its next anticipated Prime Form for Warframe. Starting... | Read more »
If you can find it and fit through the d...
The holy trinity of amazing company names have come together, to release their equally amazing and adorable mobile game, Hamster Inn. Published by HyperBeard Games, and co-developed by Mum Not Proud and Little Sasquatch Studios, it's time to... | Read more »
Amikin Survival opens for pre-orders on...
Join me on the wonderful trip down the inspiration rabbit hole; much as Palworld seemingly “borrowed” many aspects from the hit Pokemon franchise, it is time for the heavily armed animal survival to also spawn some illegitimate children as Helio... | Read more »
PUBG Mobile teams up with global phenome...
Since launching in 2019, SpyxFamily has exploded to damn near catastrophic popularity, so it was only a matter of time before a mobile game snapped up a collaboration. Enter PUBG Mobile. Until May 12th, players will be able to collect a host of... | Read more »

Price Scanner via MacPrices.net

Apple is offering significant discounts on 16...
Apple has a full line of 16″ M3 Pro and M3 Max MacBook Pros available, Certified Refurbished, starting at $2119 and ranging up to $600 off MSRP. Each model features a new outer case, shipping is free... Read more
Apple HomePods on sale for $30-$50 off MSRP t...
Best Buy is offering a $30-$50 discount on Apple HomePods this weekend on their online store. The HomePod mini is on sale for $69.99, $30 off MSRP, while Best Buy has the full-size HomePod on sale... Read more
Limited-time sale: 13-inch M3 MacBook Airs fo...
Amazon has the base 13″ M3 MacBook Air (8GB/256GB) in stock and on sale for a limited time for $989 shipped. That’s $110 off MSRP, and it’s the lowest price we’ve seen so far for an M3-powered... Read more
13-inch M2 MacBook Airs in stock today at App...
Apple has 13″ M2 MacBook Airs available for only $849 today in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty is included,... Read more
New today at Apple: Series 9 Watches availabl...
Apple is now offering Certified Refurbished Apple Watch Series 9 models on their online store for up to $80 off MSRP, starting at $339. Each Watch includes Apple’s standard one-year warranty, a new... Read more
The latest Apple iPhone deals from wireless c...
We’ve updated our iPhone Price Tracker with the latest carrier deals on Apple’s iPhone 15 family of smartphones as well as previous models including the iPhone 14, 13, 12, 11, and SE. Use our price... Read more
Boost Mobile will sell you an iPhone 11 for $...
Boost Mobile, an MVNO using AT&T and T-Mobile’s networks, is offering an iPhone 11 for $149.99 when purchased with their $40 Unlimited service plan (12GB of premium data). No trade-in is required... Read more
Free iPhone 15 plus Unlimited service for $60...
Boost Infinite, part of MVNO Boost Mobile using AT&T and T-Mobile’s networks, is offering a free 128GB iPhone 15 for $60 per month including their Unlimited service plan (30GB of premium data).... Read more
$300 off any new iPhone with service at Red P...
Red Pocket Mobile has new Apple iPhones on sale for $300 off MSRP when you switch and open up a new line of service. Red Pocket Mobile is a nationwide MVNO using all the major wireless carrier... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, available for $759 for 8-Core CPU/7-Core GPU/256GB models and $929 for 8-Core CPU/8-Core GPU/512GB models. Apple’s one-year warranty is... Read more

Jobs Board

DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Operating Room Assistant - *Apple* Hill Sur...
Operating Room Assistant - Apple Hill Surgical Center - Day Location: WellSpan Health, York, PA Schedule: Full Time Sign-On Bonus Eligible Remote/Hybrid Regular Read more
Solutions Engineer - *Apple* - SHI (United...
**Job Summary** An Apple Solution Engineer's primary role is tosupport SHI customers in their efforts to select, deploy, and manage Apple operating systems and Read more
DMR Technician - *Apple* /iOS Systems - Haml...
…relevant point-of-need technology self-help aids are available as appropriate. ** Apple Systems Administration** **:** Develops solutions for supporting, deploying, Read more
Omnichannel Associate - *Apple* Blossom Mal...
Omnichannel Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.