How can one set up persistent collaborating objects in DigitalMicrograph via scripting?

150 views Asked by At

I have come to really appreciate the benefits of using objects to deploy a given application within the DigitalMicrograph environment via the DMS language. The object-oriented approach opens the door to the use of reusable design patterns involving collaborating objects, e.g. Model-View-Controller (MVC). However, objects within DM seem to be highly volatile due to the use of automatic reference counting to manage their life cycles. In order for an MVC trio, or any other set of collaborating objects, to stay alive long enough to be useful, at least one of them must be rooted in a non-volatile object managed by the DM application. So far, the only such objects I have come across within DM are those based on the UIFrame class (i.e. modeless dialogs and UI palettes). For MVC implementations, this works out fine since it makes sense to implement the View as a UIFrame object. It's just a bit unconventional in that the View object becomes the root object that keeps the MVC trio alive and functioning. Normally it is the Controller object that is rooted in the application and manages the Model and View objects. But what about design patterns that do not involve UI? Is there any (acceptable) way to give a set of collaborating objects persistence without rooting them in a UIFrame object? Are there other application-rooted object types that can serve this purpose? I assume setting up a reference cycle would not be an acceptable approach due to the inevitable risk of memory leaks.

3

There are 3 answers

2
BmyGuest On BEST ANSWER

The third, and by far the best and cleanest solution is to launch your object as a 'listener' to some event. As you are looking for an object which should stay in scope as long as DigitalMicrograph is open, its possibly best to listen to the application itself. By listening for the "about_to_close" message you also get the ideal handle to properly release all resources before shutdown. The code is the following:

From my 3 answers this is the one I would use. (The others should just illustrate options.)

class MyPermanentObject 
{
    MyPermanentObject( object self ) { result("created MyPermanentObject :"+self.ScriptObjectGetID()+"\n");}
    ~MyPermanentObject( object self ) { result("killed MyPermanentObject :"+self.ScriptObjectGetID()+"\n");}
    void DeInitialize( object self, number eventFlags, object appObj ) 
    { 
        OKDialog( "The application is closing now. Deinitialize stuff properly!" ); 
    }
}


{
    object listener = Alloc( MyPermanentObject )
    ApplicationAddEventListener( listener, "application_about_to_close:DeInitialize" )
}
1
BmyGuest On

I can think of various ways to get this persistence, but the one which jumped to mind first was to launch one object into a background thread, like in the example below. The actual background thread can check every so often if the object should still remain, and by sharing the object ID with the outside world, other objects (which don't have to be persistent) can access the "anchored" object.

A word of warning though: If you keep things in memory like this, you have to be careful when closing DigitalMicrograph. If the object hangs on to some items DM wants to destroy, you might see errors or crashes at the end.

// This is the object "anchored". It will remain in memory, because we launch it on a separate thread.
// On this thread, it loops until a variable is set to false (or until SHIFT is pressed)
Class IPersist : Thread
{
    number keepme

    IPersist( object self ) { result("created IPersist:"+self.ScriptObjectGetID()+"\n");}
    ~IPersist( object self ) { result("killed IPersist:"+self.ScriptObjectGetID()+"\n\n\n\n");}

    void CallFromOutside( object self ) { Result( "\t IPersist can be used!\n" ); }
    void StopFromOutside( object self ) { keepme = 0; }
    void RunThread( object self )
    {
        keepme = 1
        Result( "\t Called once at start.\n")
        While( keepme && !ShiftDown() ) yield()
        Result( "\t Finished.\n")
    }
}

// Just and example class used to access the 'anchored' object
Class SomethingElse
{
    number keepID
    SomethingElse( object self ) { result("created SomethingElse:"+self.ScriptObjectGetID()+"\n");}
    ~SomethingElse( object self ) { result("killed SomethingElse:"+self.ScriptObjectGetID()+"\n");}
    void SetKeepID( object self, number id ) { keepID = id; }
    void CallOut( object self )
    {
        result( "SomethingElse object is accessing CallOut...\n" )
        object p = GetScriptObjectFromID( keepID )
        if ( p.ScriptObjectIsValid() )
        {
            p.CallFromOutside()
        }
    }
    void CallStop( object self )
    {
        result( "SomethingElse object is accessing CallOut...\n" )
        object p = GetScriptObjectFromID( keepID )
        if ( p.ScriptObjectIsValid() )
        {
            p.StopFromOutside()
        }
    }
}


// Main script. Create object on separate thread. Then feed it's ID as "weak reference" into the second object.
{
    object ob = Alloc(IPersist)
    ob.StartThread()    

    object other = Alloc(SomethingElse)
    other.SetKeepID( ob.ScriptObjectGetID() )
    other.CallOut()
    If ( TwoButtonDialog( "You can either stop IPerstis now, or by pressing SHIFT later.", "Stop now", "later" ) )
        other.CallStop()
}
1
BmyGuest On

An alternative way would be to have two objects keep references of each other. This is a deadlock-situation one would normally rather avoid, but for the purpose of anchoring it works as well. No object can go out of scope until you release one on purpose. Again, it is your responsibility to 'release' things when you want a proper shutdown of the system.

The code for the deadlock-situation is rather slim:

class SelfLock
{
    object partner
    SelfLock( object self ) { result("created SelfLock:"+self.ScriptObjectGetID()+"\n");}
    ~SelfLock( object self ) { result("killed SelfLock:"+self.ScriptObjectGetID()+"\n");}
    void SetPartner(object self, object p) { partner = p; }
    void ReleasePartner(object self) { partner = NULL; }
}


{
    object p1 = Alloc(SelfLock)
    object p2 = Alloc(SelfLock)
    p1.SetPartner(p2)
    p2.SetPartner(p1)

    if ( TwoButtonDialog( "Release partner", "Yes", "No keep locked" ) )
        p1.ReleasePartner()
}