docs: Add comments documenting subtle points of AS3 loop & tag queueing behavior
This commit is contained in:
parent
074f2ff76f
commit
2c93da9a70
|
@ -788,6 +788,8 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// Clamp frame number in bounds.
|
// Clamp frame number in bounds.
|
||||||
let frame = frame.max(1);
|
let frame = frame.max(1);
|
||||||
|
|
||||||
|
// In AS3, no-op gotos have side effects that are visible to user code.
|
||||||
|
// Hence, we have to run them anyway.
|
||||||
if frame != self.current_frame() || context.is_action_script_3() {
|
if frame != self.current_frame() || context.is_action_script_3() {
|
||||||
if self
|
if self
|
||||||
.0
|
.0
|
||||||
|
@ -1113,7 +1115,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
) {
|
) {
|
||||||
let next_frame = self.determine_next_frame();
|
let next_frame = self.determine_next_frame();
|
||||||
match next_frame {
|
match next_frame {
|
||||||
//Removals happen before frame advance.
|
// AS3 removals need to happen before frame advance (see below)
|
||||||
NextFrame::Next if context.is_action_script_3() => {}
|
NextFrame::Next if context.is_action_script_3() => {}
|
||||||
NextFrame::Next => self.0.write(context.gc_context).current_frame += 1,
|
NextFrame::Next => self.0.write(context.gc_context).current_frame += 1,
|
||||||
NextFrame::First => return self.run_goto(context, 1, true),
|
NextFrame::First => return self.run_goto(context, 1, true),
|
||||||
|
@ -1172,6 +1174,10 @@ impl<'gc> MovieClip<'gc> {
|
||||||
};
|
};
|
||||||
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
let _ = tag_utils::decode_tags(&mut reader, tag_callback, TagCode::ShowFrame);
|
||||||
|
|
||||||
|
// On AS3, we deliberately run all removals before the frame number or
|
||||||
|
// tag position updates. This ensures that code that runs gotos when a
|
||||||
|
// display object is added or removed does not catch the movie clip in
|
||||||
|
// an invalid state.
|
||||||
let remove_actions = self.unqueue_removes(context);
|
let remove_actions = self.unqueue_removes(context);
|
||||||
|
|
||||||
for (_, tag) in remove_actions {
|
for (_, tag) in remove_actions {
|
||||||
|
@ -1186,6 +1192,8 @@ impl<'gc> MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// It is now safe to update the tag position and frame number.
|
||||||
|
// TODO: Determine if explicit gotos override these or not.
|
||||||
let mut write = self.0.write(context.gc_context);
|
let mut write = self.0.write(context.gc_context);
|
||||||
|
|
||||||
write.tag_stream_pos = reader.get_ref().as_ptr() as u64 - tag_stream_start;
|
write.tag_stream_pos = reader.get_ref().as_ptr() as u64 - tag_stream_start;
|
||||||
|
@ -1424,6 +1432,9 @@ impl<'gc> MovieClip<'gc> {
|
||||||
|
|
||||||
let from_frame = self.current_frame();
|
let from_frame = self.current_frame();
|
||||||
|
|
||||||
|
// Explicit gotos in the middle of an AS3 loop cancel the loop's queued
|
||||||
|
// tags. The rest of the goto machinery can handle the side effects of
|
||||||
|
// a half-executed loop.
|
||||||
let mut write = self.0.write(context.gc_context);
|
let mut write = self.0.write(context.gc_context);
|
||||||
if write.loop_queued() {
|
if write.loop_queued() {
|
||||||
write.queued_tags = HashMap::new();
|
write.queued_tags = HashMap::new();
|
||||||
|
@ -1631,6 +1642,12 @@ impl<'gc> MovieClip<'gc> {
|
||||||
.filter(|params| params.frame >= frame)
|
.filter(|params| params.frame >= frame)
|
||||||
.for_each(|goto| run_goto_command(self, context, goto));
|
.for_each(|goto| run_goto_command(self, context, goto));
|
||||||
|
|
||||||
|
// On AVM2, all explicit gotos act the same way as a normal new frame,
|
||||||
|
// save for the lack of an enterFrame event. Since this must happen
|
||||||
|
// before AS3 continues execution, this is effectively a "recursive
|
||||||
|
// frame".
|
||||||
|
//
|
||||||
|
// Our queued place tags will now run at this time, too.
|
||||||
if !is_implicit {
|
if !is_implicit {
|
||||||
self.construct_frame(context);
|
self.construct_frame(context);
|
||||||
self.frame_constructed(context);
|
self.frame_constructed(context);
|
||||||
|
@ -1873,6 +1890,8 @@ impl<'gc> MovieClip<'gc> {
|
||||||
// Don't do this for rewinds, because they conceptually
|
// Don't do this for rewinds, because they conceptually
|
||||||
// start from an empty display list, and we also want to examine
|
// start from an empty display list, and we also want to examine
|
||||||
// the old children to decide if they persist (place_frame <= goto_frame).
|
// the old children to decide if they persist (place_frame <= goto_frame).
|
||||||
|
//
|
||||||
|
// We also have to reset the frame number as this emits AS3 events.
|
||||||
let to_frame = self.current_frame();
|
let to_frame = self.current_frame();
|
||||||
self.0.write(context.gc_context).current_frame = from_frame;
|
self.0.write(context.gc_context).current_frame = from_frame;
|
||||||
|
|
||||||
|
@ -1951,6 +1970,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all `PlaceObject` tags off the internal tag queue.
|
||||||
fn unqueue_adds(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Vec<(Depth, QueuedTag)> {
|
fn unqueue_adds(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Vec<(Depth, QueuedTag)> {
|
||||||
let mut write = self.0.write(context.gc_context);
|
let mut write = self.0.write(context.gc_context);
|
||||||
let mut unqueued: Vec<_> = write
|
let mut unqueued: Vec<_> = write
|
||||||
|
@ -1970,6 +1990,7 @@ impl<'gc> MovieClip<'gc> {
|
||||||
unqueued
|
unqueued
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove all `RemoveObject` tags off the internal tag queue.
|
||||||
fn unqueue_removes(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Vec<(Depth, QueuedTag)> {
|
fn unqueue_removes(&self, context: &mut UpdateContext<'_, 'gc, '_>) -> Vec<(Depth, QueuedTag)> {
|
||||||
let mut write = self.0.write(context.gc_context);
|
let mut write = self.0.write(context.gc_context);
|
||||||
let mut unqueued: Vec<_> = write
|
let mut unqueued: Vec<_> = write
|
||||||
|
@ -2052,6 +2073,7 @@ impl<'gc> TDisplayObject<'gc> for MovieClip<'gc> {
|
||||||
false
|
false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PlaceObject tags execute at this time.
|
||||||
let data = self.0.read().static_data.swf.clone();
|
let data = self.0.read().static_data.swf.clone();
|
||||||
let place_actions = self.unqueue_adds(context);
|
let place_actions = self.unqueue_adds(context);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue