Skip to content

Commit 2b6b2f7

Browse files
jarca0123Herschel
andcommitted
avm1: Adjust clip removal logic for rewinds
Co-authored-by: Mike Welsh <mwelsh@gmail.com>
1 parent 2d35af7 commit 2b6b2f7

File tree

9 files changed

+123
-22
lines changed

9 files changed

+123
-22
lines changed

core/src/avm1/globals/movie_clip.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,7 @@ fn attach_movie<'gc>(
799799
.library_for_movie(movie_clip.movie())
800800
.and_then(|l| l.instantiate_by_export_name(export_name, activation.gc()))
801801
{
802+
new_clip.set_placed_by_avm1_script(true);
802803
// Set name and attach to parent.
803804
new_clip.set_name(activation.gc(), new_instance_name);
804805
movie_clip.replace_at_depth(activation.context, new_clip, depth);
@@ -840,6 +841,7 @@ fn create_empty_movie_clip<'gc>(
840841
// Create empty movie clip.
841842
let swf_movie = movie_clip.movie();
842843
let new_clip = MovieClip::new(swf_movie, activation.gc());
844+
new_clip.set_placed_by_avm1_script(true);
843845

844846
// Set name and attach to parent.
845847
new_clip.set_name(activation.gc(), new_instance_name);
@@ -928,14 +930,14 @@ fn duplicate_movie_clip<'gc>(
928930
// `duplicateMovieClip` method uses biased depth compared to `CloneSprite`.
929931
let depth = depth.wrapping_add(AVM_DEPTH_BIAS);
930932

931-
let new_clip = clone_sprite(movie_clip, activation.context, name, depth, init_object);
932-
933-
// On SWF<6 undefined is returned.
934-
if activation.swf_version() < 6 {
935-
return Ok(Value::Undefined);
933+
if let Some(new_clip) = clone_sprite(movie_clip, activation.context, name, depth, init_object) {
934+
// On SWF<6 undefined is returned.
935+
if activation.swf_version() < 6 {
936+
return Ok(Value::Undefined);
937+
}
938+
return Ok(new_clip.object());
936939
}
937-
938-
Ok(new_clip.map_or(Value::Undefined, |clip| clip.object()))
940+
Ok(Value::Undefined)
939941
}
940942

941943
pub fn clone_sprite<'gc>(
@@ -973,7 +975,7 @@ pub fn clone_sprite<'gc>(
973975
// Set name and attach to parent.
974976
new_clip.set_name(context.gc(), target);
975977
parent.replace_at_depth(context, new_clip.into(), depth);
976-
978+
new_clip.set_placed_by_avm1_script(true);
977979
// Copy display properties from previous clip to new clip.
978980
new_clip.set_matrix(movie_clip.base().matrix());
979981
new_clip.set_color_transform(movie_clip.base().color_transform());

core/src/display_object.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ pub struct DisplayObjectBase<'gc> {
199199
parent: Lock<Option<DisplayObject<'gc>>>,
200200
place_frame: Cell<u16>,
201201
depth: Cell<Depth>,
202+
ratio: Cell<u16>,
202203
name: Lock<Option<AvmString<'gc>>>,
203204
clip_depth: Cell<Depth>,
204205

@@ -277,6 +278,7 @@ impl Default for DisplayObjectBase<'_> {
277278
parent: Default::default(),
278279
place_frame: Default::default(),
279280
depth: Default::default(),
281+
ratio: Default::default(),
280282
name: Lock::new(None),
281283
clip_depth: Default::default(),
282284
matrix: Default::default(),
@@ -722,6 +724,14 @@ impl<'gc> DisplayObjectBase<'gc> {
722724
self.set_flag(DisplayObjectFlags::PLACED_BY_SCRIPT, value);
723725
}
724726

727+
fn set_placed_by_avm1_script(&self, value: bool) {
728+
self.set_flag(DisplayObjectFlags::PLACED_BY_AVM1_SCRIPT, value);
729+
}
730+
731+
fn placed_by_avm1_script(&self) -> bool {
732+
self.contains_flag(DisplayObjectFlags::PLACED_BY_AVM1_SCRIPT)
733+
}
734+
725735
fn is_bitmap_cached_preference(&self) -> bool {
726736
self.contains_flag(DisplayObjectFlags::CACHE_AS_BITMAP)
727737
}
@@ -1439,6 +1449,17 @@ pub trait TDisplayObject<'gc>:
14391449
}
14401450
}
14411451

