LOOPE - Lingo Object Oriented Programming Environment by Irv Kalb

Previous Chapter

Table of Contents

Next Chapter

 

Section 3 - Behaviors and Objects Together

Chapter 12 - Multiple movies

 

In this chapter, we will discuss startup and shutdown issues when working with multiple movies. This may seem like an unlikely topic for a book dealing with object oriented programming. However, if you go back to our definition of an object, data plus code that acts on that data over time, you will see that the time aspect is one of the key elements.

When you attach a behavior to a sprite, the sprite span defines a logical starting and stopping point for that behavior - or more accurately, a creation and destruction point. Further, all behaviors are sent a standard beginSprite and endSprite message. As a behavior writer you can code an "on beginSprite me" and an "on endSprite me" method in your behaviors to easily handle the creation and destruction of behaviors. However, when dealing with objects, these events are not as straightforward.

Although it may not be considered "pure" object oriented programming, I am a strong believer in creating a small number of global objects to isolate portions of functionality in the code. This provides for a very practical approach for splitting a large programs into more manageable chunks, where each object has clearly defined responsibilities. Further, by making such objects global, each is easily identifiable and sending messages to each is done directly. A good example of this is the sound manager that we have previously described. Another good example might be a navigation manager (as will be discussed in the next chapter) which can provide a place for all navigation code and data.

Given that we want to create such global objects, the question arises as to where to create and destroy such objects. Here is where the world of development and the world of the end user diverge. When an end user uses a finished program, the code always starts from the beginning (the PrepareMovie handler of the first movie), either as a projector or as the first Shockwave movie. Clearly, any global objects should be created as soon as possible in the first movie in the PrepareMovie handler. When the user quits the program, all global objects just go away.

However, the world of development is very different. During development, you often want to start and stop the program, add code, and start again. And many times, you don't want to start from the first movie. Imagine that you have a project that is made up of multiple movies. Further, under the user's control, the program could be going from one movie to another movie - even back to the first movie. As a developer, you want to have the ability to run your program, stop at any point, make code changes, and start running again, but not have to start from the first movie again.

Specifically, imagine that you have a project made up of Main.dir, A.dir, and B.dir. The end user will always start in Main.dir and then can navigate to A.dir or B.dir. From there the user can go back to Main.dir or the other of A.dir or B.dir. During development, you would want to be able to start running with A.dir or B.dir loaded. But, if you only create your global objects in Main.dir, then you must run Main.dir to initialize your world. That is, unless you know the trick. Here's how to do it - broken down into four parts.

 

Part 1 - Create the global objects in a shared prepareMovie handler

You only want your global objects to be created when you first start running. And no matter what module you are in when you start, you just want all your global objects to be instantiated. Because only one piece of code should create your global objects, it should live in a castlib that is shared by (linked to) all your movies. For example, you can create a castlib called GCode.cst (for global code). In that castlib you would have a movie script with a handler called "SharedPrepareMovie", that instantiates all your global objects. Then each of your movies will call this code so that whichever movie runs first, it will instantiate the globals. In a movie script you would have some code like this:

global go1
global go2

on SharedPrepareMovie
  go1 = new(script
"Object1")
  go2 = new(script
"Object2")
end

Then, in each of your movies, you have their prepareMovie handlers call this handler like this:

on prepareMovie
  SharedPrepareMovie()
  -- Any other initialization code that you want for
  -- each specific movie goes here.
end

This way, no matter what movie gets started first, it will instantiate the global objects. However, every time that the user navigates from one movie to another one, your global objects will be re-instantiated.

 

Part 2 - Only create global objects at the beginning of the first movie

Clearly, this is NOT what we want. Rather, we want the global objects to be instantiated once when we start to run, and to not be touched when we navigate between movies. The way we do this is rather simple. We add a global flag that says whether or not we have instantiated the objects. In Lingo, any uninitialized variable is set to VOID. Therefore, the approach will work like this. In the SharedPrepareMovie script, we check a global initialization flag (gfInitialized). If it does not have a value of TRUE, then we will instantiate all global objects and then set this flag to TRUE. This way, when we navigate from the first to subsequent movie(s), the SharedPrepareMovie handler is called, but it will find that the initialization flag is already set to TRUE and will not re-instantiate the global objects. The code would look like this:

global gfInitialized
global go1
global go2

on SharedPrepareMovie
  if gfInitialized then
    return
-- Already initialized.
  end
if

  gfInitialized = TRUE -- set it here
  go1 = new(script
"Object1")
  go2 = new(script
"Object2")
end

 

Part 3 - Re-instantiating the global objects

This works fine the first time we open our movie in Director. But if we run the movie and find that we have to make a change to the code of one of the parent scripts that we use to create the objects, then we do want the program to re-instantiate the global objects. We need a way to force these objects to be re-instantiated only when we want them to. There are a number of ways to do this. For example, we could go to the Message Window and type:

gfInitialized = VOID

or

ClearGlobals

That way, when we run again, the initialization code would be executed. However, there is a simpler way that doesn't involve any typing in the Message Window. In the SharedPrepareMovie script, we can check for one or more modifier keys being down when we start to run. I typically use the Shift key. If the Shift key is down when I start the program running, it is a signal to the code that I want to re-instantiate global objects. The modification in the SharedPrepareMovie script is very simple:

if (the runMode = "Author") and (the ShiftDown) then
    gfInitialized = VOID
  end
if

This way, in development, whenever I stop the program, make a change, and want to re-create the global objects, I simply hold down the Shift key when I start the program running again.

Note: The above work fine if you click on a "Play" button to start your Director movie running. If, however, you use the keyboard to start your movie, you would want to use the Option or Alt key as the special modifier key.

 

 

Part 4 - Cleaning up

The final part deals with cleaning up global objects. From a design point of view, you would like to be able to clean up all global variables when your program stops. Unfortunately, Director does not have such a hook. There is a stopMovie event, but it gets generated when you leave a movie to go to another movie, and also when you stop running. Therefore, there is no way to tell the reason that the stopMovie event is being generated. Therefore, you cannot use a stopMovie handler as a convenient place to clean up your global objects.

The only reliable place to force a cleanup of global objects is right before you create new ones. That is, when you are about to instantiate your global objects, you can check to see if there already is an older global object. If so, call its cleanup method, then clear out the object reference. After that is done, then instantiate the new object. Here is what this would look like in the SharedPrepareMovie handler:

global gfInitialized
global go1
global go2

on SharedPrepareMovie
  -- Check if we are forcing a reinitialization
  if (the
runMode = "Author") and (the ShiftDown) then
    gfInitialized = VOID
  end
if

  if gfInitialized then
    return
-- Already initialized.
  end
if

  gfInitialized = TRUE -- set it here

  if objectp(go1) then -- allow object to clean up
    go1.mCleanUp() -- eliminate old version of object
    go1 = VOID
  end
if
  go1 = new(script
"Object1") -- instantiate new version

  if objectp(go2) then -- allow object to clean up
    go2.mCleanUp() -- eliminate old version of object
    go2 = VOID
  end
if
  go2 = new(script
"Object2") -- instantiate new version
end

 

Conclusion

Now we have all the steps we need to deal with creating and cleaning up global variables across multiple movie projects. When implemented, it becomes a simple thing during development to just hold down the Shift key to re-instantiate all global objects whenever you want to.



Previous Chapter

Table of Contents

Next Chapter