Callback procedures
April 16, 1998
by Andrew White
Introduction
One of the problems involved in reusing object code is reliance on particular method names to send events between objects. For example the following lingo object handles a getNetText() operation.
-- GetNetText Object property netID on new me, url set netID = getNetText(url) add the actorList, me return me end on stepFrame me if netDone(netID) then if netError(netID) = "OK then doStuff netTextResult(netID) end if deleteOne the actorList, me end if end
This object just uses the stepFrame mechanism to repeatedly poll netDone(). Once the network operation has been completed the object calls the global handler doStuff and then deletes itself.
This is fine for one project but if we want to reuse the object in another project or for something else within the same project then the doStuff handler has to determine what is the correct response for the data. This greatly complicates matters and introduces further program overhead which we can all do without. One solution to this problem is by implementing a 'callback' system
What is a callback?
A lingo callback is a way of telling a lingo object to where to send messages when the object has completed part or all of it's task. This makes the object's action dynamic so each an every instance of this script can call a different handler when it needs to. Generally the callback reference is passed in when the object is being created (but not necessarily) and stored in a property of the object. It's just like giving someone a job to do and you also give them a contact number or address where they can get back to you once the job has been completed. In our case we pass in two pieces of information - the object reference (think of it like an address) and the handler name (which is analogous to a person living at an address - multiple people can live at one address).
Where are they used.
Callbacks are especially useful where programming needs to be asynchronous, i.e. when the Lingo script cannot wait for a result using a more traditional structure such as a repeat loop. This is especially true for netLingo as this branch of Lingo is the only one where things happen asynchronously at the basic level. Using repeat loops with netLingo operations will fail as no time is given to the network stack to process the transmission and receipt of data.
Usually you have to create your own asynchronous system based around the actorList or a custom built equivalent. Uses in these circumstances can be very diverse - a custom scrollbar can 'callback' another script causing the content area for the scrollbar to update dynamically, without the user releasing the mouse. Another use could be to callback a file search object when a file finder script has found something, or a lingo based animation can callback a navigation object when the animation is completed.
Lingo implementation.
Callbacks are very straight forward to implement in Lingo, as we can use either the call function or do function to dynamically send messages. The call method is significantly quicker than using do, but requires an object reference to work. The do method has no such restriction and can be be used effectively for simple global handler calls. If we take our original example of a getNetText() object then it is transformed thus:
-- Parent script to handle -- asynchronous getNetText calls property callbackProps property netID on new me, URL, cbProps -- Store the callback references set callbackProps = duplicate(cbProps) -- Start the net operation set netID = getNetText(URL) add the actorList, me return me end on stepFrame me if netDone(netID) then deleteOne the actorList, me set cbObj = getaProp(callbackProps,#cbObj) set errObj = getaProp(callbackProps,#errObj) set cbMethod = getaProp(callbackProps,#cbMethod) set errMethod = getaProp¬ (callbackProps,#errMethod) if netError(netID) = "OK" then -- If we have a callback -- object then use call, -- otherwise we use do for global methods if not voidP(cbObj) then call cbMethod, cbObj, netTextResult(netID) else do cbMethod & " netTextResult(netID)" end if else -- If we have a error callback -- object then use call, -- otherwise if we have and error method use do if not voidP(errObj) then call errMethod, errObj, netError(netID) else if not voidP(errMethod) then do errMethod & " netError (netID)" end if end if end if end if end
The callback properties here are passed in as a list - it doesn't have to be done this way, if you want you can pass each parameter separately. The list is duplicated so that any changes to the original list don't affect our callback parameters, and stored in our local property variable.
The object starts the network operation and then adds itself to the actorList. Note that even though we return the object reference with 'return me' there is no need for the instancing script to retain that reference as the actorList does the job for us.
When the network operation is completed (when netDone() returns 1) then the parameters are extracted from the list and tested to see which ones are valid. If we have an object reference then we use call, otherwise we use do. If you are only using objects and are not worried about trapping for errors then you can skip the if ... then statements and just use 'call cbMethod, cbObj, netTextResult(netID)' straight away.
In this example we have a alternative method that can be called if the network operation returns an error. This is a simple way in which using a list to pass in the callback properties gives flexibility. This could be expanded to include any number of callback references which are called depending on the result of an operation. e.g. In this code snippet a callback system uses the result of myResult() as a key for searching a property list of object references.
-- callbackProps contains a property list of objects -- e.g. [#A:<"ObjA" 2 234ac3> -- , #B:<"ObjB" 2 564ba3>,#C:<"ObjC" 2 876cc3>] on stepFrame me if myOpDone(me) then deleteOne the actorList, me call #doMyStuff, getaProp¬ (callbackProps,myResult(me)) end if end
We've used a static method name in this example, but it could've been passed in as a parameter instead.
Alternatively you might want a group of objects to respond when an asynchronous network operation has finished. To achieve this we can pass in a list of objects in the obj property instead. e.g.
set objList = [objA,objB,objC] set cbList = [#obj: objList, ¬ #objMethod: #doStuff] set netObj = new(script "GNTextObj", ¬ "", cbList)
No changes are required to our callback getNetText object script as call() handles lists transparently. It's sometimes worth wrapping a single object as a list if you are unsure whether the object has the method you are trying to call as no errors are generated when the list method is used.
Conclusion
I hope this has given a few hints and tips on how to improve your code reuse and a few ideas on how to improve messaging within your Director projects. Although all the examples above have assumed that the callback has been passed in at object creation this needn't be so. By dynamically adding and subtracting objects to a callback list you can create complex object messaging systems for use in a variety of applications. If you have any questions about callbacks then you can email me.
Copyright 1997-2025, Director Online. Article content copyright by respective authors.