TimelineControl

From Gnash Project Wiki

Jump to: navigation, search

Trying to define an algorithm for DisplayList construction on jump-back events. Jump-back events include normal loop-back of the playhead (ie: reaching the last frame and restarting automatically from the start) and explicit jump backwards (gotoAndPlay, gotoAndStop).

Contents

Definitions

Depth zones

From: http://www.senocular.com/flash/tutorials/depths/?page=2

Timeline -16,384 to -1 handled by timeline with Flash, dynamic removal disabled
Dynamic 0 to 1,048,575 most dynamically accessible, allows dynamic removal
Reserve 1,048,576 to 2,130,690,045 overflow - ignored by timeline, dynamic removal disabled


Timeline

The timeline is the static definition of events associated to frames in an SWF. The timeline is the immutable part, which can not be modified by ActionScript. Events include character instantiation (PlaceObject), instance removal (RemoveObject) and instance transformation (PlaceObject2 with move flag on).

You can associate additional events to the playhead advancement by ActionScript (onEnterFrame, onLoad, onUnload, onConstruct, ...), but you cannot remove statically-defined events.

Timeline instances

"timeline" instances are those placed by PlaceObject* tags.

Instances placed by ActionScript (attachMovieClip, duplicateMovieClip, createEmptyMovieClip, loadMovie(?), ..) are "script" instances instead.

What "should" happen when jumping backwards

THIS WHOLE SECTION IS BOGUS, please see DisplayList reconstruction design.

Each point in the following list explains an exception to the previous one:

  1. A "gotoAndPlay(_currentframe - X)" in a simple movie without any other ActionScript code simply restores the exact DisplayList that was active in that frame, including matrix and color transforms. With other words, continously calling prevFrame() on a stopped movie should play the movie exactly backwards*.
  2. Instances that have been modified via ActionScript (for example, writing to their "_x" property**) prevents any other matrix or color transform written statically in the SWF file (PlaceObject2) to be restored. The instance remains unchanged and only ActionScript code may move the instance around or do other transforms. See scriptTransformed().
  3. When jumping to a frame number that is out of the lifetime of a particular instance, then this instance is removed even if it has been transformed by ActionScript.
  4. Static instances that have been moved to a depth > 0 using mc.swapDepths() become dynamic instances and are completely immune to static events (including RemoveObject!). It's even possible that looping back creates a second instance (with the original depth and settings). It looses any relationship to it's static origin.

* precision: the childs of the sprite instance internally still play their own timeline

** TODO: list all modifications triggering this behaviour

NOTE: I belive last point (depth > 0 using swapDepths) is incomplete. Static instances moved to another depth (whatever it is) are immune to RemoveObject and PlaceObjec2 with "MOVE" semantic (static transform)

NOTE2: The above note doesn't apply when the jump-back is due to normal playback control (loop-back). In that case, even if a timeline instance is no more at the original depth, it's STILL NOT REMOVED!!

Analysis of the proprietary player

In this section we inspect a few cases (ideally all cases) we can think of.

A case is a timeline layout (described by a table) and a set of operations defined for specific frames. All frame numbers 1-based, unless otherwise noted.

Legend for the "timeline layout" table:

P = Place (by PlaceObject* tag)
p = Place (by ActionScript)
R = Remove (by RemoveObject* tag)
X = Replace (by PlaceObject2 tag)
T = Matrix Transform (by PlaceObject2)
t = Matrix Transform (by ActionScript)
M = Move to another depth (by swapDepth)
J = Jump
* = jump target

For each case, we should provide an automated testcase.

Jumping backward to the midle of a character's lifetime after moving it to a dynamic depth

  • Timeline
Frame 1 2 3 4 5 6 7
Event P T* T M J


  • Description
