A memory space of one's own
August 20, 1998
by Alan Levine
What do you use to carry your most important stuff? For some of us, the answer may be a titanium briefcase from the Sharper Image, while others shuffle around pushing a grocery cart.
In the world of Lingo, you might use several kinds of "containers" to cart around important data in your code. In this article, I'll share some ones I've used, ranging from the obvious global variables, to property lists, to parent-script generated objects. I would never claim these to be the best way to write code, but maybe they'll generate some ideas when you are rummaging around the script window.
At some point the most elegant, sleek code in the world gives way to what is produced most rapidly and works from the outside. After all, a coder's work should be invisible, eh?
At the end, I'll toss in a whole pile of Lingo to try to show how a custom object can do more than just carry your possessions, think of it as a smart 007 briefcase (license to kill with lingo!).
Go Global
GLOBAL VARIABLES ARE THE MOST OBVIOUS PLACE to store data you want to use in different parts of your project or sharing data between Director files. I shouldn't have to explain how they work (if so, the rest of this article will be even more confusing), but the most obvious benefit is that the data persists and you can carry it anywhere during playback and authoring, from one Director file to another, from one shockwave file to another (if you have jumped via a goToNetMovie command).
For the less-global aware, the easiest way to declared them is at the top of a movie, frame, or parent script. That way, all handlers in the script can access them. In D4 and D5 days, this was an undocumented feature, but has been stable in every piece of Lingo I've strung together.
global gUserName, gCreditCardNumber, gShoppingBasket
on makeAnOrder
if length(gCreditCardNumber) > 0 then
chargeItToJohnDowdell
else
go to frame "get card num"
end if
end
Also, a good practice is the naming convention of global variables starting with the letter "g" making it easier to recognize them floating around your scripts.
More global trivia- any variable assignment you make in the Message Window while authoring in Director makes that variable a global; this can cause confusion if you use a variable by the same name in a handler that does not expect to have a global extent.
But what about globals? Well somewhere, the baggage has weight since globals live in your RAM. One benefit of global variables is that they can hold really BIG variables; I may be wrong, but I recall that there is no 32k limit on variables like there is on fields, so potentially, a global vairable could cart around a huge wad of text. In a large project or just because you are a tidy coder, it makes sense to clean up globals after you are done with them. In Director 6, there is a declared constant VOID that makes it easy:
global gVoid
on initMovie
set gVoid = value("void")
end
In the message window:
-- Welcome to Director -- put gVoid -- Void
So whenever cleanup was needed, like when leaving one Director 5 movie for another I would write:
on stopMovie global gMyVariable, gMyOtherVariable, gVoid set gMyVariable = gVoid set gMyOtherVariable = gVoid end
In Director 6, your cleanup would go like:
on stopMovie global gMyVariable, gMyOtherVariable set gMyVariable = VOID set gMyOtherVariable = VOID end
Of course, in most Director movies, this may not be necessary as you may never eat up much memory with your globals. How would you know? You can do tracking of memory usage via several Lingo functions, or use the Memmon application created by Alex Zavatone. My yardstick would be to cleanup if you end up creating more than 20 global variables, or if any of them will hold large chunks of text (>20k). But you cannot go wrong by cleaning up (well, unless you accidently trash some values you need later on). I used it on several projects that involved 10 to 1,250 Director movies that could be accessed within the same session and I used globals to store long text strings and deeply nested lists.
There is another reason to cleanup. For every global variable you declare, its name gets added to Director's symbol table, an internal inventory of all handlers and globals that Director has to find when it runs your application. So with a lot of differently named global variables, there have been reports of glitches and bad voodoo when this symbol table gets filled up.
Property Lists are Great Containers
Rather than creating a pile of different global variables, you can lump them into one large suitcase via a property list that is itself declared as a global variable. I think it can make code more readable when you refer to items in a property list by the syntax
set the accountBalance of MyBank = 1,000,000
The following example uses a global property list to save the conditions of the viewer's computer when my projector starts up and then restores things when they leave (I grumble when some application switches my monitor to another bitdepth, or yanks the volume to the max). I only try this on the Macintosh since it does not seem safe to dinker with the bit depth on all PC boxes.
This is a simple example, with only 2 items in the global list, but I have used rather long ones to do things like keeping track of the viewer's name, how many of my lessons they've completed, how long they've been in the program, etc. This is expanded in the last section, where I try to show you how to set up an object to manage the information. But let's take a look at a property list as a data container:
on saveStartingConditions
-- store the starting sound level and bit
-- depth in a 2 item property list
global gSaveState
-- Don't allow any quick exits, make
-- them use my quit button
set the exitLock = 1
if the machineType <> 256 then
-- Macintosh
set gSaveState = [#sound:the soundLevel, ¬
#colors:8]
-- crank up the Mac volume
set the soundLevel = 7
-- go to 256 colors
if the colorDepth > 8 then
setProp (gSaveState, #colors, the colorDepth)
set the colorDepth = 8
end if
end if
end
And whenever they click through my "exit button", we do:
on restoreStartingConditions
global gSaveState
if the machineType <> 256 then
-- if we are on a Mac, restore the
-- colordepth and sound volume
set the colorDepth = the colors of gSaveState
set the soundLevel = the sound of gSaveState
halt
else
-- if we are on a Mac, go to a frame with
-- the a Windows palette colors
-- so it does not cause them to see a wild palette
-- shift at the desktop
go to frame "windozed"
end if
end
The Super Dooper Object Container
NOW LET'S GO FOR THE BIG STUFF. Objects.
You can pretty much do most data tracking with a property list as a global variable, but sometimes you end up wanting to be able to manipulate or calculate things for the data you are carrying. An object created by a parent script becomes useful here because it not only contains the data, but also contains complex functionality you can build onto it. The nifty thing is that once you create the object and stick it inside a global variable, its functionality stays with it, even when you leave the movie from which you first created the object. Also, you can write cleaner code because it passes off some grunt work to the object
Now, I am so far from an OOP expert that it's not funny; most of the time I have created objects, it has been as a convenient way to organize information. This following chunk of code comes from a project where we wanted to track a user's progress in a program, which called for some fiddling with time variables, and also to carry around a text log.
In the program, but not explicitly shown here, an external file is created to store data about the viewer when they quit the program. If they return later, we prompt them to locate their data file, so we can restore them to where they left off.
So, here below, a chunk of Lingo to create an object to track information about a user. It is designed so we can pass it a list of director files that we want to track the time spent in (you can hopefully think of a way to dynamically generate this at runtime with the getNthFileNameInFolder function). We would create the object upon entry into the program, by coding:
global gUserObject
on startMovie
set gUserObject = new( script "userInfo",¬
moviesInThisFolder() )
logFileEntry gUserObject
end
on moviesInThisFolder
-- get all files in the same
-- directory as this movie
-- that are director files
put [] into fileList
repeat with i = 1 to the maxInteger
put getNthFileNameInFolder(the pathName,¬
i) into nextFile
if nextFile = EMPTY then exit repeat
if nextFile contains ".dir" then ¬
append(fileList, nextFile)
end repeat
return fileList
end moviesInThisFolder
For every movie in our project, we'll record in our log when we entered it:
global gUserObject on startMovie logFileEntry gUserObject end
Whenever we leave a movie file, include a stopMovie handler to record how much time was spent in the file we are leaving:
global gUserObject on stopMovie markTrail gUserObject end
And at anytime we can write a log entry:
global gUserObject
on mouseUp
updateLog gUserObject "Selected item from menu 4"
end
And when we exit the whole program, we can create a summary of the time spent in the different movie files and then write out the contents of the session log (use FileIO for this).
global gUserObject on leaveItAllonQuit logSummary gUserObject writetoDataFile(the sessionLog of gUserObject) end
So here is the guts, entrails, and beating heart of the object. It may not be pretty but hopefully it can give you an idea of functionality that you can embed into custom designed objects. In addition, I've created a Director 5 demo movie that lets you see how it is done:
Mac ftp://ftp.maricopa.edu/pub/director/sit/userobj.sit.hqxWin ftp://ftp.maricopa.edu/pub/director/zip/userobj.zip
property pUserName, pFirstTime
property pTimeStart, pSessionLog, pTrail
-- User Property Object
-- progrmming by alan
-- PARENT SCRIPT: userMaker
-- creates the object that holds data about the user
-- create an object to store
-- information about the user:
-- pUserName: their name
-- pFirstTime: flag (1=true, 0=false) if this
-- is first time in program
-- pTimeStart: time (in ticks) that the user
-- entered the program
-- pSessionLog: log of user actions
-- pTrail:
-- property list for time spent in
-- each movie file
-- specified by the parameter mList
on new me, mList
-- create the user object
-- mList is a linear list ofDirector
-- movie file names representing
-- the files we want to track time spent in
-- assume they have not been in the program before
set pFirstTime= 1
-- initialize properties
set pTimeStart = the ticks
set pUserName = ""
set pSessionLog = ¬
"--------------------------------------------" ¬
& return & "started session / at / " && the long date ¬
&& the long time & return & return
-- set up an initial list to hold
-- the the amount of time spent
-- in each of the files we are tracking.
set initTimeList = []
repeat with i = 1 to count(mList)
append initTimeList, 0
end repeat
-- Set up a property list to hold
-- the "trails" information
set pTrail = [ #mFiles: mList, ¬
#mTime:initTimeList, ¬ #startTime:0 ¬
, #when:the date]
return me
end
on markTrail me
-- updates the user trail, called
-- by the exit of one movie
-- get the time in seconds spent
-- in the current file
set currentTime = (the ticks - ¬
the startTime of pTrail)
-- get an integer code for the current file.
-- If it is not one of the files
-- we are tracking, this
-- function will return 0
set fileID = getPos( the mFiles ¬
of pTrail, the movieName)
if fileID <> 0 then
-- update the property list since we are
-- in a file we are tracking
-- first, dig out the total spent in the file
-- from previous visits.
set prevTime = getAt(the mTime of pTrail, fileID)
-- now update the value by adding the time
-- just spent in the file
setAt(the mTime of pTrail , fileID, ¬
prevTime + currentTime)
end if
end
on timeInFile me, fName
-- returns a formatted string
-- representing the time spent in
-- the file specified by the
-- input paramter "fName"
-- get an integer code for the current file.
-- If it is not one of the files
-- we are tracking, this
-- function will return 0
set fileID = getPos( the mFiles of pTrail, fName)
if fileID <> 0 then
return sessionLength( me, getAt¬
(the mTime of pTrail, fileID) )
else
return 0
end if
end
on updateLog me, upDateString
-- adds an entry to the log stamped
-- by the program time
-- (time relative to the time entered
-- into the program)
put "log>" && sessionLength(me, the ticks ¬
- pTimeStart) & ": " && upDateString & ¬
return after pSessionLog
end
on logFileEntry me
-- record the entry into a new movie
-- file, should be called
-- by a startMovie handler
-- reset the clock for when we entered this movie
set the startTime of pTrail = the ticks
updateLog me, "filename =" && the movieName
end
on sessionLength me, timeToCalculate
-- converts input time in ticks to
-- formatted output of hh:mm:ss
set seconds = timeToCalculate / 60
set hours = seconds / 3600
set minutes = (seconds mod 3660) / 60
set seconds = (seconds mod 3660) mod 60
return pad(hours) & ":" & pad(minutes) ¬
& ":" & pad(seconds)
end
on pad x
-- insert "0" chars for single digit values of x
if x < 10 then
return "0" & x
else
return x
end if
end
on logSummary me
-- called to write a summary to the log
set temp ="LOG SUMMARY" & return
-- step through the list of movies we are tracking
-- and convert each time total to a formatted string
repeat with i = 1 to count(the mFiles of pTrail)
set nextMovieName = getAt(the mFiles of pTrail,i)
put "time in file" && QUOTE ¬
& nextMovieName & QUOTE ¬
&& " :" && timeinFile(me, ¬
nextMovieName) & return after temp
end repeat
updateLog me, temp
end
Okay. This may be a lot of Lingo to digest. And this is but the tip of the OOP iceberg for what kinds of "intelligence" (or at least decent reflexes) you can construct with object code.
Copyright 1997-2025, Director Online. Article content copyright by respective authors.