 |
Now you can use DreamLight DreamObjects LingOOP Framework for rapid Director development. Download the free example DLI_ActorMan, the actorList manager.
|
I have compiled and edited some of my past Direct-L posts about many miscellaneous Lingo issues here
for convenient reference. Armed with these tips & tricks, you should be able to
write more effective, error free Lingo code.
The actorList is so useful weve used it for all sorts of projects including the award-winning KeyQuest edutainment CD-ROM and also Quipples: The Internet Game Show of Satirical Riddles. I had even implemented my own
TheActiveObjects stack way back in Director 3s factories for the original version of DreamLight Verttice.
This was in 1992, well before there was an official actorList, but the
concept was the same. My original Lingo source code was then included in Macromedia's first Authorized Developer CD-ROM, as an example of object oriented programming with Lingo.
There are many rumors about the actorList being as mysterious and dangerous
as the Bermuda Triangle. Well, Im here to tell you that it is not at
all that difficult and can be quite useful. The greatest pitfalls arose
in Director 4 by deleting actorList objects in stepFrames, pulling the
rug out from under your poor objects feet. This used to cause various
crashes. Director 5 was much more stable and Director 6 through 8.5.1 and later seem rock solid.
Date: Feb. 12, 2002
D8.5.1 Lingo Dictionary, Page 239, ilk()
The table is incorrect, so I wrote a little handler called "ilkTest" that you can use to generate the correct table, right from the ilk()'s mouth.
Running ilkTest in the message window will generate the following:
ilkTest
-- "X= ilk(X) ilk(X,Type) = 1 Example"
-- ""
-- "integer #integer #number #integer ilk( 128 )"
-- "float #float #number #float ilk( 128.128 )"
-- "string #string #string ilk( "A String" )"
-- "linear list #list #list #linearList ilk( [1,2,3] )"
-- "property list #propList #list #propList ilk( [#A:1,#B:2] )"
-- "rect #rect #list #rect ilk( rect(0,0,640,480) )"
-- "point #point #list #point ilk( point(320,240) )"
-- "color #color #color ilk( rgb("#FFFFFF" ) )"
-- "date #date #date ilk( the systemDate )"
-- "symbol #symbol #symbol ilk( #symbol )"
-- "picture #picture #picture ilk( (the stage).picture )"
-- "image #image #object #image ilk( (the stage).image )"
-- "script instance #instance #object #instance ilk( new( script "ilkTest" ) )"
-- "xtra instance #instance #object #instance ilk( new( xtra "fileIO" ) )"
-- "member #member #object #member ilk( member(1) )"
-- "xtra #xtra #object #xtra ilk( xtra("fileIO") )"
-- "script #script #object #script ilk( script("ilkTest") )"
-- "castlib #castLib #object #castLib ilk( castlib(1) )"
-- "sprite #sprite #object #sprite ilk( sprite(1) )"
-- "sound #instance #object #instance ilk( sound(1) )"
-- "window #window #object #window ilk( the stage )"
-- "media #media #object #media ilk( member("ilkTest").media )"
-- "timeout #timeout #object #timeout ilk( timeout("A").new(1,#a)) )"
-- "void #void #void ilk( VOID )"
-- "NAN #float #number #float ilk( sqrt(-1) )"
-- "<Null> #void #void ilk( call(#m, []) )"
<Null> is a strange beast, it is similar to VOID, but not quite, as the following will illustrate:
X = call(#m, [])
put X
-- <Null>
put X = VOID
-- 1
put ilk(X)
-- #void
put ilk(X, #void)
-- 1
put voidP(X)
-- 0
If there are any other types that ilk() reports, that I missed, let me know and I'll add them...
During the next documentation update, simply add any new types and/or thingsToTest into the appropriate lists, and run ilkTest again, it will actually use ilk() itself to generate a valid table for you, so we should be able to get this table fixed in the docs...
- Copy this script into a script castmember and name the member "ilkTest"
- Then run ilkTest in the message window...
on ilkTest
thingsToTest = [:]
thingsToTest.addProp( "integer", "128" )
thingsToTest.addProp( "float", "128.128")
thingsToTest.addProp( "string", QUOTE & "A String" & QUOTE)
thingsToTest.addProp( "linear list", "[1,2,3]")
thingsToTest.addProp( "property list", "[#A:1,#B:2]")
thingsToTest.addProp( "rect", "rect(0,0,640,480)")
thingsToTest.addProp( "point", "point(320,240)")
thingsToTest.addProp( "color", "rgb(" & QUOTE & "#FFFFFF" & QUOTE & " )")
thingsToTest.addProp( "date", "the systemDate")
thingsToTest.addProp( "symbol", "#symbol")
thingsToTest.addProp( "picture", "(the stage)Picture")
thingsToTest.addProp( "image", "(the stage)Image")
thingsToTest.addProp( "script instance", \
"new( script " & QUOTE & "ilkTest" & QUOTE & " )")
thingsToTest.addProp( "xtra instance", \
"new( xtra " & QUOTE & "fileIO" & QUOTE & " )")
thingsToTest.addProp( "member", "member(1)")
thingsToTest.addProp( "xtra", "xtra(" & QUOTE & "fileIO" & QUOTE & ")")
thingsToTest.addProp( "script", "script(" & QUOTE & "ilkTest" & QUOTE & ")")
thingsToTest.addProp( "castlib", "castlib(1)")
thingsToTest.addProp( "sprite", "sprite(1)")
thingsToTest.addProp( "sound", "sound(1)")
thingsToTest.addProp( "window", "the stage")
thingsToTest.addProp( "media", \
"member(" & QUOTE & "ilkTest" & QUOTE & ")Media")
thingsToTest.addProp( "timeout", \
"timeout(" & QUOTE & "A" & QUOTE & ")New(1,#a))")
thingsToTest.addProp( "void", "VOID")
thingsToTest.addProp( "NAN", "sqrt(-1)")
thingsToTest.addProp( "", "call(#m, [])")
ilkTypes = [ \
#object, #instance, #script, #xtra, \
#member, #castlib, #sprite, #sound, #window, #media, \
#timeout, #image, #picture, \
#list, #linearlist, #proplist, \
#number, #integer, #float, \
#string, \
#rect, #point, #color, #date, \
#symbol, #void \
]
put pad( "X=", 16 ) & pad( "ilk(X)", 10 ) && \
pad( "ilk(X,Type) = 1", 18 ) && "Example"
put EMPTY
repeat with n = 1 to thingsToTest.count()
outputString = pad( thingsToTest.getPropAt(n), 16 )
put "#" & pad( string(ilk(value(thingsToTest[n]))), 9 ) \
after outputString
validTypes = EMPTY
repeat with testType in ilkTypes
if ilk( value(thingsToTest[n]), testType ) then
put "#" & testType & SPACE after validTypes
end if
end repeat
put outputString && pad( validTypes, 18) && \
"ilk(" && thingsToTest[n] && ")"
end repeat
end
--
on pad stringToPad, paddedLength
numSpaces = paddedLength - stringToPad.length
repeat with n = 1 to numSpaces
put SPACE after stringToPad
end repeat
return stringToPad
end
Hope it helps,
-MikeS
Date: Feb. 8, 2002
> We beat each other to a pulp on another list in regards to this.
Hi Zav,
Yes we did... and it was fun... ;-)
I'm not trying to resurrect old battles though...
I was giving some tips to newbies about when to use the constant VOID and when to use the function voidP() instead. The fact (for good or ill) that VOID is equal to 0, diminishes it's use as a tester for a real void condition. It can lead to difficult to debug issues, if it catches a newbie off guard.
One very important use for testing for a void condition is to validate a function or handler's input parameters and set defaults. If you use the constant VOID for such a case, like so:
on doSomething x, y
if x = VOID then x = 320 -- set default
if y = VOID then y = 240 -- set default
put "x =" && x
put "y =" && y
end doSomething
It would work for nonzero and/or void parameters as follows:
doSomething 10, 20
-- "x = 10"
-- "y = 20"
doSomething
-- "x = 320"
-- "y = 240"
You'd run into trouble if zero were passed to doSomething in x or y, like so:
doSomething 0, 0
-- "x = 320"
-- "y = 240"
In that instance you surely don't want the values changed because the parameters are not really void. They have a set value and that value is zero.
If instead you always test for a void condition this way:
on doSomething x, y
if voidP( x ) then x = 320 -- set default
if voidP( y ) then y = 240 -- set default
put "x =" && x
put "y =" && y
end doSomething
You won't have any trouble...
doSomething 10, 20
-- "x = 10"
-- "y = 20"
doSomething
-- "x = 320"
-- "y = 240"
doSomething 0, 0
-- "x = 0"
-- "y = 0"
The docs completely miss this subtle difference and suggest using the constant VOID to test for a void condition, when voidP() would be more suited to such a test.
So, I can further boil my rules of thumb down to two:
1) If you are interested in TESTING for a void condition, use voidP() rather than the constant VOID.
if voidP( someVariable ) then someVariable = 10 -- set to a default value
Or you could also use ilk() since we got all the old ilk() bugs cleaned up and it is now presentable, since D7.0.2.
if ilk( someVariable, #void ) then someVariable = 10 -- set to a default value
2) If you are trying to SET a variable to be void, then use the constant VOID.
someVariable = VOID -- we are all done with it
Following those simple rules, avoids the entire issue...
The example in the Lingo dictionary on page: 539
if currentVariable = VOID then
put "This variable has no value"
end if
is incorrect, the variable has no value OR the value zero. So it could be rewritten as I suggested previously:
if currentVariable = VOID then
put "This variable has no value, or is equal to zero"
end if
But that's really not the best use of the constant VOID anyway. The best use for it is to SET a variable to VOID. voidP() is better for testing for a void condition.
So I'd suggest changing the examples in the Lingo dictionary on pages 539 and 540 to the examples I used in my two rules of thumb above.
It would also help newbies immensely if this subtle difference between VOID and voidP() were mentioned in the Lingo dictionary, which it's not, which is why I'm discussing it here...
Of course experienced Lingo programmers may "break" my suggested rules of thumb for specific purposes. I'm sure some people out there are indeed using VOID as a test (to test for a void OR 0 condition, such as when you expect an object and want to be sure it's not void or 0), but I'm sure they also understand the subtle issue involved, and all its potential ramifications. My tips are obviously not absolute, but I think they will help newbies avoid potentially thorny issues. It's for the newbies that I bring these subtleties up...
Besides, it gives me something interesting to think about, while I have my morning coffee and warm up my synapses for the day... ;-)
:-)
-MikeS
Date: Feb. 7, 2002
One quick follow up on the constant VOID...
I'd add one more rule of thumb to my previous two...
1) If you are testing to see if something is defined, don't use the constant VOID, instead use the Lingo function voidP() (or ilk()) which WILL determine if something is really defined or not.
put voidP( z )
-- 1
2) If you are testing for something that could be void or zero, then test for zero not the constant VOID. Personally, Lingo's VOID constant is not very useful for testing if a variable is void or not, because it's also equivalent to zero.
The following can lead to debugging issues.
z = 0
put z = VOID
-- 1
3) If you need to SET a variable to be void, that is really the best use for the constant VOID.
z = VOID
put voidP( z )
-- 1
put ilk( z )
-- #void
-MikeS
Date: Feb. 7, 2002
I've always felt that Lingo's use of the constant VOID is a little bit too "loose" (since it's equal to 0). Here are some examples to be aware of...
put value( "xyz" )
-- <Void>
The string has no value so it's considered void.
put value( "0" )
-- 0
The string has the value zero.
put value( EMPTY )
-- 0
Personally I would say the string has no value and should be considered void, just like "xyz", but Lingo considers it zero, which can lead to confusion.
put VOID = 0
-- 1
Lingo considers void to be equal to zero, this can lead to all types of confusion if you're not careful. Consider this example...
put value( "0" ) = value( "xyz" )
-- 1
You obviously wouldn't be writing such a statement, but if you are comparing the value of two variables instead, it could arise, leading to debugging issues...
So, what does this all mean for the typical Lingo programmer?
You'll avoid much confusion if you use two rules of thumb.
1) If you are testing to see if something is defined, don't use the constant VOID, instead use the Lingo function voidP() (or ilk()) which WILL determine if something is really defined or not.
2) If you are testing for something that could be zero, then test for zero not the constant VOID. Personally, Lingo's VOID constant is rendered mostly useless for testing if a value is defined (or at the least, dangerous) by it's equivalence to the integer zero. IMHO ;-)
Even the 8.5 Lingo dictionary is incorrect on page: 539 Which shows that even those writing the manual missed this subtle distinction which can lead to difficult to debug problems.
The following example is incorrect:
if currentVariable = VOID then
put "This variable has no value"
end if
and should be rewritten as follows:
if currentVariable = VOID then
put "This variable has no value, or is equal to zero"
end if
Try this on an undefined variable:
put x = VOID
-- 1
Now define the variable as zero and try again:
x = 0
put x = VOID
-- 1
Now have some real fun and compare the defined variable x to another undefined variable:
put x = BOGUS
-- 1
What is happening is that the value of an undefined variable is interpreted as being equal to zero. Since the constant VOID is little more than an undefined variable, it is interpreted as being equal to zero. Therefore, I personally see no use for the constant VOID for testing, and would simply use zero myself in such an instance.
Since, the constant VOID is not very useful for testing if a variable has a value or not, I'd suggest changing the example in the Lingo dictionary entirely, and instead use VOID to set the value of a variable to VOID. This is the best use of the constant VOID. So a better example would be:
someVariable = VOID
The only way to reliably test for an undefined value is with the Lingo function voidP() (or ilk()) as follows:
Test an undefined variable:
put voidP( y )
-- 1
put ilk( y, #void)
-- 1
put ilk( y )
-- #void
Now define the variable as zero and try again:
y = 0
put voidP( y )
-- 0
This difference between VOID and voidP() can lead to logical conundrums if used for testing, such as this:
y = 0
put ( y = VOID ) AND ( voidP( y ) )
-- 0
y is equal to the constant VOID, but it's not REALLY void... ;-)
-MikeS
Date: Aug. 6, 1997
Hi Andrew,
I just ran some tests. I ran the enclosed "actorList test" in Director 4, 5 & 6 on the Mac.
Director 4 bombs after a short while.
Director 5 does not bomb. But then if I stop the movie and quit Director 5 (without emptying the actorList), it does bomb with a type 2 error.
Director 6 seems rock solid. So it looks like the problems with the actorList may become an issue of the past. Maybe Macromedia implemented some of the buffering techniques that us and others had posted to Direct-L in the past.
Feel free to run the following test in each version of Director from 4-6. Just open the message window and start the movie. It randomly births objects into the actorList, each of which has a random life span and then commits suicide... ;-)
-MikeS
Here are the scripts for a simple test movie:
-- movie script
on startmovie
global gObjectNum
set gObjectNum = 0
set the actorList = []
end
-- frame script, place in frame 1
on exitFrame
if random( 10 ) then add the actorList, birth( script"object" )
go to the frame
end
-- parent script must be named "object"
property myStep, myNum, myLife
on birth me
global gObjectNum
set gObjectNum = gObjectNum + 1
set myNum = gObjectNum
set myStep = 0
set myLife = random( 50 )
put "birthing: Object #" & myNum
return me
end
on stepframe me
set myStep = myStep + 1
if myStep >= myLife then deleteMe( me )
end
on deleteMe me
set whichNum = getPos( the actorList, me )
put "***DELETING: Object #" && myNum
deleteAt the actorList, whichNum
end
Date: Feb. 13, 1997
>My logic was that if I birthed another object, it
>would go on it's merry way and be independant of
>any other object. Instead, it seems that the method
>I'm using still makes for very linear logic flow.
In order for an object to "go on it's merry way" you must make sure you are giving it control incrementally to update itself and do its stuff...
One way to do this is to give the object an on stepFrame handler and then put a pointer of the object on the actorList. Then each time the stage is updated the object will be given a chance to update itself. Think in terms of small bite sized actions rather than continuous action.
To have animations work while other things are happening you need to program a little differently than you would otherwise. You must break all actions down to small increments that can keep track of where they are and where they are going. One way to acheive this is through LingOOP.
Hope that points you in the right direction.
-MikeS
Date: Jan. 2, 1997
I wouldn't quite make such a blanket statement as "NEVER USE the ACTORLIST!" myself.
Yes, uncontrolled use of the actorList can lead to problems. Most notably from putting master copies of objects on the list that delete themselves from the list during a stepframe handler. This is because you can't change the list itself during the loop that is processing it or you risk having the loop run past the end etc... therefore it's far from bulletproof.
The actorlist as implemented does have uses though.
If you are using simple objects that only need to animate for specific time frames you can safely add them to the list and delete them from the list from other handlers such as frame handlers. Just don't do it from a stepframe or a handler called from a stepframe.
I also wouldn't suggest putting your "master" objects on the actorlist. We keep all master objects elsewhere and simply put a pointer to the object on the list.
What do I mean by "master" object? Simply the first pointer to the object that is used to hold the object itself and keep it in memory. Then adding this master pointer to the actorList will actually only create a second, temporary pointer to the master object itself. Then deleting this pointer from the list will not "destroy" the object since the master pointer is elsewhere.
If you need more robust use of the actorList then I would suggest implementing your own managed list similar to the "activeActors" and "deletedActors" list mechanism I had posted in the past. I also posted an analogy "the shopping excursion" that helped explain the concepts of simply using the actorlist as a queue in more depth.
-MikeS
Date: Nov. 20, 1996
>In such cases, every object on the
>actorList gets sent a stepFrame message every iteration through the
>loop--unnecessary code executes in a critical loop, resulting in a
>performance hit.
Yes, that's why you should only put items on the actor list or your own internal activeActors list only when they have real tasks to perform. By only putting objects on the list that are currently animating, you can target all the processing exactly where it needs to go.
Items that are simply sitting on the screen waiting for mouseclicks should NOT be sitting on the actorlist. We usually keep them in their own control lists. We keep a global sprite list and when an object is activated for a screen control like a button, it puts its pointer into the global sprite list. Then all mousedown and mouseup messages are passed directly to the object by indexing into the sprite list. We don't put these types of objects in the activeActors list.
So I don't necessarily agree that the actorlist (or an internal custom activeActors List) should be avoided, only that it should be properly managed and used in conjunction with other control structures. Just don't try to do everything in the actor list and don't put pointers to items in the activeActors list that don't currently need to receive stepframes.
-MikeS
Date: Nov. 11, 1996
Let me clarify a concept that may not have been clear in my last post.
When I add or delete actors from the active objects lists, I am only adding or deleting pointers from the lists. The objects themselves remain happily alive and able to go about their business as they see fit. (since their master pointers reside elsewhere)
Think of it this way.
People go into the supermarket. (objects being birthed into their master list) They may be comunicating with each other and doing different things.
Now some of them go to the meat counter and put their name on a list. Well an object putting itself on the active objects list is just like this list. The object can still do other things since it has only taken a request to be called in turn.
Now lets say the person gets its turn at the counter. Well, when the person is done, it's name is crossed off the list since it is no longer waiting to be called. This crossing off the list is the same as deleting the pointer in the active objects list. It does not destroy the object itself since the object's master pointer is elsewhere in another list or a global variable etc. No more than the person is deleted from the supermarket. The person is not deleted from the supermarket until it leaves the market. The object is not deleted from memory until ALL pointers to it are deleted.
I only use the activeObjects list as a temporary queue for objects to recevie stepframes. I don't use it to hold the master pointer to objects. Otherwise it's like saying anytime someone enters the supermarket they must proceed directly to the meat counter and put their name in, and wait there without doing anything else. Then when their turn is done they are thrown out of the store... Now that's not very nice is it? Not very flexible either.. ;-)
So we are not really shuffling objects around from list to list as it may seem. We have objects that already exist as buttons in a buttonList within a control structure. (the supermarket) When the objects need servicing they just put a pointer to themselves on the active objects list. (the meat counter)
Quite simple if you think of it this way... I hope... ;-)
Did we all enjoy our little shopping excursion?
-MikeS
Date: Nov. 14, 1996
>Sounds great. My button object is still evolving and looking through your
>post, a couple of questions occur to me.
>
>Your gTheMovieMan object is in the actorList so it receives stepFrames. The
>actorObjects you are adding, are they ancestors of gTheMovieMan? If so then
>an ancestor of an object in the actorList also receives stepframes? I'll
>have to check this myself.
No need for ancestors here. Ancestors are used to provide a heirarchy of methods and properties. Such as defining an animal ancestor with an eat method and then creating a fish decendent and a bird decendent. Both the bird and the fish may eat the same way but the bird will have a fly method where the fish will have a swim method.
What we do for the active actors is this...
We birth our movie manager object into a global variable called gTheMovieMan. We then immediately put this on the "real" actorList. This is the only object we actually put on the true actorList. Note: the global variable is used as the master pointer to this object. The pointer on the actorList is a secondary temporary pointer to the object. Therefore deleting it from the list does NOT remove the entire object from memory... use the same approach for any objects put onto the active actors list.
The movie manager object has property variables and methods of it's own used throughout any subsequent movies. They remain accessible through the global variable. These methods are used to control all generic movie functions such as reading and writing to files, User and Prefs files, Checking Memory, Checking screen depth, Checking the machine that you're running on, Setting and resetting screen depth on a Mac, etc.
One of the most important features we build in is a managed actorList of our own. We use two lists that are internal to the movie manager object: myActiveActors and myDeleteActors. (As a convention I use 'g' to signify a global variable and 'my' to signify a property variable of an object. I also use plural names to signify lists and "The" to indicate an object.)
>Also, how do you check if one object is also in another list? Previous
>posts refer to some inconsistencies regarding ojbect equality.
So when gTheMovieMan receives a stepframe, it checks to see if anything is on the myDeletedActors list. If there is, then each is removed from both the myDeletedActors and myActiveActors list. These are located on each list (with getPos) and then truely deleted from the lists (with DeleteAt).
-- first delete any objects waiting to be deleted from the active list
repeat with i = count( myDeletedObjects ) down to 1
-- find object to dump
set location to getPos( myActiveObjects, getAt( myDeletedObjects, i ) )
deleteAt( myActiveObjects, location ) -- delete object from active list
deleteAt( myDeletedObjects, i ) -- delete object from deleted list
end repeat
Then a stepframe is passed to each of the items remaining on the myActiveActors list. This way objects are deleted BEFORE going into the stepframe loop. If an item is told to delete itself during the stepframe loop, it will actually be removed just BEFORE the NEXT stepframe loop.
-- now pass the stepFrame to each object in the active list
repeat with i = count( myActiveObjects ) down to 1
stepFrame( getAt( myActiveObjects, i ) )
end repeat
>When you delete an ojbect from the deletedActors list do you totally delete
>it, ie set ? = gVoid ? or do you shuffle it to an inActiveActors list to
>hide it from stepFrames temporalily. The reason I ask is because with my
>button object I don't want to rebirth a button I only wanted to make it
>inactive while showing a certain "state" on screen, ie a director
>fabricated dialog box. This is where my gProcessStepFrame global came into
>play.
We delete it from the active actors list but not from existance.
I would suggest using some other list to manage your list of buttons. Only use the actor list as a temporary place to hold items waiting for stepframes. What we do is we have a controlStrip object that is responsible for holding all the buttons in a control area. Then when the controlStrip is activated each individual button puts itself onto the active list by issuing:
addActor( gTheMovieMan, me )
When the controlStrip is deactivated then each button pulls itself off the list with this command:
deleteActor( gTheMovieMan, me )
Here are some sample methods we use in the movie manager that process add and delete actor requests.
on showActors me
-- used for debugging...
put "the actorList =" && the actorList
put "myActiveObjects =" && myActiveObjects
put "myDeletedObjects =" && myDeletedObjects
end
--
on clearActors me
-- used to clear both lists don't use in a stepframe.
-- if in a stepframe you may deleteActor, not clearActors...
set myActiveObjects to []
set myDeletedObjects to []
end
--
on addActor me, whichObject
if getPos( myActiveObjects, whichObject ) = 0 then
-- not already in the active list
add( myActiveObjects, whichObject )
end if
set location to getPos( myDeletedObjects, whichObject )
-- see if it was marked for deletion
if NOT location = 0 then -- was marked for deletion
deleteAt( myDeletedObjects, location )
-- since it is being reactivated, take it off deleted list
end if
-showActors me -- uncomment for debugging
end
--
on deleteActor me, whichObject
set location to getPos( myActiveObjects, whichObject )
if NOT location = 0 then
-- be sure it is in the ACTIVE list before deleting
if getPos( myDeletedObjects, whichObject ) = 0 then
-- not already in the DELETED list
add( myDeletedObjects, whichObject )
end if
end if
--showActors me -- uncomment for debugging
end
--
That's about it.
Hope this helps...
-MikeS
Date: Nov. 13, 1996
>Instead of setting the actorList = [], I am doing a "setAt the actorList,
>position, 0" in a repeat loop and the GPF's vanished. I have to do this on
>quiting as well or else another GPF.
>
>Others have mentioned that birthing straight into the actorList is not a
>good idea but I've been doing this without incident for my button objects
>and it appears clean in 16 bit.
We use a two step process when accessing the ActorList...
Since the ActorList can be the cause of crashes if objects are pulled off them during stepframes etc. we use our own simulated actorlist.
We birth our movieManager object onto the actorlist when our first movie starts up. Then our movieManager has methods for addActor and deleteActor. These will add and delete actors to our internal custom activeActors list which is our managed version of the actorlist.
The addActor method checks to be sure an object is not already on the activeActors list and then adds it.
The deleteActor method checks to be sure an object is actually on the activeActors list (and not already on the deleteActors list) before enabling a delete. If enabled the deleteActor method adds the object to be deleted to our deletedActors list.
Then on the stepframe, before passing a stepframe to the activeActors list we first check the deletedActors list and remove any objects that are in the deletedActors list from both lists.
This way when our stepframes are actually processed through the activeActors list the objects are insulated from being pulled off the list during the stepFrame. If an attempt is made to delete an object during a stepframe, it won't actually be deleted until the NEXT stepframe.
This approach is actually much easier than it may sound. Once it's set up we can then easily add and remove actors from any object by issuing a single command without regard to if we are currently in a stepframe or not.
addActor( gTheMovieMan, actorObject )
deleteActor( gTheMovieMan, actorObject )
By managing the list yourself you can save many headaches...
-MikeS
Date: 1992
Before there was an official actorList, we had implemented an object update mechanism in Director 3's factories. We passed our own mNextFrame message to each object within a managed stepMovie stack. Objects could freely push themselves onto and pop themselves off of this stack. This little bit of code showed us the future of Director and strikingly similar functionality was built into Director 4's actorList. However, our old activeObjects list was bullet proof in 1992 where Director's own actorList didn't become bullet proof until 1997... ;-)
This actual code is obsolete and not fully optimized, but the concepts are the same as the newer parent child objects and today's actorList. This code, from DreamLight Verttice, was included on Macromedia's first Authorized Developer CD-ROM. I have posted it here for your enjoyment at peering into the past. It may also give you some ideas for your own managed lists. The makeStack factory is also available in the DreamLight Verttice Insight. Check out the OOP photon factory to see how an object hopped on and off TheActiveObjects stack and how it animated itself.
--====================================================
-- Object: handlers (Movie Script)
--====================================================
-- (C) 1992 DreamLight(R) Incorporated
on startMovie
global mode, theActiveObjects, theKeyChecker, nextAction, vertticeLocked
setScreenDepth(4)
set the stageColor = 0
set the exitLock = TRUE
set mode = "StartUp"
--set vertticeLocked = TRUE
set nextAction = "nothing"
set theActiveObjects = makeStack(mNew, False, theActiveObjects)
set theKeyChecker = makeKeyChecker(mNew, theKeyChecker)
when keyDown then doKeyDown
when mouseDown then doMouseDown
when mouseUp then doMouseUp
end startMovie
--
on stepMovie
global theActiveObjects, nextAction
if nextAction <> "nothing" then
do nextAction
if nextAction <> "doWinLattice" AND¬
nextAction <> "doWinLevel" AND¬
nextAction <> "doStartLevel" then exit
end if
--**DEBUG
--put "stepMovie: " & the frame
repeat with i = 1 to theActiveObjects(mGetLength)
set theActiveObject = theActiveObjects(mGet, i)
--**DEBUG
--put i && theActiveObjects(mGetLength) && theActiveObject(mName)
theActiveObject(mNextFrame)
end repeat
end stepMovie
--
on stopMovie
global theActiveObjects, oldColorDepth
set theActiveObjects = dumpObject(theActiveObjects)
set the colorDepth = oldColorDepth
releaseRearWindow
closeXLib
end stopMovie
--
on activeObjectsDone
global theActiveObjects
if theActiveObjects(mGetLength) = 0 then return True
else return False
end activeObjectsDone
--
on activateObject whichObject
-- pushes object onto the active stack for nextframe processing
global theActiveObjects
theActiveObjects(mPush, whichObject)
end activateObject
--
on deActivateObject whichObject
-- locates and removes object from the active stack
global theActiveObjects
repeat with i = 1 to theActiveObjects(mGetLength)
if theActiveObjects(mGet, i) = whichObject then -- object found
theActiveObjects(mRemove, i)
exit repeat -- done
end if
end repeat
end deActivateObject
--
on setUpSprite whichSprite, whichObject
activateSprite(whichSprite, whichObject)
whichObject(mReset)
end setUpSprite
--
on activateSprite whichSprite, whichObject
-- inserts object into the sprite stack to control the channel
global theSprites
if objectP(theSprites) then theSprites(mPut, whichSprite, whichObject)
puppetSprite whichSprite, True
end activateSprite
--
on deActivateSprite whichSprite
-- clears object from sprite stack
global theSprites
if objectP(theSprites) then theSprites(mPut, whichSprite, 0)
puppetSprite whichSprite, False
end deActivateSprite
--
on dumpObject whichObject
-- releases and disposes of an object
if objectP(whichObject) then
whichObject(mRelease)
whichObject(mDispose)
end if
return whichObject -- pass back empty object pointer
end dumpObject
-MikeS

|