Articles Archive
Articles Search
Director Wiki
 

Object Oriented Debugging in Director

October 8, 1998
by Zac Belado

The Problem

I can vaguely remember the first scripts I wrote that used objects. If I remember correctly, they were a set of objects that I used to keep track of on-screen interface elements. The experience was almost liberating as it opened an entirely new avenue of Lingo programming to me. If those memories are somewhat diminished in my memory, the pain of my first experience with debugging object code is still as fresh now as it was then.

One of the benefits of object oriented programming in Director is the creation of methods and hierarchies that exist in their own memory space. You can define properties in an object, assign them and forget all about them -- safe in the knowledge that the object will always remember them. Unlike globals, you don't have to constantly remember to include their declaration in handlers. The downside of this process is trying to view those properties while your project is running. The object's properties are accessible only to itself and any ancestors it has, which means that standard debugging tools like the Watcher window won't display the values of an object's properties unless you are stepping through execution with the debugger. The same containment that makes objects so useful is also their downfall when you are trying to debug an object or, more likely, a large number of instances of an object.

Now, this isn't a problem if your object generates a script error. You can open the Debugger window and have a close look at all your objects properties. But what are you to do if the object isn't generating a script error, but is still acting in a way that it shouldn't? Let's say, for instance, that you have a series of objects that are animating sprites on the screen. All your code compiles and runs without an error, but half of the sprites are moving in the wrong direction and not stopping when they are supposed to. How do you take a look at an objects properties and still have your project run? The usual answer to this question is that you don't. But with the addition of a few undocumented additions to some standard Lingo keywords you can easily get Director to display object properties.

Object properties as property lists

In order to enable run-time debugging of objects, you have to understand how Director saves object properties. Or, more to the point, the rather sneaky way you can access them. To demonstrate this, let's create a very simple object. In Director, open the script window and enter the following:


property thisOne, another, yetAnother
on new me
  
  set thisOne to 1
  set another to "2"
  set yetAnother to "garfle"
 
  return me
  
end new

Name the script "simpleObj", compile the code and save. Now open the Message window. We'll be doing most of our experiments from here so keep it open. Create a new instance of the object by entering set x to new (script"simpleObj"). Now let's do something counter-intuitive. Enter put count (x) in the Message window. Oddly enough, the result we get back happens to be the number of properties that our object has.

 

Director seems to retain an object's properties by saving them in a form of property list. I say "form of" because there isn't any actual list that you can access directly but some of the property list operation keywords actually work with objects, despite what their descriptions in the Lingo dictionary say. So, if your object has 5 properties, then count () will return 5 and not give you a script error as you might expect.

Let's try some property list operations on our object and see what results we get. If this is a form of a property list we're dealing with then what will getPropAt do? The syntax for getPropAt is (list, Index), so if we pass our object as the list , as we did with count, and a number within the result we got back from count (x) as index then we should get back a property name. If we enter getPropAt (x,1), the result #thisOne is the name of our objects first property returned as a symbol.