frame2: character placed at depth -16381 at position (10,200)
frame4: position of instance at depth -16381 shifted to the right (50,200)
frame5: position of instance at depth -16381 shifted to the right (100,200)
frame6: depth of instance changed to 10 (dynamic zone) and stop.
frame7: jump back to frame 4
  • Observable behaviour
Before the jump we have a single instance at depth 10 and position 100,200.
After the jump we have two instances:
 - one at depth 10 and position 100,200 (the same we had before, with its state intact)
 - another at depth -16381 and position (50,200) (newly created and placed accordingly to the PlaceObject2 tag on frame4)
Two distinct instances have been constructed in total.
Soft references to the old instance created before the jump-back still point to the old instance.
  • Automated testcase
Self-contained testcase: displaylist_depths_test2.swf
Gnash passes.

Jumping backward to the midle of a character's lifetime after moving it to a static depth

  • Timeline
Frame 1 2 3 4 5 6 7
Event P T* T M J
  • Description
frame2: character placed at depth -16381 at position (10,200)
frame4: position of instance at depth -16381 shifted to the right (50,200)
frame5: position of instance at depth -16381 shifted to the right (100,200)
frame6: depth of instance changed to -10 (static zone).
frame7: jump back to frame 4 and stop.
  • Observable behaviour
Before the jump we have a single instance at depth -10 and position (100,200).
After the jump we have a single instances at depth -16381 and position (50,200).
Two distinct instances have been constructed in total.
Soft references to the old instance created before the jump-back now point to the new instance.
  • Automated testcase
Self-contained testcase: displaylist_depths_test3.swf
Gnash passes.

Jumping backward to the midle of a character's lifetime after static transformation

  • Timeline
Frame 1 2 3 4 5 6 7
Event P T* T J
  • Description
frame2: character placed at depth -16381 at position (10,200)
frame4: position of instance at depth -16381 shifted to the right (50,200)
frame5: position of instance at depth -16381 shifted to the right (100,200)
frame7: jump back to frame 4
  • Observable behaviour
Before the jump we have a single instance at depth -16831 and position (100,200).
After the jump we have the same instances at depth -16381, repositioned at (50,200).
A single instance has been constructed in total.
Soft references to the instance created before the jump-back still point to the same instance.
  • Automated testcase
Self-contained testcase: displaylist_depths_test4.swf
Gnash succeeds.

Jumping backward to the midle of a character's lifetime after swap and swap back

  • Timeline
Frame 1 2 3 4 5 6 7
Event PMM T T* J
  • Description
frame2: character placed at depth -16381 at position (10,200); swap the character to depth -10, and then swap it back to -16381;
frame4: try to transform the character to the right (50,200)
frame5: try to transform the character to the right (200,200)
frame7: jump back to frame 5 and stop
  • Observable behaviour
After depth swapping in frame2, even if final depth is the same as the original one, static transformations in frame4 and frame5 have no effect.
Before the jump we have a single instance at depth -16381 and position (10,200).
After the jump we have the same instances at depth -16381, and still positioned at (10,200).
A single instance has been constructed in total.
  • Automated testcase
Self-contained testcase: displaylist_depths_test6.swf
Gnash succeeds.

Jumping backward to the midle of a character's lifetime after swap to same depth

  • Timeline
Frame 1 2 3 4 5 6 7
Event PM T * T J
  • Description
frame2: character placed at depth -16381 at position (10,200); swap the character to depth -16381 (YES, SAME DEPTH!)
frame4: try to transform the character to the right (50,200)
frame5: try to transform the character to the right (200,200)
frame7: jump back to frame 4 and stop
  • Observable behaviour
Depth swapping in frame2 have NO effect. In particular doesn't prevent subsequent static transformations to apply.
In frame 4 the instance is positioned at 50,200
In frame 5,6 and 7 the instance is positioned at 200,200
After the jump we have the same instances at depth -16381, and positioned at 5,200.
A single instance has been constructed in total.
  • Automated testcase
Self-contained testcase: displaylist_depths_test7.swf
Gnash succeeds.

Jumping backward to the middle of a character's lifetime after removal and replacement(1)

  • Timeline
Frame 1 2 3 4 5 6 7
Event PP * RR Pp J
  • Description
frame2: 
  a static characters is placed at depth 3 (-16381)
  a static character is placed at depth 4 (-16380) 
frame3: nothing new.
frame4: 
  character at depth 3 is removed;
  character at depth 4 is removed;
frame5:
  a static character is placed at depth 3 (-16381) with ratio set to 2.0
  a dynamic character is placed at depth 4 (-16380)       
frame6: jump to frame 3 and stop.
  • Observable behaviour
After jump back, both characters placed at frame5 get destroyed, both characters placed at 
frame2 get re-created.
  • Automated testcase
Self-contained testcase: loop_test4.swf
Gnash succeeds.

Jumping backward to the middle of a character's lifetime after removal and replacement(2)

  • Timeline
Frame 1 2 3 4 5 6 7
Event PP * RR PP J
  • Description
frame2: 
  a static characters is placed at depth 3 (-16381)
  a static character is placed at depth 4 (-16380) 
frame3: nothing new.
frame4: 
  character at depth 3 is removed;
  character at depth 4 is removed;
frame5:
  a static character is placed at depth 3 (-16381) with ratio set to 2.0
  a static character is placed at depth 4 (-16380) with ratio set to 0.0     
frame6: jump to frame 3 and stop.
  • Observable behaviour
After jump back, characters in depth 3 placed at frame5 get destroyed, 
characters in depth 4 placed in frame 5 keeps alive. 
  • Automated testcase
Self-contained testcase: loop_test5.swf
Gnash succeeds.

Jumping backward to the middle of a character's lifetime after removal and replacement(3)

  • Timeline
Frame 1 2 3 4 5 6 7
Event PPP * RR PP J
  • Description
frame2: 
  a static movieclip is placed at depth 3 (-16381) with ratio value 0.001 [ a red square ]
  a static movieclip is placed at depth 4 (-16380) with ratio value 0.001 [ a green square ]
  a static movieclip is placed at depth 5 (-16379) with ratio value 0.001 [ a blue square ]
frame3: nothing new.
frame4: 
  character at depth 3 is removed;
  character at depth 4 is removed;
frame5:
  a static movieclip is placed at depth4 again with ratio value 0.001 [a yellow square]
  a static movieclip is placed at depth5 agian with ratio value 0.003 [a black square]  
frame6: jump to frame 3 and stop.
  • Observable behaviour
 movieclip in depth4 placed at frame5 kept alive;
 movieclip in depth5 placed at frame5 get destroyed;
  • Automated testcase
Self-contained testcase: loop_test8.swf
Gnash succeeds.

Jumping backward to the start of a character's lifetime after removal and replacement of same character id

  • Timeline
Frame 1 2 3 4 5 6 7
Event P* R P J
  • Description
frame2: character 1 placed at depth -16381
frame3: remove character at depth -16381
frame4: character 1 placed at depth -16381 (same character id)
frame5: jump to frame 2 and stop.
  • Observable behaviour
Two instances have been constructed in total, the second instance
is NOT removed at time of jump-back.
  • Automated testcase
Self-contained testcase: displaylist_depths_test11.c
Gnash succeeds.

Jumping backward to the start of a character's lifetime after static transformation

  • Timeline
Frame 1 2 3 4 5 6 7
Event P* T J
  • Description
frame2: character placed at depth -16381 at position (10,200)
frame4: position of instance at depth -16381 shifted to the right (50,200)
frame7: jump back to frame 2
  • Observable behaviour
Before the jump we have a single instance at depth -16831 and position (50,200). After the jump we have the same instances at depth -16381, repositioned at (10,200).
A single instance has been constructed in total.
Soft references to the instance created before the jump-back still point to the same instance.
  • Automated testcase
Self-contained testcase: displaylist_depths_test5.swf
Gnash succeeds.

Jumping backward to the start of a character's lifetime after dynamic transformation

  • Timeline
Frame 1 2 3 4 5 6 7
Event Pt* T J
  • Description
frame2: character placed at depth -16381 at position (10,200); increment _y += 2 using ActionScript.
frame4: static matrix transform of instance at depth -16381 to position (50,200) 
frame7: jump back to frame 2
  • Observable behaviour
In frame 2 the instance is positioned at (10,202). 
In frames 4,5,6 and 7 the instance is still positioned at (10,202)
After the jump we have the same instances at depth -16381, still positioned at (10,202)
A single instance has been constructed in total.
  • Automated testcase
Self-contained testcase: displaylist_depths_test8.swf
Gnash succeeds.

Jumping backward to the start of two character's lifetime after depth swap

  • Timeline
Frame 1 2 3 4
Event PP* M J
  • Description
frame2: characters placed at static depth
frame3: characters depth swapped 
frame4: jump back to frame 2 and stop
  • Observable behaviour
After the jump we have the same instances at swapped depths (not original).
A single instance has been constructed for each character in total.
  • Automated testcase
Self-contained testcase: loop_test2.swf
Gnash succeeds

Jumping backward to the start of a character's lifetime after being swapped to the depth of a subsequently placed character

  • Timeline
Frame 1 2 3 4
Event P* PM J
  • Description
frame2: a static characters is placed at depth 2 (-16381) [ a red square ]
frame3:  a static character is placed at depth 3 (-16380) [ a black square ]. the two characters are depth-swapped.
frame4: jump back to frame 2 and stop
  • Observable behaviour
A single instance of each characters is created.
After loop-back only the character placed in frame 3 is still alive (the black square),
at depth -16381. The character placed in frame 2 has been destroyed !


  • Automated testcase
Self-contained testcase: loop_test3.swf
Gnash succeeds

Jumping backward with dynamic instances in static depth zone removed

  • Timeline
Frame 1 2 3 4 5 6 7
Event PPp p p * p J
  • Description
frame2: place a static character at depth -16381 at position (10,200);
        place a static character at depth -16380 at position (100,200);
        replace the character at depth -16380 with a dynamic character;
frame3: create a script character at depth -10;
frame4: create a script character at depth -20;
frame6: create a script character at depth -30;
frame7: jump back to frame 5 and stop
  • Observable behaviour
Before the jump we have 5 instances.
After the jump only two timeline instances keep alive, all dynamic instances get removed;
7 instances have been constructed in total.
  • Automated testcase
Self-contained testcase: displaylist_depths_test9.swf
Gnash succeeds.

Skipping frames backward

  • Timeline
Frame 1 2 3 4 5 6 7
Event * P R J
  • Description
frame3: character placed at depth -16381 
frame4: character ad depth -16381 removed
frame7: jump back to frame 2 and stop
  • Observable behaviour
A single instance have been constructed in total.
  • Automated testcase
Self-contained testcase: displaylist_depths_test10.swf
Gnash succeeds.

Jumping backward after lifetime of a character with onConstruct event handler defined

  • Timeline
Frame 1 2 3 4 5
Event PP RR * J
  • Description
frame2: a static character is placed at depth 3 (-16381) [ a red square ]
        another static character is placed at depth 4 [a green square]
        character at depth 3 with onClipInitialize, onClipConstruct defined
        character at depth 4 with onClipInitialize, onClipConstruct and onClipUnload defined
frame3: characters at depth 3 and depth 4 removed
frame5: jump back to frame 4 and stop

  • Observable behaviour
onClipConstruct event handler for character at depth 3 is invoked once.
onClipInitializeevent handler for character at depth 3 is invoked once.
onClipConstruct event handler for character at depth 4 is invoked twice.
onClipInitializeevent handler for character at depth 4 is invoked twice.
  • Automated testcase