1452+
#[no_dynamic]
1453+
fn ratio(self) -> u16 {
1454+
self.base().ratio.get()
1455+
}
1456+
1457+
#[no_dynamic]
1458+
fn set_ratio(self, ratio: u16) {
1459+
self.base().ratio.set(ratio);
1460+
self.invalidate_cached_bitmap();
1461+
}
1462+
14421463
/// The X axis scale for this display object in local space.
14431464
/// Returned by the `_xscale`/`scaleX` ActionScript properties.
14441465
#[no_dynamic]
@@ -2034,6 +2055,16 @@ pub trait TDisplayObject<'gc>:
20342055
self.base().set_placed_by_script(value)
20352056
}
20362057

2058+
#[no_dynamic]
2059+
fn placed_by_avm1_script(self) -> bool {
2060+
self.base().placed_by_avm1_script()
2061+
}
2062+
2063+
#[no_dynamic]
2064+
fn set_placed_by_avm1_script(self, value: bool) {
2065+
self.base().set_placed_by_avm1_script(value);
2066+
}
2067+
20372068
/// Whether this display object has been instantiated by the timeline.
20382069
/// When this flag is set, attempts to change the object's name from AVM2
20392070
/// throw an exception.
@@ -2321,6 +2352,8 @@ pub trait TDisplayObject<'gc>:
23212352
morph_shape.set_ratio(ratio);
23222353
} else if let Some(video) = self.as_video() {
23232354
video.seek(context, ratio.into());
2355+
} else {
2356+
self.set_ratio(ratio);
23242357
}
23252358
}
23262359
if let Some(is_bitmap_cached) = place_object.is_bitmap_cached {
@@ -2762,6 +2795,8 @@ bitflags! {
27622795

27632796
/// Whether this object has matrix3D (used for stubbing).
27642797
const HAS_MATRIX3D_STUB = 1 << 14;
2798+
2799+
const PLACED_BY_AVM1_SCRIPT = 1 << 15;
27652800
}
27662801
}
27672802

core/src/display_object/morph_shape.rs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use gc_arena::lock::Lock;
1111
use gc_arena::{Collect, Gc, Mutation};
1212
use ruffle_render::backend::ShapeHandle;
1313
use ruffle_render::commands::CommandHandler;
14-
use std::cell::{Cell, RefCell, RefMut};
14+
use std::cell::{RefCell, RefMut};
1515
use std::sync::Arc;
1616
use swf::{Fixed16, Fixed8};
1717

@@ -35,7 +35,6 @@ pub struct MorphShapeData<'gc> {
3535
shared: Lock<Gc<'gc, MorphShapeShared>>,
3636
/// The AVM2 representation of this MorphShape.
3737
object: Lock<Option<Avm2Object<'gc>>>,
38-
ratio: Cell<u16>,
3938
}
4039

4140
impl<'gc> MorphShape<'gc> {
@@ -50,18 +49,17 @@ impl<'gc> MorphShape<'gc> {
5049
MorphShapeData {
5150
base: Default::default(),
5251
shared: Lock::new(Gc::new(gc_context, shared)),
53-
ratio: Cell::new(0),
5452
object: Lock::new(None),
5553
},
5654
))
5755
}
5856

5957
pub fn ratio(self) -> u16 {
60-
self.0.ratio.get()
58+
self.base().ratio.get()
6159
}
6260

6361
pub fn set_ratio(self, ratio: u16) {
64-
self.0.ratio.set(ratio);
62+
self.base().ratio.set(ratio);
6563
self.invalidate_cached_bitmap();
6664
}
6765
}
@@ -121,7 +119,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
121119
}
122120

123121
fn render_self(self, context: &mut RenderContext) {
124-
let ratio = self.0.ratio.get();
122+
let ratio = self.ratio();
125123
let shared = self.0.shared.get();
126124
let shape_handle = shared.get_shape(context, context.library, ratio);
127125
context
@@ -130,7 +128,7 @@ impl<'gc> TDisplayObject<'gc> for MorphShape<'gc> {
130128
}
131129