So how do we get the value of that property? In a "regular" property list we would use getaProp to get the value associated with a property. So, if we enter getaProp (x, #thisOne) in the Message window it should give us the value of property thisOne.

Which it does, despite what the Lingo dictionary, and past experience, might tell us.

Displaying properties

At this point, it should be easy to add a new method to our object so that it will display a list of its properties in the Message window. Open our original script "simpleObj" up and add our new method.


on display me
  
  put "Me" && me & RETURN
  set x to count (me)
  repeat with index = 1 to x    
    set propName to getPropAt(me, index)
    set propValue to getaProp(me, propName)
    put propName & ":" && propValue
  end repeat
end display

Now we have the means to get the object to display its property values at anytime through the Message window. To test it, recompile the script, go to the message window and create a new instance of this object set x to new (script"simpleObj"). We need to create a new instance of this object because we have added a new method to the code. Now enter display x.

The object obliges by producing a list that contains its own reference and all its properties and their values.

If we can get a script to display its properties, is there any way to view these properties during runtime without having to access one of the objects methods? Luckily for us, the watcher window can not only display variables but also evaluate expressions. Open the Watcher window. Enter x, the variable we last declared as an object instance, and click the Add button. In the Watcher window display we see our variable name and the object reference it represents. Now enter getaProp (x, #thisOne) and click Add again. This time, the Watcher window evaluates the command for us (much like the message window) and displays the value of the property "thisOne".

Watching properties during runtime

In order to test this further, let's create a simple object to move a sprite across the screen. Open the paint window up and create a small coloured square (size is irrelevant, but smaller is better). Drag this member into sprite channel 1, and in the same frame enter go to the Frame as that frame's exitFrame script.

Now, open the script window and enter this new object. Name it moveHorz and compile the script.


property locX, locY, direction, mySprite 
on new me, aSprite
  
  set mySprite to aSprite
  set locX to the locH of sprite mySprite
  set locY to the locH of sprite mySprite
  set direction to 1 --forward in this case
  add (the actorList, me)
  
  return me
  
end new

on stepFrame me
  
  case (direction) of
    1:
      if locX + direction > 500 then set direction ¬
        to direction * -1
    -1:
      if locX + direction < 50 then set direction ¬
        to direction * -1
  end case
  
  set locX to locX + direction
  
  set the loc of sprite mySprite to point(locX, locY)
  updateStage
  
end stepframe 

on display me
  
  put "Me" && me
  repeat with index = 1 to count(me)
    set propName to getPropAt(me, index)
    set propValue to getaProp(me, propName)
    put propName && ":" && propValue
  end repeat
  
end display

This object simply adds itself to the actorList and on every stepframe moves itself a pixel forward or back depending on its direction. I've arbitrarily set it to move between 50 and 500 on the stage. It also moves a pixel at a time to allow us to leisurely change its properties and see the results.

With everything ready, open the Message window and run the project. Nothing should be happening at this point. Now, if we create a new instance of our object in the message window by entering set x to new (script"moveHorz",1) our object should start moving, slowly, across the screen.

We now have an object in memory to test. Go to the Watcher window, enter x and click the Add button. The watcher displays our object reference as it should. Now enter getaProp (x, #locX) and click the Add button again. Not only does the watcher window now show the locX property of our object but it updates it as the object moves across the screen.

Modifying properties on the fly

We can now watch our object's properties as they change during runtime. Now let's change them while the project is running. Obviously, you could write a method that would accept a new value for the property. But with these new keywords in our object toolbox we can modify properties without having to create a new method.

Using the moveHorz object we have already created, run the project again. If you haven't cleared the actorList, the object should begin to move the sprite across the screen. If you have, you'll have to create a new instance of the object.

In the Watcher window enter getaProp (x, #direction). The result should be either 1 or -1 depending on whether the sprite is moving to the left or right. Now, in the Message window, enter setProp (x, #direction, -1) (or 1 if the direction is already 1). The sprite should now start moving in the opposite direction.

Any of an object's existing properties can be modified in a similar manner. These few modifications of property keywords give you almost full control over the display and modification of properties during runtime. Almost as much control as you currently have over global variables using the existing debugging tools.

None of the other property list operations appear to work on objects though. Sort, addProp, deleteProp, etc all return handler not defined errors. And oddly getaProp doesn't generate an error, but does fail to return the property value. Obviously, Director does not literally treat object properties as a property list, but still allows you limited access to them through those keywords. Even so, these few new uses allow you to track and modify your object's properties during runtime.

There is one small "bug" associated with all this. If you have an object, let's say our simpleObj, and it has an ancestor, then any modifications you do to the properties of the initial object will actually be reflected in the ancestor. This applies not only to the addition of new properties but also the modification of properties that the object already had. If simpleObj has a property called mySprite and the ancestor doesn't, then setprop (simpleObj, #mySprite, 3) will actually result in the ancestor object gaining a new property called mySprite and having its value set to 3.

Adding ancestors "on the fly"

If we can add properties at will, then why don't we try to add an new ancestor property to an object after it has been created. As I noted above, you actually won't be able to modify this ancestor once it has been added (at least I haven't found a way to do it yet).

First we'll have to make some modifications to our display method in order to get it to display the properties of an ancestor. Open the simpleObj script and modify the display method so it matches the following.


on display me
  
  put "Me" && me
  repeat with index = 1 to count(me)
    set propName to getPropAt(me, index)
    set propValue to getaProp(me, propName)
    put propName && ":" && propValue
  end repeat
  
  if objectP (getaProp (me, #ancestor)) then
    put "Object's Ancestor:"
    display the ancestor of me
  end if
  
end display

The method now checks to see if the object has an ancestor property and then queries the ancestor for its properties if such an object exists. Since the new ancestor also shares the object's methods, we will be able to get the ancestor to display a list of its properties even if it doesn't have the display method itself.

Now let's create a dummy object. This new object will just initialize itself, return a reference and carry a few properties. Create a new script member and enter the following code.


property first, second, third
on new me
  
  set first to 99
  set second to "Q"
  set third to "BooBerry"
  
  return me
  
end new

Save this script as "kiddie", compile all our new code and save the project.

In the Message window, create a new instance of our main object by entering set x to new (script"simpleObj"). As before, we have to create a new object in order to make it use the new code we've entered. Now if we enter display x in the Message window we should get the following.

This is the result we got previously. Now lets test our theory by adding a new property to our object. Enter setProp (x, #newProp, "Wow!") and then display x.

Our object now has a new property that it didn't have originally, and that isn't in the script that created the object. Even though the script for simpleObj didn't contain a property called newProp, we've been able to manipulate the memory space where Director has stored the object to add a new property. Now let's try to add an ancestor.

The property needs to have a reference to the object -- that is, to act as the ancestor. Normally this is done by either setting the property to an existing object's reference or by creating a new object instance with the new keyword. We will use the second method and just embed a new keyword in the setProp command we will use. Enter setProp (x,#ancestor,new(script("kiddie") in the Message window. Now if we enter display x not only does the method report the new ancestor property, but it also reports the properties of that ancestor object.

As I noted above, if you try to modify this new ancestor property either by trying to void it or change it to a new object then the changes will actually take place in the ancestor object. If, for example, you try setProp (x,#ancestor,0) this will actually add a new ancestor property to the kiddie object. This only appears to happen with the ancestor property. You can change any of the other properties at will, but any modification of the ancestor property will appear in the object next in the inheritance hierarchy. But maybe thats the price you have to pay for warping the very fabric of Lingo.

This article was originally published in the Macromedia Users Journal and is reprinted with their kind permission.

Zac Belado is a programmer, web developer and rehabilitated ex-designer based in Vancouver, British Columbia. He currently works as an Application Developer for a Vancouver software company. His primary focus is web applications built using ColdFusion. He has been involved in multimedia and web-based development, producing work for clients such as Levi Straus, Motorola and Adobe Systems. As well, he has written for the Macromedia Users Journal and been a featured speaker at the Macromedia Users Convention.

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