Self-contained testcase: loop_test6.swf
Gnash fails by calling onClipConstruct and onClipInitialize again - zou mentioned without considering onClipUnload, but strk is not sure about onClipUnload role as it's not tested here. Any light welcome.

Jumping backward after lifetime of a character with both onConstruct and onUnload event handlers defined

  • Timeline
Frame 1 2 3 4 5
Event P R * J
  • Description
frame2: a static characters is placed at depth 3 (-16381) [ a red square ]
        Event handlers: onConstruct, onUnload
frame3: character at depth 3 (-16381) removed
frame5: jump back to frame 4 and stop

  • Observable behaviour
Both the onConstruct and onUnload event handlers are invoked twice.
  • Automated testcase
Self-contained testcase: loop_test7.swf
Gnash succeeds.

Skipping frames forward

When skipping frames that Place and Remove an instance, the frame actions don't get executed. But this is not the whole truth (read on).

Consider three instances:

  • "A" lives from frames 3 to 5
  • "B" from 8 to 10
  • "C" from 13 to 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
A A A B B B C C C

When jumping from frame 4 to frame 14 we notice the following behaviour:

1. the frames actions (code that is part of the sprite's own timeline) do not get executed

2. when there is an onClipEvent(load) assigned to the instance, it does not get fired

3. when there is both an onClipEvent(load) and an onClipEvent(unload) assigned to the instance, both get fired (so, "unload" influences "load")

Code for (1):

 trace(this+' created in frame '+_parent._currentframe);
 
 this.onUnload = function() {
   trace(this+' removed in frame '+_parent._currentframe);
 };

Code for (2):

 onClipEvent(load) {
       trace("instance A loaded");
 }

Code for (3):

 onClipEvent(load) {
         trace("instance A: loaded");
 }
 
 onClipEvent(unload) {
         trace("instance A: unload");
 }
 
 onClipEvent(enterFrame) {
         trace("instance A: enterFrame");
 }
 
 onClipEvent(construct) {
         trace("instance A: construct");
 }

Result of (3):

 --- first frame ---
 instance A: construct
 instance A: loaded
 _level0.A created in frame 3
 instance A: enterFrame
 --- gotoAndPlay ---
 instance B: construct
 instance C: construct
 instance A: unload
 _level0.A removed in frame 14
 instance B: loaded
 instance B: unload
 instance C: loaded
 _level0.C created in frame 14
 instance C: enterFrame
 --- last frame ---

static instances, jumping backward

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Having a movie that places an instance in frame 5 and removes it in frame 10, the instance gets...

#jump swapDepths created in frame removed in frame* notes
1none none 5 10
2gotoAndPlay(6) in frame 8 none 5 never
3gotoAndPlay(6) in frame 12 none first pass 5, after loop always 6 10
4gotoAndPlay(2) in frame 8 none 5 2*
5gotoAndPlay(2) in frame 12 none 5 10
6none swapDepths(100) in frame 7 5 never, but a new instance will be created again at the original depth on loop-back
7gotoAndPlay(6) in frame 8 swapDepths(100) in frame 7 5, one duplicate in 6 never (applies to both!)

In total two instances are created. The second instance is created because the first is at depth 100 when jumping back. Afterwards the depths of the two instances are swapped at each jump-back.

instance 1 / _level0.mc created in frame 5
instance 1 is at 100
instance 2 / _level0.mc created in frame 6

(jump)

instance 2 is at 100
instance 1 is at -16383

(jump)

instance 2 is at -16383
instance 1 is at 100

(jump)

instance 2 is at 100
instance 1 is at -16383

(jump)

instance 2 is at -16383
instance 1 is at 100
8gotoAndPlay(6) in frame 12 swapDepths(100) in frame 7 5, one duplicate in 6 10
instance 1 created in frame 5
instance 2 created in frame 6
instance 1 destroyed in frame 10
instance 3 created in frame 6
instance 2 destroyed in frame 10
instance 4 created in frame 6
instance 3 destroyed in frame 10
...
9gotoAndPlay(2) in frame 8 swapDepths(100) in frame 7 5, including duplicate 2
instance 1 created in frame 5
instance 2 created in frame 5
instance 1 destroyed in frame 2
instance 3 created in frame 5
instance 2 destroyed in frame 2
...
10gotoAndPlay(2) in frame 12 swapDepths(100) in frame 7 5, including duplicate 10
instance 1 created in frame 5
instance 2 created in frame 5
instance 1 destroyed in frame 10
instance 3 created in frame 5
instance 2 destroyed in frame 10
instance 4 created in frame 5
instance 3 destroyed in frame 10
...
11none swapDepths(-100) in frame 7 5 1
12gotoAndPlay(6) in frame 8 swapDepths(-100) in frame 7 5, one duplicate in 6 6
instance 1 created in frame 5
instance 2 created in frame 6
instance 1 destroyed in frame 6
instance 3 created in frame 6
instance 2 destroyed in frame 6
instance 4 created in frame 6
instance 3 destroyed in frame 6
...
13gotoAndPlay(6) in frame 12 swapDepths(-100) in frame 7 5, one duplicate in 6 6
instance 1 created in frame 5
instance 2 created in frame 6
instance 1 destroyed in frame 6
instance 3 created in frame 6
instance 2 destroyed in frame 6
instance 4 created in frame 6
instance 3 destroyed in frame 6
...
14gotoAndPlay(2) in frame 8 swapDepths(-100) in frame 7 5 2 (no duplicates)
15gotoAndPlay(2) in frame 12 swapDepths(-100) in frame 7 5 2 (no duplicates)

* frame number is what _parent._currentframe reports in onUnload

Notes on duplicates:

  • they are different instances, but have the same name (even "this" shows the same string)
  • when a reference to the first instance is held in a variable, before the duplicate instance is created, then it will still reference the older instance afterwards

detailed swapDepth analysis

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
P S R M G

Test movie, played with PP:

  • instance placed in frame 4
  • depth (=d) swapped in frame 6
  • depth restored in frame 8 (if at all)
  • statically moved in frame 11 (PlaceObject2)
  • gotoAndPlay(t) in frame 15
# New depth (d) Restored (f.8)? Target (t) did move? did disappear? notes
1 -100 yes 12 no n/a
2 -100 yes 2 no yes
3 100 yes 12 no n/a
4 100 yes 2 no yes
5 -100 no 12 no, second instance created at that position *** n/a
6 -100 no 2 no yes
7 100 no 12 not really *** no, second instance at first position*
8 100 no 2 no (never) no, second instance overlapping**

* two instances are visible: one at the after-move position, and another one at the before-move position.

** In frame 4 a new copy (duplicate) is overlapped at the same position. After looping back, during frames 2-3, only one instance remains visible.

*** Actually the instance doesn't get moved. Instead, a new instance is created (probably at the original depth) after jumping back which replaces the first instance. The visual effect equals to a movement, but it's not the same. This does not mean that it is being replaced esplicitely. More probable is that the "replaced" instance get's removed because it is not supposed to exist (following our remove-unknown-static-instances logic)

Cause:

  • "New depth" = value passed to swapDepth() in frame 6
  • "Restored" = whether the old depth has been restored using swapDepth() again, in frame 8
  • "Target" = target frame number for gotoAndPlay. The jump to frame 12 is still in it's lifetime, therefore the "n/a" (which equals to "no").

Effect:

  • "did move?" = the new matrix defined in frame 11 was applied
  • "did disappear?" = the instance disappeared after jumping back to frame 2

DisplayList reconstruction design

The idea is to modify the current 'resetDisplayList' by calling it 'reconstructDisplayList' and having it accept an argument, being the target frame (0 for normal loop-backs, any target for gotoFrame).

1st attempt (now known to be bogus)

This is not sure to be bogus, just not clearly defined. For example, if we interpret "timeline instances supposed to exist" like "depth supposed to be occupied by timeline instances" things might work.

1. Find timeline instances supposed to exist in target frame

  • "timeline" instances are those placed by PlaceObject* tags.
  • "supposed to exist" are those for which a PlaceObject tag is defined before or at the target frame and NO RemoveObject tag follows it up to and including the target frame.
  • The product of this step is a list of TimelineInfo record pointers.

2. Remove from m_display_list all chars in the static depth zone except the ones found in step 1

  • "the ones found in step 1" means chars that have the same depth, same character ID and same lifetime
  • "static depth zone" is from character::staticDepthOffset to -1
  • trigger onUnload events on any removed chars

3. Add any instance found in step 1 not currently in m_display_list - Triggering onLoad events

  • resort the DisplayList by depth
  • Informations about how to place the character (create the instances) are found in the TimelineInfo records.

4. Set matrix and color transform of all chars found in step 1 (== all static chars) to the values provided by TimelineInfo

Implementation details

Udo proposes the following implementation details for 1st attempt at Displaylist reconstruction design:

For (1): Scan all TimelineInfo records that have a smallest "first frame" less or equal the target frame and having their "last frame number alive" greater or equal the target frame and sharing the same depth

For (2): All sprite instances in the current display list whose TimelineInfo record starts* after the target frame need to be removed

For (3): All found TimelineInfo records whole "last frame" is < current frame need to be added

  • it's lowest "first frame" is > target frame

The TimelineInfo record should look like this:

   * depth
   * name
   * id
   * last frame number in which the instance is alive (can be temporarily unknown)
   * last frame number for which the following list is valid ("known"):
   * a list containing: 
       * first frame number
       * matrix
       * color transform

2nd attempt (known to be bogus)

1. Find the instances currently in DisplayList that need to be removed

2. Execute all displaylist tags from first to target frame

  • a PlaceObject2 with PLACE semantic must behave as having a MOVE semantic if the specified depth is occupied by an instance which is not scriptTransformed(). Which is, statically transform the existing instance. See Jumping backward to the start of a character's lifetime after static transformation scenario. If the existing instance is scriptTransformed(), the PlaceObject2 is simply *ignored*. See "Jumping backward to the start of a character's lifetime after dynamic transformation".

Implementation details

Informations about "original depth" and "frame of construction" of a "Timeline instance" can be obtained by fetching a "TimelineInfo" pointer from the character. Only "Timeline instance" will return a non-NULL value from the getTimelineInfo() call.

The "TimelineInfo" would have more or less these fields:

  • Frame number in which it the instance has been created
  • Depth at which the instance was created

3rd attempt (implemented in 0.8.1, enable defining NEW_TIMELINE_DESIGN to 3)

1. Find all "timeline depth" for the target frame, querying the Timeline object in the sprite/movie definition (see implementation details)

2. Remove step

2.1 Remove all current dynamic instances found in static depth zone
2.2 Remove all current timeline instances at a depth NOT in the setfound in step 1
2.3 Remove all current timeline instances at a depth in the set found in step1 but have different ratio values.

3. Execute all displaylist tags from first to target frame

3.1 PlaceObject tags with PLACE semantic referring to a depth currently occupied by an instance placed by REPLACE tag in later frame will act as a REPLACE tag itself. See clip_as_button2.swf. [check if this a general case]

4th attempt

1. Find all "timeline depth" for the target frame.

2. Remove step

2.1 Remove all current dynamic instances found in static depth zone
2.2 Remove all current timeline instances at a depth NOT in the set found in step 1
2.3 Remove all non-referenceable characters(shapes and morphs)[suboptimal, need more test to see if we can avoid this]

3. Execute all displaylist tags from first to target frame

3.1 In normal mode, PlaceObject tags with PLACE semantic referring to a depth currently occupied will act as no-ops.
3.2 In jump back mode, PlaceObject tags with PLACE semantic will act as a MOVE semantic if the ratio of referred character is compatible, or act as replace(remove the old one and place a new one) if the ratio of referred character is uncompatible.

Implementation details

The 'Timeline' class

We'd define a 'Timeline' class consisting of a set of depths for each frame. Only depths in the static zone are valid depths for the set.

Timeline production

During parsing (shape_character_def, movie_def_impl) we'd fill up the Timeline vector elements in this way:

  • Copy depth set from previous frame if any. Empty if first frame
  • Everytime a PlaceObject* tag is found add a depth defined in the PlaceObject* to the set.
  • Everytime a RemoveObject* tag is found remove the referenced depth from the set
Timeline services

The Timeline class would provide the following services:

  • Get the set of depths occupied in a given frame
  • Add a depth to a given frame (for generation)
  • Remove a depth from a given frame (for generation)

5th attempt (implemented in cvs-head and 0.8.2 release branch)

0. (This section only describes what happens when jump back.)

1. Execute all displaylist tags from first to target frame. Characters at this step are held on a new temporary display list, and appropriate INITIALIZE, CONSTRUCT and LOAD events are queued.

2. Scan the old display list and the new display list

2.1 if the depth occupied in the old list is empty in the new list, unload the character in the old list.
2.2 if the depth is occupied in both lists, then compare the ratio of the two characters.
2.2.1 if not equal, replace the character in the old list with corresponding character in the new list, and unload or destroy the old character. No INITIALIZE/CONSTRUCT events are queued, UNLOAD is still being researched on.
2.2.2 if equal and the old character accepts static transformation, replace the transformation matrix of the old character with the matrix of the new character, and destroy the new character. UNLOAD events might be queued but won't be executed as the target would be marked as isDestroyed() at time of execution.
2.3 if the depth occupied in the new list is empty in the old list, add the character in the new list to the old list

3. Copy all unloaded characters from the new display list to the old display list, and clear the new display list. [Q: this step is not clear, is it just the point in which we install the new display list ?]

A: unloaded but not destroyed sprites are accessible from the AS code before GC, they should be  
in a certain display list. The 'new display list' is a temporary list, which should be cleared 
before next gotoFrame. For the AS code, all characters are accessed from the original display list.
I am going to drop the 'temporary displaylist', which is just a container for displaylist merging algorithm.


4. Flush all action queues.

5. GC

5.1 Reset all references to unloaded/destroyed characters, wipe out unloaded/destroyed characters from their display list. [see discussion for more]

Implementation notes

TimelineInfo records

"known" timeline instances are instances placed at least once at any given time. Once a timeline instance is "known" we won't forget about it's existance, even if it is destroied by later tags.

A TimelineInfo record is stored in memory for each of these instances. This can happen at parse time, so all "timeline instances" are immediately known.

When a "timeline" instance is actually placed (ie: it's actually instantiated), a pointer to the TimelineInfo record containing information on its lifetime is stored with it. For "script" instances this pointer is NULL.

A sprite character definition can hold a number of TimelineInfo records. These describe the childs of that sprite character!

Constructing TimelineInfo records

To build the TimelineInfo records we do for each frame in the definition:

  • for PlaceObject* events, search for a existing TimelineInfo with the same depth, that's still "open"
  • if found, and the character ID is the same, store matrix and cxform in it's list, and update both "last frame number" fields
  • if found, but character ID is different, update both "last frame number" fields (but "alive" frame number is current_frame-1), close that TimelineInfo and go on with next case ("not found")
  • if not found, create a new TimelineInfo record
  • for RemoveObject events, update "last frame number" fields and close the record

Note: a record is closed when the "alive" last frame number is less then the "known" last frame number.


References