Articles Archive
Articles Search
Director Wiki
 

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.

Andrew White is a senior partner in Cathode, a multimedia partnership based in the West Midlands, UK. He has been using Director since it was VideoWorks and has produced Director and Shockwave work for a number of clients including HP, NCR, Budwieser, Polaroid, Digital and Sun. Contact Details: Andrew White Cathode, UK Tel: +44 (0) 1203 689395 Email: andyw@cathode.co.uk

Copyright 1997-2017, Director Online. Article content copyright by respective authors.