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

Dropbox 193.4.5594 - Cloud backup and sy...
Dropbox is a file hosting service that provides cloud storage, file synchronization, personal cloud, and client software. It is a modern workspace that allows you to get to all of your files, manage... Read more
Google Chrome 122.0.6261.57 - Modern and...
Google Chrome is a Web browser by Google, created to be a modern platform for Web pages and applications. It utilizes very fast loading of Web pages and has a V8 engine, which is a custom built... Read more
Skype 8.113.0.210 - Voice-over-internet...
Skype is a telecommunications app that provides HD video calls, instant messaging, calling to any phone number or landline, and Skype for Business for productive cooperation on the projects. This... Read more
Tor Browser 13.0.10 - Anonymize Web brow...
Using Tor Browser you can protect yourself against tracking, surveillance, and censorship. Tor was originally designed, implemented, and deployed as a third-generation onion-routing project of the U.... Read more
Deeper 3.0.4 - Enable hidden features in...
Deeper is a personalization utility for macOS which allows you to enable and disable the hidden functions of the Finder, Dock, QuickTime, Safari, iTunes, login window, Spotlight, and many of Apple's... Read more
OnyX 4.5.5 - Maintenance and optimizatio...
OnyX is a multifunction utility that you can use to verify the startup disk and the structure of its system files, to run miscellaneous maintenance and cleaning tasks, to configure parameters in the... Read more
Hopper Disassembler 5.14.1 - Binary disa...
Hopper Disassembler is a binary disassembler, decompiler, and debugger for 32- and 64-bit executables. It will let you disassemble any binary you want, and provide you all the information about its... Read more

Latest Forum Discussions

See All

Zenless Zone Zero opens entries for its...
miHoYo, aka HoYoverse, has become such a big name in mobile gaming that it's hard to believe that arguably their flagship title, Genshin Impact, is only three and a half years old. Now, they continue the road to the next title in their world, with... | Read more »
Live, Playdate, Live! – The TouchArcade...
In this week’s episode of The TouchArcade Show we kick things off by talking about all the games I splurged on during the recent Playdate Catalog one-year anniversary sale, including the new Lucas Pope jam Mars After Midnight. We haven’t played any... | Read more »
TouchArcade Game of the Week: ‘Vroomies’
So here’s a thing: Vroomies from developer Alex Taber aka Unordered Games is the Game of the Week! Except… Vroomies came out an entire month ago. It wasn’t on my radar until this week, which is why I included it in our weekly new games round-up, but... | Read more »
SwitchArcade Round-Up: ‘MLB The Show 24’...
Hello gentle readers, and welcome to the SwitchArcade Round-Up for March 15th, 2024. We’re closing out the week with a bunch of new games, with Sony’s baseball franchise MLB The Show up to bat yet again. There are several other interesting games to... | Read more »
Steam Deck Weekly: WWE 2K24 and Summerho...
Welcome to this week’s edition of the Steam Deck Weekly. The busy season has begun with games we’ve been looking forward to playing including Dragon’s Dogma 2, Horizon Forbidden West Complete Edition, and also console exclusives like Rise of the... | Read more »
Steam Spring Sale 2024 – The 10 Best Ste...
The Steam Spring Sale 2024 began last night, and while it isn’t as big of a deal as say the Steam Winter Sale, you may as well take advantage of it to save money on some games you were planning to buy. I obviously recommend checking out your own... | Read more »
New ‘SaGa Emerald Beyond’ Gameplay Showc...
Last month, Square Enix posted a Let’s Play video featuring SaGa Localization Director Neil Broadley who showcased the worlds, companions, and more from the upcoming and highly-anticipated RPG SaGa Emerald Beyond. | Read more »
Choose Your Side in the Latest ‘Marvel S...
Last month, Marvel Snap (Free) held its very first “imbalance" event in honor of Valentine’s Day. For a limited time, certain well-known couples were given special boosts when conditions were right. It must have gone over well, because we’ve got a... | Read more »
Warframe welcomes the arrival of a new s...
As a Warframe player one of the best things about it launching on iOS, despite it being arguably the best way to play the game if you have a controller, is that I can now be paid to talk about it. To whit, we are gearing up to receive the first... | Read more »
Apple Arcade Weekly Round-Up: Updates an...
Following the new releases earlier in the month and April 2024’s games being revealed by Apple, this week has seen some notable game updates and events go live for Apple Arcade. What The Golf? has an April Fool’s Day celebration event going live “... | Read more »