132130
fn self_bounds(self) -> Rectangle<Twips> {
133-
let ratio = self.0.ratio.get();
131+
let ratio = self.ratio();
134132
let shared = self.0.shared.get();
135133
let frame = shared.get_frame(ratio);
136134
frame.bounds

core/src/display_object/movie_clip.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! `MovieClip` display object and support code.
2+
use crate::avm1::globals::AVM_DEPTH_BIAS;
23
use crate::avm1::Avm1;
34
use crate::avm1::{Activation as Avm1Activation, ActivationIdentifier};
45
use crate::avm1::{NativeObject as Avm1NativeObject, Object as Avm1Object, Value as Avm1Value};
@@ -1567,6 +1568,58 @@ impl<'gc> MovieClip<'gc> {
15671568
}
15681569
let hit_target_frame = self.0.current_frame() == frame;
15691570

1571+
fn survives_rewind(old_object: DisplayObject<'_>, new_params: &swf::PlaceObject) -> bool {
1572+
if !old_object.movie().is_action_script_3()
1573+
&& old_object.placed_by_avm1_script()
1574+
&& old_object.depth() < AVM_DEPTH_BIAS
1575+
{
1576+
return false;
1577+
}
1578+
let id = match new_params.action {
1579+
swf::PlaceObjectAction::Place(id) | swf::PlaceObjectAction::Replace(id) => id,
1580+
_ => 0,
1581+
};
1582+
let ratio_equals = match new_params.ratio {
1583+
Some(ratio) => old_object.ratio() == ratio,
1584+
None => true,
1585+
};
1586+
1587+
let clip_depth_equals = match new_params.clip_depth {
1588+
Some(clip_depth) => old_object.clip_depth() == clip_depth as Depth,
1589+
None => true,
1590+
};
1591+
1592+
let color_transform_equals = match new_params.color_transform {
1593+
Some(color_transform) => old_object.base().color_transform() == color_transform,
1594+
None => true,
1595+
};
1596+
1597+
let base_matrix_equals = match new_params.matrix {
1598+
Some(matrix) => old_object.base().matrix() == matrix.into(),
1599+
None => true,
1600+
};
1601+
1602+
match old_object {
1603+
DisplayObject::MorphShape(_)
1604+
| DisplayObject::Graphic(_)
1605+
| DisplayObject::Text(_) => {
1606+
ratio_equals
1607+
&& old_object.id() == id
1608+
&& clip_depth_equals
1609+
&& base_matrix_equals
1610+
&& color_transform_equals
1611+
}
1612+
DisplayObject::Avm1Button(_)
1613+
| DisplayObject::Avm2Button(_)
1614+
| DisplayObject::EditText(_)
1615+
| DisplayObject::Bitmap(_)
1616+
| DisplayObject::Video(_) => {
1617+
ratio_equals && old_object.id() == id && clip_depth_equals
1618+
}
1619+
_ => ratio_equals,
1620+
}
1621+
}
1622+
15701623
if is_rewind {
15711624
// Remove all display objects that were created after the
15721625
// destination frame.
@@ -1577,10 +1630,29 @@ impl<'gc> MovieClip<'gc> {
15771630
// TODO: We want to do something like self.children.retain here,
15781631
// but BTreeMap::retain does not exist.
15791632
// TODO: Should AS3 children ignore GOTOs?
1633+
let final_placements: std::collections::HashMap<Depth, &GotoPlaceObject<'_>> =
1634+
goto_commands.iter().map(|cmd| (cmd.depth(), cmd)).collect();
1635+
15801636
let children: SmallVec<[_; 16]> = self
15811637
.iter_render_list()
1582-
.filter(|clip| clip.place_frame() > frame)
1638+
.filter(|child| {
1639+
let is_candidate_for_removal = if self.movie().is_action_script_3() {
1640+
child.place_frame() > frame || child.placed_by_script()
1641+
} else {
1642+
child.depth() < AVM_DEPTH_BIAS
1643+
};
1644+
1645+
if !is_candidate_for_removal && child.as_morph_shape().is_none() {
1646+
return false;
1647+
}
1648+
if let Some(final_placement) = final_placements.get(&child.depth()) {
1649+
!survives_rewind(*child, &final_placement.place_object)
1650+
} else {
1651+
true
1652+
}
1653+
})
15831654
.collect();
1655+
15841656
for child in children {
15851657
if !child.placed_by_script() {
15861658
self.remove_child(context, child);
Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
num_frames = 6
2-
# FIXME - Ruffle doesn't handle AVM1 'goto's correctly
3-
known_failure = true
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# Test adapted from Shumway at https://github.com/mozilla/shumway/tree/master/test/swfs/avm1/duplicatemovieclip
22

33
num_frames = 4
4-
known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12270
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# Test adapted from Shumway at https://github.com/mozilla/shumway/tree/master/test/swfs/timeline/nav
22

33
num_frames = 3
4-
known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12143
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# Test adapted from Shumway at https://github.com/mozilla/shumway/tree/master/test/swfs/timeline/nav
22

33
num_frames = 3
4-
known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12144
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
# Test adapted from Shumway at https://github.com/mozilla/shumway/tree/master/test/swfs/timeline/nav
22

33
num_frames = 3
4-
known_failure = true # https://github.com/ruffle-rs/ruffle/issues/12145

0 commit comments

Comments
 (0)