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

Tokkun Studio unveils alpha trailer for...
We are back on the MMORPG news train, and this time it comes from the sort of international developers Tokkun Studio. They are based in France and Japan, so it counts. Anyway, semantics aside, they have released an alpha trailer for the upcoming... | Read more »
Win a host of exclusive in-game Honor of...
To celebrate its latest Jujutsu Kaisen crossover event, Honor of Kings is offering a bounty of login and achievement rewards kicking off the holiday season early. [Read more] | Read more »
Miraibo GO comes out swinging hard as it...
Having just launched what feels like yesterday, Dreamcube Studio is wasting no time adding events to their open-world survival Miraibo GO. Abyssal Souls arrives relatively in time for the spooky season and brings with it horrifying new partners to... | Read more »
Ditch the heavy binders and high price t...
As fun as the real-world equivalent and the very old Game Boy version are, the Pokemon Trading Card games have historically been received poorly on mobile. It is a very strange and confusing trend, but one that The Pokemon Company is determined to... | Read more »
Peace amongst mobile gamers is now shatt...
Some of the crazy folk tales from gaming have undoubtedly come from the EVE universe. Stories of spying, betrayal, and epic battles have entered history, and now the franchise expands as CCP Games launches EVE Galaxy Conquest, a free-to-play 4x... | Read more »
Lord of Nazarick, the turn-based RPG bas...
Crunchyroll and A PLUS JAPAN have just confirmed that Lord of Nazarick, their turn-based RPG based on the popular OVERLORD anime, is now available for iOS and Android. Starting today at 2PM CET, fans can download the game from Google Play and the... | Read more »
Digital Extremes' recent Devstream...
If you are anything like me you are impatiently waiting for Warframe: 1999 whilst simultaneously cursing the fact Excalibur Prime is permanently Vault locked. To keep us fed during our wait, Digital Extremes hosted a Double Devstream to dish out a... | Read more »
The Frozen Canvas adds a splash of colou...
It is time to grab your gloves and layer up, as Torchlight: Infinite is diving into the frozen tundra in its sixth season. The Frozen Canvas is a colourful new update that brings a stylish flair to the Netherrealm and puts creativity in the... | Read more »
Back When AOL WAS the Internet – The Tou...
In Episode 606 of The TouchArcade Show we kick things off talking about my plans for this weekend, which has resulted in this week’s show being a bit shorter than normal. We also go over some more updates on our Patreon situation, which has been... | Read more »
Creative Assembly's latest mobile p...
The Total War series has been slowly trickling onto mobile, which is a fantastic thing because most, if not all, of them are incredibly great fun. Creative Assembly's latest to get the Feral Interactive treatment into portable form is Total War:... | Read more »

Price Scanner via MacPrices.net

Early Black Friday Deal: Apple’s newly upgrad...
Amazon has Apple 13″ MacBook Airs with M2 CPUs and 16GB of RAM on early Black Friday sale for $200 off MSRP, only $799. Their prices are the lowest currently available for these newly upgraded 13″ M2... Read more
13-inch 8GB M2 MacBook Airs for $749, $250 of...
Best Buy has Apple 13″ MacBook Airs with M2 CPUs and 8GB of RAM in stock and on sale on their online store for $250 off MSRP. Prices start at $749. Their prices are the lowest currently available for... Read more
Amazon is offering an early Black Friday $100...
Amazon is offering early Black Friday discounts on Apple’s new 2024 WiFi iPad minis ranging up to $100 off MSRP, each with free shipping. These are the lowest prices available for new minis anywhere... Read more
Price Drop! Clearance 14-inch M3 MacBook Pros...
Best Buy is offering a $500 discount on clearance 14″ M3 MacBook Pros on their online store this week with prices available starting at only $1099. Prices valid for online orders only, in-store... Read more
Apple AirPods Pro with USB-C on early Black F...
A couple of Apple retailers are offering $70 (28%) discounts on Apple’s AirPods Pro with USB-C (and hearing aid capabilities) this weekend. These are early AirPods Black Friday discounts if you’re... Read more
Price drop! 13-inch M3 MacBook Airs now avail...
With yesterday’s across-the-board MacBook Air upgrade to 16GB of RAM standard, Apple has dropped prices on clearance 13″ 8GB M3 MacBook Airs, Certified Refurbished, to a new low starting at only $829... Read more
Price drop! Apple 15-inch M3 MacBook Airs now...
With yesterday’s release of 15-inch M3 MacBook Airs with 16GB of RAM standard, Apple has dropped prices on clearance Certified Refurbished 15″ 8GB M3 MacBook Airs to a new low starting at only $999.... Read more
Apple has clearance 15-inch M2 MacBook Airs a...
Apple has clearance, Certified Refurbished, 15″ M2 MacBook Airs now available starting at $929 and ranging up to $410 off original MSRP. These are the cheapest 15″ MacBook Airs for sale today at... Read more
Apple drops prices on 13-inch M2 MacBook Airs...
Apple has dropped prices on 13″ M2 MacBook Airs to a new low of only $749 in their Certified Refurbished store. These are the cheapest M2-powered MacBooks for sale at Apple. Apple’s one-year warranty... Read more
Clearance 13-inch M1 MacBook Airs available a...
Apple has clearance 13″ M1 MacBook Airs, Certified Refurbished, now available for $679 for 8-Core CPU/7-Core GPU/256GB models. Apple’s one-year warranty is included, shipping is free, and each... Read more

Jobs Board

Seasonal Cashier - *Apple* Blossom Mall - J...
Seasonal Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Read more
Seasonal Fine Jewelry Commission Associate -...
…Fine Jewelry Commission Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) Read more
Seasonal Operations Associate - *Apple* Blo...
Seasonal Operations Associate - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Read more
Hair Stylist - *Apple* Blossom Mall - JCPen...
Hair Stylist - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Read more
Cashier - *Apple* Blossom Mall - JCPenney (...
Cashier - Apple Blossom Mall Location:Winchester, VA, United States (https://jobs.jcp.com/jobs/location/191170/winchester-va-united-states) - Apple Blossom Mall Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.