Price Scanner via MacPrices.net

Apple Education is offering $100 discounts on...
If you’re a student, teacher, or staff member at any educational institution, you can use your .edu email address when ordering at Apple Education to take $100 off the price of a new M3 MacBook Air.... Read more
Apple Watch Ultra 2 with Blood Oxygen feature...
Best Buy is offering Apple Watch Ultra 2 models for $50 off MSRP on their online store this week. Sale prices available for online orders only, in-store prices may vary. Order online, and choose... Read more
New promo at Sams Club: Apple HomePods for $2...
Sams Club has Apple HomePods on sale for $259 through March 31, 2024. Their price is $40 off Apple’s MSRP, and both Space Gray and White colors are available. Sale price is for online orders only, in... Read more
Get Apple’s 2nd generation Apple Pencil for $...
Apple’s Pencil (2nd generation) works with the 12″ iPad Pro (3rd, 4th, 5th, and 6th generation), 11″ iPad Pro (1st, 2nd, 3rd, and 4th generation), iPad Air (4th and 5th generation), and iPad mini (... Read more
10th generation Apple iPads on sale for $100...
Best Buy has Apple’s 10th-generation WiFi iPads back on sale for $100 off MSRP on their online store, starting at only $349. With the discount, Best Buy’s prices are the lowest currently available... Read more
iPad Airs on sale again starting at $449 on B...
Best Buy has 10.9″ M1 WiFi iPad Airs on record-low sale prices again for $150 off Apple’s MSRP, starting at $449. Sale prices for online orders only, in-store price may vary. Order online, and choose... Read more
Best Buy is blowing out clearance 13-inch M1...
Best Buy is blowing out clearance Apple 13″ M1 MacBook Airs this weekend for only $649.99, or $350 off Apple’s original MSRP. Sale prices for online orders only, in-store prices may vary. Order... Read more
Low price alert! You can now get a 13-inch M1...
Walmart has, for the first time, begun offering new Apple MacBooks for sale on their online store, albeit clearance previous-generation models. They now have the 13″ M1 MacBook Air (8GB RAM, 256GB... Read more
Best Apple MacBook deal this weekend: Get the...
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 15-inch M3 MacBook Air (Midnight) on sale...
Amazon has the new 15″ M3 MacBook Air (8GB RAM/256GB SSD/Midnight) in stock and on sale today for $1249.99 including free shipping. Their price is $50 off MSRP, and it’s the lowest price currently... Read more

Jobs Board

Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
Senior Software Engineer - *Apple* Fundamen...
…center of Microsoft's efforts to empower our users to do more. The Apple Fundamentals team focused on defining and improving the end-to-end developer experience in Read more
Relationship Banker *Apple* Valley Main - W...
…Alcohol Policy to learn more. **Company:** WELLS FARGO BANK **Req Number:** R-350696 **Updated:** Mon Mar 11 00:00:00 UTC 2024 **Location:** APPLE VALLEY,California Read more
Medical Assistant - Surgical Oncology- *Apple...
Medical Assistant - Surgical Oncology- Apple Hill WellSpan Medical Group, York, PA | Nursing | Nursing Support | FTE: 1 | Regular | Tracking Code: 200555 Apply Now Read more
Early Preschool Teacher - Glenda Drive/ *Appl...
Early Preschool Teacher - Glenda Drive/ Apple ValleyTeacher Share by Email Share on LinkedIn Share on Twitter Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.