Articles Archive
Articles Search
Director Wiki
 

Unglobals

August 27, 1998
by Brennan Young

This is inspired by a post I saw recently on Direct-L about Object Oriented Programming theory. It was suggested that "true" OOP avoids any objects with names like "driver" or "manager". For some time now I have been using OOP lingo, quite happy with the idea of object hierarchies, often using the framescript behavior as a parent object and so on. This seemed to be all proper and decent, but it was true; some of these parent objects were "driving" or "managing" the sprites in the frame below. If this is not really "cool", then how could it be possible to have a bunch of sprites sharing the same data without making that data global, or at least at a higher level than themselves.

The answer is (as is so often the case) to use lists, indeed to make use of one of the characteristics of lists that is more often a nuisance than an advantage. Lists are stored in variables as pointers, not as values. Many (including myself) have fallen foul of this by setting a variable to a list that already exists, thinking that a new list will be created for the new variable. You can demonstrate this for yourself in the message window:


set mylist to [1,2,3,4]
set yourlist to mylist
add yourlist, 99
put mylist
-- [1,2,3,4,99]

Your world falls apart until it is realised that modifying variable yourlist also modifies variable mylist because they both point to the same list in RAM. (The solution to this dilemma of course is to use the duplicate() function so that a new, but otherwise identical list is created in RAM).

If it is possible for lists to have more than one reference, it is also possible for these identical pointers to be stored in the properties of more than one behavior, rather than in a global. The behavior then "sees" the data as its own private list, it just so happens that a whole bunch of other sprites do too. This means that the shared data is fully encapsualated and is only ever the property of one or more sprite behaviors, but it is still effectively "global".

So much for the theory, now for the practice. The main problem with implementing this is to prevent the list from being recreated anew by each sprite. Remember, the list is being stored in a property, not a global. This means that the behavior must only create the list if it is the first object to be instantiated. It therefore needs to check all the other sprites and find out whether any of them, using the same behavior, have already created the list. Fortunately, Director provides the scriptInstanceList of sprite as a default sprite property, so we can at any time find out whether a sprite has an attached and instantiated behavior. If no behavior has yet been instantiated, the list is empty.

As we might (or might not) expect, Director starts at the top and runs all the way down to sprite 120, calling the beginsprite handler of each new behavior it finds in turn. This happens whenever the playback head enters the span of a new sprite. This means that beginsprite has finished executing in sprite n before it is called in sprite n+1. The beginsprite handler, therefore, is an ideal place to create, or get a pointer to the shared data. Let's have a look at some code:


--UnglobalDemoBehavior 
property sharedData
--this would otherwise be global!
property localdata, mysprite
on beginsprite me
  --initialise all local properties first,
  set localdata to 100
  set mysprite to the spritenum of me
  -- then...
  set myscriptname to the name of member ¬
    the scriptnum of sprite mysprite 
  repeat with s = 1 to 120
    if the scriptnum of sprite s = 0 ¬
      then next repeat 
    --no behavior, do nothing
    if s = mysprite  then next repeat
        -- it's me, do nothing
    
    if string(the scriptInstanceList of sprite s) ¬
      contains myscriptname then
      -- it's another instantiated 
      -- behavior of the same type
      set imTheFirst to false
      -- another sprite of this type has 
      -- already been instantiated
      set sharedData to the sharedData of sprite s
          --get a pointer to its sharedData
      return
          -- and that's that.
    end if
    
  end repeat
  
  -- if we got this far then
  -- no other sprites of this type 
  -- have been instantiated yet
  set sharedData to [#scale:8, #boundaries:¬
    rect(0,0,320,240)]
end

In this example, I am using a repeat loop to run through all the sprite channels. I'm also using the scriptnum of sprite, to find out whether each sprite has a behavior at all, and also whether the behavior has been instantiated in that sprite. Now,


string(the scriptInstanceList of sprite s)

is likely to have a value something like


"[<offspring "UnglobalDemobehavior" 1 4266c52>]"

So if the name of the behavior...


the name of member the scriptnum of sprite mysprite

...is present in the string, we can be fairly sure it is a behavior of this type. I therefore use contains... to identify whether a sprite has an instantiated behavior of this type. As soon as a match is found, we know that the sprite can point to the same list that is stored in the property of that behavior, which of course has the same name, the sharedData of sprite s. The handler (and loop) then finishes executing with the return statement.

If, however, the repeat loop runs its course through all the sprite channels and does not find a single instance of its own type, it can freely create the shared data. Note that this data can be of type #list, #plist, #instance and #object, because all of these are passed by reference rather than by value.

Be careful that this last part of the handler will only execute the behavior the creates the shared data. Any local data should be initialised before the loop. You could also initialise a flag variable as true before the loop called imthefirst (or something) which will be set to false as soon as it is realised that the behavior is not the first of its type to be instantiated. This could be tested after the loop and if it is still true, create the sharedData.

I have made use of this approach a few times now to more fully encapsulate behaviors that otherwise required external control or to share data. Most recently I used this technique to modify Ian Clay's excellent free3d behavior so that all the essential code is now in one place, a single drag-and-drop 3d behavior.

The dream of OO is reuseability through blackbox encapsulation. I would urge anyone who is making behaviors "with blurry edges" to follow these ideas so that a single cast member contains all the necessary code to do its job.

Brennan Young is an English freelance multimedia designer / programmer living and working in Copenhagen, Denmark. He started making interactive presentations on Commodore and Acorn computers as a teenager in the early 1980s, but went on to study Fine Art and Art history at Goldsmith's College, London where he discovered Director. He teaches and lectures on multimedia at various institutions around Denmark. When he is not fiddling with interesting authoring software, he composes and performs music, writes theoretical and practical articles, and 'makes art' for exhibitions or private consumption.

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